From d68d137e120cb8261393f640b5c00d82f8bd96e4 Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 29 2020 09:01:34 +0000 Subject: mod_fcgid-2.3.9 base --- diff --git a/CHANGES-FCGID b/CHANGES-FCGID new file mode 100644 index 0000000..d473aa9 --- /dev/null +++ b/CHANGES-FCGID @@ -0,0 +1,278 @@ + -*- coding: utf-8 -*- +Changes with mod_fcgid 2.3.9 + + *) Revert fix for PR 53693, added in 2.3.8 but undocumented. Fix + issues with a minor optimization added in 2.3.8. [Jeff Trawick] + +Changes with mod_fcgid 2.3.8 + + *) SECURITY: CVE-2013-4365 (cve.mitre.org) + Fix possible heap buffer overwrite. Reported and solved by: + [Robert Matthews ] + + *) Add experimental cmake-based build system for Windows. [Jeff Trawick] + + *) Correctly parse quotation and escaped spaces in FcgidWrapper and the + AAA Authenticator/Authorizor/Access directives' command line argument, + as currently documented. PR 51194 [William Rowe] + + *) Honor quoted FcgidCmdOptions arguments (notably for InitialEnv + assignments). PR 51657 [William Rowe] + + *) Conform script response parsing with mod_cgid and ensure no response + body is sent when ap_meets_conditions() determines that request + conditions are met. [Chris Darroch] + + *) Improve logging in access control hook functions. [Chris Darroch] + + *) Avoid making internal sub-requests and processing Location headers + when in FCGI_AUTHORIZER mode, as the auth hook functions already + treat Location headers returned by scripts as an error since + redirections are not meaningful in this mode. [Chris Darroch] + +Changes with mod_fcgid 2.3.7 + + *) Introduce FcgidWin32PreventOrphans directive on Windows to use OS + Job Control Objects to terminate all running fcgi's when the worker + process has been abruptly terminated. PR: 51078 + [Thangaraj AntonyCrouse ] + + *) Periodically clean out the brigades which are pulling in the request + body for handoff to the fcgid child. PR: 51749 + [Dominic Benson ] + + *) Resolve crash during graceful restarts. PR 50309 + [Mario Brandt ] + + *) Solve latency/cogestion of resolving effective user file access rights + when no such info is desired, for config related filename stats. + PR: 51020 [Thangaraj AntonyCrouse , William Rowe] + + *) Fix regression in 2.3.6 which broke process controls when using vhost- + specific configuration. [Jeff Trawick] + + *) Account for first process in class in the spawn score. [Jeff Trawick] + +Changes with mod_fcgid 2.3.6 + + *) SECURITY: CVE-2010-3872 (cve.mitre.org) + Fix possible stack buffer overwrite. Diagnosed by the reporter. + PR 49406. [Edgar Frank ] + + *) Change the default for FcgidMaxRequestLen from 1GB to 128K. + Administrators should change this to an appropriate value based on + site requirements. [Jeff Trawick] + + *) Allow FastCGI apps more time to exit at shutdown before being + forcefully killed. [Jeff Trawick] + + *) Correct a problem that resulted in FcgidMaxProcesses being ignored + in some situations. PR 48981. [] + + *) Fix the search for processes with the proper vhost config when + ServerName isn't set in every vhost or a module updates + r->server->server_hostname dynamically (e.g., mod_vhost_cdb) + or a module updates r->server dynamically (e.g., mod_vhost_ldap). + [Jeff Trawick] + + *) FcgidPassHeader now maps header names to environment variable names + in the usual manner: The header name is converted to upper case and + is prefixed with HTTP_. An additional environment variable is + created with the legacy name. PR 48964. [Jeff Trawick] + + *) Allow processes to be reused within multiple phases of a request + by releasing them into the free list as soon as possible. + [Chris Darroch] + + *) Fix lookup of process command lines when using FcgidWrapper or + access control directives, including within .htaccess files. + [Chris Darroch] + + *) Resolve a regression in 2.3.5 with httpd 2.0.x on some Unix platforms; + ownership of mutex files was incorrect, resulting in a startup failure. + PR 48651. [Jeff Trawick, ] + + *) Return 500 instead of segfaulting when the application returns no output. + [Tatsuki Sugiura , Jeff Trawick] + + *) In FCGI_AUTHORIZER role, avoid spawning a new process for every + different HTTP request. [Chris Darroch] + +Changes with mod_fcgid 2.3.5 + + *) Stop using the unsuppressable "notice" log level for debug and + informational messages. PR 48536. [Jeff Trawick] + + *) Respect DEFAULT_REL_RUNTIMEDIR for default values of FcgidIPCDir and + FcgidProcessTableFile. [Jeff Trawick] + + *) Resolve fatal EDEADLK errors with threaded MPMs on Solaris. [Jeff Trawick] + + *) Display information about active processes in the server-status page. + [Ryan Pan] + + *) Fix compatibility of httpd.conf-editing logic with non-GNU awk. PR 48067. + [Hans Werner Strube ] + + *) Fix startup errors creating shared memory in constrained systems, such + as OS X in its default configuration. This is a regression since mod_fcgid + 2.2. [Jeff Trawick] + + *) Recover from most "Resource temporarily unavailable" errors writing the + request to the FastCGI application. These were common with large request + bodies on Mac OS X and intermittent on Solaris. PR 48025. [Jeff Trawick] + + *) Fix a bug in fixconf.sed that resulted in a prefix of "FcgidFcgid" on the + updated directives. [Dan Hulme ] + + *) Fix possible corruption or truncation of request bodies which exceed + FcgidMaxRequestInMem. This is a regression since mod_fcgid 2.2, which + effectively ignored FcgidMaxRequestInMem if larger than 8K. PR 48021. + [Jeff Trawick] + + *) Fix handling of the request body when a FastCGI access checker/ + authenticator/authorizer (AAA) was configured. The body wasn't available + for the request handler. PR 47973. + [Jeff Trawick, Barry Scott ] + + *) Fix handling of FcgidCmdOptions so that it can apply to wrapper scripts + which were defined with command-line arguments on the FcgidWrapper + directive. [Jeff Trawick] + +Changes with mod_fcgid 2.3.4 + + *) Corrected unix 'make install' target regression in 2.3.3. [Jeff Trawick] + +Changes with mod_fcgid 2.3.3 + + *) Add FcgidCmdOptions directive to associate some of the existing + configuration settings with a specific command. [Jeff Trawick] + + *) Allow/respect virtual host settings for the following directives: + FcgidBusyTimeout, FcgidMaxProcessesPerClass, FcgidMinProcessesPerClass, + FcgidIdleTimeout, and FcgidProcessLifetime. [Jeff Trawick] + +Changes with mod_fcgid 2.3.2 + + *) Fix a make install DESTDIR problem handling the reference manual and + potentially other files (specific to 2.3.1). + [Paul Howarth ] + + *) Fix a mod_fcgid 2.3.1 failure with when building for + httpd 2.0.x on some platforms. [Paul Howarth ] + + *) Termination of idle processes after inactivity timeout can now be + disabled by setting FcgidIdleTimeout to 0. Termination of idle + processes based on the process lifetime can now be disabled by setting + FcgidProcessLifeTime to 0. FcgidMaxRequestsPerProcess now accepts 0 + for unlimited. [Ricardo Cantu ] + + *) All directives have been renamed in order to use a common prefix "Fcgid". + Underscores in directive names have been eliminated in favor of + CamelCase. The old directive names will still work but are deprecated. + To fix your configuration you can use the sed script build/fixconf.sed. + The following tables contains old and new directive names. + + Old Name New Name + ................................................................... + BusyScanInterval FcgidBusyScanInterval + BusyTimeout FcgidBusyTimeout + DefaultInitEnv FcgidInitialEnv + DefaultMaxClassProcessCount FcgidMaxProcessesPerClass + DefaultMinClassProcessCount FcgidMinProcessesPerClass + ErrorScanInterval FcgidErrorScanInterval + FastCgiAccessChecker FcgidAccessChecker + FastCgiAccessCheckerAuthoritative FcgidAccessCheckerAuthoritative + FastCgiAuthenticator FcgidAuthenticator + FastCgiAuthenticatorAuthoritative FcgidAuthenticatorAuthoritative + FastCgiAuthorizer FcgidAuthorizer + FastCgiAuthorizerAuthoritative FcgidAuthorizerAuthoritative + FCGIWrapper FcgidWrapper + IdleScanInterval FcgidIdleScanInterval + IdleTimeout FcgidIdleTimeout + IPCCommTimeout FcgidIOTimeout + IPCConnectTimeout FcgidConnectTimeout + MaxProcessCount FcgidMaxProcesses + MaxRequestInMem FcgidMaxRequestInMem + MaxRequestLen FcgidMaxRequestLen + MaxRequestsPerProcess FcgidMaxRequestsPerProcess + OutputBufferSize FcgidOutputBufferSize + PassHeader FcgidPassHeader + PHP_Fix_Pathinfo_Enable FcgidFixPathinfo + ProcessLifeTime FcgidProcessLifeTime + SharememPath FcgidProcessTableFile + SocketPath FcgidIPCDir + SpawnScore FcgidSpawnScore + SpawnScoreUpLimit FcgidSpawnScoreUpLimit + TerminationScore FcgidTerminationScore + TimeScore FcgidTimeScore + ZombieScanInterval FcgidZombieScanInterval + + *) Separate classes by virtual host also on Windows. [Rainer Jung] + + *) Log client IP address with many more error log messages. [Jeff Trawick] + + *) Fix basic implementation of FcgidMaxRequestInMem and FcgidMaxRequestLen + directives. [Jeff Trawick] + + *) Merge per-directory directives so that they can be inherited or + overridden within other containers as expected. Merge server config/ + virtual host directives so that they can be inherited or overridden + within a virtual host as expected. [Jeff Trawick] + + *) Use the virtual host settings for the request being processed instead + of those of the first FastCGI request handled by this httpd child process. + Affected directives: FcgidBusyTimeout, FcgidIOTimeout, + FcgidConnectTimeout, FcgidMaxRequestsPerProcess, and FcgidOutputBufferSize. + [Jeff Trawick] + + *) Directives which previously were ignored in a virtual host context are no + longer allowed. [Jeff Trawick] + + *) Add an optional flag "virtual" to FcgidWrapper. + If virtual is set, the URLs passed to the wrapper are not + checked, whether they resolve to a file. [Rainer Jung] + + *) Make the second argument (suffix) for FcgidWrapper optional. + A wrapper defined without a suffix applies to all URLs, unless + there is another more specific wrapper with a suffix. [Rainer Jung] + +Changes with mod_fcgid 2.3.1 + + *) Suppress "need AuthType to note auth failure" error-level messages when a + FastCGIAccessChecker fails without any other kind of authentication + (Basic, Digest) configured. [Eric Covener] + + *) Complete the unix port to 2.3-dev trunk. [William Rowe] + + *) Provide a default, mandatory environment as with mod_cgi (with the + inclusion of LD_LIBRARY_PATH or similar variables on other platforms), + unless overridden by DefaultInitEnv. [William Rowe] + + *) Handle DefaultInitEnv for case-insensitive platforms by forcing the env + variable names to uppercase on Win32, OS2 and Netware. [William Rowe] + + *) Don't try to set the ownership of the socket directory unless running + as root and the directory was just created. This allows the default + httpd.conf (with some daemon User/Group) to be used by non-root. + [Jeff Trawick] + + *) Fix formatting of several messages, including the oft-seen "mod_fcgid: + Can't create shared memory for size %zu byte". [Jeff Trawick] + + *) Fix declared names of FastCgiAuthenticator and FastCgiAuthenticator- + Authoritative directives, allowing them to be used. [Ulf Haueisen + ] + + *) Fix vhost-specific DefaultInitEnv settings. Previously, when setting + multiple virtual hosts with the same SuexecUserGroup user and group, the + process manager use the same process pool for both virtual hosts. This + means if one virtual host has a DefaultInitEnv and the other has + different values set, a fastcgi request from any of these virtual host + can go to the same processes, which is inconsistent (a request from + virtualhost a with DefaultInitEnv VAL "a", can go to a process spawned + with virtualhost b with DefaultInitEnv VAL "b" set). [Gabriel Barazer + ] + +Note: A log of changes released before moving to the ASF (releases 2.2 and +earlier) is in the file ChangeLog. diff --git a/LICENSE-FCGID b/LICENSE-FCGID new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE-FCGID @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/Makefile-fcgid.win b/Makefile-fcgid.win new file mode 100644 index 0000000..37d87cc --- /dev/null +++ b/Makefile-fcgid.win @@ -0,0 +1,216 @@ +# +# NMAKE Makefile for Microsoft Windows +# +# Targets are: +# _apacher - build Apache in Release mode +# _apached - build Apache in Debug mode +# installr - build and install a Release build +# installd - build and install a Debug build +# clean - remove (most) generated files +# +# The following install defaults may be customized; +# +# Option Default +# INSTDIR /Apache22 +# +# For example; +# +# nmake -f Makefile-fcgid.win INSTDIR="d:/Program Files/Apache" installr +# +# Be aware that certain awk's will not accept backslashed names, +# so the server root should be given in forward slashes (quoted), +# preferably with the drive designation! + +!IF "$(INSTDIR)" == "" +INSTDIR=\Apache22 +!ENDIF + +!IF EXIST("modules\fcgid\mod_fcgid.vcproj") \ + && ([devenv /help > NUL 2>&1] == 0) \ + && !defined(USEMAK) && !defined(USEDSW) +USESLN=1 +USEMAK=0 +USEDSW=0 +!ELSEIF EXIST("modules\fcgid\mod_fcgid.mak") && !defined(USEDSW) +USESLN=0 +USEMAK=1 +USEDSW=0 +!ELSE +USESLN=0 +USEMAK=0 +USEDSW=1 +!ENDIF + +!IF ("$(CTARGET)" == "") && ($(USESLN) == 1) +CTARGET=/build +!ENDIF + +!IF "$(APACHE2_HOME)" == "" +APACHE2_HOME=$(INSTDIR) +!ENDIF + +!IF EXIST("$(APACHE2_HOME)\lib\libapr-2.lib") +APACHE2_APRSFX=-2 +!ELSEIF EXIST("$(APACHE2_HOME)\lib\libapr-1.lib") +APACHE2_APRSFX=-1 +!ELSEIF EXIST("$(APACHE2_HOME)\lib\libapr.lib") +APACHE2_APRSFX= +!ELSE +!MESSAGE Point INSTDIR at the directory containing an already installed httpd +!MESSAGE including build support directories of lib and include. It must +!MESSAGE include apr and apr-util. mod_fcgid cannot build without these files! +!ENDIF + +!IF "$(LONG)" == "" +!MESSAGE +!MESSAGE INSTDIR = $(INSTDIR) +!MESSAGE APACHE2_HOME = $(APACHE2_HOME) +!MESSAGE +!MESSAGE To change these options use 'nmake -f Makefile-fcgid.win [option=value]' +!MESSAGE Example: nmake -f Makefile-fcgid.win +!MESSAGE +!ENDIF + +!IFNDEF MAKEOPT +# Only default the behavior if MAKEOPT= is omitted +!IFDEF _NMAKE_VER +# Microsoft NMake options +MAKEOPT=-nologo +!ELSEIF "$(MAKE)" == "make" +# Borland make options? Not really supported (yet) +MAKEOPT=-s -N +!ENDIF +!ENDIF + +_buildr: + @$(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=R LONG=Release _build + +_buildd: + @$(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=D LONG=Debug _build + +installr: + @$(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=R LONG=Release _build _install + +installd: + @$(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=D LONG=Debug _build _install + +clean: _cleanr _cleand + -if exist Browse\. rd /s Browse < << > nul +y +<< + +!IF $(USEMAK) == 1 + +_cleanr: + $(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=R LONG=Release CTARGET=CLEAN _build + +_cleand: + $(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=D LONG=Debug CTARGET=CLEAN _build + +_build: + echo Building Win32 $(LONG) targets ($(SHORT) suffixes) + cd modules\fcgid + $(MAKE) $(MAKEOPT) -f mod_fcgid.mak CFG="mod_fcgid - Win32 $(LONG)" RECURSE=0 $(CTARGET) + cd ..\.. + +!ELSEIF $(USESLN) == 1 + +_cleanr: + $(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=R LONG=Release CTARGET="/clean" _build + +_cleand: + $(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=D LONG=Debug CTARGET="/clean" _build + +_build: + echo Building Win32 $(LONG) targets ($(SHORT) suffixes) + devenv mod_fcgid.sln /useenv $(CTARGET) $(LONG) /project mod_fcgid + +!ELSE + +_cleanr: + @$(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=R LONG=Release CTARGET="/CLEAN" _build + +_cleand: + @$(MAKE) $(MAKEOPT) -f Makefile-fcgid.win \ + INSTDIR="$(INSTDIR)" APACHE2_HOME="$(APACHE2_HOME)" \ + SHORT=D LONG=Debug CTARGET="/CLEAN" _build + +_build: + @echo Building Win32 $(LONG) targets ($(SHORT) suffixes) + @msdev mod_fcgid.dsw /USEENV /MAKE "mod_fcgid - Win32 $(LONG)" $(CTARGET) + +!ENDIF + +httpd_conffile=$(INSTDIR)\conf\httpd.conf +httpd_origconffile=$(INSTDIR)\conf\original\httpd.conf + +_install: + echo Y >.y + echo A >.A + -mkdir "$(INSTDIR)" + -mkdir "$(INSTDIR)\conf" + -mkdir "$(INSTDIR)\conf\original" +!IFDEF HAVE_HTTPD_FCGID_CONF + -mkdir "$(INSTDIR)\conf\extra" + -mkdir "$(INSTDIR)\conf\original\extra" +!ENDIF +!IFDEF HAVE_MOD_FCGID_H + -mkdir "$(INSTDIR)\include" +!ENDIF + -mkdir "$(INSTDIR)\manual" + -mkdir "$(INSTDIR)\modules" + copy CHANGES-FCGID "$(INSTDIR)\CHANGES-FCGID.txt" <.y + copy LICENSE-FCGID "$(INSTDIR)\LICENSE-FCGID.txt" <.y + copy NOTICE-FCGID "$(INSTDIR)\NOTICE-FCGID.txt" <.y + copy README-FCGID "$(INSTDIR)\README-FCGID.txt" <.y + copy modules\fcgid\$(LONG)\mod_fcgid.so "$(INSTDIR)\modules" <.y + copy modules\fcgid\$(LONG)\mod_fcgid.pdb "$(INSTDIR)\modules" <.y + xcopy docs\manual "$(INSTDIR)\manual" /s /d < .a +!IFDEF HAVE_MOD_FCGID_H + copy include\mod_fcgid.h "$(INSTDIR)\include" < .y > nul +!ENDIF + for %f in ("$(httpd_origconffile)" "$(httpd_conffile)") do \ + if exist "%f" ( \ + awk -f build/addloadexample.awk -v MODULE=fcgid -v DSO=.so \ +!IFDEF HAVE_HTTPD_FCGID_CONF + -v EXAMPLECONF=conf/extra/httpd-fcgid.conf \ +!ENDIF + -v LIBPATH=modules "%f" > "%f.new" && \ + move "%f" "%f.bak" && move "%f.new" "%f" \ + ) +!IFDEF HAVE_HTTPD_FCGID_CONF + copy docs\conf\extra\httpd-fcgid.conf "$(INSTDIR)\conf\original\extra\httpd-fcgid.conf" <.y + awk -f << docs\conf\extra\httpd-fcgid.conf > "$(INSTDIR)\conf\original\extra\httpd-fcgid.conf" +/^\#@@LoadFcgidModules@@/ { next; } +{ sub(/@exp_runtimedir@/, "logs"); + sub(/@exp_sysconfdir@/, "conf"); + sub(/@rel_sysconfdir@/, "conf"); + sub(/@rel_logfiledir@/, "logs"); + print $0; +} +<< + if not exist "$(INSTDIR)\conf\extra\httpd-fcgid.conf" \ + copy "$(INSTDIR)\conf\original\extra\httpd-fcgid.conf" \ + "$(INSTDIR)\conf\extra\httpd-fcgid.conf" <.y +!ENDIF + del .y + del .a + diff --git a/Makefile.apxs b/Makefile.apxs new file mode 100644 index 0000000..b51f6f0 --- /dev/null +++ b/Makefile.apxs @@ -0,0 +1,125 @@ +## +## Makefile.apxs -- Build procedure for mod_fcgid Apache module +## +## Do not use the .apxs makefile, run ./configure.apxs and build from 'Makefile' +## + +# top_builddir and top_srcdir are misnomers, because build/*.mk scripts +# expect each of them to be the parent of the build directory, and fail +# to trust the installbuilddir. +exp_installbuilddir=$(shell $(APXS) -q exp_installbuilddir) +top_srcdir=$(installbuilddir)/.. +top_builddir=$(installbuilddir)/.. + +fcgid_builddir=. +fcgid_srcdir=. +builddir=. +srcdir=. +awk=. + +SUBDIRS = modules/fcgid +CLEAN_SUBDIRS = + +TARGETS = +INSTALL_TARGETS = install-conf install-manual +## no such targets yet; install-include +DISTCLEAN_TARGETS = config.apxs.log modules/fcgid/fcgid_config.h +EXTRACLEAN_TARGETS = + +include $(exp_installbuilddir)/rules.mk + +x-local-distclean: + rm -rf docs/manual/build docs/manual/style + +# Dang nabbit, these are stripped! Reconstitute them; +rel_libexecdir=`echo $(exp_libexecdir) | sed -e "s#^$(prefix)/##;"` +rel_sysconfdir=`echo $(exp_sysconfdir) | sed -e "s#^$(prefix)/##;"` +rel_logfiledir=`echo $(exp_logfiledir) | sed -e "s#^$(prefix)/##;"` +httpd_conffile=$(exp_sysconfdir)/$(progname).conf +httpd_origconffile=$(exp_sysconfdir)/original/$(progname).conf + +install-conf: + @echo Installing configuration files + @$(MKINSTALLDIRS) $(DESTDIR)$(exp_sysconfdir) \ + $(DESTDIR)$(exp_sysconfdir)/original + for i in $(DESTDIR)$(httpd_conffile) $(DESTDIR)$(httpd_origconffile); do \ + if test -f $$i; then \ + ($(awk) -f $(fcgid_srcdir)/build/addloadexample.awk \ + -v MODULE=fcgid -v DSO=.so -v LIBPATH=$(rel_libexecdir) \ + < $$i > $$i.new && \ + mv $$i $$i.bak && mv $$i.new $$i \ + ) || true; \ + fi; \ + done +# Todo - add this flag to awk above, if/when this Include is distributed +# -v EXAMPLECONF=$(rel_sysconfdir)/extra/httpd-fcgid.conf + +install-conf-unused: + @$(MKINSTALLDIRS) $(DESTDIR)$(exp_sysconfdir)/extra \ + $(DESTDIR)$(exp_sysconfdir)/original/extra + @cd $(fcgid_srcdir)/docs/conf; \ + for j in $(fcgid_srcdir)/docs/conf; do \ + cd $$j ; \ + for i in extra/httpd-fcgid.conf; do \ + if test -f $$i; then \ + sed -e '/^\#@@LoadFcgidModules@@/d;' \ + -e 's#@exp_runtimedir@#$(exp_runtimedir)#;' \ + -e 's#@exp_sysconfdir@#$(exp_sysconfdir)#;' \ + -e 's#@rel_sysconfdir@#$(rel_sysconfdir)#;' \ + -e 's#@rel_logfiledir@#$(rel_logfiledir)#;' \ + < $$i > $(DESTDIR)$(exp_sysconfdir)/original/$$i; \ + chmod 0644 $(DESTDIR)$(exp_sysconfdir)/original/$$i; \ + if test ! -f $(DESTDIR)$(exp_sysconfdir)/$$i; then \ + cp $(DESTDIR)$(exp_sysconfdir)/original/$$i \ + $(DESTDIR)$(exp_sysconfdir)/$$i; \ + chmod 0644 $(DESTDIR)$(exp_sysconfdir)/$$i; \ + fi; \ + fi; \ + done ; \ + done + +svnroot=http://svn.apache.org/repos/asf/httpd +manualdir=$(fcgid_srcdir)/docs/manual + +# Note; by default, make generate-docs rebuilds the local pages +# To regenerate the installed pages (after using make install to +# drop in the fcgid content), simply +# +# make manualdir=/path/to/manual generate-docs +# +generate-docs: + @if test ! -d $(manualdir)/build; then \ + cd $(manualdir); \ + svn export $(svnroot)/docs-build/trunk build; \ + fi + @if test ! -d $(manualdir)/style; then \ + cd $(manualdir); \ + svn export $(svnroot)/httpd/trunk/docs/manual/style; \ + fi + cd $(manualdir)/build; \ + ./build.sh all + +generate-dox: + cd $(fcgid_srcdir); \ + doxygen $(fcgid_srcdir)/docs/doxygen-fcgid.conf + +install-manual: + @echo Installing online manual + @test -d $(DESTDIR)$(exp_manualdir) \ + || $(MKINSTALLDIRS) $(DESTDIR)$(exp_manualdir) + @if test "x$(RSYNC)" != "x" && test -x $(RSYNC) ; then \ + $(RSYNC) --exclude .svn -rlpt --numeric-ids \ + $(fcgid_srcdir)/docs/manual/ $(DESTDIR)$(exp_manualdir)/; \ + else \ + cp -rp $(fcgid_srcdir)/docs/manual/* $(DESTDIR)$(exp_manualdir)/ && \ + find $(DESTDIR)$(exp_manualdir) -name ".svn" -type d -print \ + | xargs rm -rf 2>/dev/null || true; \ + fi + +install-include-unused: + @echo Installing header files + @$(MKINSTALLDIRS) $(DESTDIR)$(exp_includedir) && \ + cp $(fcgid_srcdir)/include/mod_fcgid.h $(DESTDIR)$(exp_includedir)/ && \ + chmod 0644 $(DESTDIR)$(exp_includedir)/mod_fcgid.h + +.PHONY: generate-dox generate-docs diff --git a/NOTICE-FCGID b/NOTICE-FCGID new file mode 100644 index 0000000..da8f0ca --- /dev/null +++ b/NOTICE-FCGID @@ -0,0 +1,5 @@ +Apache HTTP Server mod_fcgid +Copyright 2013 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/README-FCGID b/README-FCGID new file mode 100644 index 0000000..0a24118 --- /dev/null +++ b/README-FCGID @@ -0,0 +1,218 @@ +Unix Build Instructions +----------------------- + +To build and install as a DSO outside of the httpd source +build, from the fcgid source root directory, simply; + + ./configure.apxs + make + make install + +If apxs is not in your path, or you are building to a different +httpd installation, or your distribution has an alternate script +name for apxs (e.g. apxs2), then either set the APXS environment +variable, or use the syntax; + + APXS=/path/to/bin/apxs ./configure.apxs + +so the desired configuration is used. + +To build static, or as a DSO but within the same build as httpd, +copy the entire fcgid source directory tree on top of your existing +httpd source tree, and from the httpd source root directory + + ./buildconf (to pick up fcgid) + ./configure --enable-fcgid {your usual options} + +and proceed as usual. + + +Win32 Build Instructions +------------------------ + +1. Win32 build based on Visual Studio + +The windows packages prior to 2.2.7 (or 2.0.62) left out the file +include\mod_log_config.h, just copy these from the source tree +or you can export them from subversion, just change to your +installed Apache 2.2 (or 2.0) include subdirectory and... + + svn export http://svn.apache.org/repos/asf/httpd/httpd/branches/2.2.x/modules/loggers/mod_log_config.h + +(for your 2.0 installation, replace 2.2.x with 2.0.x in the +command above). You should be ready to compile the project. + +On windows, before building for httpd-2.0, you must adjust the +two apr 1.x specific lines in modules\fcgid\mod_fcgid.dsp which begin + + # ADD LINK32 libapr-1.lib libaprutil-1.lib ... + +to start with the apr 0.9 equivilants for httpd-2.0, + + # ADD LINK32 libapr.lib libaprutil.lib ... + +If using httpd development version 2.3 plus APR 2.0, replace both +of these entries with the single (combined) entry; + + # ADD LINK32 libapr-2.lib ... + +To build on windows out-of-tree as a DSO, simply + + set APACHE2_HOME=c:\path\to\Apache2.2 + +and then, for Visual Studio 6.0 (98)... + + msdev /useenv mod_fcgid.dsw + +or for Visual Studio .NET (2002) and later ... + + devenv /useenv mod_fcgid.dsw + +The later command is needed on Visual Studio .NET/2002 and later, +and converts mod_fcgid.dsw to mod_fcgid.sln. So after converting once, +use the newly converted solution instead... + + devenv /useenv mod_fcgid.sln + +On windows you can overlay mod_fcgid source files into the httpd source +file tree, and make the following changes for an in-tree build; + + * Manually add the project mod_fcgid.dsp to the Apache.dsw workspace. + * Ensure the BuildBin project includes the mod_fcgid project dependency. + * Add mod_fcgid project dependencies of libhttpd, libapr and libaprutil. + * Remove /D "FCGID_APXS_BUILD" from the # ADD CPP lines of + modules\fcgid\mod_fcgid.dsp. + * Replace /I "$(APACHE2_HOME)/include" with /I "../../modules/loggers" + for both # ADD CPP lines of modules\fcgid\mod_fcgid.dsp. + * Remove the libraries libapr[-1].lib libaprutil[-1].lib libhttpd.lib + and the /libpath:"$(APACHE2_HOME)\lib" flag from the # ADD LINK32 + lines of modules\fcgid\mod_fcgid.dsp. + +Note that mod_fcgid.so needs to be added to the module installation +lines in Makefile.win, or you must manually copy the .so module from +modules\fcgid\Release after compiling. + +2. Win32 build based on cmake: + +Note: This support is experimental and may not build mod_fcgid in a + manner compatible with the existing Windows build support. The + build interfaces may change as feedback is received and bugs are + resolved. Currently a .conf file is not created. + +Install httpd and APR to a common prefix, and point CMAKE_INSTALL_PREFIX +to that prefix when configuring mod_fcgid. + +Example using the "NMake Makefiles" generator from a Visual Studio command +prompt: + + cd some-build-directory + cmake -G "NMake Makefiles" ^ + -DCMAKE_INSTALL_PREFIX=C:\Apache246 ^ + -DCMAKE_BUILD_TYPE=RelWithDebInfo ^ + C:\path\to\fcgid-sources\modules\fcgid + nmake && nmake install + +The last argument to cmake in the example is the directory "modules\fcgid" +within your svn checkout or tarball/zip extract of mod_fcgid. + +Add -DINSTALL_PDB=OFF to the cmake invocation to leave mod_fcgid.pdb (if +generated) in the build directory. + +Add the following LoadModule directive to your configuration: + + LoadModule fcgid_module modules/mod_fcgid.so + +Documentation Build +------------------- + +To regenerate the html.en documentation, here again it's as simple +as copying the content docs/ into an httpd/docs/ tree and regenerating +httpd's documentation. However, it's also possible you are generating +a local copy for reference in mod_fcgid's tree, in that case you must +have a copy of the httpd docs/manual/style. For example; + + cd docs/manual + svn co http://svn.apache.org/repos/asf/httpd/httpd/branches/2.2.x/docs/manual/style + +In either case; + + cd docs/manual + svn co http://svn.apache.org/repos/asf/httpd/docs-build/trunk build + cd build + +and finally; + + ./build.sh all + +or on windows... + + build.bat all + +To make this simpler on unix, after invoking ./configure.apxs in the +top level directory, you can simply; + + make generate-docs + +which will fetch up those style and build directories (for httpd-2.2) +and generate the docs for you. After using make and make install, you +can even merge the directives for the installed manual using this target; + + make manualdir=/path/to/httpd/manual generate-docs + +The same rules about an installed, locatable JAVA_HOME apply to building +mod_fcgid docs as apply to building the httpd manual. + +The advantage to building in-tree within httpd is that you gain the complete +directive cross references applicable to all httpd and mod_fcgid directives, +before installing the httpd\manual files. + + +Incompatible configuration changes +---------------------------------- +Some changes have been made in the ASF release of mod_fcgid which can affect +existing configurations: + +i. All directives have been renamed in order to use a common prefix "Fcgid". + Underscores in directive names have been eliminated in favor of + CamelCase. The old directive names will still work but are deprecated. + To fix your configuration you can use the sed script build/fixconf.sed. + A table with old and new directive names is included in CHANGES-FCGID. + +ii. Some directives which could be placed inside but were + ignored before now result in configuration errors. As before, these + directives must be set at global scope to have the desired effect. + The directives are FcgidBusyScanInterval, FcgidBusyTimeout, + FcgidMaxProcessesPerClass, FcgidDefaultMinProcessCount, + FcgidErrorScanInterval, FcgidIdleScanInterval, FcgidIdleTimeout, + FcgidMaxProcesses, FcgidFixPathinfo, FcgidProcessLifetime, + FcgidProcessTableFile, FcgidIPCDir, FcgidSpawnScore, + FcgidSpawnScoreUpLimit, FcgidTerminationScore, FcgidTimeScore, and + FcgidZombieScanInterval. + +iii. Some directives which could be placed inside but were + ignored before are now respected. These include FcgidIdleTimeout, + FcgidProcessLifeTime, and others. (Consult CHANGES-FCGID for the complete + list.) + +iv. Some directives which can optionally be placed inside + were not inherited as expected in older releases. This has been + corrected, and behavior will change for some configurations. The + affected directives are FcgidIOTimeout, FcgidConnectTimeout, + FcgidMaxRequestInMem, FcgidMaxRequestLen, FcgidMaxRequestsPerProcess, + and FcgidOutputBufferSize. + +v. Some directives which can be placed inside , , + etc. were not inherited as expected in older releases. This has been + corrected, and behavior will change for some configurations. The affected + directives are FcgidAccessChecker, FcgidAccessCheckerAuthoritative, + FcgidAuthenticator, FcgidAuthenticatorAuthoritative, FcgidAuthorizer, + FcgidAuthorizerAuthoritative, and FcgidWrapper. + +Acknowledgements +---------------- +Portions of this software were originally developed by +Ryan Pan (Pan Qingfeng) . + +This software implements portions of the FastCGI specification +as defined by Open Market, Inc. The specification is available from + http://www.fastcgi.com/devkit/doc/fcgi-spec.html diff --git a/README.RPM b/README.RPM new file mode 100644 index 0000000..89165c5 --- /dev/null +++ b/README.RPM @@ -0,0 +1,75 @@ +Using the mod_fcgid RPM Package +=============================== + +This mod_fcgid package includes a configuration file +/etc/httpd/conf.d/fcgid.conf that ensures that the module is loaded and +added as the handler for .fcg, .fcgi, and .fpl applications. + +Example: setting up moin with mod_fcgid +======================================= + +Setting up moin with mod_fcgid is very similar to setting it up as a regular +CGI application. + + * Create a directory for your wiki instance: + + DESTDIR=/var/www/mywiki + mkdir -p $DESTDIR/cgi-bin + + * Copy in the wiki template data and the application itself: + + cp -a /usr/share/moin/{data,underlay} $DESTDIR + cp -a /usr/share/moin/server/moin.fcg $DESTDIR/cgi-bin + cp -a /usr/share/moin/config/wikiconfig.py $DESTDIR/cgi-bin + + * Fix the directory ownership + + chown -R apache:apache $DESTDIR/{data,underlay} + + * Edit $DESTDIR/cgi-bin/wikiconfig.py to suit your needs + + * Create a httpd configuration file for the wiki, e.g. + /etc/httpd/conf.d/mywiki.conf + + # Wiki application data common to all wiki instances + Alias /moin_static185 "/usr/share/moin/htdocs/" + + Options Indexes FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + + ExpiresActive On + ExpiresDefault "access plus 1 year" + + + + # Wiki instance with mod_fcgid + + ScriptAlias /mywiki "/var/www/mywiki/cgi-bin/moin.fcg" + + Options Indexes FollowSymLinks ExecCGI + AllowOverride None + Order allow,deny + Allow from all + + + + * Restart the web server to load the new configuration: + + service httpd restart + +That should do it! + +Ruby on Rails with mod_fcgid +============================ + +One of the differences between mod_fastcgi and mod_fcgid is that the former +sets the SCRIPT_NAME environment variable whilst the latter does not, and it's +reported (http://bugzilla.redhat.com/476658) that Ruby on Rails expects this +environment variable to be present. A workaround for this is to add: + +ActionController::AbstractRequest.relative_url_root = "" + +to the Rails::Initializer.run segment of config/environment.rb + diff --git a/README.SELinux b/README.SELinux new file mode 100644 index 0000000..981cf59 --- /dev/null +++ b/README.SELinux @@ -0,0 +1,63 @@ +Using mod_fcgid with SELinux in Fedora Core 5 / RHEL 5 onwards +============================================================== + +Versions of this package built for Fedora Core 5, 6, or 7 include an SELinux +policy module to support FastCGI applications. Later Fedora releases and Red +Hat Enterprise Linux 5.3 onwards include the policy in the main selinux-policy +package and do not require the separate module. + +The module source (fastcgi.{fc,te}) is included for reference as documentation +in the package. + +The module uses the same set of SELinux types for FastCGI applications as for +regular CGI scripts (or "system scripts" as they are known in SELinux), as +described in "man httpd_selinux". + + * httpd_sys_content_t + - Set files with httpd_sys_content_t for content that is available + from all FastCGI scripts and the daemon. + + * httpd_sys_script_exec_t + - Set FastCGI scripts with httpd_sys_script_exec_t to allow them to run + with access to all system script types. + + * httpd_sys_script_ro_t + - Set files with httpd_sys_script_ro_t if you want httpd_sys_script_exec_t + scripts to read but not write the data, and disallow other processes from + access. + + * httpd_sys_script_rw_t + - Set files with httpd_sys_script_rw_t if you want httpd_sys_script_exec_t + scripts to read/write the data, and disallow other processes from access. + + * httpd_sys_script_ra_t + - Set files with httpd_sys_script_ra_t if you want httpd_sys_script_exec_t + scripts to read/append to the file, and disallow other processes from + access. + +So for the moin wiki layout described in README.RPM of the main mod_fcgid +package, the contexts would be set as follows: + + cd /var/www/mywiki + chcon -t httpd_sys_content_t . + chcon -R -t httpd_sys_script_exec_t cgi-bin + chcon -R -t httpd_sys_script_rw_t data underlay + +It is necessary to turn on the httpd_enable_cgi boolean to run either regular +or FastCGI scripts: + + setsebool -P httpd_enable_cgi 1 + +The httpd_can_sendmail boolean is used to specify whether any of your +web applications can make outbound SMTP connections (e.g. moin sending +notifications). By default it is off, but can be enabled as follows: + + setsebool -P httpd_can_sendmail 1 + +Only enable this functionality if you actually need it, since it increases the +chances that any vulnerability in any of your web applications could be +exploited by a spammer. + +If you have any questions or issues regarding FastCGI and SELinux, please don't +hesitate to bring them up on fedora-selinux-list. + diff --git a/STATUS-FCGID b/STATUS-FCGID new file mode 100644 index 0000000..f28c1a6 --- /dev/null +++ b/STATUS-FCGID @@ -0,0 +1,82 @@ +MOD_FCGID STATUS: -*-text-*- +Last modified at [$Date: 2013-10-04 20:59:58 +0000 (Fri, 04 Oct 2013) $] + +The current version of this file can be found at: + + * http://svn.apache.org/repos/asf/httpd/mod_fcgid/trunk/STATUS + +Consult the following STATUS files for information on related projects: + + * http://svn.apache.org/repos/asf/httpd/httpd/trunk/STATUS + * http://svn.apache.org/repos/asf/apr/apr/trunk/STATUS + +Release history: + [NOTE that x.{odd}.z versions are strictly Alpha/Beta releases, + while x.{even}.z versions are Stable/GA releases.] + + 2.3.10 : in development + 2.3.9 : tagged October 4, 2013 + 2.3.8 : not released + 2.3.7 : released April 23, 2012 + 2.3.6 : released November 6, 2010 + 2.3.5 : released January 28, 2010 + 2.3.4 : released October 15, 2009 + 2.3.3 : not released + 2.3.2 : not released + 2.3.1 : released September 16, 2009 + 2.3.0 : not released + 2.2.x : Final release branch external to the ASF + +Contributors looking for a mission: + + * Just do an egrep on "TODO" or "XXX" in the source. + + * Review the bug database at: http://issues.apache.org/bugzilla/ + + * Review the "PatchAvailable" bugs in the bug database: + + https://issues.apache.org/bugzilla/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=Apache+httpd-2&component=mod_fcgid&keywords=PatchAvailable + + After testing, you can append a comment saying "Reviewed and tested". + + * Open bugs in the bug database + + https://issues.apache.org/bugzilla/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=Apache+httpd-2&component=mod_fcgid + + +RELEASE SHOWSTOPPERS: + + + +CURRENT RELEASE NOTES: + + + +CURRENT VOTES: + + + +REALLY NICE TO WRAP THESE UP: + + * For in-tree builds, extending config_vars.mk with our local + [exp_]vars and installing that tree. + + * Create a manual module page. + + * Create and install an conf/extra/httpd-fcgid.conf. + + * Win32 stderr is broken. It's not possible to capture errors immediately + upon invocation; this is apparently a design failure in std handle passing. + + * defining the process class + + a few references: + + http://mail-archives.apache.org/mod_mbox/httpd-dev/201004.mbox/%3Cq2l81403a941004131831lce28460bqfc9fa53c2058e79b@mail.gmail.com%3E + http://mail-archives.apache.org/mod_mbox/httpd-dev/201002.mbox/%3C4B7EA227.90100@binero.se%3E + http://mail-archives.apache.org/mod_mbox/httpd-dev/200909.mbox/%3C4ABA234F.3000002@kippdata.de%3E + http://mail-archives.apache.org/mod_mbox/httpd-dev/200909.mbox/%3C4239a4320909210011w6c716fe6kec5915edc49bc546@mail.gmail.com%3E + https://issues.apache.org/bugzilla/show_bug.cgi?id=49902 + +REFERENCES: + diff --git a/build/Makefile.apxs b/build/Makefile.apxs new file mode 100644 index 0000000..bda1154 --- /dev/null +++ b/build/Makefile.apxs @@ -0,0 +1,71 @@ +## +## Makefile.apxs -- Feature test procedures for mod_fcgid Apache module +## +## Do not use this target; ./configure.apxs from the mod_fcgid dir root +## + +# top_builddir and top_srcdir are misnomers, because build/*.mk +# scripts expect it them be the parent of the build directory, +# and fail to trust the installbuilddir. +exp_installbuilddir=$(shell $(APXS) -q exp_installbuilddir) +top_srcdir=$(installbuilddir)/.. +top_builddir=$(installbuilddir)/.. + +fcgid_builddir=.. +fcgid_srcdir=.. +builddir=. +srcdir=. + +CLEAN_TARGETS = conftest_foofn.c \ + conftest_sys_file_h.c \ + conftest_sys_mman_h.c \ + conftest_sys_mutex_h.c \ + conftest_sys_shm_h.c \ + *.loT +TARGETS = conftest_foofn \ + conftest_sys_file_h \ + conftest_sys_mman_h \ + conftest_sys_mutex_h \ + conftest_sys_shm_h + +PROGRAM_LDADD = +PROGRAM_DEPENDENCIES = + +include $(exp_installbuilddir)/rules.mk + +# Function checks follow this pattern; one to execute, one to create source +# Header file checks simply validate that compilation into an .lo succeeds +# TODO When the first function test is added, hijack conftest_foofn + +conftest_foofn: conftest_foofn.lo + $(LINK) conftest_foofn.lo + +conftest_sys_file_h: conftest_sys_file_h.lo + @echo "success" > $@ + +conftest_sys_mman_h: conftest_sys_mman_h.lo + @echo "success" > $@ + +conftest_sys_mutex_h: conftest_sys_mutex_h.lo + @echo "success" > $@ + +conftest_sys_shm_h: conftest_sys_shm_h.lo + @echo "success" > $@ + +conftest_foofn.c: + @echo "#include " > $@ + @echo "#include " >> $@ + @echo "int main() { return foofn(2, 0644); }" >> $@ + +conftest_sys_file_h.c: + @echo "#include " > $@ + +conftest_sys_mman_h.c: + @echo "#include " > $@ + +conftest_sys_mutex_h.c: + @echo "#include " > $@ + +conftest_sys_shm_h.c: + @echo "#include " > $@ + diff --git a/build/addloadexample.awk b/build/addloadexample.awk new file mode 100644 index 0000000..55df683 --- /dev/null +++ b/build/addloadexample.awk @@ -0,0 +1,46 @@ +# Invoke as awk addloadexample.awk + +BEGIN { + lms = 0; +} + +tolower($0) ~ /^[# \t]*loadmodule[ \t]/ { + if ( $2 == MODULE "_module" ) { + print "LoadModule " MODULE "_module " LIBPATH "/mod_" MODULE DSO; + lms = 2; + next; + } + # test $3 since # LoadModule is split into two tokens + else if ( $3 == MODULE "_module" ) { + print $1 "LoadModule " MODULE "_module " LIBPATH "/mod_" MODULE DSO; + lms = 2; + next; + } + else if ( ! lms ) lms = 1; +} + +$0 ~ /^[ \t]*$/ && lms == 1 { + print "LoadModule " MODULE "_module " LIBPATH "/mod_" MODULE DSO; + lms = 2; +} + +tolower($0) ~ /^[# \t]*include[ \t]/ && $NF == EXAMPLECONF { + lms = 3; +} + +{ print } + +END { + if ( lms < 3 ) { + if ( $0 !~ /^[ \t]*$/ ) print ""; + if ( lms < 2 ) { + print "LoadModule " MODULE "_module " LIBPATH "/mod_" MODULE DSO; + print ""; + } + if ( length(EXAMPLECONF) ) { + print "# Example mod_" MODULE " configuration"; + print "#Include " EXAMPLECONF "\n"; + } + } +} + diff --git a/build/fixconf.sed b/build/fixconf.sed new file mode 100755 index 0000000..74b2671 --- /dev/null +++ b/build/fixconf.sed @@ -0,0 +1,87 @@ +#!/usr/bin/sed -f +# +# 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. +# +# +# sed script to replace all old directive names with the new ones. +# +# First we fix occurences at the beginning of lines +s/^BusyScanInterval/FcgidBusyScanInterval/g +s/^BusyTimeout/FcgidBusyTimeout/g +s/^DefaultInitEnv/FcgidInitialEnv/g +s/^DefaultMaxClassProcessCount/FcgidMaxProcessesPerClass/g +s/^DefaultMinClassProcessCount/FcgidMinProcessesPerClass/g +s/^ErrorScanInterval/FcgidErrorScanInterval/g +s/^FastCgiAccessChecker/FcgidAccessChecker/g +s/^FastCgiAccessCheckerAuthoritative/FcgidAccessCheckerAuthoritative/g +s/^FastCgiAuthenticator/FcgidAuthenticator/g +s/^FastCgiAuthenticatorAuthoritative/FcgidAuthenticatorAuthoritative/g +s/^FastCgiAuthorizer/FcgidAuthorizer/g +s/^FastCgiAuthorizerAuthoritative/FcgidAuthorizerAuthoritative/g +s/^FCGIWrapper/FcgidWrapper/g +s/^IdleScanInterval/FcgidIdleScanInterval/g +s/^IdleTimeout/FcgidIdleTimeout/g +s/^IPCCommTimeout/FcgidIOTimeout/g +s/^IPCConnectTimeout/FcgidConnectTimeout/g +s/^MaxProcessCount/FcgidMaxProcesses/g +s/^MaxRequestInMem/FcgidMaxRequestInMem/g +s/^MaxRequestLen/FcgidMaxRequestLen/g +s/^MaxRequestsPerProcess/FcgidMaxRequestsPerProcess/g +s/^OutputBufferSize/FcgidOutputBufferSize/g +s/^PassHeader/FcgidPassHeader/g +s/^PHP_Fix_Pathinfo_Enable/FcgidFixPathinfo/g +s/^ProcessLifeTime/FcgidProcessLifeTime/g +s/^SharememPath/FcgidProcessTableFile/g +s/^SocketPath/FcgidIPCDir/g +s/^SpawnScore/FcgidSpawnScore/g +s/^SpawnScoreUpLimit/FcgidSpawnScoreUpLimit/g +s/^TerminationScore/FcgidTerminationScore/g +s/^TimeScore/FcgidTimeScore/g +s/^ZombieScanInterval/FcgidZombieScanInterval/g +# Next we fix all other occurences without matching +# the ones, that are already OK +s/\([^d]\)BusyScanInterval/\1FcgidBusyScanInterval/g +s/\([^d]\)BusyTimeout/\1FcgidBusyTimeout/g +s/\([^d]\)DefaultInitEnv/\1FcgidInitialEnv/g +s/\([^d]\)DefaultMaxClassProcessCount/\1FcgidMaxProcessesPerClass/g +s/\([^d]\)DefaultMinClassProcessCount/\1FcgidMinProcessesPerClass/g +s/\([^d]\)ErrorScanInterval/\1FcgidErrorScanInterval/g +s/\([^d]\)FastCgiAccessChecker/\1FcgidAccessChecker/g +s/\([^d]\)FastCgiAccessCheckerAuthoritative/\1FcgidAccessCheckerAuthoritative/g +s/\([^d]\)FastCgiAuthenticator/\1FcgidAuthenticator/g +s/\([^d]\)FastCgiAuthenticatorAuthoritative/\1FcgidAuthenticatorAuthoritative/g +s/\([^d]\)FastCgiAuthorizer/\1FcgidAuthorizer/g +s/\([^d]\)FastCgiAuthorizerAuthoritative/\1FcgidAuthorizerAuthoritative/g +s/\([^d]\)FCGIWrapper/\1FcgidWrapper/g +s/\([^d]\)IdleScanInterval/\1FcgidIdleScanInterval/g +s/\([^d]\)IdleTimeout/\1FcgidIdleTimeout/g +s/\([^d]\)IPCCommTimeout/\1FcgidIOTimeout/g +s/\([^d]\)IPCConnectTimeout/\1FcgidConnectTimeout/g +s/\([^d]\)MaxProcessCount/\1FcgidMaxProcesses/g +s/\([^d]\)MaxRequestInMem/\1FcgidMaxRequestInMem/g +s/\([^d]\)MaxRequestLen/\1FcgidMaxRequestLen/g +s/\([^d]\)MaxRequestsPerProcess/\1FcgidMaxRequestsPerProcess/g +s/\([^d]\)OutputBufferSize/\1FcgidOutputBufferSize/g +s/\([^d]\)PassHeader/\1FcgidPassHeader/g +s/\([^d]\)PHP_Fix_Pathinfo_Enable/\1FcgidFixPathinfo/g +s/\([^d]\)ProcessLifeTime/\1FcgidProcessLifeTime/g +s/\([^d]\)SharememPath/\1FcgidProcessTableFile/g +s/\([^d]\)SocketPath/\1FcgidIPCDir/g +s/\([^d]\)SpawnScore/\1FcgidSpawnScore/g +s/\([^d]\)SpawnScoreUpLimit/\1FcgidSpawnScoreUpLimit/g +s/\([^d]\)TerminationScore/\1FcgidTerminationScore/g +s/\([^d]\)TimeScore/\1FcgidTimeScore/g +s/\([^d]\)ZombieScanInterval/\1FcgidZombieScanInterval/g diff --git a/configure.apxs b/configure.apxs new file mode 100755 index 0000000..452674c --- /dev/null +++ b/configure.apxs @@ -0,0 +1,124 @@ +#!/bin/sh +# +# configure.apxs --- build configuration script for creating mod_fcgid +# out of tree using the apxs utility and httpd build toolset +# +if test "$APXS" = ""; then + APXS=`which apxs 2>/dev/null` +fi; + +if test "$APXS" = ""; then + echo $0 must be able to find apxs in your path, + echo or the environment variable APXS must provide the full path of APXS, + echo or you may specify it with: + echo + echo APXS=/path/to/apxs $0 + echo + echo configuration failed + exit 1 +fi + +echo Configuring mod_fcgid for APXS in $APXS + +# top_builddir and top_srcdir are a misnomers, because build/*.mk +# scripts expect them to be the parent of the build directory +# they fail to use the $installbuilddir path. +exp_installbuilddir=`$APXS -q exp_installbuilddir` +top_installbuilddir=`cd $exp_installbuilddir/..; pwd` +top_installbuilddir=`echo $exp_installbuilddir | sed -e "s#/[^/]*\\\$##;"` + +builddir=`pwd` +srcdir=$builddir +fcgid_builddir=$builddir +fcgid_srcdir=$builddir + +# prefix is the default @@ServerRoot@@, where libexecdir/sysconfdir may +# be relative (if it is not their prefix, the rel_ paths remain unchanged +rel_fix_prefix=`$APXS -q prefix` +rel_libexecdir=`$APXS -q exp_libexecdir | sed -e "s#^$rel_fix_prefix/##;"` +rel_sysconfdir=`$APXS -q exp_sysconfdir | sed -e "s#^$rel_fix_prefix/##;"` +rel_logfiledir=`$APXS -q exp_logfiledir | sed -e "s#^$rel_fix_prefix/##;"` +httpd_conffile=`$APXS -q exp_sysconfdir`/`$APXS -q progname`.conf + +if test -z "$AWK"; then + AWK=`$APXS -q AWK` +fi + +for i in Makefile build/Makefile modules/fcgid/Makefile modules/fcgid/modules.mk; do + l_r=`echo $i|sed -e "s#/*[^/]*\\\$##;s#^\(..*\)\\\$#/\1#"` + sed -e "s#^\(exp_installbuilddir\)=.*#\1=$exp_installbuilddir#;" \ + -e "s#^\(include\) \$(exp_installbuilddir)#\1 $exp_installbuilddir#;" \ + -e "s#^\(top_builddir\)=.*#\1=$top_installbuilddir#;" \ + -e "s#^\(top_srcdir\)=.*#\1=$top_installbuilddir#;" \ + -e "s#^\(fcgid_srcdir\)=.*#\1=$srcdir#;" \ + -e "s#^\(fcgid_builddir\)=.*#\1=$builddir#;" \ + -e "s#^\(srcdir\)=.*#\1=$srcdir$l_r#;" \ + -e "s#^\(builddir\)=.*#\1=$builddir$l_r#;" \ + -e "s#^\(rel_libexecdir\)=.*#\1=$rel_libexecdir#;" \ + -e "s#^\(rel_sysconfdir\)=.*#\1=$rel_sysconfdir#;" \ + -e "s#^\(rel_logfiledir\)=.*#\1=$rel_logfiledir#;" \ + -e "s#^\(httpd_conffile\)=.*#\1=$httpd_conffile#;" \ + -e "s#^\(awk\)=.*#\1=$AWK#;" \ + < $i.apxs > $i +done + +touch .deps +touch build/.deps +touch modules/fcgid/.deps + +cd build +found_features="" +echo "Detecting features" +echo "Detecting features" > ../config.apxs.log + +#test invocations follow the pattern; +# +#if make local-clean conftest_foofn >>../config.apxs.log 2>&1; then +# found_features="$found_features \ +# -e \"s/^#undef \(HAVE_FOOFN\)[ \t]*/#define \1 1/;\"" +#fi + +if make local-clean conftest_sys_file_h >>../config.apxs.log 2>&1; then + found_features="$found_features \ + -e \"s/^#undef \(HAVE_SYS_FILE_H\)[ \t]*/#define \1 1/;\"" +fi + +if make local-clean conftest_sys_mman_h >>../config.apxs.log 2>&1; then + found_features="$found_features \ + -e \"s/^#undef \(HAVE_SYS_MMAN_H\)[ \t]*/#define \1 1/;\"" +fi + +if make local-clean conftest_sys_mutex_h >>../config.apxs.log 2>&1; then + found_features="$found_features \ + -e \"s/^#undef \(HAVE_SYS_MUTEX_H\)[ \t]*/#define \1 1/;\"" +fi + +if make local-clean conftest_sys_shm_h >>../config.apxs.log 2>&1; then + found_features="$found_features \ + -e \"s/^#undef \(HAVE_SYS_SHM_H\)[ \t]*/#define \1 1/;\"" +fi + +make local-distclean >>../config.apxs.log 2>&1 +cd .. + +if test "x$found_features" = "x"; then + cp modules/fcgid/fcgid_config.h.in modules/fcgid/fcgid_config.h +else + eval sed "$found_features" < modules/fcgid/fcgid_config.h.in \ + > modules/fcgid/fcgid_config.h +fi + +echo "" +echo "Finished, run 'make' to compile mod_fcgid" +echo "" +echo "Run 'make install' to install mod_fcgid" +echo "" +#echo "The manual pages fcgid/index.html and mod/mod_fcgid.html" +#echo "will be installed to help get you started." +#echo +#echo "The conf/extra/httpd-fcgid.conf will be installed as an example" +#echo "for you to work from. In your configuration file," +#echo " `$APXS -q exp_sysconfdir`/`$APXS -q progname`.conf" +#echo "uncomment the line '#Include conf/extra/httpd-fcgid.conf'" +#echo "to activate this example mod_fcgid configuration." + diff --git a/docs/manual/mod/mod_fcgid.html b/docs/manual/mod/mod_fcgid.html new file mode 100644 index 0000000..633c233 --- /dev/null +++ b/docs/manual/mod/mod_fcgid.html @@ -0,0 +1,5 @@ +# GENERATED FROM XML -- DO NOT EDIT + +URI: mod_fcgid.html.en +Content-Language: en +Content-type: text/html; charset=ISO-8859-1 diff --git a/docs/manual/mod/mod_fcgid.html.en b/docs/manual/mod/mod_fcgid.html.en new file mode 100644 index 0000000..2e951d3 --- /dev/null +++ b/docs/manual/mod/mod_fcgid.html.en @@ -0,0 +1,1297 @@ + + + +mod_fcgid - Apache HTTP Server + + + + + + +
<-
+ +
+

Apache Module mod_fcgid

+
+

Available Languages:  en 

+
+ + + + +
Description:Provides for execution of FastCGI applications
Status:External
Module�Identifier:fcgid_module
Source�File:mod_fcgid.c
Compatibility:Apache 2.0 and higher
+

Summary

+ +

Any program assigned to the handler fcgid-script is processed + using the FastCGI protocol; mod_fcgid starts a sufficient + number instances of the program to handle concurrent requests, and these + programs remain running to handle further incoming requests. This is + significantly faster than using the default mod_cgi or + mod_cgid modules to launch the program upon each request. + However, the programs invoked by mod_fcgid continue to + consume resources, so the administrator must weigh the impact of invoking + a particular program once per request against the resources required to + leave a sufficient number of instances running continuously.

+ +

The pool of fcgid-invoked programs is shared between all httpd workers. + Configuration directives below let the administrator tune the number of + instances of the program that will run concurrently.

+ +

Specific executables are assigned this handler either by having a name + containing an extension defined by the + AddHandler directive, or with an + override using the SetHandler + directive (e.g., for all files in a specific directory such as cgi-bin).

+ +

Some changes have been made in the ASF release of mod_fcgid which + can affect existing configurations. All documentation refers to new + names for the directives. (The old names still work but are now + deprecated.) Please read the Upgrade Notes for + details.

+ +

For an introduction to using CGI scripts with Apache, see + the generic tutorial on Dynamic Content + With CGI.

+ +
+ +
top
+
+

Upgrade Notes

+ +

The following changes have been made in the ASF release of mod_fcgid + and should be considered when upgrading from the original version by + Ryan Pan (Pan Qingfeng).

+ + +
top
+
+

Examples

+ + +

Note

+

The examples assume that mod_fcgid and other necessary + modules are loaded into the server already, either built-in or + via the LoadModule + directive.

+ +

Additionally, the example configurations provide full access + to the applications using access control directives which work + with Apache 2.0 and 2.2. These directives are not appropriate + for all environments, and they do not work for development + levels of Apache HTTP Server (Subversion trunk).

+
+ +

The first example is a very simple Perl FastCGI application, + and its configuration directives. This is typical for FastCGI + applications which require no special configuration.

+ +

Perl FastCGI application - /usr/local/apache/fcgi-bin/foo.pl

+ #!/usr/bin/perl
+ use CGI::Fast;
+
+ while (my $q = CGI::Fast->new) {
+ + print("Content-Type: text/plain\n\n");
+ foreach $var (sort(keys(%ENV))) {
+ + $val = $ENV{$var};
+ $val =~ s|\n|\\n|g;
+ $val =~ s|"|\\"|g;
+ print "${var}=\"${val}\"\n";
+
+ }
+
+ }
+

+ +

Configuration directives

+ <Directory /usr/local/apache/fcgi-bin/>
+ + SetHandler fcgid-script
+ Options +ExecCGI
+
+ # Customize the next two directives for your requirements.
+ Order allow,deny
+ Allow from all
+
+ </Directory>
+

+ +

PHP applications are usually configured using the + FcgidWrapper directive + and a corresponding wrapper script. The wrapper script can be + an appropriate place to define any environment variables required + by the application, such as PHP_FCGI_MAX_REQUESTS + or anything else. (Environment variables can also be set with + FcgidInitialEnv, + but they then apply to all applications.)

+ +

Here is an example that uses a wrapper script to invoke PHP:

+ +

PHP application - /usr/local/phpapp/phpinfo.php

+ <?php
+ + phpinfo();
+
+ ?>
+

+ +

Configuration directives

+ # FcgidMaxRequestsPerProcess should be <= PHP_FCGI_MAX_REQUESTS
+ # The example PHP wrapper script overrides the default PHP setting.
+ FcgidMaxRequestsPerProcess 10000
+
+ # Uncomment the following line if cgi.fix_pathinfo is set to 1 in
+ # php.ini:
+ # FcgidFixPathinfo 1
+
+ Alias /phpapp/ /usr/local/phpapp/
+ <Location /phpapp/>
+ + AddHandler fcgid-script .php
+ Options +ExecCGI
+ FcgidWrapper /usr/local/bin/php-wrapper .php
+
+ # Customize the next two directives for your requirements.
+ Order allow,deny
+ Allow from all
+
+ </Location>
+

+ +

PHP wrapper script - /usr/local/bin/php-wrapper

+ #!/bin/sh
+ # Set desired PHP_FCGI_* environment variables.
+ # Example:
+ # PHP FastCGI processes exit after 500 requests by default.
+ PHP_FCGI_MAX_REQUESTS=10000
+ export PHP_FCGI_MAX_REQUESTS
+
+ # Replace with the path to your FastCGI-enabled PHP executable
+ exec /usr/local/bin/php-cgi
+

+ +

Special PHP considerations

+

By default, PHP FastCGI processes exit after handling 500 + requests, and they may exit after this module has already + connected to the application and sent the next request. When that + occurs, an error will be logged and 500 Internal Server + Error will be returned to the client. This PHP behavior + can be disabled by setting PHP_FCGI_MAX_REQUESTS to + 0, but that can be a problem if the PHP application leaks + resources. Alternatively, PHP_FCGI_MAX_REQUESTS can + be set to a much higher value than the default to reduce the + frequency of this problem. + FcgidMaxRequestsPerProcess + can be set to a value less than or equal to + PHP_FCGI_MAX_REQUESTS to resolve the problem.

+ +

PHP child process management (PHP_FCGI_CHILDREN) + should always be disabled with mod_fcgid, which will only route + one request at a time to application processes it has spawned; + thus, any child processes created by PHP will not be used + effectively. (Additionally, the PHP child processes may not be + terminated properly.) By default, and with the environment + variable setting PHP_FCGI_CHILDREN=0, PHP child + process management is disabled.

+ +

The popular APC opcode cache for PHP cannot share a cache + between PHP FastCGI processes unless PHP manages the child + processes. Thus, the effectiveness of the cache is limited with + mod_fcgid; concurrent PHP requests will use different opcode + caches.

+
+ +
top
+
+

Process Management

+ + +

mod_fcgid has several types of controls which affect the creation + of additional application processes:

+ + + + + + + + + + + + + + +
Type of controlDirective
global limit on number of processesFcgidMaxProcesses
limit on number of processes per applicationFcgidMaxProcessesPerClass
limit on rate of spawning new application processesFcgidSpawnScoreUpLimit and + other score-related directives
+ +

mod_fcgid has several types of controls which affect the termination + of existing application processes:

+ + + + + + + + + + + + + + +
Type of controlDirective
termination after an idle periodFcgidIdleTimeout
termination after it handles a certain number of requestsFcgidMaxRequestsPerProcess
termination after a certain lifetimeFcgidProcessLifetime
+ +

Several of the directives control processing for a process + class. A process class is the set of processes which were started + with the same executable file and share certain other characteristics such + as virtual host and identity. Two commands which are links to or otherwise + refer to the same executable file share the same process class.

+ +

Note

+

Certain settings or other concepts that depend on the virtual host, + such as FcgidInitialEnv + or process classes, distinguish between virtual hosts only if they + have distinct server names. (See the ServerName + documentation for more information.) In the case of + FcgidInitialEnv, if two + virtual hosts have the same server name but different environments as + defined by + FcgidInitialEnv, the + environment used for a particular request will be that defined for the + virtual host of the request that caused the FastCGI process to be + started.

+
+ +

Information about each process will be displayed in the + mod_status server-status page.

+
+
top
+

FcgidAccessChecker Directive

+ + + + + + + + +
Description:full path to FastCGI access checker
Syntax:FcgidAccessChecker command
Default:none
Context:directory, .htaccess
Override:FileInfo
Status:External
Module:mod_fcgid
+

Access checking or, more formally, access control, is a procedure + which verifies that the client is allowed to access a resource, using + some mechanism other than authentication and authorization.

+ +

Key environment variables passed to the application for access + checking are:

+ +
+
FCGI_APACHE_ROLE
+
set to ACCESS_CHECKER; by checking the current role, + the same FastCGI application can handle multiple stages of request + processing
+
+ +

The application must output a Status line to indicate + the result of the check.

+ +

Warning

+

Before 2.3.6, only one FastCGI application of any type (AAA or handler) + can be used for a particular request URI. Otherwise, the wrong FastCGI + application may be invoked for one or more phases of request processing.

+
+ +
+
top
+

FcgidAccessCheckerAuthoritative Directive

+ + + + + + + + +
Description:Set to 'off' to allow access control to be passed along to lower modules upon failure
Syntax:FcgidAccessCheckerAuthoritative On|Off
Default:FcgidAccessCheckerAuthoritative On
Context:directory, .htaccess
Override:FileInfo
Status:External
Module:mod_fcgid
+

This directive controls whether or not other access checkers + are allowed to run when this module has an access checker configured + and it fails a request. If this directive is On (default) + and a FastCGI access checker returns a failure status, a failure is + returned to the client without giving other access checkers a chance to + allow access. If this directive is Off, other access + checkers will be called.

+ +
+
top
+

FcgidAuthenticator Directive

+ + + + + + + + +
Description:full path to FastCGI authenticator
Syntax:FcgidAuthenticator command
Default:none
Context:directory, .htaccess
Override:FileInfo
Status:External
Module:mod_fcgid
+

Authentication is the procedure which verifies that the user is + who they claim they are. This directive specifies the full path to + a FastCGI application which will handle authentication for a particular + context, such as a directory.

+ +

Key environment variables passed to the application on authentication + are:

+ +
+
REMOTE_USER
+
set to the user id of the client
+ +
REMOTE_PASSWD
+
set to the plain text password provided by the client
+ +
FCGI_APACHE_ROLE
+
set to AUTHENTICATOR; by checking the current role, + the same FastCGI application can handle multiple stages of request + processing
+
+ +

The application must output a Status line to indicate + the result of authentication.

+ +

Warning

+

Before 2.3.6, only one FastCGI application of any type (AAA or handler) + can be used for a particular request URI. Otherwise, the wrong FastCGI + application may be invoked for one or more phases of request processing.

+
+ +
+
top
+

FcgidAuthenticatorAuthoritative Directive

+ + + + + + + + +
Description:Set to 'off' to allow authentication to be passed along to lower modules upon failure
Syntax:FcgidAuthenticatorAuthoritative On|Off
Default:FcgidAuthenticatorAuthoritative On
Context:directory, .htaccess
Override:FileInfo
Status:External
Module:mod_fcgid
+

This directive controls whether or not other authenticators + are allowed to run when this module has an authenticator configured + and it fails a request. If this directive is On (default) + and a FastCGI authenticator returns a failure status, a failure is + returned to the client without giving other authenticators a chance to + validate the client identity. If this directive is Off, + other authenticators will be called.

+ +
+
top
+

FcgidAuthorizer Directive

+ + + + + + + + +
Description:full path to FastCGI authorizer
Syntax:FcgidAuthorizer command
Default:none
Context:directory, .htaccess
Override:FileInfo
Status:External
Module:mod_fcgid
+

Authorization is the procedure which verifies that the user is + allowed to access a particular resource. This directive specifies + the full path to a FastCGI application which will handle authorization + for a particular context, such as a directory.

+ +

Key environment variables passed to the application on authorization + are:

+ +
+
REMOTE_USER
+
set to the user id of the client, which has already been + authenticated
+ +
FCGI_APACHE_ROLE
+
set to AUTHORIZER; by checking the current role, the + same FastCGI application can handle multiple stages of request + processing
+
+ +

The application must output a Status line to indicate + the result of authorization.

+ +

Warning

+

Before 2.3.6, only one FastCGI application of any type (AAA or handler) + can be used for a particular request URI. Otherwise, the wrong FastCGI + application may be invoked for one or more phases of request processing.

+
+ +
+
top
+

FcgidAuthorizerAuthoritative Directive

+ + + + + + + + +
Description:Set to 'off' to allow authorization to be passed along to lower modules upon failure
Syntax:FcgidAuthorizerAuthoritative On|Off
Default:FcgidAuthorizerAuthoritative On
Context:directory, .htaccess
Override:FileInfo
Status:External
Module:mod_fcgid
+

This directive controls whether or not other authorizers + are allowed to run when this module has an authorizer configured + and it fails a request. If this directive is On (default) + and a FastCGI authorizer returns a failure status, a failure is + returned to the client without giving other authorizer a chance to + access the resource. If this directive is Off, other + authorizers will be called.

+ +
+
top
+

FcgidBusyScanInterval Directive

+ + + + + + + +
Description:scan interval for busy timeout process
Syntax:FcgidBusyScanInterval seconds
Default:FcgidBusyScanInterval 120
Context:server config
Status:External
Module:mod_fcgid
+

The module performs the + FcgidBusyTimeout check at this + interval.

+ +
+
top
+

FcgidBusyTimeout Directive

+ + + + + + + +
Description:a FastCGI application will be killed after handling a request for FcgidBusyTimeout
Syntax:FcgidBusyTimeout seconds
Default:FcgidBusyTimeout 300
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This is the maximum time limit for request handling. If a FastCGI + request does not complete within FcgidBusyTimeout seconds, it will be + subject to termination. Because the check is performed at the + interval defined by FcgidBusyScanInterval, + request handling may be allowed to proceed for a longer period of time.

+ +

The purpose of this directive is to terminate hung applications. + The default timeout may need to be increased for applications that + can take longer to process the request.

+ +
+
top
+

FcgidCmdOptions Directive

+ + + + + + +
Description:Set processing options for a FastCGI + command
Syntax:FcgidCmdOptions command option + [option] ...
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This directive allows processing options to be specified for + a specific command spawned by mod_fcgid. Each option for the + command corresponds to another directive that normally applies to + all commands started within a particular context. If a + particular option is not specified on this directive, the + default will be used.

+ +

The following table provides a list of options and + corresponding directives:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Option name and syntaxCorresponding directive
ConnectTimeout secondsFcgidConnectTimeout
IdleTimeout secondsFcgidIdleTimeout
InitialEnv name[=value]FcgidInitialEnv
IOTimeout secondsFcgidIOTimeout
MaxProcesses valueFcgidMaxProcessesPerClass
MaxProcessLifeTime secondsFcgidProcessLifeTime
MaxRequestsPerProcess valueFcgidMaxRequestsPerProcess
MinProcesses valueFcgidMinProcessesPerClass
+ +

Multiple environment variables are defined by repeating + the InitialEnv option.

+ +

Example

+ FcgidCmdOptions /usr/local/bin/wrapper \
+ + InitialEnv MAX_REQUESTS=2000 \
+ MaxRequestsPerProcess 2000 \
+ IOTimeout 90
+
+

+ +

When /usr/local/bin/wrapper is spawned, its initial + environment contains the MAX_REQUESTS=2000 + environment variable setting; additionally, mod_fcgid will + terminate it after it has handled 2000 requests, and I/O + operations will time out after 90 seconds. Directives + corresponding to other options, such as + FcgidIdleTimeout or + FcgidProcessLifeTime, + will be ignored for this command; defaults will be used for options + not specified on FcgidCmdOptions.

+ +
+
top
+

FcgidConnectTimeout Directive

+ + + + + + + +
Description:Connect timeout to FastCGI server
Syntax:FcgidConnectTimeout seconds
Default:FcgidConnectTimeout 3
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This is the maximum period of time the module will wait + while trying to connect to a FastCGI application on Windows. + (This directive is not respected on Unix, where AF_UNIX defaults + will apply.)

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidErrorScanInterval Directive

+ + + + + + + +
Description:scan interval for exited process
Syntax:FcgidErrorScanInterval seconds
Default:FcgidErrorScanInterval 3
Context:server config
Status:External
Module:mod_fcgid
+

This is the interval at which the module will handle + pending process termination. Termination is pending for + any processes which have exceeded + FcgidIdleTimeout or + FcgidProcessLifeTime.

+ +

Unix: mod_fcgid will terminate such processes with SIGTERM; + if the process is still active during the next scan, the process + will be terminated with SIGKILL. Thus, this directive controls the + amount of time for orderly process terminate before being forcibly + killed.

+ +
+
top
+

FcgidFixPathinfo Directive

+ + + + + + + +
Description:Mirror the PHP cgi.fix_pathinfo + setting
Syntax:FcgidFixPathinfo 1
Default:FcgidFixPathinfo 0
Context:server config
Status:External
Module:mod_fcgid
+

This directive enables special SCRIPT_NAME + processing which allows PHP to provide additional path information. + The setting of FcgidFixPathinfo + should mirror the cgi.fix_pathinfo setting in + php.ini.

+ +
+
top
+

FcgidIdleScanInterval Directive

+ + + + + + + +
Description:scan interval for idle timeout process
Syntax:FcgidIdleScanInterval seconds
Default:FcgidIdleScanInterval 120
Context:server config
Status:External
Module:mod_fcgid
+

This is the interval at which the module will search for + processes which have exceeded + FcgidIdleTimeout or + FcgidProcessLifeTime.

+ +
+
top
+

FcgidIdleTimeout Directive

+ + + + + + + +
Description:An idle FastCGI application will be killed after FcgidIdleTimeout
Syntax:FcgidIdleTimeout seconds
Default:FcgidIdleTimeout 300
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

Application processes which have not handled a request for this + period of time will be terminated, if the number of processses for the + class exceeds + FcgidMinProcessesPerClass. + A value of 0 disables the check.

+ +

This idle timeout check is performed at the frequency of the configured + FcgidIdleScanInterval.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidInitialEnv Directive

+ + + + + + + +
Description:an environment variable name and optional value to pass to FastCGI.
Syntax:FcgidInitialEnv name [ value ]
Default:none
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

Use FcgidInitialEnv to define environment + variables to pass to the FastCGI application. This directive can + be used multiple times.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidIOTimeout Directive

+ + + + + + + +
Description:Communication timeout to FastCGI server
Syntax:FcgidIOTimeout seconds
Default:FcgidIOTimeout 40
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This is the maximum period of time the module will wait + while trying to read from or write to a FastCGI application.

+ +

Note

+

The FastCGI application must begin generating the response within + this period of time. Increase this directive as necessary to handle + applications which take a relatively long period of time to respond.

+
+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidIPCDir Directive

+ + + + + + + +
Description:directory for AF_UNIX sockets (Unix) or pipes (Windows)
Syntax:FcgidIPCDir pathname
Default:FcgidIPCDir logs/fcgidsock
Context:server config
Status:External
Module:mod_fcgid
+

This module uses AF_UNIX sockets or named pipes, depending on the + platform, to communicate with FastCGI applications. This directive + specifies the directory where those sockets or named pipes will be + created.

+ +
+
top
+

FcgidMaxProcesses Directive

+ + + + + + + +
Description:maximum number of FastCGI application processes
Syntax:FcgidMaxProcesses value
Default:FcgidMaxProcesses 1000
Context:server config
Status:External
Module:mod_fcgid
+

This directive sets the maximum number of FastCGI application + processes which can be active at one time.

+ +
+
top
+

FcgidMaxProcessesPerClass Directive

+ + + + + + + +
Description:Max process count of one class of FastCGI application
Syntax:FcgidMaxProcessesPerClass value
Default:FcgidMaxProcessesPerClass 100
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This directive sets the maximum number of processes that can be + started for each process class.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidMaxRequestInMem Directive

+ + + + + + + +
Description:maximum size of a request which will be held in memory
Syntax:FcgidMaxRequestInMem bytes
Default:FcgidMaxRequestInMem 65536
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This module reads the entire request body from the client + before sending it to the application. Normally the request body + will be stored in memory. Once the amount of request body read + from the client exceeds FcgidMaxRequestInMem + bytes, the remainder of the request body will be stored in a + temporary file.

+ +
+
top
+

FcgidMaxRequestLen Directive

+ + + + + + + +
Description:maximum HTTP request length
Syntax:FcgidMaxRequestLen bytes
Default:FcgidMaxRequestLen 131072
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

If the size of the request body exceeds this amount, the + request will fail with 500 Server Error.

+ +

Administrators should change this to an appropriate value for their site based + on application requirements.

+ +

Warning

+

Before 2.3.6, this defaulted to 1GB. Most users of earlier versions should + use this directive to set a more reasonable limit.

+
+ +

See also

+ +
+
top
+

FcgidMaxRequestsPerProcess Directive

+ + + + + + + +
Description:Max requests handled by each FastCGI application
Syntax:FcgidMaxRequestsPerProcess value
Default:FcgidMaxRequestsPerProcess 0
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

FastCGI application processes will be terminated after handling + the specified number of requests. A value of 0 + disables the check.

+

Note

+

A value of -1 is currently accepted for ease of + migration for existing configurations. It is treated the same as + 0.

+
+

Certain applications, notably PHP as FastCGI, have their own + facility for terminating after handling a certain number of + requests. This directive can be used to avoid sending + additional requests to the application after it has handled its + limit.

+

Note

+

If this is set such that frequent process creation will be + required, you will likely need to adjust + FcgidSpawnScoreUpLimit + or other score-related directives to allow more frequent process + creation.

+
+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidMinProcessesPerClass Directive

+ + + + + + + +
Description:Min process count of one class of FastCGI application
Syntax:FcgidMinProcessesPerClass value
Default:FcgidMinProcessesPerClass 3
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This directive sets the minimum number of processes that will be + retained in a process class after finishing requests.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidOutputBufferSize Directive

+ + + + + + + +
Description:CGI output buffer size
Syntax:FcgidOutputBufferSize bytes
Default:FcgidOutputBufferSize 65536
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This is the maximum amount of response data the module will read + from the FastCGI application before flushing the data to the client.

+ +
+
top
+

FcgidPassHeader Directive

+ + + + + + + +
Description:Header name which will be passed to FastCGI as environment variable.
Syntax:FcgidPassHeader name
Default:none
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

This directive specifies the name of a request header which + will be passed to the FastCGI application as an environment + variable. The name of the environment variable is derived from + the value specified on this directive, as discussed below:

+ +

The legacy behavior is to use the value specified on this directive + as the environment variable name, converting hyphens to underscores. + No case conversion is performed.

+ +

Beginning with release 2.3.6, an additional environment variable + is created. The value specified on this directive is converted to + upper case, prefixed with HTTP_, and hyphens are + converted to underscores.

+ +

Note

+

Most request headers are already available to the application + as environment variables, and generally are prefixed with + HTTP_. (Notable exceptions are Content-type + and Content-length, which do not have the + HTTP_ prefix.) Thus, this directive is only required + for request headers that are purposefully omitted, such as + Authorization and Proxy-Authorization. + Only pass these request headers if absolutely required.

+
+ +
+
top
+

FcgidProcessLifeTime Directive

+ + + + + + + +
Description:maximum FastCGI application process lifetime
Syntax:FcgidProcessLifeTime seconds
Default:FcgidProcessLifeTime 3600
Context:server config, virtual host
Status:External
Module:mod_fcgid
+

Idle application processes which have existed for greater + than this time will be terminated, if the number of processses for the + class exceeds + FcgidMinProcessesPerClass. + A value of 0 disables the check.

+ +

This process lifetime check is performed at the frequency of the configured + FcgidIdleScanInterval.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+ +
+
top
+

FcgidProcessTableFile Directive

+ + + + + + + +
Description:shared memory file path
Syntax:FcgidProcessTableFile pathname
Default:FcgidProcessTableFile logs/fcgid_shm
Context:server config
Status:External
Module:mod_fcgid
+

This module uses shared memory on Unix to maintain state which + is shared between httpd processes. This directive specifies the + name of the shared memory file.

+ +
+
top
+

FcgidSpawnScore Directive

+ + + + + + + +
Description:Each spawn adds this value to the process activity score.
Syntax:FcgidSpawnScore value
Default:FcgidSpawnScore 1
Context:server config
Status:External
Module:mod_fcgid
+

Lower values of this directive increase the allowed spawn rate.

+ +

Refer to the FcgidSpawnScoreUpLimit + directive for more information.

+ +
+
top
+

FcgidSpawnScoreUpLimit Directive

+ + + + + + + +
Description:Maximum value of the process activity score which allows + a spawn to occur
Syntax:FcgidSpawnScoreUpLimit value
Default:FcgidSpawnScoreUpLimit 10
Context:server config
Status:External
Module:mod_fcgid
+

A process activity score is maintained for each FastCGI application; + the score is used to control the rate of spawning in order to avoid + placing too much load on the system, particularly for applications that + are repeatedly exiting abnormally.

+ +

The value of FcgidSpawnScore + is added to the score for every spawned application process. The value of + FcgidTerminationScore is added + to the score for every terminated application process. The value of + FcgidTimeScore is subtracted + from the score every second.

+ +

When the current score is higher than the value of + FcgidSpawnScoreUpLimit, no additional application + processes will be spawned; subsequent requests must wait until an existing + process is free or until the score decreases below the limit.

+ +

If the limit is reached under normal load, it may not be sufficient to + simply increase the limit, as that would only delay the amount of time + before the limit is reached again. Decrease the value of + FcgidSpawnScore and/or + FcgidTerminationScore, or + increase the value of FcgidTimeScore, + to allow a higher rate of spawning.

+ +
+
top
+

FcgidTerminationScore Directive

+ + + + + + + +
Description:Each terminated process adds this value to the process activity + score.
Syntax:FcgidTerminationScore value
Default:FcgidTerminationScore 2
Context:server config
Status:External
Module:mod_fcgid
+

Lower values of this directive increase the allowed spawn rate. Negative + values can be useful in some circumstances, such as allowing process + replacement without increasing the score.

+ +

Refer to the FcgidSpawnScoreUpLimit + directive for more information.

+ +
+
top
+

FcgidTimeScore Directive

+ + + + + + + +
Description:Amount subtracted from process activity score each + second
Syntax:FcgidTimeScore value
Default:FcgidTimeScore 1
Context:server config
Status:External
Module:mod_fcgid
+

Higher values of this directive increase the allowed spawn rate.

+ +

Refer to the FcgidSpawnScoreUpLimit + directive for more information.

+ +
+
top
+

FcgidWin32PreventOrphans Directive

+ + + + + + + +
Description:Job Control orphan prevention for fcgi workers.
Syntax:FcgidWin32PreventOrphans On|Off
Default:FcgidWin32PreventOrphans Off
Context:server config
Status:External
Module:mod_fcgid
+

Uses Job Control Objects on Windows, only, to enforce shutdown of all + fcgi processes created by the httpd worker when the httpd worker has been + terminated. Processes terminated in this way do not have the opportunity + to clean up gracefully, complete pending disk writes, or similar closure + transactions, therefore this behavior is experimental and disabled, by + default.

+ +
+
top
+

FcgidWrapper Directive

+ + + + + + + + +
Description:The CGI wrapper setting
Syntax:FcgidWrapper command [ suffix ] [ virtual ]
Default:none
Context:server config, virtual host, directory, .htaccess
Override:FileInfo
Status:External
Module:mod_fcgid
+

The given command is used to spawn FCGI server processes. If this directive + is not used, the file pointed to by the request URL will be used instead. + Options for the command can be included using quotation marks surrounding + the command and options.

+

The optional suffix argument restricts the use of this FCGI + server to all URLs with the given exact path suffix. A suffix needs to start + with '.'.

+

The virtual flag signals that there will be no check + whether the request URL actually points to an existing file. The only + file which needs to exist is the wrapper itself.

+

The directive can be used multiple times. A wrapper defined without a suffix + is used as a default in case no suffix matches.

+ +
+
top
+

FcgidZombieScanInterval Directive

+ + + + + + + +
Description:scan interval for zombie process
Syntax:FcgidZombieScanInterval seconds
Default:FcgidZombieScanInterval 3
Context:server config
Status:External
Module:mod_fcgid
+

The module checks for exited FastCGI applications at this interval. + During this period of time, the application may exist in the process + table as a zombie (on Unix).

+ +
+
+
+

Available Languages:  en 

+
+ \ No newline at end of file diff --git a/docs/manual/mod/mod_fcgid.xml b/docs/manual/mod/mod_fcgid.xml new file mode 100644 index 0000000..f510a90 --- /dev/null +++ b/docs/manual/mod/mod_fcgid.xml @@ -0,0 +1,1181 @@ + + + + + + + mod_fcgid + Provides for execution of FastCGI applications + External + mod_fcgid.c + fcgid_module + Apache 2.0 and higher + + +

Any program assigned to the handler fcgid-script is processed + using the FastCGI protocol; mod_fcgid starts a sufficient + number instances of the program to handle concurrent requests, and these + programs remain running to handle further incoming requests. This is + significantly faster than using the default mod_cgi or + mod_cgid modules to launch the program upon each request. + However, the programs invoked by mod_fcgid continue to + consume resources, so the administrator must weigh the impact of invoking + a particular program once per request against the resources required to + leave a sufficient number of instances running continuously.

+ +

The pool of fcgid-invoked programs is shared between all httpd workers. + Configuration directives below let the administrator tune the number of + instances of the program that will run concurrently.

+ +

Specific executables are assigned this handler either by having a name + containing an extension defined by the + AddHandler directive, or with an + override using the SetHandler + directive (e.g., for all files in a specific directory such as cgi-bin).

+ +

Some changes have been made in the ASF release of mod_fcgid which + can affect existing configurations. All documentation refers to new + names for the directives. (The old names still work but are now + deprecated.) Please read the Upgrade Notes for + details.

+ +

For an introduction to using CGI scripts with Apache, see + the generic tutorial on Dynamic Content + With CGI.

+ +
+ + + Dynamic Content With CGI + mod_cgi + mod_cgid + +
+ Upgrade Notes +

The following changes have been made in the ASF release of mod_fcgid + and should be considered when upgrading from the original version by + Ryan Pan (Pan Qingfeng).

+
    +
  • All directives have been renamed in order to use a common prefix "Fcgid". + Underscores in directive names have been eliminated in favor of + CamelCase. The old directive names will still work but are deprecated. + To fix your configuration you can use the sed script build/fixconf.sed. + The following table contains old and new directive names: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Old NameNew Name
    BusyScanIntervalFcgidBusyScanInterval
    BusyTimeoutFcgidBusyTimeout
    DefaultInitEnvFcgidInitialEnv
    DefaultMaxClassProcessCountFcgidMaxProcessesPerClass
    DefaultMinClassProcessCountFcgidMinProcessesPerClass
    ErrorScanIntervalFcgidErrorScanInterval
    FastCgiAccessCheckerFcgidAccessChecker
    FastCgiAccessCheckerAuthoritativeFcgidAccessCheckerAuthoritative
    FastCgiAuthenticatorFcgidAuthenticator
    FastCgiAuthenticatorAuthoritativeFcgidAuthenticatorAuthoritative
    FastCgiAuthorizerFcgidAuthorizer
    FastCgiAuthorizerAuthoritativeFcgidAuthorizerAuthoritative
    FCGIWrapperFcgidWrapper
    IdleScanIntervalFcgidIdleScanInterval
    IdleTimeoutFcgidIdleTimeout
    IPCCommTimeoutFcgidIOTimeout
    IPCConnectTimeoutFcgidConnectTimeout
    MaxProcessCountFcgidMaxProcesses
    MaxRequestInMemFcgidMaxRequestInMem
    MaxRequestLenFcgidMaxRequestLen
    MaxRequestsPerProcessFcgidMaxRequestsPerProcess
    OutputBufferSizeFcgidOutputBufferSize
    PassHeaderFcgidPassHeader
    PHP_Fix_Pathinfo_EnableFcgidFixPathinfo
    ProcessLifeTimeFcgidProcessLifeTime
    SharememPathFcgidProcessTableFile
    SocketPathFcgidIPCDir
    SpawnScoreFcgidSpawnScore
    SpawnScoreUpLimitFcgidSpawnScoreUpLimit
    TerminationScoreFcgidTerminationScore
    TimeScoreFcgidTimeScore
    ZombieScanIntervalFcgidZombieScanInterval
    +
  • +
+ +
+ +
+ Examples + + Note +

The examples assume that mod_fcgid and other necessary + modules are loaded into the server already, either built-in or + via the LoadModule + directive.

+ +

Additionally, the example configurations provide full access + to the applications using access control directives which work + with Apache 2.0 and 2.2. These directives are not appropriate + for all environments, and they do not work for development + levels of Apache HTTP Server (Subversion trunk).

+
+ +

The first example is a very simple Perl FastCGI application, + and its configuration directives. This is typical for FastCGI + applications which require no special configuration.

+ + Perl FastCGI application - /usr/local/apache/fcgi-bin/foo.pl + #!/usr/bin/perl
+ use CGI::Fast;
+
+ while (my $q = CGI::Fast->new) {
+ + print("Content-Type: text/plain\n\n");
+ foreach $var (sort(keys(%ENV))) {
+ + $val = $ENV{$var};
+ $val =~ s|\n|\\n|g;
+ $val =~ s|"|\\"|g;
+ print "${var}=\"${val}\"\n";
+
+ }
+
+ }
+
+ + Configuration directives + <Directory /usr/local/apache/fcgi-bin/>
+ + SetHandler fcgid-script
+ Options +ExecCGI
+
+ # Customize the next two directives for your requirements.
+ Order allow,deny
+ Allow from all
+
+ </Directory>
+
+ +

PHP applications are usually configured using the + FcgidWrapper directive + and a corresponding wrapper script. The wrapper script can be + an appropriate place to define any environment variables required + by the application, such as PHP_FCGI_MAX_REQUESTS + or anything else. (Environment variables can also be set with + FcgidInitialEnv, + but they then apply to all applications.)

+ +

Here is an example that uses a wrapper script to invoke PHP:

+ + PHP application - /usr/local/phpapp/phpinfo.php + <?php
+ + phpinfo();
+
+ ?>
+
+ + Configuration directives + # FcgidMaxRequestsPerProcess should be <= PHP_FCGI_MAX_REQUESTS
+ # The example PHP wrapper script overrides the default PHP setting.
+ FcgidMaxRequestsPerProcess 10000
+
+ # Uncomment the following line if cgi.fix_pathinfo is set to 1 in
+ # php.ini:
+ # FcgidFixPathinfo 1
+
+ Alias /phpapp/ /usr/local/phpapp/
+ <Location /phpapp/>
+ + AddHandler fcgid-script .php
+ Options +ExecCGI
+ FcgidWrapper /usr/local/bin/php-wrapper .php
+
+ # Customize the next two directives for your requirements.
+ Order allow,deny
+ Allow from all
+
+ </Location>
+
+ + PHP wrapper script - /usr/local/bin/php-wrapper + #!/bin/sh
+ # Set desired PHP_FCGI_* environment variables.
+ # Example:
+ # PHP FastCGI processes exit after 500 requests by default.
+ PHP_FCGI_MAX_REQUESTS=10000
+ export PHP_FCGI_MAX_REQUESTS
+
+ # Replace with the path to your FastCGI-enabled PHP executable
+ exec /usr/local/bin/php-cgi
+
+ + Special PHP considerations +

By default, PHP FastCGI processes exit after handling 500 + requests, and they may exit after this module has already + connected to the application and sent the next request. When that + occurs, an error will be logged and 500 Internal Server + Error will be returned to the client. This PHP behavior + can be disabled by setting PHP_FCGI_MAX_REQUESTS to + 0, but that can be a problem if the PHP application leaks + resources. Alternatively, PHP_FCGI_MAX_REQUESTS can + be set to a much higher value than the default to reduce the + frequency of this problem. + FcgidMaxRequestsPerProcess + can be set to a value less than or equal to + PHP_FCGI_MAX_REQUESTS to resolve the problem.

+ +

PHP child process management (PHP_FCGI_CHILDREN) + should always be disabled with mod_fcgid, which will only route + one request at a time to application processes it has spawned; + thus, any child processes created by PHP will not be used + effectively. (Additionally, the PHP child processes may not be + terminated properly.) By default, and with the environment + variable setting PHP_FCGI_CHILDREN=0, PHP child + process management is disabled.

+ +

The popular APC opcode cache for PHP cannot share a cache + between PHP FastCGI processes unless PHP manages the child + processes. Thus, the effectiveness of the cache is limited with + mod_fcgid; concurrent PHP requests will use different opcode + caches.

+
+ +
+ +
+ Process Management + +

mod_fcgid has several types of controls which affect the creation + of additional application processes:

+ + + + + + + + + + + + + + + +
Type of controlDirective
global limit on number of processesFcgidMaxProcesses
limit on number of processes per applicationFcgidMaxProcessesPerClass
limit on rate of spawning new application processesFcgidSpawnScoreUpLimit and + other score-related directives
+ +

mod_fcgid has several types of controls which affect the termination + of existing application processes:

+ + + + + + + + + + + + + + + +
Type of controlDirective
termination after an idle periodFcgidIdleTimeout
termination after it handles a certain number of requestsFcgidMaxRequestsPerProcess
termination after a certain lifetimeFcgidProcessLifetime
+ +

Several of the directives control processing for a process + class. A process class is the set of processes which were started + with the same executable file and share certain other characteristics such + as virtual host and identity. Two commands which are links to or otherwise + refer to the same executable file share the same process class.

+ + Note +

Certain settings or other concepts that depend on the virtual host, + such as FcgidInitialEnv + or process classes, distinguish between virtual hosts only if they + have distinct server names. (See the ServerName + documentation for more information.) In the case of + FcgidInitialEnv, if two + virtual hosts have the same server name but different environments as + defined by + FcgidInitialEnv, the + environment used for a particular request will be that defined for the + virtual host of the request that caused the FastCGI process to be + started.

+
+ +

Information about each process will be displayed in the + mod_status server-status page.

+
+ + + FcgidAccessChecker + full path to FastCGI access checker + FcgidAccessChecker command + none + directory .htaccess + FileInfo + +

Access checking or, more formally, access control, is a procedure + which verifies that the client is allowed to access a resource, using + some mechanism other than authentication and authorization.

+ +

Key environment variables passed to the application for access + checking are:

+ +
+
FCGI_APACHE_ROLE
+
set to ACCESS_CHECKER; by checking the current role, + the same FastCGI application can handle multiple stages of request + processing
+
+ +

The application must output a Status line to indicate + the result of the check.

+ + Warning +

Before 2.3.6, only one FastCGI application of any type (AAA or handler) + can be used for a particular request URI. Otherwise, the wrong FastCGI + application may be invoked for one or more phases of request processing.

+
+
+
+ + + FcgidAccessCheckerAuthoritative + Set to 'off' to allow access control to be passed along to lower modules upon failure + FcgidAccessCheckerAuthoritative On|Off + FcgidAccessCheckerAuthoritative On + directory .htaccess + FileInfo + +

This directive controls whether or not other access checkers + are allowed to run when this module has an access checker configured + and it fails a request. If this directive is On (default) + and a FastCGI access checker returns a failure status, a failure is + returned to the client without giving other access checkers a chance to + allow access. If this directive is Off, other access + checkers will be called.

+
+
+ + + FcgidAuthenticator + full path to FastCGI authenticator + FcgidAuthenticator command + none + directory .htaccess + FileInfo + +

Authentication is the procedure which verifies that the user is + who they claim they are. This directive specifies the full path to + a FastCGI application which will handle authentication for a particular + context, such as a directory.

+ +

Key environment variables passed to the application on authentication + are:

+ +
+
REMOTE_USER
+
set to the user id of the client
+ +
REMOTE_PASSWD
+
set to the plain text password provided by the client
+ +
FCGI_APACHE_ROLE
+
set to AUTHENTICATOR; by checking the current role, + the same FastCGI application can handle multiple stages of request + processing
+
+ +

The application must output a Status line to indicate + the result of authentication.

+ + Warning +

Before 2.3.6, only one FastCGI application of any type (AAA or handler) + can be used for a particular request URI. Otherwise, the wrong FastCGI + application may be invoked for one or more phases of request processing.

+
+
+
+ + + FcgidAuthenticatorAuthoritative + Set to 'off' to allow authentication to be passed along to lower modules upon failure + FcgidAuthenticatorAuthoritative On|Off + FcgidAuthenticatorAuthoritative On + directory .htaccess + FileInfo + +

This directive controls whether or not other authenticators + are allowed to run when this module has an authenticator configured + and it fails a request. If this directive is On (default) + and a FastCGI authenticator returns a failure status, a failure is + returned to the client without giving other authenticators a chance to + validate the client identity. If this directive is Off, + other authenticators will be called.

+
+
+ + + FcgidAuthorizer + full path to FastCGI authorizer + FcgidAuthorizer command + none + directory .htaccess + FileInfo + +

Authorization is the procedure which verifies that the user is + allowed to access a particular resource. This directive specifies + the full path to a FastCGI application which will handle authorization + for a particular context, such as a directory.

+ +

Key environment variables passed to the application on authorization + are:

+ +
+
REMOTE_USER
+
set to the user id of the client, which has already been + authenticated
+ +
FCGI_APACHE_ROLE
+
set to AUTHORIZER; by checking the current role, the + same FastCGI application can handle multiple stages of request + processing
+
+ +

The application must output a Status line to indicate + the result of authorization.

+ + Warning +

Before 2.3.6, only one FastCGI application of any type (AAA or handler) + can be used for a particular request URI. Otherwise, the wrong FastCGI + application may be invoked for one or more phases of request processing.

+
+
+
+ + + FcgidAuthorizerAuthoritative + Set to 'off' to allow authorization to be passed along to lower modules upon failure + FcgidAuthorizerAuthoritative On|Off + FcgidAuthorizerAuthoritative On + directory .htaccess + FileInfo + +

This directive controls whether or not other authorizers + are allowed to run when this module has an authorizer configured + and it fails a request. If this directive is On (default) + and a FastCGI authorizer returns a failure status, a failure is + returned to the client without giving other authorizer a chance to + access the resource. If this directive is Off, other + authorizers will be called.

+
+
+ + + FcgidBusyScanInterval + scan interval for busy timeout process + FcgidBusyScanInterval seconds + FcgidBusyScanInterval 120 + server config + +

The module performs the + FcgidBusyTimeout check at this + interval.

+
+
+ + + FcgidBusyTimeout + a FastCGI application will be killed after handling a request for FcgidBusyTimeout + FcgidBusyTimeout seconds + FcgidBusyTimeout 300 + server config virtual host + +

This is the maximum time limit for request handling. If a FastCGI + request does not complete within FcgidBusyTimeout seconds, it will be + subject to termination. Because the check is performed at the + interval defined by FcgidBusyScanInterval, + request handling may be allowed to proceed for a longer period of time.

+ +

The purpose of this directive is to terminate hung applications. + The default timeout may need to be increased for applications that + can take longer to process the request.

+
+
+ + + FcgidCmdOptions + Set processing options for a FastCGI + command + FcgidCmdOptions command option + [option] ... + server config virtual host + +

This directive allows processing options to be specified for + a specific command spawned by mod_fcgid. Each option for the + command corresponds to another directive that normally applies to + all commands started within a particular context. If a + particular option is not specified on this directive, the + default will be used.

+ +

The following table provides a list of options and + corresponding directives:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Option name and syntaxCorresponding directive
ConnectTimeout secondsFcgidConnectTimeout
IdleTimeout secondsFcgidIdleTimeout
InitialEnv name[=value]FcgidInitialEnv
IOTimeout secondsFcgidIOTimeout
MaxProcesses valueFcgidMaxProcessesPerClass
MaxProcessLifeTime secondsFcgidProcessLifeTime
MaxRequestsPerProcess valueFcgidMaxRequestsPerProcess
MinProcesses valueFcgidMinProcessesPerClass
+ +

Multiple environment variables are defined by repeating + the InitialEnv option.

+ + Example + FcgidCmdOptions /usr/local/bin/wrapper \
+ + InitialEnv MAX_REQUESTS=2000 \
+ MaxRequestsPerProcess 2000 \
+ IOTimeout 90
+
+
+ +

When /usr/local/bin/wrapper is spawned, its initial + environment contains the MAX_REQUESTS=2000 + environment variable setting; additionally, mod_fcgid will + terminate it after it has handled 2000 requests, and I/O + operations will time out after 90 seconds. Directives + corresponding to other options, such as + FcgidIdleTimeout or + FcgidProcessLifeTime, + will be ignored for this command; defaults will be used for options + not specified on FcgidCmdOptions.

+
+
+ + + FcgidInitialEnv + an environment variable name and optional value to pass to FastCGI. + FcgidInitialEnv name [ value ] + none + server config virtual host + +

Use FcgidInitialEnv to define environment + variables to pass to the FastCGI application. This directive can + be used multiple times.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidMaxProcessesPerClass + Max process count of one class of FastCGI application + FcgidMaxProcessesPerClass value + FcgidMaxProcessesPerClass 100 + server config virtual host + +

This directive sets the maximum number of processes that can be + started for each process class.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidMinProcessesPerClass + Min process count of one class of FastCGI application + FcgidMinProcessesPerClass value + FcgidMinProcessesPerClass 3 + server config virtual host + +

This directive sets the minimum number of processes that will be + retained in a process class after finishing requests.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidErrorScanInterval + scan interval for exited process + FcgidErrorScanInterval seconds + FcgidErrorScanInterval 3 + server config + +

This is the interval at which the module will handle + pending process termination. Termination is pending for + any processes which have exceeded + FcgidIdleTimeout or + FcgidProcessLifeTime.

+ +

Unix: mod_fcgid will terminate such processes with SIGTERM; + if the process is still active during the next scan, the process + will be terminated with SIGKILL. Thus, this directive controls the + amount of time for orderly process terminate before being forcibly + killed.

+
+
+ + + FcgidIdleScanInterval + scan interval for idle timeout process + FcgidIdleScanInterval seconds + FcgidIdleScanInterval 120 + server config + +

This is the interval at which the module will search for + processes which have exceeded + FcgidIdleTimeout or + FcgidProcessLifeTime.

+
+
+ + + FcgidIdleTimeout + An idle FastCGI application will be killed after FcgidIdleTimeout + FcgidIdleTimeout seconds + FcgidIdleTimeout 300 + server config virtual host + +

Application processes which have not handled a request for this + period of time will be terminated, if the number of processses for the + class exceeds + FcgidMinProcessesPerClass. + A value of 0 disables the check.

+ +

This idle timeout check is performed at the frequency of the configured + FcgidIdleScanInterval.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidIOTimeout + Communication timeout to FastCGI server + FcgidIOTimeout seconds + FcgidIOTimeout 40 + server config virtual host + +

This is the maximum period of time the module will wait + while trying to read from or write to a FastCGI application.

+ + Note +

The FastCGI application must begin generating the response within + this period of time. Increase this directive as necessary to handle + applications which take a relatively long period of time to respond.

+
+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidConnectTimeout + Connect timeout to FastCGI server + FcgidConnectTimeout seconds + FcgidConnectTimeout 3 + server config virtual host + +

This is the maximum period of time the module will wait + while trying to connect to a FastCGI application on Windows. + (This directive is not respected on Unix, where AF_UNIX defaults + will apply.)

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidMaxProcesses + maximum number of FastCGI application processes + FcgidMaxProcesses value + FcgidMaxProcesses 1000 + server config + +

This directive sets the maximum number of FastCGI application + processes which can be active at one time.

+
+
+ + + FcgidMaxRequestInMem + maximum size of a request which will be held in memory + FcgidMaxRequestInMem bytes + FcgidMaxRequestInMem 65536 + server config virtual host + +

This module reads the entire request body from the client + before sending it to the application. Normally the request body + will be stored in memory. Once the amount of request body read + from the client exceeds FcgidMaxRequestInMem + bytes, the remainder of the request body will be stored in a + temporary file.

+
+
+ + + FcgidMaxRequestLen + maximum HTTP request length + FcgidMaxRequestLen bytes + FcgidMaxRequestLen 131072 + server config virtual host + +

If the size of the request body exceeds this amount, the + request will fail with 500 Server Error.

+ +

Administrators should change this to an appropriate value for their site based + on application requirements.

+ + Warning +

Before 2.3.6, this defaulted to 1GB. Most users of earlier versions should + use this directive to set a more reasonable limit.

+
+
+ FcgidMaxRequestInMem +
+ + + FcgidMaxRequestsPerProcess + Max requests handled by each FastCGI application + FcgidMaxRequestsPerProcess value + FcgidMaxRequestsPerProcess 0 + server config virtual host + +

FastCGI application processes will be terminated after handling + the specified number of requests. A value of 0 + disables the check.

+ Note +

A value of -1 is currently accepted for ease of + migration for existing configurations. It is treated the same as + 0.

+
+

Certain applications, notably PHP as FastCGI, have their own + facility for terminating after handling a certain number of + requests. This directive can be used to avoid sending + additional requests to the application after it has handled its + limit.

+ Note +

If this is set such that frequent process creation will be + required, you will likely need to adjust + FcgidSpawnScoreUpLimit + or other score-related directives to allow more frequent process + creation.

+
+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidOutputBufferSize + CGI output buffer size + FcgidOutputBufferSize bytes + FcgidOutputBufferSize 65536 + server config virtual host + +

This is the maximum amount of response data the module will read + from the FastCGI application before flushing the data to the client.

+
+
+ + + FcgidPassHeader + Header name which will be passed to FastCGI as environment variable. + FcgidPassHeader name + none + server config virtual host + +

This directive specifies the name of a request header which + will be passed to the FastCGI application as an environment + variable. The name of the environment variable is derived from + the value specified on this directive, as discussed below:

+ +

The legacy behavior is to use the value specified on this directive + as the environment variable name, converting hyphens to underscores. + No case conversion is performed.

+ +

Beginning with release 2.3.6, an additional environment variable + is created. The value specified on this directive is converted to + upper case, prefixed with HTTP_, and hyphens are + converted to underscores.

+ + Note +

Most request headers are already available to the application + as environment variables, and generally are prefixed with + HTTP_. (Notable exceptions are Content-type + and Content-length, which do not have the + HTTP_ prefix.) Thus, this directive is only required + for request headers that are purposefully omitted, such as + Authorization and Proxy-Authorization. + Only pass these request headers if absolutely required.

+
+
+
+ + + FcgidFixPathinfo + Mirror the PHP cgi.fix_pathinfo + setting + FcgidFixPathinfo 1 + FcgidFixPathinfo 0 + server config + +

This directive enables special SCRIPT_NAME + processing which allows PHP to provide additional path information. + The setting of FcgidFixPathinfo + should mirror the cgi.fix_pathinfo setting in + php.ini.

+
+
+ + + FcgidProcessLifeTime + maximum FastCGI application process lifetime + FcgidProcessLifeTime seconds + FcgidProcessLifeTime 3600 + server config virtual host + +

Idle application processes which have existed for greater + than this time will be terminated, if the number of processses for the + class exceeds + FcgidMinProcessesPerClass. + A value of 0 disables the check.

+ +

This process lifetime check is performed at the frequency of the configured + FcgidIdleScanInterval.

+ +

This setting will apply to all applications spawned for this + server or virtual host. Use + FcgidCmdOptions to apply + this setting to a single application.

+
+
+ + + FcgidProcessTableFile + shared memory file path + FcgidProcessTableFile pathname + FcgidProcessTableFile logs/fcgid_shm + server config + +

This module uses shared memory on Unix to maintain state which + is shared between httpd processes. This directive specifies the + name of the shared memory file.

+
+
+ + + FcgidIPCDir + directory for AF_UNIX sockets (Unix) or pipes (Windows) + FcgidIPCDir pathname + FcgidIPCDir logs/fcgidsock + server config + +

This module uses AF_UNIX sockets or named pipes, depending on the + platform, to communicate with FastCGI applications. This directive + specifies the directory where those sockets or named pipes will be + created.

+
+
+ + + FcgidSpawnScore + Each spawn adds this value to the process activity score. + FcgidSpawnScore value + FcgidSpawnScore 1 + server config + +

Lower values of this directive increase the allowed spawn rate.

+ +

Refer to the FcgidSpawnScoreUpLimit + directive for more information.

+
+
+ + + FcgidSpawnScoreUpLimit + Maximum value of the process activity score which allows + a spawn to occur + FcgidSpawnScoreUpLimit value + FcgidSpawnScoreUpLimit 10 + server config + +

A process activity score is maintained for each FastCGI application; + the score is used to control the rate of spawning in order to avoid + placing too much load on the system, particularly for applications that + are repeatedly exiting abnormally.

+ +

The value of FcgidSpawnScore + is added to the score for every spawned application process. The value of + FcgidTerminationScore is added + to the score for every terminated application process. The value of + FcgidTimeScore is subtracted + from the score every second.

+ +

When the current score is higher than the value of + FcgidSpawnScoreUpLimit, no additional application + processes will be spawned; subsequent requests must wait until an existing + process is free or until the score decreases below the limit.

+ +

If the limit is reached under normal load, it may not be sufficient to + simply increase the limit, as that would only delay the amount of time + before the limit is reached again. Decrease the value of + FcgidSpawnScore and/or + FcgidTerminationScore, or + increase the value of FcgidTimeScore, + to allow a higher rate of spawning.

+
+
+ + + FcgidTerminationScore + Each terminated process adds this value to the process activity + score. + FcgidTerminationScore value + FcgidTerminationScore 2 + server config + +

Lower values of this directive increase the allowed spawn rate. Negative + values can be useful in some circumstances, such as allowing process + replacement without increasing the score.

+ +

Refer to the FcgidSpawnScoreUpLimit + directive for more information.

+
+
+ + + FcgidTimeScore + Amount subtracted from process activity score each + second + FcgidTimeScore value + FcgidTimeScore 1 + server config + +

Higher values of this directive increase the allowed spawn rate.

+ +

Refer to the FcgidSpawnScoreUpLimit + directive for more information.

+
+
+ + + FcgidWin32PreventOrphans + Job Control orphan prevention for fcgi workers. + FcgidWin32PreventOrphans On|Off + FcgidWin32PreventOrphans Off + server config + +

Uses Job Control Objects on Windows, only, to enforce shutdown of all + fcgi processes created by the httpd worker when the httpd worker has been + terminated. Processes terminated in this way do not have the opportunity + to clean up gracefully, complete pending disk writes, or similar closure + transactions, therefore this behavior is experimental and disabled, by + default.

+
+
+ + + FcgidWrapper + The CGI wrapper setting + FcgidWrapper command [ suffix ] [ virtual ] + none + + server config virtual host + directory .htaccess + + FileInfo + +

The given command is used to spawn FCGI server processes. If this directive + is not used, the file pointed to by the request URL will be used instead. + Options for the command can be included using quotation marks surrounding + the command and options.

+

The optional suffix argument restricts the use of this FCGI + server to all URLs with the given exact path suffix. A suffix needs to start + with '.'.

+

The virtual flag signals that there will be no check + whether the request URL actually points to an existing file. The only + file which needs to exist is the wrapper itself.

+

The directive can be used multiple times. A wrapper defined without a suffix + is used as a default in case no suffix matches.

+
+
+ + + FcgidZombieScanInterval + scan interval for zombie process + FcgidZombieScanInterval seconds + FcgidZombieScanInterval 3 + server config + +

The module checks for exited FastCGI applications at this interval. + During this period of time, the application may exist in the process + table as a zombie (on Unix).

+
+
+ +
diff --git a/docs/manual/mod/mod_fcgid.xml.meta b/docs/manual/mod/mod_fcgid.xml.meta new file mode 100644 index 0000000..8ac3090 --- /dev/null +++ b/docs/manual/mod/mod_fcgid.xml.meta @@ -0,0 +1,12 @@ + + + + + mod_fcgid + /mod/ + .. + + + en + + diff --git a/fastcgi.fc b/fastcgi.fc new file mode 100644 index 0000000..2006d97 --- /dev/null +++ b/fastcgi.fc @@ -0,0 +1 @@ +/var/run/mod_fcgid(/.*)? gen_context(system_u:object_r:httpd_var_run_t,s0) diff --git a/fastcgi.te b/fastcgi.te new file mode 100644 index 0000000..373d920 --- /dev/null +++ b/fastcgi.te @@ -0,0 +1,71 @@ +# This policy module provides support for mod_fcgid using the httpd system script domain. +# It provides "allow" rules that will overlap to varying degrees with selinux-policy +# packages for Fedora 5 onwards, and is a stepping stone to the merged policy included +# as updates for selinux-policy in Fedora 8, 9, and 10. +# +# Rules existing in selinux-policy 2.3.7 (FC5) have been stripped from this policy +# +# Previous versions of this policy module used a separate domain, httpd_fastcgi_script_t, +# which is now an alias for httpd_sys_script_t. + +policy_module(fastcgi, 0.1.11) + +require { + type devpts_t; + type httpd_t; + type httpd_log_t; + type httpd_sys_content_t; + type httpd_sys_script_exec_t; + type httpd_sys_script_ra_t; + type httpd_sys_script_ro_t; + type httpd_sys_script_rw_t; + type httpd_sys_script_t; + type httpd_tmp_t; + type httpd_var_run_t; +}; + +# Type aliases for contexts used with older policy modules +typealias httpd_sys_content_t alias httpd_fastcgi_content_t; +typealias httpd_sys_script_exec_t alias httpd_fastcgi_script_exec_t; +typealias httpd_sys_script_ra_t alias httpd_fastcgi_script_ra_t; +typealias httpd_sys_script_ro_t alias httpd_fastcgi_script_ro_t; +typealias httpd_sys_script_rw_t alias httpd_fastcgi_script_rw_t; +typealias httpd_sys_script_t alias httpd_fastcgi_script_t; +typealias httpd_var_run_t alias httpd_fastcgi_var_run_t; + +# ========================================================== +# Re-use httpd_sys_script_t for mod_fcgid apps +# ========================================================== + +# Allow web applications to call getpw* functions +auth_use_nsswitch(httpd_sys_script_t) + +# Allow httpd to create and use files and sockets for communicating with mod_fcgid +# Rules to do this are already in selinux-policy apart from dir setattr +allow httpd_t httpd_var_run_t:dir setattr; + +# Allow FastCGI applications to listen for FastCGI requests on their +# sockets and respond to them +allow httpd_sys_script_t httpd_t:unix_stream_socket { rw_stream_socket_perms }; + +# These are probably leaked file descriptors +dontaudit httpd_t devpts_t:chr_file ioctl; +dontaudit httpd_sys_script_t httpd_log_t:file ioctl; + +# Search automount filesystem to use automatically mounted filesystems +fs_search_auto_mountpoints(httpd_sys_script_t) + +# PHP uploads a file to /tmp and then execs programs to action them +allow httpd_sys_script_t httpd_tmp_t:dir manage_dir_perms; +allow httpd_sys_script_t httpd_tmp_t:file manage_file_perms; +files_tmp_filetrans(httpd_sys_script_t,httpd_sys_script_rw_t,{ dir file lnk_file sock_file fifo_file }) + +# Support network home directories +tunable_policy(`httpd_enable_homedirs && use_nfs_home_dirs',` + fs_read_nfs_files(httpd_sys_script_t) + fs_read_nfs_symlinks(httpd_sys_script_t) +') +tunable_policy(`httpd_enable_homedirs && use_samba_home_dirs',` + fs_read_cifs_files(httpd_sys_script_t) + fs_read_cifs_symlinks(httpd_sys_script_t) +') diff --git a/fcgid.conf b/fcgid.conf new file mode 100644 index 0000000..90f208e --- /dev/null +++ b/fcgid.conf @@ -0,0 +1,14 @@ +# This is the Apache server configuration file for providing FastCGI support +# through mod_fcgid +# +# Documentation is available at +# http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + +LoadModule fcgid_module modules/mod_fcgid.so + +# Use FastCGI to process .fcg .fcgi & .fpl scripts +AddHandler fcgid-script fcg fcgi fpl + +# Sane place to put sockets and shared memory file +FcgidIPCDir /var/run/mod_fcgid +FcgidProcessTableFile /var/run/mod_fcgid/fcgid_shm diff --git a/fcgid24.conf b/fcgid24.conf new file mode 100644 index 0000000..2e7d486 --- /dev/null +++ b/fcgid24.conf @@ -0,0 +1,12 @@ +# This is the Apache server configuration file for providing FastCGI support +# through mod_fcgid +# +# Documentation is available at +# http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html + +# Use FastCGI to process .fcg .fcgi & .fpl scripts +AddHandler fcgid-script fcg fcgi fpl + +# Sane place to put sockets and shared memory file +FcgidIPCDir /run/mod_fcgid +FcgidProcessTableFile /run/mod_fcgid/fcgid_shm diff --git a/mod_fcgid.dsw b/mod_fcgid.dsw new file mode 100644 index 0000000..1f3187d --- /dev/null +++ b/mod_fcgid.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "mod_fcgid"=modules\fcgid\mod_fcgid.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/modules/fcgid/CMakeLists.txt b/modules/fcgid/CMakeLists.txt new file mode 100644 index 0000000..64863fc --- /dev/null +++ b/modules/fcgid/CMakeLists.txt @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Read the section on cmake builds in README-FCGID. + +PROJECT(mod_fcgid C) + +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) + +IF(NOT EXISTS ${CMAKE_INSTALL_PREFIX}/lib/libhttpd.lib) + MESSAGE(FATAL_ERROR "libhttpd.lib was not found in prefix ${CMAKE_INSTALL_PREFIX}") +ENDIF() + +# Select APR trunk (libapr-2.lib) if it exists in PREFIX/lib; otherwise, select +# APR 1.x + APR-util 1.x +IF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/libapr-2.lib") + SET(apr_libraries + ${CMAKE_INSTALL_PREFIX}/lib/libapr-2.lib) +ELSEIF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/libapr-1.lib") + SET(apr_libraries + ${CMAKE_INSTALL_PREFIX}/lib/libapr-1.lib + ${CMAKE_INSTALL_PREFIX}/lib/libaprutil-1.lib) +ELSE() + MESSAGE(FATAL_ERROR "APR libraries were not found in prefix ${CMAKE_INSTALL_PREFIX}") +ENDIF() + +# Misc. options +OPTION(INSTALL_PDB "Install .pdb file (if generated)" ON) + +SET(mod_fcgid_sources + fcgid_bridge.c fcgid_bucket.c fcgid_conf.c fcgid_filter.c + fcgid_pm_main.c fcgid_pm_win.c fcgid_proc_win.c fcgid_proctbl_win.c + fcgid_protocol.c fcgid_spawn_ctl.c mod_fcgid.c mod_fcgid.rc +) +INCLUDE_DIRECTORIES(${CMAKE_INSTALL_PREFIX}/include) +ADD_LIBRARY(mod_fcgid SHARED ${mod_fcgid_sources}) +# magic base address taken from traditional Windows build +SET_TARGET_PROPERTIES(mod_fcgid PROPERTIES SUFFIX .so LINK_FLAGS /base:0x46430000) +TARGET_LINK_LIBRARIES(mod_fcgid ${CMAKE_INSTALL_PREFIX}/lib/libhttpd.lib ${apr_libraries}) +INSTALL(TARGETS mod_fcgid RUNTIME DESTINATION modules) +IF(INSTALL_PDB) + INSTALL(FILES ${CMAKE_BINARY_DIR}/mod_fcgid.pdb DESTINATION modules + CONFIGURATIONS RelWithDebInfo Debug) +ENDIF() diff --git a/modules/fcgid/ChangeLog b/modules/fcgid/ChangeLog new file mode 100644 index 0000000..4f95647 --- /dev/null +++ b/modules/fcgid/ChangeLog @@ -0,0 +1,185 @@ +Note: A log of changes released after moving to the ASF (releases after 2.2) +is in the file CHANGES-FCGID. + +version 2.2 +1. Support configuration "PassHeader" + Thank Hans Christian Saustrup for the suggestion. +2. Support apr_shm_remove() in httpd.2.0.X + Thank Hans Christian Saustrup for bug report. +3. Support configuration "TimeScore" + Thank Tim Jensen for the patch. +4. Support new configurations "MaxRequestInMem" and "MaxRequestLen" + If the length of http request longer than "MaxRequestInMem", it will store in tmp file. + It the length of http request longer than "MaxRequestLen", it will return internal server error. + Thank Gabriel Barazer(gabriel at oxeva.fr) for the bug report. + Thank Steffen(info at apachelounge.com) for the help on this issue. +5. Fix miner Sanity check bug + Thank Yuya Tanaka for bug report + +version 2.1 ( Feb 15th 2007 ) +1. Add missing config.m4 and Makefile.in for static linking + Thank Mark Drago for notice +2. FCGIWrapper disallowed in .htaccess + Thank Balinares for patch +3. Bug fix. Authoritative flag reversed + Thank Chris Darroch for the patch +4. Support arguments in FCGIWrapper + Thank Andre Nathan for suggestion and great help on testing it. +5. Support new config "SharememPath", which specifies the location of share memory path. +6. Check running user is root or not, while suexec is enabled. + Thank Chris Holleman for the bug report. +7. Bug fix. Should not pass respond to auth checkers. + Thank Szabolcs Hock for bug report. + +version 2.0 ( Oct 29th 2006 ) +1. Support FastCGI Authorizer protocols now. +2. Add apxs compile instruction in INSTALL.txt. + Thank Hans Christian Saustrup, hc at saustrup.net for the suggestion. +3. Bug fix. (Win32 only) PHP script can not create socket on Win32. + Thank bbscool at zjip.com for the bug report and the help. +4. GREAT patchs from Robert L Mathews, rob at tigertech.com + Fix compile warnings + Adds a MaxRequestsPerProcess parameter that allows mod_fcgid to exit after handling a certain number of requests + Close socket before fork + avoid the 1-second sleep the first time a process is spawned +5. Print warning log while read data error from FastCGI process. +6. Apply patch from Scott Lamb, Fix mod_fcgid 1.10 warnings on x86_64 + +version 1.10 ( Jul 3rd 2006 ) +1. Use poll() instead of select() in UNIX. "It becomes problematic on apache2 with +large number of logfiles. Apache2 calls poll() (when OS supports it), and in that +case it doesn't need to be recompiled with larger FD_SETSIZE. select() is +still limited to FD_SETSIZE." + Thank Piotr Gackiewicz gacek at intertele.pl for the patch. +2. Bug fix: "Some requests fail with HTTP 500 and no errorlog entry is generated" + Thank Piotr Gackiewicz gacek at intertele.pl for the patch. +3. Use anonymouse share memeory to make OS X happy. + Thank andkjar at obtech.net for the patch. +4. Add config.m4, mod_fcgid now can be static linked in httpd(See INSTALL.txt) + +version 1.09 ( Apr 25th 2006 ) +1. Add configuration DefaultMinClassProcessCount(default 3). Idle fastcgi will not be killed if their count + less than DefaultMinClassProcessCount. + (Thank Finn Smith, finn at timeghost.net for suggestion) +2. Add configuration PHP_Fix_Pathinfo_Enable(default 0). If you are using PHP and set cgi.fix_pathinfo=1 in + php.ini, please add "PHP_Fix_Pathinfo_Enable 1" in httpd.conf. + (Thank Florian Munz, flo at myhosting.de for bug report)? +3. Split error log whle '\r' or '\n' are inside the text send to "stderr". + (Thank frederic at jolliton.com for the patch) + +version 1.08 ( Jan 22nd 2006 ) +1. apr_bucket_copy() does not work with buckets from mod_ssl, use apr_bucket_heap_create() instead :( + (Thank Grzegorz Sampolski, gs at blink.pl for the bug report) +2. Wrapper binary can be stored in a different location to the web content (like /usr/local/apache2/fcgi-bin) + (Patch from Stephen Grier, s.e.grier at qmul.ac.uk) +3. Support Apache 2.2 now + (Patch from RyoYazaki, yazaki.ryo at mind.co.jp, and many other people report the compiling problem) +4. Support "\r\n\r\n " HTTP header from CGI + (Thank Grzegorz Sampolski, gs at blink.pl for the bug report) + +version 1.07 ( Nov 10th 2005 ) +1. Configuration IPCConnectTimeout, IPCCommTimeout, BusyTimeout can be overwrite in VirtualHost section + (Thank cthulhu at planet-multiplayer.de for the suggestion) +2. Add EXTRA_CFLAGS = -I$(builddir) in Makefile + (Thank contagion at gmail.com for the suggestion) +3. Support Apache 2.1 now (Patch by nick at webthing.com) + (Thank nick at webthing.com for the excellent patch) +4. Support "\r\n\t" HTTP header from CGI + +version 1.06 ( Apr 27th 2005 ) +1. "DefaultInitEnv" now can be placed inside VirtualHost section +2. Bug fix. "FCGIWrapper" now stores in a per-directory config structure. (The old implementation + stores it in a per-server config structure, which may be overwrited by another + section) + (Thank phyre at rogers.com for bug report and the great help for the bug fix AGAIN) + +version 1.05 ( Mar 4th 2005 ) +1. Bug fix. suEXEC wrapper in virtualhost environment will shares the process interpreters. + (Thank phyre at rogers.com for bug report and the great help for the bug fix) + +version 1.04 ( Dec 2nd 2004 ) +1. Bug fix. ap_scan_script_header_err_core can return non OK without errors. + e.g. CGI outputs Last-Modified header and browser request with + If-Mofieided-Since header, ap_scan_script_header_err_core() may + returns 302(Not Modified) + (Thank Tatsuki Sugiura, sugi at nemui.org for the bug fix patch) +2. Choose FCGI wrappers based on file extentions. + e.g. + FCGIWrapper /usr/local/bin/php .php + Tells mod_fcgid calling all *.php scripts with wrapper /usr/local/bin/php. + (Thank Mathijs Brands, mathijs at crooked.net for the suggestion) + +verison 1.03 ( Nov 3rd 2004 ) +1. Add configuration "OutputBufferSize". The old implementation keep CGI output in a 64k bytes buffer, + before send them to web browser(for better network IO performance). + If FCGI_Fflush() is called in your application, please add "OutputBufferSize 0" in you + httpd.conf, which will not keep any CGI output in cache buffer. + The default value of OutputBufferSize is 64k byte. + (Thank Grzegorz Sampolski, gs at blink.pl) +2. Return HTTP_SERVICE_UNAVAILABLE instead of HTTP_INTERNAL_SERVER_ERROR, while mod_fcgid can not + apply a FastCGI process slot. (Thank Grzegorz Sampolski, gs at blink.pl for the suggestion) + +version 1.02 ( Oct 1st 2004 ) +1. Bug fix. (Win32 only) Forward request to incorrect process when a script is a directory in the URL. + For example, if in the "cgi-bin" directory there are two programs, "a.exe" and "b.exe". + If you go to the URL "http://localhost/cgi-bin/a.exe/defghi" then a.exe will start. + Then if you go to the URL "http://localhost/cgi-bin/b.exe/uvwxyz" the request will be + processed by the already running a.exe. (Thank rripley at amadvertising.com) + +version 1.01 ( Sep 21st 2004 ) +1. More graceful implementation of suEXEC on UNIX. (Thank radek at karnet.pl for the advice) + +version 1.00 ( Sep 10th 2004 ) +1. Release FastCGI process slot before sending buffer back to browser. (less process count) + +version 0.88 ( Sep 3rd 2004 ) +1. Bug fix. File descriptor is closed twice if connect to UNIX domain socket error. +2. Bug fix. Get server last active time incorrectly. + +version 0.87 ( Aug 26th 2004 ) +1. suEXEC supported. + +version 0.86 ( Aug 22th 2004 ) +Some major changes for performance. (Hits/Second is 30% greater than the old implementation) +1. Socket bucket instead of heap bucket. (less memory) +2. Pass buffer to browser, once the size of buffer exceed 64k bytes. (less memory) +3. Non-block unix domain socket on UNIX. (better I/O performance) +4. writev() instead of write() on UNIX. (better I/O performance) +5. Try to read at least 8k bytes each single reading. (better I/O perormance) +6. Disconnect FastCGI server, once browser disconnect. (less process count) + +version 0.80 ( Jul 27th 2004 ) +1. Duplex channel added, Apache now will get notification once FastCGI process is spawned. +2. Bug fix. The FastCGI process share the signal handler with PM(Process Manager) process, if + the FastCGI process get signal after fork() and before execve(), it will corrupt the + share memory. ( It's a very short interval between fork() and execve(), so this rarely happens ) + +version 0.77 ( Jul 9th 2004 ) +1. Organize the configuration again + 1) add "FCGIWrapperGroup" setting + 2) "FCGIWrapper" now takes only one argument + 3) remove "ServerConfig" setting, because it't not work with wrapper yet +Please visit http://fastcgi.coremail.cn/doc.htm for more information about configuration + +version 0.76 ( Jul 6th 2004 ) +1. Code fix. Replace the depreciated BRIGADE_FOREACH macro, which compile against httpd 2.1-Dev. + (Patch by Paul Querna(chip at force-elite.com)) +2. PHP FastCGI Wrapper now can be run both with "share" and "non-share" mode. + +version 0.74 ( Jun 23rd 2004 ) +1. Bug fix. Ignore script checking while running in Win32 PHP wrapper mode. +2. Indent the code with K&R style. + +version 0.72 ( Jun 13rd 2004 ) +1. Bug fix. Trim the padding nuls at the end of data. (Thank rick.stewart at theinternetco.net) + +version 0.7 ( May 22nd 2004 ) +1. PHP FastCGI Wrapper is supported on UNIX and Windows. + +version 0.62 ( May 18th 2004 ) +1. Unix version now compilable with gcc2.95.2 +2. Unix version is tested on Solaris7 & Solaris8 + +version 0.6 ( May 8th 2004 ) +1. Unix version is tested on Redhat8(gcc3) +2. Windows version is tested on Win2k(VC6) diff --git a/modules/fcgid/Makefile.apxs b/modules/fcgid/Makefile.apxs new file mode 100644 index 0000000..500449b --- /dev/null +++ b/modules/fcgid/Makefile.apxs @@ -0,0 +1,28 @@ +## +## Makefile.apxs -- Build procedure for mod_fcgid Apache module +## +## Do not use this target; build from the mod_fcgid dir root +## + +# top_builddir and top_srcdir are misnomers, because build/*.mk +# scripts expect it them be the parent of the build directory, +# and fail to trust the installbuilddir. +exp_installbuilddir=$(shell $(APXS) -q exp_installbuilddir) +top_srcdir=$(installbuilddir)/.. +top_builddir=$(installbuilddir)/.. + +fcgid_builddir=../.. +fcgid_srcdir=../.. +builddir=. +srcdir=. + +CLEAN_TARGETS = *.loT +include $(exp_installbuilddir)/special.mk + +all: local-shared-build all-recursive + +# additional defines, includes and libraries +DEFS=-DFCGID_APXS_BUILD +INCLUDES=-I$(builddir) -I$(srcdir) -I$(fcgid_srcdir)/include +#LIBS=-Lmy/lib/dir -lmylib + diff --git a/modules/fcgid/Makefile.in b/modules/fcgid/Makefile.in new file mode 100644 index 0000000..a9fd35f --- /dev/null +++ b/modules/fcgid/Makefile.in @@ -0,0 +1,23 @@ +# 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. + +# +# standard stuff +# + +include $(top_srcdir)/build/special.mk + +all: all-recursive + diff --git a/modules/fcgid/config.m4 b/modules/fcgid/config.m4 new file mode 100644 index 0000000..eb8f67f --- /dev/null +++ b/modules/fcgid/config.m4 @@ -0,0 +1,49 @@ +dnl Licensed to the Apache Software Foundation (ASF) under one or more +dnl contributor license agreements. See the NOTICE file distributed with +dnl this work for additional information regarding copyright ownership. +dnl The ASF licenses this file to You under the Apache License, Version 2.0 +dnl (the "License"); you may not use this file except in compliance with +dnl the License. You may obtain a copy of the License at +dnl +dnl http://www.apache.org/licenses/LICENSE-2.0 +dnl +dnl Unless required by applicable law or agreed to in writing, software +dnl distributed under the License is distributed on an "AS IS" BASIS, +dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +dnl See the License for the specific language governing permissions and +dnl limitations under the License. + +dnl # start of module specific part +APACHE_MODPATH_INIT(fcgid) + +case $host in + *mingw*) + fcgid_platform_objs="fcgid_pm_win.lo fcgid_proc_win.lo fcgid_proctbl_win.lo" + ;; + *) + fcgid_platform_objs="fcgid_pm_unix.lo fcgid_proc_unix.lo fcgid_proctbl_unix.lo fcgid_mutex_unix.lo" + ;; +esac + +dnl # list of module object files +fcigd_objs="dnl +mod_fcgid.lo dnl +fcgid_bridge.lo dnl +fcgid_conf.lo dnl +fcgid_pm_main.lo dnl +fcgid_protocol.lo dnl +fcgid_spawn_ctl.lo dnl +fcgid_bucket.lo dnl +fcgid_filter.lo dnl +$fcgid_platform_objs dnl +" + +APACHE_MODULE(fcgid, [FastCGI support (mod_fcgid)], $fcigd_objs, , no, [ + AC_CHECK_HEADERS(sys/file.h) + AC_CHECK_HEADERS(sys/mman.h) + AC_CHECK_HEADERS(sys/mutex.h) + AC_CHECK_HEADERS(sys/shm.h) +]) + +dnl # end of module specific part +APACHE_MODPATH_FINISH diff --git a/modules/fcgid/fcgid_bridge.c b/modules/fcgid/fcgid_bridge.c new file mode 100644 index 0000000..c8b45c2 --- /dev/null +++ b/modules/fcgid/fcgid_bridge.c @@ -0,0 +1,766 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_request.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_pools.h" +#include "apr_file_io.h" +#include "util_script.h" +#include "fcgid_bridge.h" +#include "fcgid_pm.h" +#include "fcgid_proctbl.h" +#include "fcgid_proc.h" +#include "fcgid_conf.h" +#include "fcgid_spawn_ctl.h" +#include "fcgid_protocol.h" +#include "fcgid_bucket.h" +#define FCGID_APPLY_TRY_COUNT 2 +#define FCGID_REQUEST_COUNT 32 +#define FCGID_BRIGADE_CLEAN_STEP 32 + +static fcgid_procnode *apply_free_procnode(request_rec *r, + fcgid_command * command) +{ + /* Scan idle list, find a node match inode, deviceid and groupid + If there is no one there, return NULL */ + fcgid_procnode *previous_node, *current_node, *next_node; + fcgid_procnode *busy_list_header, *proc_table; + apr_ino_t inode = command->inode; + apr_dev_t deviceid = command->deviceid; + uid_t uid = command->uid; + gid_t gid = command->gid; + const char *cmdline = command->cmdline; + + proc_table = proctable_get_table_array(); + previous_node = proctable_get_idle_list(); + busy_list_header = proctable_get_busy_list(); + + proctable_lock(r); + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + next_node = &proc_table[current_node->next_index]; + + if (current_node->inode == inode + && current_node->deviceid == deviceid + && !strcmp(current_node->cmdline, cmdline) + && current_node->vhost_id == command->vhost_id + && current_node->uid == uid && current_node->gid == gid) { + /* Unlink from idle list */ + previous_node->next_index = current_node->next_index; + + /* Link to busy list */ + current_node->next_index = busy_list_header->next_index; + busy_list_header->next_index = current_node - proc_table; + + proctable_unlock(r); + return current_node; + } + else + previous_node = current_node; + + current_node = next_node; + } + proctable_unlock(r); + + /* Found nothing */ + return NULL; +} + +static void +return_procnode(request_rec *r, + fcgid_procnode *procnode, int communicate_error) +{ + fcgid_procnode *previous_node, *current_node, *next_node; + fcgid_procnode *proc_table = proctable_get_table_array(); + fcgid_procnode *error_list_header = proctable_get_error_list(); + fcgid_procnode *idle_list_header = proctable_get_idle_list(); + fcgid_procnode *busy_list_header = proctable_get_busy_list(); + + proctable_lock(r); + + /* Unlink the node from busy list first */ + previous_node = busy_list_header; + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + next_node = &proc_table[current_node->next_index]; + if (current_node == procnode) { + /* Unlink from busy list */ + previous_node->next_index = current_node->next_index; + break; + } + else + previous_node = current_node; + current_node = next_node; + } + + /* Return to error list or idle list */ + if (communicate_error) { + /* Link to error list */ + procnode->next_index = error_list_header->next_index; + error_list_header->next_index = procnode - proc_table; + } + else { + /* Link to idle list */ + procnode->next_index = idle_list_header->next_index; + idle_list_header->next_index = procnode - proc_table; + } + + proctable_unlock(r); +} + +static int count_busy_processes(request_rec *r, fcgid_command *command) +{ + int result = 0; + fcgid_procnode *previous_node, *current_node, *next_node; + fcgid_procnode *proc_table = proctable_get_table_array(); + fcgid_procnode *busy_list_header = proctable_get_busy_list(); + + proctable_lock(r); + + previous_node = busy_list_header; + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + if (current_node->inode == command->inode + && current_node->deviceid == command->deviceid + && !strcmp(current_node->cmdline, command->cmdline) + && current_node->vhost_id == command->vhost_id + && current_node->uid == command->uid + && current_node->gid == command->gid) { + result++; + } + next_node = &proc_table[current_node->next_index]; + current_node = next_node; + } + + proctable_unlock(r); + + return result; +} + +apr_status_t bucket_ctx_cleanup(void *thectx) +{ + /* Cleanup jobs: + 1. Free bucket buffer + 2. Return procnode + NOTE: ipc will be clean when request pool cleanup, so I don't need to close it here + */ + fcgid_bucket_ctx *ctx = (fcgid_bucket_ctx *) thectx; + request_rec *r = ctx->ipc.request; + + /* Free bucket buffer */ + if (ctx->buffer) { + apr_bucket_destroy(ctx->buffer); + ctx->buffer = NULL; + } + + /* proc_close_ipc() and ipc_handle_cleanup() do their own sanity + * checks, but we'll do our own anyway + */ + if (ctx->ipc.ipc_handle_info) { + proc_close_ipc(&ctx->ipc); + ctx->ipc.ipc_handle_info = NULL; + } + + if (ctx->procnode) { + ++ctx->procnode->requests_handled; + + /* Return procnode + I will return this slot to idle(or error) list + */ + if (ctx->procnode->diewhy == FCGID_DIE_BUSY_TIMEOUT) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + "mod_fcgid: %s took longer than busy timeout " + "(%d secs)", + r->uri, + ctx->procnode->cmdopts.busy_timeout); + return_procnode(r, ctx->procnode, 1 /* busy timeout */ ); + } + else if (ctx->has_error) { + ctx->procnode->diewhy = FCGID_DIE_COMM_ERROR; + return_procnode(r, ctx->procnode, 1 /* communication error */ ); + } + else if (ctx->procnode->cmdopts.max_requests_per_process + && ctx->procnode->requests_handled >= + ctx->procnode->cmdopts.max_requests_per_process) { + ctx->procnode->diewhy = FCGID_DIE_LIFETIME_EXPIRED; + return_procnode(r, ctx->procnode, 1 /* handled all requests */ ); + } + else + return_procnode(r, ctx->procnode, 0 /* communication ok */ ); + + ctx->procnode = NULL; + } + + return APR_SUCCESS; +} + +static int getsfunc_fcgid_BRIGADE(char *buf, int len, void *arg) +{ + apr_bucket_brigade *bb = (apr_bucket_brigade *) arg; + const char *dst_end = buf + len - 1; /* leave room for terminating null */ + char *dst = buf; + apr_bucket *e = APR_BRIGADE_FIRST(bb); + apr_status_t rv; + int done = 0; + int getLF = 0; + int getColon = 0; + + while ((dst < dst_end) && !done && e != APR_BRIGADE_SENTINEL(bb)) { + const char *bucket_data; + apr_size_t bucket_data_len; + const char *src; + const char *src_end; + apr_bucket *next; + + rv = apr_bucket_read(e, &bucket_data, &bucket_data_len, + APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + return 0; + } + + /* Move on to next bucket if it's fastcgi header bucket */ + if (e->type == &ap_bucket_type_fcgid_header + || e->type == &apr_bucket_type_immortal) { + next = APR_BUCKET_NEXT(e); + apr_bucket_delete(e); + e = next; + if (getLF) { + done = 1; + } + continue; + } + + if (bucket_data_len == 0) + return 0; + + /* Base on RFC2616 section 4.2 */ + src = bucket_data; + src_end = bucket_data + bucket_data_len; + while ((src < src_end) && (dst < dst_end) && !done) { + if (*src == ':') + getColon = 1; + + if (getLF && ((*src != ' ' && *src != '\t') || !getColon)) { + done = 1; + getColon = 0; + break; + } + else if (getLF && (*src == ' ' || *src == '\t')) { + *dst++ = '\r'; + *dst++ = '\n'; + getLF = 0; + } + + if (*src == '\n') { + getLF = 1; + } + else if (*src != '\r') { + *dst++ = *src; + } + src++; + } + + if (src < src_end) { + apr_bucket_split(e, src - bucket_data); + } + next = APR_BUCKET_NEXT(e); + apr_bucket_delete(e); + e = next; + } + *dst = 0; + return done; +} + +static int +handle_request_ipc(request_rec *r, int role, + apr_bucket_brigade *output_brigade, + fcgid_bucket_ctx *bucket_ctx, const char **location_ptr) +{ + int cond_status; + apr_status_t rv; + apr_bucket_brigade *brigade_stdout; + char sbuf[MAX_STRING_LEN]; + const char *location; + + /* Write output_brigade to fastcgi server */ + if ((rv = proc_write_ipc(&bucket_ctx->ipc, + output_brigade)) != APR_SUCCESS) { + bucket_ctx->has_error = 1; + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Create brigade */ + brigade_stdout = + apr_brigade_create(r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(brigade_stdout, + ap_bucket_fcgid_header_create(r->connection-> + bucket_alloc, + bucket_ctx)); + + /* Check the script header first; return immediately on error. */ + if ((cond_status = + ap_scan_script_header_err_core(r, sbuf, getsfunc_fcgid_BRIGADE, + brigade_stdout))) { + /* + * cond_status could be HTTP_NOT_MODIFIED in the case that the FCGI + * script does not set an explicit status and ap_meets_conditions, + * which is called by ap_scan_script_header_err_brigade, detects that + * the conditions of the requests are met and the response is + * not modified. + * In this case set r->status and return OK in order to prevent + * running through the error processing stack as this would + * break with mod_cache, if the conditions had been set by + * mod_cache itself to validate a stale entity. + * BTW: We circumvent the error processing stack anyway if the + * FCGI script set an explicit status code (whatever it is) and + * the only possible values for cond_status here are: + * + * HTTP_NOT_MODIFIED (set by ap_meets_conditions) + * HTTP_PRECONDITION_FAILED (set by ap_meets_conditions) + * HTTP_GATEWAY_TIME_OUT (script timed out, returned no headers) + * HTTP_INTERNAL_SERVER_ERROR (if something went wrong during the + * processing of the response of the FCGI script, e.g broken headers + * or a crashed FCGI process). + */ + if (cond_status == HTTP_NOT_MODIFIED) { + /* We need to remove our fcgid_filter before returning this + * status and code; otherwise, when ap_process_async_request() + * invokes ap_finalize_request_protocol() and that calls + * ap_pass_brigade(), fcgid_filter notices it has an empty + * brigade and returns without calling ap_pass_brigade() itself, + * which incorrectly circumvents the standard output filters. + */ + ap_remove_output_filter(r->output_filters); + + r->status = cond_status; + return OK; + } + + return cond_status; + } + + if (role == FCGI_AUTHORIZER) { + return cond_status; + } + + /* Check redirect */ + location = apr_table_get(r->headers_out, "Location"); + + if (location && location[0] == '/' && r->status == 200) { + /* This redirect needs to be a GET no matter what the original + * method was. + */ + r->method = apr_pstrdup(r->pool, "GET"); + r->method_number = M_GET; + + /* We already read the message body (if any), so don't allow + * the redirected request to think it has one. We can ignore + * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR. + */ + apr_table_unset(r->headers_in, "Content-Length"); + + /* Setting this Location header value causes handle_request() to + * invoke ap_internal_redirect_handler(); that calls + * internal_internal_redirect() which sets the new sub-request's + * r->output_filters back to r->proto_output_filters before + * running the sub-request's handler. Because we return here + * without invoking ap_pass_brigade(), our fcgid_filter is ignored. + */ + *location_ptr = location; + return OK; + } + else if (location && r->status == 200) { + /* XX Note that if a script wants to produce its own Redirect + * body, it now has to explicitly *say* "Status: 302" + */ + + /* This return code causes ap_process_async_request() to invoke + * ap_die(); that calls ap_send_error_response(), which resets + * r->output_filters back to r->proto_output_filters, thus removing + * our fcgid_filter from the output chain before making a final call + * to ap_finalize_request_protocol(), which passes the brigade to + * the standard output filters. + */ + return HTTP_MOVED_TEMPORARILY; + } + + /* Now pass any remaining response body data to output filters */ + if ((rv = ap_pass_brigade(r->output_filters, + brigade_stdout)) != APR_SUCCESS) { + if (!APR_STATUS_IS_ECONNABORTED(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: ap_pass_brigade failed in " + "handle_request_ipc function"); + } + + return HTTP_INTERNAL_SERVER_ERROR; + } + + return cond_status; +} + +static int +handle_request(request_rec * r, int role, fcgid_cmd_conf *cmd_conf, + apr_bucket_brigade * output_brigade) +{ + fcgid_command fcgi_request; + fcgid_bucket_ctx *bucket_ctx; + int i, j, cond_status; + const char *location = NULL; + + bucket_ctx = apr_pcalloc(r->pool, sizeof(*bucket_ctx)); + + bucket_ctx->ipc.request = r; + apr_pool_cleanup_register(r->pool, bucket_ctx, + bucket_ctx_cleanup, apr_pool_cleanup_null); + procmgr_init_spawn_cmd(&fcgi_request, r, cmd_conf); + + /* Try to get a connected ipc handle */ + for (i = 0; i < FCGID_REQUEST_COUNT; i++) { + /* Apply a free process slot, send a spawn request if I can't get one */ + for (j = 0; j < FCGID_APPLY_TRY_COUNT; j++) { + bucket_ctx->ipc.connect_timeout = + fcgi_request.cmdopts.ipc_connect_timeout; + bucket_ctx->ipc.communation_timeout = + fcgi_request.cmdopts.ipc_comm_timeout; + + /* Apply a process slot */ + bucket_ctx->procnode = apply_free_procnode(r, &fcgi_request); + if (bucket_ctx->procnode) + break; + + /* Avoid sleeping the very first time through if there are no + busy processes; the problem is just that we haven't spawned + anything yet, so waiting is pointless */ + if (i > 0 || j > 0 || count_busy_processes(r, &fcgi_request)) { + apr_sleep(apr_time_from_sec(1)); + + bucket_ctx->procnode = apply_free_procnode(r, &fcgi_request); + if (bucket_ctx->procnode) + break; + } + + /* Send a spawn request if I can't get a process slot */ + procmgr_send_spawn_cmd(&fcgi_request, r); + } + + /* Connect to the fastcgi server */ + if (bucket_ctx->procnode) { + if (proc_connect_ipc(bucket_ctx->procnode, + &bucket_ctx->ipc) != APR_SUCCESS) { + proc_close_ipc(&bucket_ctx->ipc); + bucket_ctx->procnode->diewhy = FCGID_DIE_CONNECT_ERROR; + return_procnode(r, bucket_ctx->procnode, 1 /* has error */ ); + bucket_ctx->procnode = NULL; + } + else + break; + } + } + + /* Now I get a connected ipc handle */ + if (!bucket_ctx->procnode) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: can't apply process slot for %s", + cmd_conf->cmdline); + return HTTP_SERVICE_UNAVAILABLE; + } + bucket_ctx->active_time = bucket_ctx->procnode->last_active_time = + apr_time_now(); + bucket_ctx->procnode->diewhy = FCGID_DIE_KILLSELF; + + cond_status = handle_request_ipc(r, role, output_brigade, + bucket_ctx, &location); + + /* Release the process ASAP. This may already have been done in + * ap_pass_brigade() by fcgid_header_bucket_read(), but not in the + * case where handle_request_ipc() returned early without reading + * the body of the HTTP response. This could be because of an error, + * or because of a role or a status code which permits us to ignore + * the message body. + * + * As an example, when handling a request in the FCGI_AUTHORIZER role, + * we don't read through to the end of the response from the process, + * we just read the HTTP headers. That means each phase of the + * request handling sequence (e.g., authentication, authorization, etc.) + * will require its own process unless we make sure to always release + * any process we acquired regardless of whether we're reading the + * response body. + * + * As another example, if we perform or cause an internal redirection + * (for instance, by returning an error code that invokes a script + * handler in ap_die() because of an ErrorDocument configuration), then + * we must also release the process we acquired here so that it is + * potentially available during the next handling phase. + */ + + apr_pool_cleanup_run(r->pool, bucket_ctx, bucket_ctx_cleanup); + + /* Perform internal redirection if necessary */ + if (location) { + ap_internal_redirect_handler(location, r); + } + + /* Return condition status */ + return cond_status; +} + +static int add_request_body(request_rec *r, apr_pool_t *request_pool, + apr_bucket_brigade *output_brigade) +{ + apr_bucket *bucket_input, *bucket_header; + apr_file_t *fd = NULL; + apr_off_t cur_pos = 0, request_size = 0; + apr_status_t rv; + FCGI_Header *stdin_request_header; + fcgid_server_conf *sconf = ap_get_module_config(r->server->module_config, + &fcgid_module); + int seen_eos = 0; + + /* Stdin header and body */ + /* I have to read all the request into memory before sending it + to fastcgi application server, this prevents slow clients from + keeping the server in processing too long. + But sometimes it's not acceptable (think about uploading a large attachment) + Request will be stored in tmp file if the size larger than max_mem_request_len + */ + + apr_bucket_brigade *input_brigade = apr_brigade_create(request_pool, + r->connection-> + bucket_alloc); + apr_bucket_brigade *tmp_brigade = apr_brigade_create(request_pool, + r->connection-> + bucket_alloc); + + do { + int loop_counter = 0; + + if ((rv = ap_get_brigade(r->input_filters, input_brigade, + AP_MODE_READBYTES, + APR_BLOCK_READ, + HUGE_STRING_LEN)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't get data from http client"); + apr_brigade_destroy(output_brigade); + apr_brigade_destroy(tmp_brigade); + apr_brigade_destroy(input_brigade); + return HTTP_INTERNAL_SERVER_ERROR; + } + + + + while ((bucket_input = APR_BRIGADE_FIRST(input_brigade)) != APR_BRIGADE_SENTINEL(input_brigade)) { + const char *data; + apr_size_t len; + apr_bucket *bucket_stdin; + + ++loop_counter; + if ((loop_counter % FCGID_BRIGADE_CLEAN_STEP) == 0) { + apr_brigade_cleanup(tmp_brigade); + } + APR_BUCKET_REMOVE(bucket_input); + APR_BRIGADE_INSERT_TAIL(tmp_brigade, bucket_input); + + if (APR_BUCKET_IS_EOS(bucket_input)) { + seen_eos = 1; + break; + } + + if (APR_BUCKET_IS_METADATA(bucket_input)) + continue; + + if ((rv = apr_bucket_read(bucket_input, &data, &len, + APR_BLOCK_READ)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't read request from HTTP client"); + apr_brigade_destroy(input_brigade); + apr_brigade_destroy(tmp_brigade); + apr_brigade_destroy(output_brigade); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Append a header, and the the bucket */ + stdin_request_header = apr_bucket_alloc(sizeof(FCGI_Header), + r->connection-> + bucket_alloc); + bucket_header = + apr_bucket_heap_create((const char *) stdin_request_header, + sizeof(*stdin_request_header), + apr_bucket_free, + r->connection->bucket_alloc); + + request_size += len; + if (request_size > sconf->max_request_len) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: HTTP request length %" APR_OFF_T_FMT + " (so far) exceeds MaxRequestLen (%" + APR_OFF_T_FMT ")", request_size, + sconf->max_request_len); + return HTTP_INTERNAL_SERVER_ERROR; + } + + if (request_size > sconf->max_mem_request_len) { + apr_size_t wrote_len; + static const char *fd_key = "fcgid_fd"; + + if (fd == NULL) { + void *tmp; + apr_pool_userdata_get(&tmp, fd_key, r->connection->pool); + fd = tmp; + + if (fd != NULL) { + if ((rv = apr_file_trunc(fd, 0)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't truncate existing " + "temporary file"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + if (fd == NULL) { + const char *tempdir = NULL; + char *template; + + rv = apr_temp_dir_get(&tempdir, r->pool); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't get tmp dir"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + apr_filepath_merge(&template, tempdir, + "fcgid.tmp.XXXXXX", + APR_FILEPATH_NATIVE, r->pool); + rv = apr_file_mktemp(&fd, template, 0, + r->connection->pool); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't open tmp file fot stdin request"); + return HTTP_INTERNAL_SERVER_ERROR; + } + apr_pool_userdata_set((const void *) fd, fd_key, + apr_pool_cleanup_null, + r->connection->pool); + } + + /* Write request to tmp file */ + if ((rv = + apr_file_write_full(fd, (const void *) data, len, + &wrote_len)) != APR_SUCCESS + || len != wrote_len) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, + rv, r, + "mod_fcgid: can't write tmp file for stdin request"); + return HTTP_INTERNAL_SERVER_ERROR; + } + /* Create file bucket */ + bucket_stdin = + apr_bucket_file_create(fd, cur_pos, len, r->pool, + r->connection->bucket_alloc); + cur_pos += len; + } + else { + if (APR_BUCKET_IS_HEAP(bucket_input)) + apr_bucket_copy(bucket_input, &bucket_stdin); + else { + /* mod_ssl have a bug? */ + char *pcopydata = + apr_bucket_alloc(len, r->connection->bucket_alloc); + memcpy(pcopydata, data, len); + bucket_stdin = + apr_bucket_heap_create(pcopydata, len, + apr_bucket_free, + r->connection->bucket_alloc); + } + } + + if (!init_header(FCGI_STDIN, 1, len, 0, stdin_request_header)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: header overflow"); + apr_brigade_destroy(input_brigade); + apr_brigade_destroy(tmp_brigade); + apr_brigade_destroy(output_brigade); + return HTTP_INTERNAL_SERVER_ERROR; + } + APR_BRIGADE_INSERT_TAIL(output_brigade, bucket_header); + APR_BRIGADE_INSERT_TAIL(output_brigade, bucket_stdin); + } + + apr_brigade_cleanup(input_brigade); + apr_brigade_cleanup(tmp_brigade); + } + while (!seen_eos); + + apr_brigade_destroy(input_brigade); + apr_brigade_destroy(tmp_brigade); + + /* Append an empty body stdin header */ + stdin_request_header = apr_bucket_alloc(sizeof(FCGI_Header), + r->connection->bucket_alloc); + bucket_header = + apr_bucket_heap_create((const char *) stdin_request_header, + sizeof(*stdin_request_header), + apr_bucket_free, r->connection->bucket_alloc); + if (!init_header(FCGI_STDIN, 1, 0, 0, stdin_request_header)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: header overflow"); + return HTTP_INTERNAL_SERVER_ERROR; + } + APR_BRIGADE_INSERT_TAIL(output_brigade, bucket_header); + + return 0; +} + +int bridge_request(request_rec * r, int role, fcgid_cmd_conf *cmd_conf) +{ + apr_bucket_brigade *output_brigade; + apr_bucket *bucket_eos; + char **envp = ap_create_environment(r->pool, + r->subprocess_env); + int rc; + + /* Create brigade for the request to fastcgi server */ + output_brigade = + apr_brigade_create(r->pool, r->connection->bucket_alloc); + + /* Build the begin request and environ request, append them to output_brigade */ + if (!build_begin_block + (role, r, r->connection->bucket_alloc, output_brigade) + || !build_env_block(r, envp, r->connection->bucket_alloc, + output_brigade)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: can't build begin or env request"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + if (role == FCGI_RESPONDER) { + rc = add_request_body(r, r->pool, output_brigade); + if (rc) { + return rc; + } + } + + /* The eos bucket now */ + bucket_eos = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(output_brigade, bucket_eos); + + /* Bridge the request */ + return handle_request(r, role, cmd_conf, output_brigade); +} diff --git a/modules/fcgid/fcgid_bridge.h b/modules/fcgid/fcgid_bridge.h new file mode 100644 index 0000000..f0e12f2 --- /dev/null +++ b/modules/fcgid/fcgid_bridge.h @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FCGID_BRIDGE_H +#define FCGID_BRIDGE_H +#include "httpd.h" +#include "ap_config.h" +#include "http_config.h" +#include "apr_hash.h" +#include "fcgid_conf.h" + +apr_status_t bucket_ctx_cleanup(void *thectx); +int bridge_request(request_rec * r, int role, fcgid_cmd_conf *cmd_conf); + +#endif diff --git a/modules/fcgid/fcgid_bucket.c b/modules/fcgid/fcgid_bucket.c new file mode 100644 index 0000000..bbc336d --- /dev/null +++ b/modules/fcgid/fcgid_bucket.c @@ -0,0 +1,286 @@ +/* + * 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 "fcgid_bucket.h" +#include "fcgid_protocol.h" +#include "fcgid_bridge.h" + +#define FCGID_FEED_LEN 8192 +static apr_status_t fcgid_feed_data(fcgid_bucket_ctx * ctx, + apr_bucket_alloc_t * bucketalloc, + char **buffer, apr_size_t * bufferlen) +{ + apr_status_t rv; + + if (!ctx->buffer) { + *buffer = apr_bucket_alloc(FCGID_FEED_LEN, bucketalloc); + + *bufferlen = FCGID_FEED_LEN; + if ((rv = + proc_read_ipc(&ctx->ipc, *buffer, + bufferlen)) != APR_SUCCESS) { + ctx->has_error = 1; + apr_bucket_free(*buffer); + return rv; + } + + ctx->buffer = + apr_bucket_heap_create(*buffer, FCGID_FEED_LEN, + apr_bucket_free, bucketalloc); + if (*bufferlen != FCGID_FEED_LEN) { + apr_bucket *buckettmp; + + apr_bucket_split(ctx->buffer, *bufferlen); + buckettmp = APR_BUCKET_NEXT(ctx->buffer); + apr_bucket_delete(buckettmp); + } + } else { + apr_bucket_read(ctx->buffer, (const char **) buffer, bufferlen, + APR_BLOCK_READ); + } + return APR_SUCCESS; +} + +static void fcgid_ignore_bytes(fcgid_bucket_ctx * ctx, + apr_size_t ignorebyte) +{ + apr_bucket *buckettmp; + + if (ignorebyte == ctx->buffer->length) { + apr_bucket_destroy(ctx->buffer); + ctx->buffer = NULL; + } else { + apr_bucket_split(ctx->buffer, ignorebyte); + buckettmp = ctx->buffer; + ctx->buffer = APR_BUCKET_NEXT(ctx->buffer); + apr_bucket_delete(buckettmp); + } +} + +static apr_status_t fcgid_header_bucket_read(apr_bucket * b, + const char **str, + apr_size_t * len, + apr_read_type_e block) +{ + fcgid_bucket_ctx *ctx = (fcgid_bucket_ctx *) b->data; + apr_status_t rv; + apr_size_t hasread, bodysize; + FCGI_Header header; + apr_bucket *curbucket = b; + + /* Keep reading until I get a fastcgi header */ + hasread = 0; + while (hasread < sizeof(header)) { + char *buffer; + apr_size_t bufferlen, putsize; + + /* Feed some data if necessary */ + if ((rv = + fcgid_feed_data(ctx, b->list, &buffer, + &bufferlen)) != APR_SUCCESS) + return rv; + + /* Initialize header */ + putsize = fcgid_min(bufferlen, sizeof(header) - hasread); + memcpy((apr_byte_t *)&header + hasread, buffer, putsize); + hasread += putsize; + + /* Ignore the bytes that have read */ + fcgid_ignore_bytes(ctx, putsize); + } + + /* Get the body size */ + bodysize = header.contentLengthB1; + bodysize <<= 8; + bodysize += header.contentLengthB0; + + /* Handle FCGI_STDERR body, write the content to log file */ + if (header.type == FCGI_STDERR) { + char *logbuf = apr_bucket_alloc(APR_BUCKET_BUFF_SIZE, b->list); + char *line; + apr_size_t hasput; + + memset(logbuf, 0, APR_BUCKET_BUFF_SIZE); + + hasread = 0; + hasput = 0; + while (hasread < bodysize) { + char *buffer; + apr_size_t bufferlen, canput, willput; + + /* Feed some data if necessary */ + if ((rv = + fcgid_feed_data(ctx, b->list, &buffer, + &bufferlen)) != APR_SUCCESS) { + apr_bucket_free(logbuf); + return rv; + } + + canput = fcgid_min(bufferlen, bodysize - hasread); + willput = + fcgid_min(canput, APR_BUCKET_BUFF_SIZE - hasput - 1); + memcpy(logbuf + hasput, buffer, willput); + hasread += canput; + hasput += willput; + + /* Ignore the "canput" bytes */ + fcgid_ignore_bytes(ctx, canput); + } + + /* Now I get the log data, write log and release the buffer */ + line = logbuf; + while (*line) { + char *end = strpbrk(line, "\r\n"); + + if (end != NULL) { + *end = '\0'; + } + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ctx->ipc.request, + "mod_fcgid: stderr: %s", line); + if (end == NULL) { + break; + } + ++end; + line = end + strspn(end, "\r\n"); + } + + apr_bucket_free(logbuf); + } + + /* if( header.type==FCGI_STDERR ) */ + /* Now handle FCGI_STDOUT */ + else if (header.type == FCGI_STDOUT) { + hasread = 0; + while (hasread < bodysize) { + char *buffer; + apr_size_t bufferlen, canput; + apr_bucket *buckettmp; + + /* Feed some data if necessary */ + if ((rv = + fcgid_feed_data(ctx, b->list, &buffer, + &bufferlen)) != APR_SUCCESS) + return rv; + + canput = fcgid_min(bufferlen, bodysize - hasread); + + /* Change the current bucket to refer to what we read */ + buckettmp = ctx->buffer; + if (canput == (bodysize - hasread)) { + apr_bucket_split(ctx->buffer, canput); + ctx->buffer = APR_BUCKET_NEXT(ctx->buffer); + APR_BUCKET_REMOVE(buckettmp); + } else { + /* canput==bufferlen */ + ctx->buffer = NULL; + } + + APR_BUCKET_INSERT_AFTER(curbucket, buckettmp); + curbucket = buckettmp; + hasread += canput; + } /* while( hasreadlist, &buffer, + &bufferlen)) != APR_SUCCESS) + return rv; + + canignore = fcgid_min(bufferlen, bodysize); + hasread += canignore; + + /* Ignore the bytes */ + fcgid_ignore_bytes(ctx, canignore); + } + } + + /* Now ignore padding data */ + hasread = 0; + while (hasread < header.paddingLength) { + char *buffer; + apr_size_t bufferlen, canignore; + + /* Feed some data if necessary */ + if ((rv = + fcgid_feed_data(ctx, b->list, &buffer, + &bufferlen)) != APR_SUCCESS) + return rv; + + canignore = fcgid_min(bufferlen, header.paddingLength - hasread); + hasread += canignore; + + /* Ignore the bytes */ + fcgid_ignore_bytes(ctx, canignore); + } + + /* Tail another fastcgi header bucket if it's not ending */ + if (header.type != FCGI_END_REQUEST) { + apr_bucket *headerbucket = + ap_bucket_fcgid_header_create(b->list, ctx); + APR_BUCKET_INSERT_AFTER(curbucket, headerbucket); + } else { + /* Release the process ASAP */ + if ((rv = apr_pool_cleanup_run(ctx->ipc.request->pool, + ctx, + bucket_ctx_cleanup)) != APR_SUCCESS) + return rv; + } + + b = apr_bucket_immortal_make(b, "", 0); + return apr_bucket_read(b, str, len, APR_BLOCK_READ); +} + +apr_bucket *ap_bucket_fcgid_header_make(apr_bucket * b, + fcgid_bucket_ctx * ctx) +{ + b->length = (apr_size_t) (-1); + b->start = -1; + b->data = ctx; + b->type = &ap_bucket_type_fcgid_header; + + return b; +} + +apr_bucket *ap_bucket_fcgid_header_create(apr_bucket_alloc_t * list, + fcgid_bucket_ctx * ctx) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + return ap_bucket_fcgid_header_make(b, ctx); +} + +const apr_bucket_type_t ap_bucket_type_fcgid_header = { + "FCGID_HEADER", 5, APR_BUCKET_DATA, + apr_bucket_destroy_noop, + fcgid_header_bucket_read, + apr_bucket_setaside_notimpl, + apr_bucket_split_notimpl, + apr_bucket_copy_notimpl +}; diff --git a/modules/fcgid/fcgid_bucket.h b/modules/fcgid/fcgid_bucket.h new file mode 100644 index 0000000..56e6f73 --- /dev/null +++ b/modules/fcgid/fcgid_bucket.h @@ -0,0 +1,36 @@ +/* + * 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 FCGID_BUCKET_H +#define FCGID_BUCKET_H +#include "httpd.h" +#include "fcgid_proc.h" + +typedef struct fcgid_bucket_ctx_t { + fcgid_ipc ipc; + apr_bucket *buffer; + fcgid_procnode *procnode; + apr_time_t active_time; + int has_error; +} fcgid_bucket_ctx; + +extern const apr_bucket_type_t ap_bucket_type_fcgid_header; +apr_bucket *ap_bucket_fcgid_header_create(apr_bucket_alloc_t * list, + fcgid_bucket_ctx * ctx); +apr_bucket *ap_bucket_fcgid_header_make(apr_bucket *, fcgid_bucket_ctx *); + +#endif diff --git a/modules/fcgid/fcgid_conf.c b/modules/fcgid/fcgid_conf.c new file mode 100644 index 0000000..3cc9117 --- /dev/null +++ b/modules/fcgid/fcgid_conf.c @@ -0,0 +1,1162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ap_config.h" +#include "ap_mmn.h" +#include "apr_strings.h" +#include "apr_hash.h" +#include "apr_lib.h" +#include "apr_tables.h" +#include "apr_version.h" +#include "http_main.h" +#include "httpd.h" +#include "http_config.h" +#include "fcgid_global.h" +#include "fcgid_conf.h" + +#ifndef DEFAULT_REL_RUNTIMEDIR /* Win32, etc. */ +#define DEFAULT_REL_RUNTIMEDIR "logs" +#endif + +#define DEFAULT_IDLE_TIMEOUT 300 +#define DEFAULT_IDLE_SCAN_INTERVAL 120 +#define DEFAULT_BUSY_TIMEOUT 300 +#define DEFAULT_BUSY_SCAN_INTERVAL 120 +#define DEFAULT_ERROR_SCAN_INTERVAL 3 +#define DEFAULT_ZOMBIE_SCAN_INTERVAL 3 +#define DEFAULT_PROC_LIFETIME (60*60) +#define DEFAULT_SOCKET_PREFIX DEFAULT_REL_RUNTIMEDIR "/fcgidsock" +#define DEFAULT_SHM_PATH DEFAULT_REL_RUNTIMEDIR "/fcgid_shm" +#define DEFAULT_SPAWNSOCRE_UPLIMIT 10 +#define DEFAULT_SPAWN_SCORE 1 +#define DEFAULT_TERMINATION_SCORE 2 +#define DEFAULT_TIME_SCORE 1 +#define DEFAULT_MAX_PROCESS_COUNT 1000 +#define DEFAULT_MAX_CLASS_PROCESS_COUNT 100 +#define DEFAULT_MIN_CLASS_PROCESS_COUNT 3 +#define DEFAULT_IPC_CONNECT_TIMEOUT 3 +#define DEFAULT_IPC_COMM_TIMEOUT 40 +#define DEFAULT_OUTPUT_BUFFERSIZE 65536 +#define DEFAULT_MAX_REQUESTS_PER_PROCESS 0 +/* by default, allow spooling of request bodies up to + * 128k (first 64k in memory) + */ +#define DEFAULT_MAX_REQUEST_LEN (1024*128) +#define DEFAULT_MAX_MEM_REQUEST_LEN (1024*64) +#define DEFAULT_WRAPPER_KEY "ALL" +#define WRAPPER_FLAG_VIRTUAL "virtual" + +void *create_fcgid_server_config(apr_pool_t * p, server_rec * s) +{ + fcgid_server_conf *config = apr_pcalloc(p, sizeof(*config)); + static int vhost_id = 0; + + /* allow vhost comparison even when some mass-vhost module + * makes a copy of the server_rec to override docroot or + * other such settings + */ + ++vhost_id; + config->vhost_id = vhost_id; + + if (!s->is_virtual) { + config->busy_scan_interval = DEFAULT_BUSY_SCAN_INTERVAL; + config->error_scan_interval = DEFAULT_ERROR_SCAN_INTERVAL; + config->idle_scan_interval = DEFAULT_IDLE_SCAN_INTERVAL; + config->max_process_count = DEFAULT_MAX_PROCESS_COUNT; + config->shmname_path = ap_server_root_relative(p, DEFAULT_SHM_PATH); + config->sockname_prefix = + ap_server_root_relative(p, DEFAULT_SOCKET_PREFIX); + config->spawn_score = DEFAULT_SPAWN_SCORE; + config->spawnscore_uplimit = DEFAULT_SPAWNSOCRE_UPLIMIT; + config->termination_score = DEFAULT_TERMINATION_SCORE; + config->time_score = DEFAULT_TIME_SCORE; + config->zombie_scan_interval = DEFAULT_ZOMBIE_SCAN_INTERVAL; + } + /* Redundant; pcalloc creates this structure; + * config->default_init_env = NULL; + * config->pass_headers = NULL; + * config->php_fix_pathinfo_enable = 0; + * config->*_set = 0; + */ + config->cmdopts_hash = apr_hash_make(p); + config->ipc_comm_timeout = DEFAULT_IPC_COMM_TIMEOUT; + config->ipc_connect_timeout = DEFAULT_IPC_CONNECT_TIMEOUT; + config->max_mem_request_len = DEFAULT_MAX_MEM_REQUEST_LEN; + config->max_request_len = DEFAULT_MAX_REQUEST_LEN; + config->max_requests_per_process = DEFAULT_MAX_REQUESTS_PER_PROCESS; + config->output_buffersize = DEFAULT_OUTPUT_BUFFERSIZE; + config->max_class_process_count = DEFAULT_MAX_CLASS_PROCESS_COUNT; + config->min_class_process_count = DEFAULT_MIN_CLASS_PROCESS_COUNT; + config->busy_timeout = DEFAULT_BUSY_TIMEOUT; + config->idle_timeout = DEFAULT_IDLE_TIMEOUT; + config->proc_lifetime = DEFAULT_PROC_LIFETIME; + + return config; +} + +#define MERGE_SCALAR(base, local, merged, field) \ + if (!(local)->field##_set) { \ + merged->field = base->field; \ + } + +void *merge_fcgid_server_config(apr_pool_t * p, void *basev, void *locv) +{ + fcgid_server_conf *base = (fcgid_server_conf *) basev; + fcgid_server_conf *local = (fcgid_server_conf *) locv; + fcgid_server_conf *merged = + (fcgid_server_conf *) apr_pmemdup(p, local, sizeof(fcgid_server_conf)); + + merged->cmdopts_hash = apr_hash_overlay(p, local->cmdopts_hash, + base->cmdopts_hash); + + /* Merge environment variables */ + if (base->default_init_env == NULL) { + /* merged already set to local */ + } + else if (local->default_init_env == NULL) { + merged->default_init_env = base->default_init_env; + } + else { + merged->default_init_env = + apr_table_copy(p, base->default_init_env); + apr_table_overlap(merged->default_init_env, + local->default_init_env, + APR_OVERLAP_TABLES_SET); + } + + /* Merge pass headers */ + if (base->pass_headers == NULL) { + /* merged already set to local */ + } + else if (local->pass_headers == NULL) { + merged->pass_headers = base->pass_headers; + } + else { + merged->pass_headers = + apr_array_append(p, + base->pass_headers, + local->pass_headers); + } + + /* Merge the scalar settings */ + + MERGE_SCALAR(base, local, merged, ipc_comm_timeout); + MERGE_SCALAR(base, local, merged, ipc_connect_timeout); + MERGE_SCALAR(base, local, merged, max_mem_request_len); + MERGE_SCALAR(base, local, merged, max_request_len); + MERGE_SCALAR(base, local, merged, max_requests_per_process); + MERGE_SCALAR(base, local, merged, output_buffersize); + MERGE_SCALAR(base, local, merged, max_class_process_count); + MERGE_SCALAR(base, local, merged, min_class_process_count); + MERGE_SCALAR(base, local, merged, busy_timeout); + MERGE_SCALAR(base, local, merged, idle_timeout); + MERGE_SCALAR(base, local, merged, proc_lifetime); + + return merged; +} + +void *create_fcgid_dir_config(apr_pool_t * p, char *dummy) +{ + fcgid_dir_conf *config = apr_pcalloc(p, sizeof(fcgid_dir_conf)); + + config->wrapper_info_hash = apr_hash_make(p); + /* config->authenticator_info = NULL; */ + config->authenticator_authoritative = 1; + /* config->authorizer_info = NULL; */ + config->authorizer_authoritative = 1; + /* config->access_info = NULL; */ + config->access_authoritative = 1; + return (void *) config; +} + +void *merge_fcgid_dir_config(apr_pool_t *p, void *basev, void *locv) +{ + fcgid_dir_conf *base = (fcgid_dir_conf *) basev; + fcgid_dir_conf *local = (fcgid_dir_conf *) locv; + fcgid_dir_conf *merged = + (fcgid_dir_conf *) apr_pmemdup(p, local, sizeof(fcgid_dir_conf)); + + merged->wrapper_info_hash = + apr_hash_overlay(p, local->wrapper_info_hash, + base->wrapper_info_hash); + + if (!local->authenticator_info) { + merged->authenticator_info = base->authenticator_info; + } + + if (!local->authorizer_info) { + merged->authorizer_info = base->authorizer_info; + } + + if (!local->access_info) { + merged->access_info = base->access_info; + } + + MERGE_SCALAR(base, local, merged, authenticator_authoritative); + MERGE_SCALAR(base, local, merged, authorizer_authoritative); + MERGE_SCALAR(base, local, merged, access_authoritative); + + return merged; +} + +const char *set_idle_timeout(cmd_parms * cmd, void *dummy, const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + + config->idle_timeout = atol(arg); + config->idle_timeout_set = 1; + return NULL; +} + +const char *set_idle_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->idle_scan_interval = atol(arg); + return NULL; +} + +const char *set_busy_timeout(cmd_parms * cmd, void *dummy, const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + + config->busy_timeout = atol(arg); + config->busy_timeout_set = 1; + return NULL; +} + +const char *set_busy_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->busy_scan_interval = atol(arg); + return NULL; +} + +const char *set_proc_lifetime(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + + config->proc_lifetime = atol(arg); + config->proc_lifetime_set = 1; + return NULL; +} + +const char *set_error_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->error_scan_interval = atol(arg); + return NULL; +} + +const char *set_zombie_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->zombie_scan_interval = atol(arg); + return NULL; +} + +const char *set_socketpath(cmd_parms * cmd, void *dummy, const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->sockname_prefix = ap_server_root_relative(cmd->pool, arg); + if (!config->sockname_prefix) + return "Invalid socket path"; + + return NULL; +} + +const char *set_shmpath(cmd_parms * cmd, void *dummy, const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->shmname_path = ap_server_root_relative(cmd->pool, arg); + if (!config->shmname_path) + return "Invalid shmname path"; + + return NULL; +} + +const char *set_spawnscore_uplimit(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->spawnscore_uplimit = atol(arg); + return NULL; +} + +static int strtoff(apr_off_t *val, const char *arg) +{ + char *errp; + +#if APR_MAJOR_VERSION < 1 + *val = (apr_off_t)strtol(arg, &errp, 10); + if (*errp) { + return 1; + } +#else + if (APR_SUCCESS != apr_strtoff(val, arg, &errp, 10) || *errp) { + return 1; + } +#endif + return 0; +} + +const char *set_max_request_len(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + + if (strtoff(&config->max_request_len, arg) + || config->max_request_len < 0) { + return "FcgidMaxRequestLen requires a non-negative integer."; + } + + config->max_request_len_set = 1; + return NULL; +} + +const char *set_max_mem_request_len(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + config->max_mem_request_len = atol(arg); + config->max_mem_request_len_set = 1; + return NULL; +} + +const char *set_spawn_score(cmd_parms * cmd, void *dummy, const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->spawn_score = atol(arg); + return NULL; +} + +const char *set_time_score(cmd_parms * cmd, void *dummy, const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->time_score = atol(arg); + return NULL; +} + +const char *set_termination_score(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->termination_score = atol(arg); + return NULL; +} + +const char *set_max_process(cmd_parms * cmd, void *dummy, const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->max_process_count = atol(arg); + return NULL; +} + +const char *set_output_buffersize(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + config->output_buffersize = atol(arg); + config->output_buffersize_set = 1; + return NULL; +} + +const char *set_max_class_process(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + + config->max_class_process_count = atol(arg); + config->max_class_process_count_set = 1; + return NULL; +} + +const char *set_min_class_process(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + + config->min_class_process_count = atol(arg); + config->min_class_process_count_set = 1; + return NULL; +} + +const char *set_php_fix_pathinfo_enable(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + config->php_fix_pathinfo_enable = atol(arg); + return NULL; +} + +const char *set_max_requests_per_process(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + if ((config->max_requests_per_process = atol(arg)) == -1) { + config->max_requests_per_process = 0; + } + config->max_requests_per_process_set = 1; + return NULL; +} + +const char *set_ipc_connect_timeout(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + config->ipc_connect_timeout = atol(arg); + config->ipc_connect_timeout_set = 1; + return NULL; +} + +const char *set_ipc_comm_timeout(cmd_parms * cmd, void *dummy, + const char *arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = + ap_get_module_config(s->module_config, &fcgid_module); + config->ipc_comm_timeout = atol(arg); + if (config->ipc_comm_timeout <= 0) { + return "FcgidIOTimeout must be greater than 0"; + } + config->ipc_comm_timeout_set = 1; + return NULL; +} + +static void add_envvar_to_table(apr_table_t *t, apr_pool_t *p, + const char *name, const char *value) +{ +#if defined(WIN32) || defined(OS2) || defined(NETWARE) + /* Case insensitive environment platforms */ + char *pstr; + for (name = pstr = apr_pstrdup(p, name); *pstr; ++pstr) { + *pstr = apr_toupper(*pstr); + } +#endif + apr_table_set(t, name, value ? value : ""); +} + +const char *add_default_env_vars(cmd_parms * cmd, void *dummy, + const char *name, const char *value) +{ + fcgid_server_conf *config = + ap_get_module_config(cmd->server->module_config, &fcgid_module); + if (config->default_init_env == NULL) + config->default_init_env = apr_table_make(cmd->pool, 20); + + add_envvar_to_table(config->default_init_env, cmd->pool, name, value); + return NULL; +} + +const char *add_pass_headers(cmd_parms * cmd, void *dummy, + const char *names) +{ + const char **header; + fcgid_server_conf *config = + ap_get_module_config(cmd->server->module_config, &fcgid_module); + if (config->pass_headers == NULL) + config->pass_headers = + apr_array_make(cmd->pool, 10, sizeof(const char *)); + + header = (const char **) apr_array_push(config->pass_headers); + *header = ap_getword_conf(cmd->pool, &names); + + return header ? NULL : "Invalid PassHeaders"; +} + +apr_array_header_t *get_pass_headers(request_rec * r) +{ + fcgid_server_conf *config = + ap_get_module_config(r->server->module_config, &fcgid_module); + return config->pass_headers; +} + +static const char *missing_file_msg(apr_pool_t *p, const char *filetype, const char *filename, + apr_status_t rv) +{ + char errbuf[120]; + + apr_strerror(rv, errbuf, sizeof errbuf); + return apr_psprintf(p, "%s %s cannot be accessed: (%d)%s", + filetype, filename, rv, errbuf); +} + +const char *set_authenticator_info(cmd_parms * cmd, void *config, + const char *authenticator) +{ + apr_status_t rv; + apr_finfo_t finfo; + fcgid_dir_conf *dirconfig = (fcgid_dir_conf *) config; + char **args; + + /* Get wrapper path */ + apr_tokenize_to_argv(authenticator, &args, cmd->temp_pool); + + if (*args == NULL || **args == '\0') + return "Invalid authenticator config"; + + /* Fetch only required file details inode + device */ + if ((rv = apr_stat(&finfo, args[0], APR_FINFO_IDENT, + cmd->temp_pool)) != APR_SUCCESS) { + return missing_file_msg(cmd->pool, "Authenticator", authenticator, rv); + } + + /* Create the wrapper node */ + dirconfig->authenticator_info = + apr_pcalloc(cmd->server->process->pconf, + sizeof(*dirconfig->authenticator_info)); + dirconfig->authenticator_info->cgipath = apr_pstrdup(cmd->pool, args[0]); + dirconfig->authenticator_info->cmdline = authenticator; + dirconfig->authenticator_info->inode = finfo.inode; + dirconfig->authenticator_info->deviceid = finfo.device; + return NULL; +} + +const char *set_authenticator_authoritative(cmd_parms * cmd, + void *config, int arg) +{ + fcgid_dir_conf *dirconfig = (fcgid_dir_conf *) config; + + dirconfig->authenticator_authoritative = arg; + dirconfig->authenticator_authoritative_set = 1; + return NULL; +} + +fcgid_cmd_conf *get_authenticator_info(request_rec * r, int *authoritative) +{ + fcgid_dir_conf *config = + ap_get_module_config(r->per_dir_config, &fcgid_module); + + if (config != NULL && config->authenticator_info != NULL) { + *authoritative = config->authenticator_authoritative; + return config->authenticator_info; + } + + return NULL; +} + +const char *set_authorizer_info(cmd_parms * cmd, void *config, + const char *authorizer) +{ + apr_status_t rv; + apr_finfo_t finfo; + fcgid_dir_conf *dirconfig = (fcgid_dir_conf *) config; + char **args; + + /* Get wrapper path */ + apr_tokenize_to_argv(authorizer, &args, cmd->temp_pool); + + if (*args == NULL || **args == '\0') + return "Invalid authorizer config"; + + /* Fetch only required file details inode + device */ + if ((rv = apr_stat(&finfo, args[0], APR_FINFO_IDENT, + cmd->temp_pool)) != APR_SUCCESS) { + return missing_file_msg(cmd->pool, "Authorizer", authorizer, rv); + } + + /* Create the wrapper node */ + dirconfig->authorizer_info = + apr_pcalloc(cmd->server->process->pconf, + sizeof(*dirconfig->authorizer_info)); + dirconfig->authorizer_info->cgipath = apr_pstrdup(cmd->pool, args[0]); + dirconfig->authorizer_info->cmdline = authorizer; + dirconfig->authorizer_info->inode = finfo.inode; + dirconfig->authorizer_info->deviceid = finfo.device; + return NULL; +} + +const char *set_authorizer_authoritative(cmd_parms * cmd, + void *config, int arg) +{ + fcgid_dir_conf *dirconfig = (fcgid_dir_conf *) config; + + dirconfig->authorizer_authoritative = arg; + dirconfig->authorizer_authoritative_set = 1; + return NULL; +} + +fcgid_cmd_conf *get_authorizer_info(request_rec * r, int *authoritative) +{ + fcgid_dir_conf *config = + ap_get_module_config(r->per_dir_config, &fcgid_module); + + if (config != NULL && config->authorizer_info != NULL) { + *authoritative = config->authorizer_authoritative; + return config->authorizer_info; + } + + return NULL; +} + +const char *set_access_info(cmd_parms * cmd, void *config, + const char *access) +{ + apr_status_t rv; + apr_finfo_t finfo; + fcgid_dir_conf *dirconfig = (fcgid_dir_conf *) config; + char **args; + + /* Get wrapper path */ + apr_tokenize_to_argv(access, &args, cmd->temp_pool); + + if (*args == NULL || **args == '\0') + return "Invalid access config"; + + /* Fetch only required file details inode + device */ + if ((rv = apr_stat(&finfo, args[0], APR_FINFO_IDENT, + cmd->temp_pool)) != APR_SUCCESS) { + return missing_file_msg(cmd->pool, "Access checker", access, rv); + } + + /* Create the wrapper node */ + dirconfig->access_info = + apr_pcalloc(cmd->server->process->pconf, + sizeof(*dirconfig->access_info)); + dirconfig->access_info->cgipath = apr_pstrdup(cmd->pool, args[0]); + dirconfig->access_info->cmdline = access; + dirconfig->access_info->inode = finfo.inode; + dirconfig->access_info->deviceid = finfo.device; + return NULL; +} + +const char *set_access_authoritative(cmd_parms * cmd, + void *config, int arg) +{ + fcgid_dir_conf *dirconfig = (fcgid_dir_conf *) config; + + dirconfig->access_authoritative = arg; + dirconfig->access_authoritative_set = 1; + return NULL; +} + +fcgid_cmd_conf *get_access_info(request_rec * r, int *authoritative) +{ + fcgid_dir_conf *config = + ap_get_module_config(r->per_dir_config, &fcgid_module); + + if (config != NULL && config->access_info != NULL) { + *authoritative = config->access_authoritative; + return config->access_info; + } + + return NULL; +} + +#ifdef WIN32 +/* FcgidWin32PreventOrphans + * + * When Apache process gets recycled or shutdown abruptly, CGI processes + * spawned by mod_fcgid will get orphaned. Orphaning happens mostly when + * Apache worker threads take more than 30 seconds to exit gracefully. + * + * Apache when run as windows service during shutdown/restart of service + * process (master/parent) will terminate child httpd process within 30 + * seconds (refer \server\mpm\winnt\mpm_winnt.c:master_main() + * int timeout = 30000; ~line#1142), therefore if Apache worker threads + * are too busy to react to Master's graceful exit signal within 30 seconds + * mod_fcgid cleanup routines will not get invoked (refer child_main() + * \server\mpm\winnt\child.c: apr_pool_destroy(pchild); ~line#2275) + * thereby orphaning all mod_fcgid spwaned CGI processes. Therefore we utilize + * Win32 JobObjects to clean up child processes automatically so that CGI + * processes are force-killed by win32 during abnormal mod_fcgid termination. + * + */ +const char *set_win32_prevent_process_orphans(cmd_parms *cmd, void *dummy, + int arg) +{ + server_rec *s = cmd->server; + fcgid_server_conf *config = ap_get_module_config(s->module_config, + &fcgid_module); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); +#define SETUP_ERR_MSG "Error enabling CGI process orphan prevention" + + if (err != NULL) { + return err; + } + + if (arg && config->hJobObjectForAutoCleanup == NULL) { + /* Create Win32 job object to prevent CGI process oprhaning + */ + JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = { 0 }; + config->hJobObjectForAutoCleanup = CreateJobObject(NULL, NULL); + + if (config->hJobObjectForAutoCleanup == NULL) { + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, apr_get_os_error(), + cmd->pool, "mod_fcgid: unable to create job object."); + return SETUP_ERR_MSG; + } + + /* Set job info so that all spawned CGI processes are associated + * with mod_fcgid + */ + job_info.BasicLimitInformation.LimitFlags + = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (SetInformationJobObject(config->hJobObjectForAutoCleanup, + JobObjectExtendedLimitInformation, + &job_info, sizeof(job_info)) == 0) { + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, apr_get_os_error(), + cmd->pool, "mod_fcgid: unable to set job object information."); + CloseHandle(config->hJobObjectForAutoCleanup); + config->hJobObjectForAutoCleanup = NULL; + return SETUP_ERR_MSG; + } + } + + return NULL; +} +#endif /* WIN32*/ + +const char *set_wrapper_config(cmd_parms * cmd, void *dirconfig, + const char *wrapper_cmdline, + const char *extension, + const char *virtual) +{ + const char *path; + apr_status_t rv; + apr_finfo_t finfo; + fcgid_cmd_conf *wrapper = NULL; + fcgid_dir_conf *config = (fcgid_dir_conf *) dirconfig; + char **args; + + /* Sanity checks */ + + if (virtual == NULL && extension != NULL && !strcasecmp(extension, WRAPPER_FLAG_VIRTUAL)) { + virtual = WRAPPER_FLAG_VIRTUAL; + extension = NULL; + } + + if (virtual != NULL && strcasecmp(virtual, WRAPPER_FLAG_VIRTUAL)) { + return "Invalid wrapper flag"; + } + + if (extension != NULL + && (*extension != '.' || *(extension + 1) == '\0' + || ap_strchr_c(extension, '/') || ap_strchr_c(extension, '\\'))) + return "Invalid wrapper file extension"; + + /* Get wrapper path */ + apr_tokenize_to_argv(wrapper_cmdline, &args, cmd->temp_pool); + path = apr_pstrdup(cmd->pool, args[0]); + + if (path == NULL || *path == '\0') + return "Invalid wrapper config"; + + /* Fetch only required file details inode + device */ + if ((rv = apr_stat(&finfo, path, APR_FINFO_IDENT, + cmd->temp_pool)) != APR_SUCCESS) { + return missing_file_msg(cmd->pool, "Wrapper", path, rv); + } + + wrapper = apr_pcalloc(cmd->pool, sizeof(*wrapper)); + + if (strlen(path) >= FCGID_PATH_MAX) { + return "Executable path length exceeds compiled-in limit"; + } + wrapper->cgipath = apr_pstrdup(cmd->pool, path); + + if (strlen(wrapper_cmdline) >= FCGID_CMDLINE_MAX) { + return "Command line length exceeds compiled-in limit"; + } + wrapper->cmdline = apr_pstrdup(cmd->pool, wrapper_cmdline); + + wrapper->inode = finfo.inode; + wrapper->deviceid = finfo.device; + wrapper->virtual = (virtual != NULL && !strcasecmp(virtual, WRAPPER_FLAG_VIRTUAL)); + + if (extension == NULL) + extension = DEFAULT_WRAPPER_KEY; + + /* Add the node now */ + /* If an extension is configured multiple times, the last directive wins. */ + apr_hash_set(config->wrapper_info_hash, extension, strlen(extension), + wrapper); + + return NULL; +} + +fcgid_cmd_conf *get_wrapper_info(const char *cgipath, request_rec * r) +{ + const char *extension; + fcgid_cmd_conf *wrapper; + fcgid_dir_conf *config = + ap_get_module_config(r->per_dir_config, &fcgid_module); + + /* Get file name extension */ + extension = ap_strrchr_c(cgipath, '.'); + + if (extension == NULL) + extension = DEFAULT_WRAPPER_KEY; + + /* Search file name extension in per_dir_config */ + if (config) { + wrapper = apr_hash_get(config->wrapper_info_hash, extension, + strlen(extension)); + if (wrapper == NULL) + wrapper = apr_hash_get(config->wrapper_info_hash, DEFAULT_WRAPPER_KEY, + strlen(DEFAULT_WRAPPER_KEY)); + return wrapper; + } + + return NULL; +} + +static int set_cmd_envvars(fcgid_cmd_env *cmdenv, apr_table_t *envvars) +{ + const apr_array_header_t *envvars_arr; + const apr_table_entry_t *envvars_entry; + int i; + int overflow = 0; + + if (envvars) { + envvars_arr = apr_table_elts(envvars); + envvars_entry = (apr_table_entry_t *) envvars_arr->elts; + if (envvars_arr->nelts > INITENV_CNT) { + overflow = envvars_arr->nelts - INITENV_CNT; + } + + for (i = 0; i < envvars_arr->nelts && i < INITENV_CNT; ++i) { + if (envvars_entry[i].key == NULL + || envvars_entry[i].key[0] == '\0') + break; + apr_cpystrn(cmdenv->initenv_key[i], envvars_entry[i].key, + INITENV_KEY_LEN); + apr_cpystrn(cmdenv->initenv_val[i], envvars_entry[i].val, + INITENV_VAL_LEN); + } + if (i < INITENV_CNT) { + cmdenv->initenv_key[i][0] = '\0'; + } + } + else { + cmdenv->initenv_key[0][0] = '\0'; + } + + return overflow; +} + +const char *set_cmd_options(cmd_parms *cmd, void *dummy, const char *args) +{ + server_rec *s = cmd->server; + fcgid_server_conf *sconf = + ap_get_module_config(s->module_config, &fcgid_module); + const char *cmdname; + fcgid_cmd_options *cmdopts; + apr_table_t *envvars = NULL; + int overflow; + apr_finfo_t finfo; + apr_status_t rv; + + cmdopts = apr_pcalloc(cmd->pool, sizeof *cmdopts); + cmdopts->cmdenv = apr_pcalloc(cmd->pool, sizeof *cmdopts->cmdenv); + + cmdopts->busy_timeout = DEFAULT_BUSY_TIMEOUT; + cmdopts->idle_timeout = DEFAULT_IDLE_TIMEOUT; + cmdopts->ipc_comm_timeout = DEFAULT_IPC_COMM_TIMEOUT; + cmdopts->ipc_connect_timeout = DEFAULT_IPC_CONNECT_TIMEOUT; + cmdopts->max_class_process_count = DEFAULT_MAX_CLASS_PROCESS_COUNT; + cmdopts->max_requests_per_process = DEFAULT_MAX_REQUESTS_PER_PROCESS; + cmdopts->min_class_process_count = DEFAULT_MIN_CLASS_PROCESS_COUNT; + cmdopts->proc_lifetime = DEFAULT_PROC_LIFETIME; + /* via pcalloc: cmdopts->initenv_key[0][0] = '\0'; */ + + cmdname = ap_getword_conf(cmd->pool, &args); + if (!strlen(cmdname)) { + return "A command must be specified for FcgidCmdOptions"; + } + + /* Test only for file existence */ + rv = apr_stat(&finfo, cmdname, APR_FINFO_MIN, cmd->temp_pool); + if (rv != APR_SUCCESS) { + return missing_file_msg(cmd->pool, "Command", cmdname, rv); + } + + if (!*args) { + return "At least one option must be specified for FcgidCmdOptions"; + } + + while (*args) { + const char *option = ap_getword_conf(cmd->pool, &args); + const char *val; + + /* TODO: Consider supporting BusyTimeout. + */ + + if (!strcasecmp(option, "ConnectTimeout")) { + val = ap_getword_conf(cmd->pool, &args); + if (!strlen(val)) { + return "ConnectTimeout must have an argument"; + } + cmdopts->ipc_connect_timeout = atoi(val); + continue; + } + + if (!strcasecmp(option, "IdleTimeout")) { + val = ap_getword_conf(cmd->pool, &args); + if (!strlen(val)) { + return "IdleTimeout must have an argument"; + } + cmdopts->idle_timeout = atoi(val); + continue; + } + + if (!strcasecmp(option, "InitialEnv")) { + char *name; + char *eql; + + name = ap_getword_conf(cmd->pool, &args); + if (!strlen(name)) { + return "InitialEnv must have an argument"; + } + + eql = strchr(name, '='); + if (eql) { + *eql = '\0'; + ++eql; + } + + if (!envvars) { + envvars = apr_table_make(cmd->pool, 20); + } + add_envvar_to_table(envvars, cmd->pool, name, eql); + continue; + } + + if (!strcasecmp(option, "IOTimeout")) { + val = ap_getword_conf(cmd->pool, &args); + if (!strlen(val)) { + return "IOTimeout must have an argument"; + } + cmdopts->ipc_comm_timeout = atoi(val); + continue; + } + + if (!strcasecmp(option, "MaxProcesses")) { + val = ap_getword_conf(cmd->pool, &args); + if (!strlen(val)) { + return "MaxProcesses must have an argument"; + } + cmdopts->max_class_process_count = atoi(val); + continue; + } + + if (!strcasecmp(option, "MaxProcessLifetime")) { + val = ap_getword_conf(cmd->pool, &args); + if (!strlen(val)) { + return "MaxProcessLifetime must have an argument"; + } + cmdopts->proc_lifetime = atoi(val); + continue; + } + + if (!strcasecmp(option, "MaxRequestsPerProcess")) { + val = ap_getword_conf(cmd->pool, &args); + if (!strlen(val)) { + return "MaxRequestsPerProcess must have an argument"; + } + cmdopts->max_requests_per_process = atoi(val); + continue; + } + + if (!strcasecmp(option, "MinProcesses")) { + val = ap_getword_conf(cmd->pool, &args); + if (!strlen(val)) { + return "MinProcesses must have an argument"; + } + cmdopts->min_class_process_count = atoi(val); + continue; + } + + return apr_psprintf(cmd->pool, + "Invalid option for FcgidCmdOptions: %s", + option); + } + + if ((overflow = set_cmd_envvars(cmdopts->cmdenv, envvars)) != 0) { + return apr_psprintf(cmd->pool, "mod_fcgid: environment variable table " + "overflow; increase INITENV_CNT in fcgid_pm.h from" + " %d to at least %d", + INITENV_CNT, INITENV_CNT + overflow); + } + + apr_hash_set(sconf->cmdopts_hash, cmdname, strlen(cmdname), cmdopts); + + return NULL; +} + +void get_cmd_options(request_rec *r, const char *cmdpath, + fcgid_cmd_options *cmdopts, + fcgid_cmd_env *cmdenv) +{ + fcgid_server_conf *sconf = + ap_get_module_config(r->server->module_config, &fcgid_module); + fcgid_cmd_options *cmd_specific = apr_hash_get(sconf->cmdopts_hash, + cmdpath, + strlen(cmdpath)); + int overflow; + + if (cmd_specific) { /* ignore request context configuration */ + *cmdopts = *cmd_specific; + *cmdenv = *cmdopts->cmdenv; + cmdopts->cmdenv = NULL; + /* pick up configuration for values that can't be configured + * on FcgidCmdOptions + */ + cmdopts->busy_timeout = sconf->busy_timeout; + return; + } + + cmdopts->busy_timeout = sconf->busy_timeout; + cmdopts->idle_timeout = sconf->idle_timeout; + cmdopts->ipc_comm_timeout = sconf->ipc_comm_timeout; + cmdopts->ipc_connect_timeout = sconf->ipc_connect_timeout; + cmdopts->max_class_process_count = sconf->max_class_process_count; + cmdopts->max_requests_per_process = sconf->max_requests_per_process; + cmdopts->min_class_process_count = sconf->min_class_process_count; + cmdopts->proc_lifetime = sconf->proc_lifetime; + + if ((overflow = set_cmd_envvars(cmdenv, sconf->default_init_env)) != 0) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: %d environment variables dropped; increase " + "INITENV_CNT in fcgid_pm.h from %d to at least %d", + overflow, + INITENV_CNT, + INITENV_CNT + overflow); + } + + cmdopts->cmdenv = NULL; +} diff --git a/modules/fcgid/fcgid_conf.h b/modules/fcgid/fcgid_conf.h new file mode 100644 index 0000000..60aeee8 --- /dev/null +++ b/modules/fcgid/fcgid_conf.h @@ -0,0 +1,278 @@ +/* + * 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 FCGID_CONF_H +#define FCGID_CONF_H + +#define MODFCGID_COPYRIGHT \ + "Copyright 2013 The Apache Software Foundation." + +#define MODFCGID_VERSION_MAJOR 2 +#define MODFCGID_VERSION_MINOR 3 +#define MODFCGID_VERSION_SUBVER 9 +#define MODFCGID_VERSION_DEV 0 + +#if MODFCGID_VERSION_DEV +#define MODFCGID_VERSION_DEVSTR "-dev" +#else +#define MODFCGID_VERSION_DEVSTR "" +#endif + +/* APR_STRINGIFY is defined here, and also in apr_general.h, so wrap it */ +#ifndef APR_STRINGIFY +/** Properly quote a value as a string in the C preprocessor */ +#define APR_STRINGIFY(n) APR_STRINGIFY_HELPER(n) +/** Helper macro for APR_STRINGIFY */ +#define APR_STRINGIFY_HELPER(n) #n +#endif + +#define MODFCGID_REVISION APR_STRINGIFY(MODFCGID_VERSION_MAJOR) \ + "." APR_STRINGIFY(MODFCGID_VERSION_MINOR) \ + "." APR_STRINGIFY(MODFCGID_VERSION_SUBVER) +#define MODFCGID_VERSION MODFCGID_REVISION MODFCGID_VERSION_DEVSTR + +#define MODFCGID_PRODUCT "mod_fcgid/" MODFCGID_VERSION + +#ifndef VERSION_ONLY + +#include "apr_user.h" +#include "fcgid_global.h" + +typedef struct { + const char *cgipath; /* executable file path */ + const char *cmdline; /* entire command line */ + apr_ino_t inode; + apr_dev_t deviceid; + int virtual; +} fcgid_cmd_conf; + +typedef struct { + /* not based on config */ + int vhost_id; + /* global only */ + apr_hash_t *cmdopts_hash; + int busy_scan_interval; + int error_scan_interval; + int idle_scan_interval; + int max_process_count; + int php_fix_pathinfo_enable; + char *shmname_path; + char *sockname_prefix; + int spawn_score; + int spawnscore_uplimit; + int termination_score; + int time_score; + int zombie_scan_interval; +#ifdef WIN32 + /* FcgidWin32PreventOrphans - Win32 CGI processes automatic cleanup */ + HANDLE hJobObjectForAutoCleanup; +#endif + /* global or vhost + * scalar values have corresponding _set field to aid merging + */ + apr_table_t *default_init_env; + int ipc_comm_timeout; + int ipc_comm_timeout_set; + int ipc_connect_timeout; + int ipc_connect_timeout_set; + int max_mem_request_len; + int max_mem_request_len_set; + apr_off_t max_request_len; + int max_request_len_set; + int max_requests_per_process; + int max_requests_per_process_set; + int output_buffersize; + int output_buffersize_set; + apr_array_header_t *pass_headers; + int max_class_process_count; + int max_class_process_count_set; + int min_class_process_count; + int min_class_process_count_set; + int busy_timeout; + int busy_timeout_set; + int idle_timeout; + int idle_timeout_set; + int proc_lifetime; + int proc_lifetime_set; +} fcgid_server_conf; + +typedef struct { + /* scalar values have corresponding _set field to aid merging */ + + /* wrapper */ + apr_hash_t *wrapper_info_hash; + + /* authenticator */ + fcgid_cmd_conf *authenticator_info; + int authenticator_authoritative; + int authenticator_authoritative_set; + + /* authorizer */ + fcgid_cmd_conf *authorizer_info; + int authorizer_authoritative; + int authorizer_authoritative_set; + + /* access check */ + fcgid_cmd_conf *access_info; + int access_authoritative; + int access_authoritative_set; +} fcgid_dir_conf; + +/* processing options which are sent to the PM with a spawn request + * and/or configurable via FCGIDCmdOptions; envvars are kept in a + * separate structure to keep them out of the process table in order + * to limit shared memory use + */ +#define INITENV_KEY_LEN 64 +#define INITENV_VAL_LEN 128 +#define INITENV_CNT 64 +typedef struct { + char initenv_key[INITENV_CNT][INITENV_KEY_LEN]; + char initenv_val[INITENV_CNT][INITENV_VAL_LEN]; +} fcgid_cmd_env; + +typedef struct { + int busy_timeout; + int idle_timeout; + int ipc_comm_timeout; + int ipc_connect_timeout; + int max_class_process_count; + int max_requests_per_process; + int min_class_process_count; + int proc_lifetime; + fcgid_cmd_env *cmdenv; +} fcgid_cmd_options; + +void *create_fcgid_server_config(apr_pool_t * p, server_rec * s); +void *merge_fcgid_server_config(apr_pool_t * p, void *basev, + void *overridesv); + +void *create_fcgid_dir_config(apr_pool_t * p, char *dummy); +void *merge_fcgid_dir_config(apr_pool_t * p, void *basev, + void *overridesv); + +const char *set_idle_timeout(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_idle_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_busy_timeout(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_busy_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_proc_lifetime(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_error_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_zombie_scan_interval(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_socketpath(cmd_parms * cmd, void *dummy, const char *arg); + +const char *set_shmpath(cmd_parms * cmd, void *dummy, const char *arg); + +const char *set_time_score(cmd_parms * cmd, void *dummy, const char *arg); + +const char *set_max_request_len(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_max_mem_request_len(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_termination_score(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_spawn_score(cmd_parms * cmd, void *dummy, const char *arg); + +const char *set_spawnscore_uplimit(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_max_process(cmd_parms * cmd, void *dummy, const char *arg); + +const char *set_max_class_process(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_min_class_process(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_ipc_connect_timeout(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_ipc_comm_timeout(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_output_buffersize(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *add_default_env_vars(cmd_parms * cmd, void *sconf, + const char *name, const char *value); + +const char *add_pass_headers(cmd_parms * cmd, void *sconf, + const char *name); + +apr_array_header_t *get_pass_headers(request_rec * r); + +const char *set_wrapper_config(cmd_parms * cmd, void *dummy, + const char *wrapper, const char *extension, const char* virtual); +fcgid_cmd_conf *get_wrapper_info(const char *cgipath, request_rec * r); + +const char *set_authenticator_info(cmd_parms * cmd, void *config, + const char *arg); +const char *set_authenticator_authoritative(cmd_parms * cmd, + void *config, int arg); +fcgid_cmd_conf *get_authenticator_info(request_rec * r, int *authoritative); + +const char *set_authorizer_info(cmd_parms * cmd, void *config, + const char *arg); +const char *set_authorizer_authoritative(cmd_parms * cmd, + void *config, int arg); +fcgid_cmd_conf *get_authorizer_info(request_rec * r, int *authoritative); + +const char *set_access_info(cmd_parms * cmd, void *config, + const char *arg); +const char *set_access_authoritative(cmd_parms * cmd, + void *config, int arg); +fcgid_cmd_conf *get_access_info(request_rec * r, int *authoritative); + +const char *set_php_fix_pathinfo_enable(cmd_parms * cmd, void *dummy, + const char *arg); + +const char *set_max_requests_per_process(cmd_parms * cmd, void *dummy, + const char *arg); + +#ifdef WIN32 +const char *set_win32_prevent_process_orphans(cmd_parms *cmd, void *dummy, + int arg); +#endif + +const char *set_cmd_options(cmd_parms *cmd, void *dummy, + const char *arg); + +void get_cmd_options(request_rec *r, const char *cmdpath, + fcgid_cmd_options *cmdopts, fcgid_cmd_env *cmdenv); + + +AP_MODULE_DECLARE_DATA extern module fcgid_module; + +#endif + +#endif diff --git a/modules/fcgid/fcgid_config.h.in b/modules/fcgid/fcgid_config.h.in new file mode 100644 index 0000000..d0c8cdf --- /dev/null +++ b/modules/fcgid/fcgid_config.h.in @@ -0,0 +1,13 @@ +/* modules/fcgid/fcgid_config.h.in used only for apxs builds */ + +/* Define to 1 if you have the `sys/file.h' header. */ +#undef HAVE_SYS_FILE_H + +/* Define to 1 if you have the `sys/mman.h' header. */ +#undef HAVE_SYS_MMAN_H + +/* Define to 1 if you have the `sys/mutex.h' header. */ +#undef HAVE_SYS_MUTEX_H + +/* Define to 1 if you have the `sys/shm.h' header. */ +#undef HAVE_SYS_SHM_H diff --git a/modules/fcgid/fcgid_filter.c b/modules/fcgid/fcgid_filter.c new file mode 100644 index 0000000..3ea2f62 --- /dev/null +++ b/modules/fcgid/fcgid_filter.c @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "fcgid_filter.h" +#include "fcgid_bucket.h" +#include "fcgid_conf.h" + +apr_status_t fcgid_filter(ap_filter_t * f, apr_bucket_brigade * bb) +{ + apr_status_t rv; + apr_bucket_brigade *tmp_brigade; + apr_size_t save_size = 0; + conn_rec *c = f->c; + server_rec *s = f->r->server; + fcgid_server_conf *sconf = ap_get_module_config(s->module_config, + &fcgid_module); + + tmp_brigade = + apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); + while (!APR_BRIGADE_EMPTY(bb)) { + apr_size_t readlen; + const char *buffer; + + apr_bucket *e = APR_BRIGADE_FIRST(bb); + + if (APR_BUCKET_IS_EOS(e)) + break; + + if (APR_BUCKET_IS_METADATA(e)) { + apr_bucket_delete(e); + continue; + } + + /* Read the bucket now */ + if ((rv = apr_bucket_read(e, &buffer, &readlen, + APR_BLOCK_READ)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, + "mod_fcgid: can't read data from fcgid handler"); + return rv; + } + + /* Move on to next bucket if it's fastcgi header bucket */ + if (e->type == &ap_bucket_type_fcgid_header + || (e->type == &apr_bucket_type_immortal && readlen == 0)) { + apr_bucket_delete(e); + continue; + } + save_size += readlen; + + /* Cache it to tmp_brigade */ + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(tmp_brigade, e); + + /* I will pass tmp_brigade to next filter if I have got too much buckets */ + if (save_size > sconf->output_buffersize) { + APR_BRIGADE_INSERT_TAIL(tmp_brigade, + apr_bucket_flush_create(f->r-> + connection-> + bucket_alloc)); + + if ((rv = + ap_pass_brigade(f->next, tmp_brigade)) != APR_SUCCESS) + return rv; + + /* Is the client aborted? */ + if (c && c->aborted) + return APR_SUCCESS; + + save_size = 0; + } + } + + /* Any thing left? */ + if (!APR_BRIGADE_EMPTY(tmp_brigade)) { + if ((rv = ap_pass_brigade(f->next, tmp_brigade)) != APR_SUCCESS) + return rv; + } + + /* This filter is done once it has served up its content */ + ap_remove_output_filter(f); + return APR_SUCCESS; +} diff --git a/modules/fcgid/fcgid_filter.h b/modules/fcgid/fcgid_filter.h new file mode 100644 index 0000000..b030684 --- /dev/null +++ b/modules/fcgid/fcgid_filter.h @@ -0,0 +1,25 @@ +/* + * 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 FCGID_FILTER_H +#define FCGID_FILTER_H +#include "util_filter.h" +#include "apr_buckets.h" + +apr_status_t fcgid_filter(ap_filter_t * f, apr_bucket_brigade * bb); + +#endif diff --git a/modules/fcgid/fcgid_global.h b/modules/fcgid/fcgid_global.h new file mode 100644 index 0000000..d52a2fb --- /dev/null +++ b/modules/fcgid/fcgid_global.h @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FCGID_GLOBAL_H +#define FCGID_GLOBAL_H +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" + +#if AP_MODULE_MAGIC_AT_LEAST(20100606,0) +APLOG_USE_MODULE(fcgid); +#endif + +#ifdef FCGID_APXS_BUILD +#include "fcgid_config.h" +#endif + +/* FCGID_PATH_MAX + * - includes terminating '\0' + * - based on minimum supported path length (on Unix at least) + * - should be used in declarations, but logic should use sizeof + * wherever possible + */ +#ifndef FCGID_PATH_MAX +#ifdef _POSIX_PATH_MAX +#define FCGID_PATH_MAX _POSIX_PATH_MAX +#else +#define FCGID_PATH_MAX 256 +#endif +#endif + +/* FCGID_CMDLINE_MAX + * - includes terminating '\0' + * - FCGID_PATH_MAX represents the executable, remainder represents + * the args + * - should be used in declarations, but logic should use sizeof + * wherever possible + */ +#ifndef FCGID_CMDLINE_MAX +#define FCGID_CMDLINE_MAX (FCGID_PATH_MAX + 256) +#endif + +#define fcgid_min(a,b) (((a) < (b)) ? (a) : (b)) + +#endif diff --git a/modules/fcgid/fcgid_mutex.h b/modules/fcgid/fcgid_mutex.h new file mode 100644 index 0000000..60c99d8 --- /dev/null +++ b/modules/fcgid/fcgid_mutex.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +apr_status_t fcgid_mutex_register(const char *mutex_type, + apr_pool_t *pconf); + +apr_status_t fcgid_mutex_create(apr_global_mutex_t **mutex, + const char **lockfile, + const char *mutex_type, + apr_pool_t *pconf, + server_rec *s); diff --git a/modules/fcgid/fcgid_mutex_unix.c b/modules/fcgid/fcgid_mutex_unix.c new file mode 100644 index 0000000..1010d90 --- /dev/null +++ b/modules/fcgid/fcgid_mutex_unix.c @@ -0,0 +1,139 @@ +/* + * 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 + +#include "apr_global_mutex.h" +#include "httpd.h" +#include "ap_mmn.h" +#include "http_log.h" +#include "fcgid_mutex.h" + +#if AP_MODULE_MAGIC_AT_LEAST(20100504,0) + +#include "util_mutex.h" + +apr_status_t fcgid_mutex_register(const char *mutex_type, + apr_pool_t *pconf) +{ + return ap_mutex_register(pconf, mutex_type, NULL, APR_LOCK_DEFAULT, 0); +} + +apr_status_t fcgid_mutex_create(apr_global_mutex_t **mutex, + const char **lockfile, + const char *mutex_type, + apr_pool_t *pconf, + server_rec *main_server) +{ + apr_status_t rv; + + rv = ap_global_mutex_create(mutex, lockfile, mutex_type, NULL, main_server, + pconf, 0); + if (rv != APR_SUCCESS) { + return rv; + } + + return APR_SUCCESS; +} + +#else + +/* no support for Mutex directive and related APIs */ + +#include "ap_mpm.h" + +#if MODULE_MAGIC_NUMBER_MAJOR < 20051115 +#ifndef AP_NEED_SET_MUTEX_PERMS +#define AP_NEED_SET_MUTEX_PERMS 1 +#endif +#endif + +#if AP_NEED_SET_MUTEX_PERMS +#include "unixd.h" +#endif + +#if MODULE_MAGIC_NUMBER_MAJOR < 20081201 +#define ap_unixd_set_global_mutex_perms unixd_set_global_mutex_perms +#endif + +apr_status_t fcgid_mutex_register(const char *mutex_type, + apr_pool_t *pconf) +{ + return APR_SUCCESS; +} + +static apr_lockmech_e pick_mutex_mechanism(void) +{ + apr_lockmech_e mechanism = APR_LOCK_DEFAULT; + +#if defined(SOLARIS2) && APR_USE_FCNTL_SERIALIZE + /* default is fcntl(), which isn't thread-aware on Solaris; fcgid will + * exit with EDEADLK errors if it is used with a threaded MPM + */ + int threaded; + apr_status_t rv; + + rv = ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded); + if (rv == APR_SUCCESS && threaded) { +#if APR_HAS_PROC_PTHREAD_SERIALIZE + mechanism = APR_LOCK_PROC_PTHREAD; +#elif APR_HAS_SYSVSEM_SERIALIZE + mechanism = APR_LOCK_SYSVSEM; +#endif + } +#endif + + return mechanism; +} + +apr_status_t fcgid_mutex_create(apr_global_mutex_t **mutex, + const char **lockfilep, + const char *mutex_type, + apr_pool_t *pconf, + server_rec *s) +{ + apr_status_t rv; + apr_lockmech_e mechanism = pick_mutex_mechanism(); + char *lockfile; + + /* XXX This lock file name generation is unfortunate, but defaulting + * to a better place would require a directive to override it. This + * is resolved for httpd 2.3+ by hooking into the Mutex support. + */ + lockfile = apr_palloc(pconf, L_tmpnam); + tmpnam(lockfile); + rv = apr_global_mutex_create(mutex, lockfile, mechanism, pconf); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, + "mod_fcgid: Can't create global %s mutex", mutex_type); + return rv; + } + +#ifdef AP_NEED_SET_MUTEX_PERMS + rv = ap_unixd_set_global_mutex_perms(*mutex); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, + "mod_fcgid: Can't set global %s mutex perms", mutex_type); + return rv; + } +#endif + + *lockfilep = lockfile; + return APR_SUCCESS; +} + +#endif diff --git a/modules/fcgid/fcgid_pm.h b/modules/fcgid/fcgid_pm.h new file mode 100644 index 0000000..b815a39 --- /dev/null +++ b/modules/fcgid/fcgid_pm.h @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FCGID_PM_H +#define FCGID_PM_H +#include "fcgid_global.h" +#include "fcgid_conf.h" + +typedef struct { + char cgipath[FCGID_PATH_MAX]; + char cmdline[FCGID_CMDLINE_MAX]; + apr_ino_t inode; + dev_t deviceid; + /* can't reference these via server_rec because some mass vhost + * module may have copied it for per-request customization + */ + int vhost_id; + char server_hostname[32]; /* for logging only; ok to truncate */ + uid_t uid; /* For suEXEC */ + gid_t gid; /* For suEXEC */ + int userdir; /* For suEXEC */ + fcgid_cmd_options cmdopts; /* context-specific configuration, other than + * envvars + */ + fcgid_cmd_env cmdenv; /* start the command with these env settings */ +} fcgid_command; + +void procmgr_init_spawn_cmd(fcgid_command * command, request_rec * r, + fcgid_cmd_conf *cmd_conf); +apr_status_t procmgr_send_spawn_cmd(fcgid_command * command, + request_rec * r); +apr_status_t procmgr_fetch_cmd(fcgid_command * command, + server_rec * main_server); +apr_status_t procmgr_finish_notify(server_rec * main_server); + +apr_status_t procmgr_child_init(server_rec * main_server, + apr_pool_t * pchild); +apr_status_t procmgr_pre_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp); +apr_status_t procmgr_post_config(server_rec * main_server, + apr_pool_t * pconf); + +apr_status_t procmgr_stop_procmgr(void *dummy); +int procmgr_must_exit(void); + +#endif diff --git a/modules/fcgid/fcgid_pm_main.c b/modules/fcgid/fcgid_pm_main.c new file mode 100644 index 0000000..74e1888 --- /dev/null +++ b/modules/fcgid/fcgid_pm_main.c @@ -0,0 +1,662 @@ +/* + * 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. + */ + +/* For DEFAULT_PATH */ +#define CORE_PRIVATE +#include "httpd.h" +#include "http_config.h" +#include "apr_strings.h" + +#include "fcgid_pm.h" +#include "fcgid_pm_main.h" +#include "fcgid_conf.h" +#include "fcgid_proctbl.h" +#include "fcgid_proc.h" +#include "fcgid_spawn_ctl.h" + +#define HAS_GRACEFUL_KILL "Gracefulkill" + +static void +link_node_to_list(server_rec * main_server, + fcgid_procnode * header, + fcgid_procnode * node, fcgid_procnode * table_array) +{ + proctable_pm_lock(main_server); + node->next_index = header->next_index; + header->next_index = node - table_array; + proctable_pm_unlock(main_server); +} + +static apr_time_t lastidlescan = 0; +static void scan_idlelist(server_rec * main_server) +{ + /* + Scan the idle list + 1. move all processes idle timeout to error list + 2. move all processes lifetime expired to error list + */ + fcgid_procnode *previous_node, *current_node, *next_node; + fcgid_procnode *error_list_header; + fcgid_procnode *proc_table; + apr_time_t last_active_time, start_time; + apr_time_t now = apr_time_now(); + int idle_timeout, proc_lifetime; + fcgid_server_conf *sconf = + ap_get_module_config(main_server->module_config, + &fcgid_module); + + /* Should I check the idle list now? */ + if (procmgr_must_exit() + || apr_time_sec(now) - apr_time_sec(lastidlescan) <= + sconf->idle_scan_interval) + return; + lastidlescan = now; + + /* Check the list */ + proc_table = proctable_get_table_array(); + previous_node = proctable_get_idle_list(); + error_list_header = proctable_get_error_list(); + + proctable_pm_lock(main_server); + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + next_node = &proc_table[current_node->next_index]; + last_active_time = current_node->last_active_time; + start_time = current_node->start_time; + idle_timeout = current_node->cmdopts.idle_timeout; + proc_lifetime = current_node->cmdopts.proc_lifetime; + if (((idle_timeout && + (apr_time_sec(now) - apr_time_sec(last_active_time) > + idle_timeout)) + || (proc_lifetime + && (apr_time_sec(now) - apr_time_sec(start_time) > + proc_lifetime))) + && is_kill_allowed(main_server, current_node)) { + /* Set die reason for log */ + if (idle_timeout && + (apr_time_sec(now) - apr_time_sec(last_active_time) > + idle_timeout)) + current_node->diewhy = FCGID_DIE_IDLE_TIMEOUT; + else if (proc_lifetime && + (apr_time_sec(now) - apr_time_sec(start_time) > + proc_lifetime)) + current_node->diewhy = FCGID_DIE_LIFETIME_EXPIRED; + + /* Unlink from idle list */ + previous_node->next_index = current_node->next_index; + + /* Link to error list */ + current_node->next_index = error_list_header->next_index; + error_list_header->next_index = current_node - proc_table; + } + else + previous_node = current_node; + + current_node = next_node; + } + proctable_pm_unlock(main_server); +} + +static apr_time_t lastbusyscan = 0; +static void scan_busylist(server_rec * main_server) +{ + fcgid_procnode *current_node; + fcgid_procnode *proc_table; + apr_time_t last_active_time; + apr_time_t now = apr_time_now(); + fcgid_server_conf *sconf = + ap_get_module_config(main_server->module_config, + &fcgid_module); + + /* Should I check the busy list? */ + if (procmgr_must_exit() + || apr_time_sec(now) - apr_time_sec(lastbusyscan) <= + sconf->busy_scan_interval) + return; + lastbusyscan = now; + + /* Check busy list */ + proc_table = proctable_get_table_array(); + + proctable_pm_lock(main_server); + current_node = &proc_table[proctable_get_busy_list()->next_index]; + while (current_node != proc_table) { + last_active_time = current_node->last_active_time; + if (apr_time_sec(now) - apr_time_sec(last_active_time) > + (current_node->cmdopts.busy_timeout)) { + /* Protocol: + 1. diewhy init with FCGID_DIE_KILLSELF + 2. Process manager set diewhy to FCGID_DIE_BUSY_TIMEOUT and gracefully kill process while busy timeout + 3. Process manager forced kill process while busy timeout and diewhy is FCGID_DIE_BUSY_TIMEOUT + */ + if (current_node->diewhy == FCGID_DIE_BUSY_TIMEOUT) + proc_kill_force(current_node, main_server); + else { + current_node->diewhy = FCGID_DIE_BUSY_TIMEOUT; + proc_kill_gracefully(current_node, main_server); + } + } + current_node = &proc_table[current_node->next_index]; + } + proctable_pm_unlock(main_server); +} + +static apr_time_t lastzombiescan = 0; +static void scan_idlelist_zombie(server_rec * main_server) +{ + /* + Scan the idle list + 1. pick up the node for scan(now-last_activ>g_zombie_scan_interval) + 2. check if it's zombie process + 3. if it's zombie process, wait() and return to free list + 4. return to idle list if it's not zombie process + */ + pid_t thepid; + fcgid_procnode *previous_node, *current_node, *next_node; + fcgid_procnode *check_list_header; + fcgid_procnode *proc_table; + apr_time_t last_active_time; + apr_time_t now = apr_time_now(); + fcgid_procnode temp_header; + fcgid_server_conf *sconf = + ap_get_module_config(main_server->module_config, + &fcgid_module); + + memset(&temp_header, 0, sizeof(temp_header)); + + /* Should I check zombie processes in idle list now? */ + if (procmgr_must_exit() + || apr_time_sec(now) - apr_time_sec(lastzombiescan) <= + sconf->zombie_scan_interval) + return; + lastzombiescan = now; + + /* + Check the list + */ + proc_table = proctable_get_table_array(); + previous_node = proctable_get_idle_list(); + check_list_header = &temp_header; + + proctable_pm_lock(main_server); + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + next_node = &proc_table[current_node->next_index]; + + /* Is it time for zombie check? */ + last_active_time = current_node->last_active_time; + if (apr_time_sec(now) - apr_time_sec(last_active_time) > + sconf->zombie_scan_interval) { + /* Unlink from idle list */ + previous_node->next_index = current_node->next_index; + + /* Link to check list */ + current_node->next_index = check_list_header->next_index; + check_list_header->next_index = current_node - proc_table; + } + else + previous_node = current_node; + + current_node = next_node; + } + proctable_pm_unlock(main_server); + + /* + Now check every node in check list + 1) If it's zombie process, wait() and return to free list + 2) If it's not zombie process, link it to the tail of idle list + */ + previous_node = check_list_header; + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + next_node = &proc_table[current_node->next_index]; + + /* Is it zombie process? */ + thepid = current_node->proc_id.pid; + if (proc_wait_process(main_server, current_node) == APR_CHILD_DONE) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: cleanup zombie process %" + APR_PID_T_FMT, thepid); + + /* Unlink from check list */ + previous_node->next_index = current_node->next_index; + + /* Link to free list */ + link_node_to_list(main_server, proctable_get_free_list(), + current_node, proc_table); + } + else + previous_node = current_node; + + current_node = next_node; + } + + /* + Now link the check list back to the tail of idle list + */ + if (check_list_header->next_index) { + proctable_pm_lock(main_server); + previous_node = proctable_get_idle_list(); + current_node = &proc_table[previous_node->next_index]; + + /* Find the tail of idle list */ + while (current_node != proc_table) { + previous_node = current_node; + current_node = &proc_table[current_node->next_index]; + } + + /* Link check list to the tail of idle list */ + previous_node->next_index = check_list_header->next_index; + proctable_pm_unlock(main_server); + } +} + +static apr_time_t lasterrorscan = 0; +static void scan_errorlist(server_rec * main_server) +{ + /* + kill() and wait() every node in error list + put them back to free list after that + */ + void *dummy; + fcgid_procnode *previous_node, *current_node, *next_node; + apr_time_t now = apr_time_now(); + fcgid_procnode *error_list_header = proctable_get_error_list(); + fcgid_procnode *free_list_header = proctable_get_free_list(); + fcgid_procnode *proc_table = proctable_get_table_array(); + fcgid_procnode temp_error_header; + fcgid_server_conf *sconf = + ap_get_module_config(main_server->module_config, + &fcgid_module); + int graceful_terminations = 0; + + /* Should I check the busy list? */ + if (procmgr_must_exit() + || apr_time_sec(now) - apr_time_sec(lasterrorscan) <= + sconf->error_scan_interval) + return; + lasterrorscan = now; + + /* Try wait dead processes, restore to free list */ + /* Note: I can't keep the lock during the scan */ + proctable_pm_lock(main_server); + temp_error_header.next_index = error_list_header->next_index; + error_list_header->next_index = 0; + proctable_pm_unlock(main_server); + + previous_node = &temp_error_header; + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + next_node = &proc_table[current_node->next_index]; + + if (proc_wait_process(main_server, current_node) != APR_CHILD_NOTDONE) { + /* Unlink from error list */ + previous_node->next_index = current_node->next_index; + + /* Link to free list */ + current_node->next_index = free_list_header->next_index; + free_list_header->next_index = current_node - proc_table; + } + else + previous_node = current_node; + + current_node = next_node; + } + + /* Kill the left processes, wait() them in the next round */ + for (current_node = &proc_table[temp_error_header.next_index]; + current_node != proc_table; + current_node = &proc_table[current_node->next_index]) { + /* Try gracefully first */ + dummy = NULL; + apr_pool_userdata_get(&dummy, HAS_GRACEFUL_KILL, + current_node->proc_pool); + if (!dummy) { + proc_kill_gracefully(current_node, main_server); + ++graceful_terminations; + apr_pool_userdata_set("set", HAS_GRACEFUL_KILL, + apr_pool_cleanup_null, + current_node->proc_pool); + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: process %" APR_PID_T_FMT + " graceful kill fail, sending SIGKILL", + current_node->proc_id.pid); + proc_kill_force(current_node, main_server); + } + } + + /* Link the temp error list back */ + proctable_pm_lock(main_server); + /* Find the tail of error list */ + previous_node = error_list_header; + current_node = &proc_table[previous_node->next_index]; + while (current_node != proc_table) { + previous_node = current_node; + current_node = &proc_table[current_node->next_index]; + } + previous_node->next_index = temp_error_header.next_index; + proctable_pm_unlock(main_server); + + if (graceful_terminations) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server, + "mod_fcgid: gracefully terminated %d processes", + graceful_terminations); + } +} + +typedef enum action_t {DO_NOTHING, KILL_GRACEFULLY, KILL_FORCEFULLY, + HARD_WAIT} action_t; + +static int reclaim_one_pid(server_rec *main_server, fcgid_procnode *proc, + action_t action) +{ + int exitcode; + apr_exit_why_e exitwhy; + apr_wait_how_e wait_how = action == HARD_WAIT ? APR_WAIT : APR_NOWAIT; + + if (apr_proc_wait(&proc->proc_id, &exitcode, &exitwhy, + wait_how) != APR_CHILD_NOTDONE) { + proc->diewhy = FCGID_DIE_SHUTDOWN; + proc_print_exit_info(proc, exitcode, exitwhy, + main_server); + proc->proc_pool = NULL; + return 1; + } + + switch(action) { + case DO_NOTHING: + case HARD_WAIT: + break; + + case KILL_GRACEFULLY: + proc_kill_gracefully(proc, main_server); + break; + + case KILL_FORCEFULLY: + ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server, + "FastCGI process %" APR_PID_T_FMT + " still did not exit, " + "terminating forcefully", + proc->proc_id.pid); + proc_kill_force(proc, main_server); + break; + } + + return 0; +} + +static void kill_all_subprocess(server_rec *main_server) +{ + apr_time_t waittime = 1024 * 16; + size_t i, table_size = proctable_get_table_size(); + int not_dead_yet; + int cur_action, next_action; + apr_time_t starttime = apr_time_now(); + struct { + action_t action; + apr_time_t action_time; + } action_table[] = { + {DO_NOTHING, 0}, /* dummy entry for iterations where + * we reap children but take no action + * against stragglers + */ + {KILL_GRACEFULLY, apr_time_from_sec(0)}, + {KILL_GRACEFULLY, apr_time_from_sec(1)}, + {KILL_FORCEFULLY, apr_time_from_sec(8)}, + {HARD_WAIT, apr_time_from_sec(8)} + }; + fcgid_procnode *proc_table = proctable_get_table_array(); + + next_action = 1; + do { + apr_sleep(waittime); + /* don't let waittime get longer than 1 second; otherwise, we don't + * react quickly to the last child exiting, and taking action can + * be delayed + */ + waittime = waittime * 4; + if (waittime > apr_time_from_sec(1)) { + waittime = apr_time_from_sec(1); + } + + /* see what action to take, if any */ + if (action_table[next_action].action_time <= apr_time_now() - starttime) { + cur_action = next_action; + ++next_action; + } + else { + cur_action = 0; /* index of DO_NOTHING entry */ + } + + /* now see who is done */ + not_dead_yet = 0; + for (i = 0; i < table_size; i++) { + if (!proc_table[i].proc_pool) { + continue; /* unused */ + } + + if (!reclaim_one_pid(main_server, &proc_table[i], + action_table[cur_action].action)) { + ++not_dead_yet; + } + } + + } while (not_dead_yet && + action_table[cur_action].action != HARD_WAIT); +} + +/* This should be proposed as a stand-alone improvement to the httpd module, + * either in the arch/ platform-specific modules or util_script.c from whence + * it came. + */ +static void default_proc_env(apr_table_t * e) +{ + const char *env_temp; + + if (!(env_temp = getenv("PATH"))) { + env_temp = DEFAULT_PATH; + } + apr_table_addn(e, "PATH", env_temp); + +#ifdef WIN32 + if ((env_temp = getenv("SYSTEMROOT"))) { + apr_table_addn(e, "SYSTEMROOT", env_temp); + } + if ((env_temp = getenv("COMSPEC"))) { + apr_table_addn(e, "COMSPEC", env_temp); + } + if ((env_temp = getenv("PATHEXT"))) { + apr_table_addn(e, "PATHEXT", env_temp); + } + if ((env_temp = getenv("WINDIR"))) { + apr_table_addn(e, "WINDIR", env_temp); + } +#elif defined(OS2) + if ((env_temp = getenv("COMSPEC")) != NULL) { + apr_table_addn(e, "COMSPEC", env_temp); + } + if ((env_temp = getenv("ETC")) != NULL) { + apr_table_addn(e, "ETC", env_temp); + } + if ((env_temp = getenv("DPATH")) != NULL) { + apr_table_addn(e, "DPATH", env_temp); + } + if ((env_temp = getenv("PERLLIB_PREFIX")) != NULL) { + apr_table_addn(e, "PERLLIB_PREFIX", env_temp); + } +#elif defined(BEOS) + if ((env_temp = getenv("LIBRARY_PATH")) != NULL) { + apr_table_addn(e, "LIBRARY_PATH", env_temp); + } +#elif defined (AIX) + if ((env_temp = getenv("LIBPATH"))) { + apr_table_addn(e, "LIBPATH", env_temp); + } +#else +/* DARWIN, HPUX vary depending on circumstance */ +#if defined (DARWIN) + if ((env_temp = getenv("DYLD_LIBRARY_PATH"))) { + apr_table_addn(e, "DYLD_LIBRARY_PATH", env_temp); + } +#elif defined (HPUX11) || defined (HPUX10) || defined (HPUX) + if ((env_temp = getenv("SHLIB_PATH"))) { + apr_table_addn(e, "SHLIB_PATH", env_temp); + } +#endif + if ((env_temp = getenv("LD_LIBRARY_PATH"))) { + apr_table_addn(e, "LD_LIBRARY_PATH", env_temp); + } +#endif +} + +/* End of common to util_script.c */ + +static void +fastcgi_spawn(fcgid_command * command, server_rec * main_server, + apr_pool_t * configpool) +{ + fcgid_procnode *free_list_header, *proctable_array, + *procnode, *idle_list_header; + fcgid_proc_info procinfo; + apr_status_t rv; + int i; + + free_list_header = proctable_get_free_list(); + idle_list_header = proctable_get_idle_list(); + proctable_array = proctable_get_table_array(); + + /* Apply a slot from free list */ + proctable_pm_lock(main_server); + if (free_list_header->next_index == 0) { + proctable_pm_unlock(main_server); + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: too much processes, please increase FCGID_MAX_APPLICATION"); + return; + } + procnode = &proctable_array[free_list_header->next_index]; + free_list_header->next_index = procnode->next_index; + procnode->next_index = 0; + proctable_pm_unlock(main_server); + + /* Prepare to spawn */ + procnode->deviceid = command->deviceid; + procnode->inode = command->inode; + + /* no truncation should ever occur */ + AP_DEBUG_ASSERT(sizeof procnode->cmdline > strlen(command->cmdline)); + apr_cpystrn(procnode->cmdline, command->cmdline, sizeof procnode->cmdline); + + procnode->vhost_id = command->vhost_id; + procnode->uid = command->uid; + procnode->gid = command->gid; + procnode->start_time = procnode->last_active_time = apr_time_now(); + procnode->requests_handled = 0; + procnode->diewhy = FCGID_DIE_KILLSELF; + procnode->proc_pool = NULL; + procnode->cmdopts = command->cmdopts; + + procinfo.cgipath = command->cgipath; + procinfo.configpool = configpool; + procinfo.main_server = main_server; + procinfo.uid = command->uid; + procinfo.gid = command->gid; + procinfo.userdir = command->userdir; + if ((rv = + apr_pool_create(&procnode->proc_pool, configpool)) != APR_SUCCESS + || (procinfo.proc_environ = + apr_table_make(procnode->proc_pool, INITENV_CNT)) == NULL) { + /* Link the node back to free list in this case */ + if (procnode->proc_pool) + apr_pool_destroy(procnode->proc_pool); + link_node_to_list(main_server, free_list_header, procnode, + proctable_array); + + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, main_server, + "mod_fcgid: can't create pool for process"); + return; + } + /* Set up longer, system defaults before falling into parsing fixed-limit + * request-by-request variables, so if any are overriden, they preempt + * any system default assumptions + */ + default_proc_env(procinfo.proc_environ); + for (i = 0; i < INITENV_CNT; i++) { + if (command->cmdenv.initenv_key[i][0] == '\0') + break; + apr_table_set(procinfo.proc_environ, command->cmdenv.initenv_key[i], + command->cmdenv.initenv_val[i]); + } + + /* Spawn the process now */ + /* XXX Spawn uses wrapper_cmdline, but log uses cgipath ? */ + if ((rv = + proc_spawn_process(command->cmdline, &procinfo, + procnode)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, main_server, + "mod_fcgid: spawn process %s error", command->cgipath); + + apr_pool_destroy(procnode->proc_pool); + link_node_to_list(main_server, free_list_header, + procnode, proctable_array); + return; + } + else { + /* The job done */ + link_node_to_list(main_server, idle_list_header, + procnode, proctable_array); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server, + "mod_fcgid: server %s:%s(%" APR_PID_T_FMT ") started", + command->server_hostname[0] ? + command->server_hostname : "(unknown)", + command->cgipath, + procnode->proc_id.pid); + register_spawn(main_server, procnode); + } +} + +apr_status_t pm_main(server_rec * main_server, apr_pool_t * configpool) +{ + fcgid_command command; + + while (1) { + if (procmgr_must_exit()) + break; + + /* Wait for command */ + if (procmgr_fetch_cmd(&command, main_server) == APR_SUCCESS) { + if (is_spawn_allowed(main_server, &command)) + fastcgi_spawn(&command, main_server, configpool); + + procmgr_finish_notify(main_server); + } + + /* Move matched node to error list */ + scan_idlelist_zombie(main_server); + scan_idlelist(main_server); + scan_busylist(main_server); + + /* Kill() and wait() nodes in error list */ + scan_errorlist(main_server); + } + + /* Stop all processes */ + kill_all_subprocess(main_server); + + return APR_SUCCESS; +} diff --git a/modules/fcgid/fcgid_pm_main.h b/modules/fcgid/fcgid_pm_main.h new file mode 100644 index 0000000..963e0a8 --- /dev/null +++ b/modules/fcgid/fcgid_pm_main.h @@ -0,0 +1,23 @@ +/* + * 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 FCGID_PM_MAIN_H +#define FCGID_PM_MAIN_H + +apr_status_t pm_main(server_rec * main_server, apr_pool_t * configpool); + +#endif diff --git a/modules/fcgid/fcgid_pm_unix.c b/modules/fcgid/fcgid_pm_unix.c new file mode 100644 index 0000000..500d289 --- /dev/null +++ b/modules/fcgid/fcgid_pm_unix.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. + */ + +#include "unixd.h" +#include "ap_mpm.h" +#include "apr_thread_proc.h" +#include "apr_strings.h" +#include "apr_queue.h" +#include "apr_global_mutex.h" +#include "apr_version.h" +#if APR_MAJOR_VERSION < 2 +#include "apr_support.h" +#endif +#include "http_config.h" +#include "fcgid_pm.h" +#include "fcgid_pm_main.h" +#include "fcgid_conf.h" +#include "fcgid_proctbl.h" +#include "fcgid_spawn_ctl.h" +#include "fcgid_mutex.h" +#include + +#if MODULE_MAGIC_NUMBER_MAJOR >= 20090209 +#include "mod_unixd.h" +#endif + +#if MODULE_MAGIC_NUMBER_MAJOR < 20081201 +#define ap_unixd_config unixd_config +#define ap_unixd_setup_child unixd_setup_child +#endif + +/* The APR other-child API doesn't tell us how the daemon exited + * (SIGSEGV vs. exit(1)). The other-child maintenance function + * needs to decide whether to restart the daemon after a failure + * based on whether or not it exited due to a fatal startup error + * or something that happened at steady-state. This exit status + * is unlikely to collide with exit signals. + */ +#define DAEMON_STARTUP_ERROR 254 + +static apr_status_t create_process_manager(server_rec * main_server, + apr_pool_t * configpool); + +static int g_wakeup_timeout = 0; +static apr_proc_t *g_process_manager = NULL; +static apr_file_t *g_pm_read_pipe = NULL; +static apr_file_t *g_pm_write_pipe = NULL; +static apr_file_t *g_ap_write_pipe = NULL; +static apr_file_t *g_ap_read_pipe = NULL; +static apr_global_mutex_t *g_pipelock = NULL; +static const char *g_pipelock_name; +static const char *g_pipelock_mutex_type = "fcgid-pipe"; + +static int volatile g_caughtSigTerm = 0; +static pid_t g_pm_pid; +static void signal_handler(int signo) +{ + /* Sanity check, Make sure I am not the subprocess. A subprocess may + get signale after fork() and before execve() */ + if (getpid() != g_pm_pid) { + exit(0); + return; + } + + if ((signo == SIGTERM) || (signo == SIGUSR1) || (signo == SIGHUP)) { + g_caughtSigTerm = 1; + /* Tell the world it's time to die */ + proctable_get_globalshare()->must_exit = 1; + } +} + +static apr_status_t init_signal(server_rec * main_server) +{ + struct sigaction sa; + + /* Setup handlers */ + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + if (sigaction(SIGTERM, &sa, NULL) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: Can't install SIGTERM handler"); + return APR_EGENERAL; + } + + /* Httpd restart */ + if (sigaction(SIGHUP, &sa, NULL) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: Can't install SIGHUP handler"); + return APR_EGENERAL; + } + + /* Httpd graceful restart */ + if (sigaction(SIGUSR1, &sa, NULL) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: Can't install SIGUSR1 handler"); + return APR_EGENERAL; + } + + /* Ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: Can't install SIGPIPE handler"); + return APR_EGENERAL; + } + + return APR_SUCCESS; +} + +static void fcgid_maint(int reason, void *data, apr_wait_t status) +{ + apr_proc_t *proc = data; + int mpm_state; + + switch (reason) { + case APR_OC_REASON_DEATH: + apr_proc_other_child_unregister(data); + if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state) == APR_SUCCESS + && mpm_state != AP_MPMQ_STOPPING) { + if (status == DAEMON_STARTUP_ERROR) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, + "mod_fcgid: fcgid process manager failed to initialize; " + "stopping httpd"); + /* mod_fcgid requests will hang due to lack of a process manager; + * try to terminate httpd + */ + kill(getpid(), SIGTERM); + + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + "mod_fcgid: fcgid process manager died, restarting the server"); + + /* HACK: I can't just call create_process_manager() to + restart a process manager, because it will use the dirty + share memory, I have to kill myself a SIGHUP, to make + a clean restart */ + /* FIXME: This is the httpd parent; mod_fcgid is doing a hard + * restart of the server! + */ + if (kill(getpid(), SIGHUP) < 0) { + ap_log_error(APLOG_MARK, APLOG_EMERG, + apr_get_os_error(), NULL, + "mod_fcgid: can't send SIGHUP to self"); + exit(0); + } + } + } + break; + case APR_OC_REASON_RESTART: + apr_proc_other_child_unregister(data); + break; + case APR_OC_REASON_LOST: + apr_proc_other_child_unregister(data); + /* It hack here too, a note above */ + /* FIXME: This is the httpd parent; mod_fcgid is doing a hard + * restart of the server! + */ + if (kill(getpid(), SIGHUP) < 0) { + ap_log_error(APLOG_MARK, APLOG_EMERG, + apr_get_os_error(), NULL, + "mod_fcgid: can't send SIGHUP to self"); + exit(0); + } + break; + case APR_OC_REASON_UNREGISTER: + /* I don't think it's going to happen */ + kill(proc->pid, SIGHUP); + break; + } +} + +static int set_group_privs(void) +{ + if (!geteuid()) { + const char *name; + + + /* Get username if passed as a uid */ + if (ap_unixd_config.user_name[0] == '#') { + struct passwd *ent; + + uid_t uid = atoi(&ap_unixd_config.user_name[1]); + + if ((ent = getpwuid(uid)) == NULL) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, + "getpwuid: couldn't determine user name from uid %u, " + "you probably need to modify the User directive", + (unsigned) uid); + return -1; + } + name = ent->pw_name; + } + + else + name = ap_unixd_config.user_name; + +#if !defined(OS2) && !defined(TPF) + /* OS/2 and TPF don't support groups. */ + + /* + * Set the GID before initgroups(), since on some platforms + * setgid() is known to zap the group list. + */ + if (setgid(ap_unixd_config.group_id) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, + "setgid: unable to set group id to Group %u", + (unsigned) ap_unixd_config.group_id); + return -1; + } + + /* Reset `groups' attributes. */ + if (initgroups(name, ap_unixd_config.group_id) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, + "initgroups: unable to set groups for User %s " + "and Group %u", name, + (unsigned) ap_unixd_config.group_id); + return -1; + } +#endif /* !defined(OS2) && !defined(TPF) */ + } + return 0; +} + + +/* Base on ap_unixd_setup_child() */ +static int suexec_setup_child(void) +{ + if (set_group_privs()) { + exit(-1); + } + + /* Only try to switch if we're running as root */ + if (!geteuid() && (seteuid(ap_unixd_config.user_id) == -1)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, + "seteuid: unable to change to uid %ld", + (long) ap_unixd_config.user_id); + exit(-1); + } + return 0; +} + +static apr_status_t +create_process_manager(server_rec * main_server, apr_pool_t * configpool) +{ + apr_status_t rv; + + g_process_manager = + (apr_proc_t *) apr_pcalloc(configpool, sizeof(*g_process_manager)); + rv = apr_proc_fork(g_process_manager, configpool); + if (rv == APR_INCHILD) { + /* I am the child */ + g_pm_pid = getpid(); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server, + "mod_fcgid: Process manager %" APR_PID_T_FMT " started", getpid()); + + if ((rv = init_signal(main_server)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: can't install signal handler, exiting now"); + exit(DAEMON_STARTUP_ERROR); + } + + /* If running as root, switch to configured user. + * + * When running children via suexec, only the effective uid is + * switched, so that the PM can return to euid 0 to kill child + * processes. + * + * When running children as the configured user, the real uid + * is switched. + */ + if (ap_unixd_config.suexec_enabled) { + if (getuid() != 0) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, main_server, + "mod_fcgid: current user is not root while suexec is enabled, exiting now"); + exit(DAEMON_STARTUP_ERROR); + } + suexec_setup_child(); + } else + ap_unixd_setup_child(); + apr_file_pipe_timeout_set(g_pm_read_pipe, + apr_time_from_sec(g_wakeup_timeout)); + apr_file_close(g_ap_write_pipe); + apr_file_close(g_ap_read_pipe); + + /* Initialize spawn controler */ + spawn_control_init(main_server, configpool); + + pm_main(main_server, configpool); + + ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server, + "mod_fcgid: Process manager %" APR_PID_T_FMT " stopped", getpid()); + exit(0); + } else if (rv != APR_INPARENT) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Create process manager error"); + exit(1); + } + + /* I am the parent + I will send the stop signal in procmgr_stop_procmgr() */ + apr_pool_note_subprocess(configpool, g_process_manager, + APR_KILL_ONLY_ONCE); + apr_proc_other_child_register(g_process_manager, fcgid_maint, + g_process_manager, NULL, configpool); + + return APR_SUCCESS; +} + +apr_status_t +procmgr_child_init(server_rec * main_server, apr_pool_t * configpool) +{ + apr_status_t rv; + + if ((rv = apr_global_mutex_child_init(&g_pipelock, + g_pipelock_name, + main_server->process->pconf)) != + APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: apr_global_mutex_child_init error for pipe mutex"); + exit(1); + } + + return APR_SUCCESS; +} + +apr_status_t procmgr_pre_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + return fcgid_mutex_register(g_pipelock_mutex_type, p); +} + +apr_status_t +procmgr_post_config(server_rec * main_server, apr_pool_t * configpool) +{ + apr_status_t rv; + apr_finfo_t finfo; + fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, + &fcgid_module); + + /* Calculate procmgr_fetch_cmd wake up interval */ + g_wakeup_timeout = fcgid_min(sconf->error_scan_interval, + sconf->busy_scan_interval); + g_wakeup_timeout = fcgid_min(sconf->idle_scan_interval, + g_wakeup_timeout); + if (g_wakeup_timeout == 0) + g_wakeup_timeout = 1; /* Make it reasonable */ + + rv = apr_stat(&finfo, sconf->sockname_prefix, APR_FINFO_USER, + configpool); + if (rv != APR_SUCCESS) { + /* Make dir for unix domain socket */ + if ((rv = apr_dir_make_recursive(sconf->sockname_prefix, + APR_UREAD | APR_UWRITE | + APR_UEXECUTE, + configpool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_server, + "mod_fcgid: Can't create unix socket dir %s", + sconf->sockname_prefix); + exit(1); + } + + /* Child processes need to be able to create sockets in the unix + * socket dir. Change the ownership to the child user only if + * running as root and we just successfully created the directory + * (avoiding any concerns about changing the target of a link + * created by another user). + * + * If the directory already existed and was owned by a different user, + * FastCGI requests will fail at steady state, and manual intervention + * will be required. + */ + + if (!geteuid()) { + if (chown(sconf->sockname_prefix, + ap_unixd_config.user_id, -1) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: Can't set ownership of unix socket dir %s", + sconf->sockname_prefix); + exit(1); + } + } + } + + /* Create pipes to communicate between process manager and apache */ + if ((rv = apr_file_pipe_create(&g_pm_read_pipe, &g_ap_write_pipe, + configpool)) != APR_SUCCESS + || (rv = apr_file_pipe_create(&g_ap_read_pipe, &g_pm_write_pipe, + configpool))) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_server, + "mod_fcgid: Can't create pipe between PM and stub"); + return rv; + } + + /* Create mutex for pipe reading and writing */ + rv = fcgid_mutex_create(&g_pipelock, &g_pipelock_name, + g_pipelock_mutex_type, + main_server->process->pconf, main_server); + if (rv != APR_SUCCESS) { + exit(1); + } + + /* Create process manager process */ + return create_process_manager(main_server, configpool); +} + +void procmgr_init_spawn_cmd(fcgid_command * command, request_rec * r, + fcgid_cmd_conf *cmd_conf) +{ + ap_unix_identity_t *ugid; + fcgid_server_conf *sconf = + ap_get_module_config(r->server->module_config, &fcgid_module); + + /* suEXEC check */ + if ((ugid = ap_run_get_suexec_identity(r))) { + command->uid = ugid->uid; + command->gid = ugid->gid; + command->userdir = ugid->userdir; + } else { + command->uid = (uid_t) - 1; + command->gid = (gid_t) - 1; + command->userdir = 0; + } + + /* no truncation should ever occur */ + AP_DEBUG_ASSERT(sizeof command->cgipath > strlen(cmd_conf->cgipath)); + apr_cpystrn(command->cgipath, cmd_conf->cgipath, sizeof command->cgipath); + AP_DEBUG_ASSERT(sizeof command->cmdline > strlen(cmd_conf->cmdline)); + apr_cpystrn(command->cmdline, cmd_conf->cmdline, sizeof command->cmdline); + + command->deviceid = cmd_conf->deviceid; + command->inode = cmd_conf->inode; + command->vhost_id = sconf->vhost_id; + if (r->server->server_hostname) { + apr_cpystrn(command->server_hostname, r->server->server_hostname, + sizeof command->server_hostname); + } + else { + command->server_hostname[0] = '\0'; + } + + get_cmd_options(r, command->cgipath, &command->cmdopts, &command->cmdenv); +} + +apr_status_t procmgr_send_spawn_cmd(fcgid_command * command, + request_rec * r) +{ + apr_status_t rv; + char notifybyte; + apr_size_t nbytes = sizeof(*command); + + /* Get the global mutex before posting the request */ + if ((rv = apr_global_mutex_lock(g_pipelock)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't get pipe mutex"); + exit(0); + } + + if ((rv = + apr_file_write_full(g_ap_write_pipe, command, nbytes, + NULL)) != APR_SUCCESS) { + /* Just print some error log and fall through */ + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't write spawn command"); + } else { + /* Wait the finish notify while send the request successfully */ + nbytes = sizeof(notifybyte); + if ((rv = + apr_file_read(g_ap_read_pipe, ¬ifybyte, + &nbytes)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't get notify from process manager"); + } + } + + /* Release the lock */ + if ((rv = apr_global_mutex_unlock(g_pipelock)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't release pipe mutex"); + exit(0); + } + + return APR_SUCCESS; +} + +apr_status_t procmgr_finish_notify(server_rec * main_server) +{ + apr_status_t rv; + char notifybyte = 'p'; + apr_size_t nbytes = sizeof(notifybyte); + + if ((rv = + apr_file_write(g_pm_write_pipe, ¬ifybyte, + &nbytes)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, main_server, + "mod_fcgid: can't send notify from process manager"); + } + + return rv; +} + +#define FOR_READ 1 +apr_status_t procmgr_fetch_cmd(fcgid_command * command, + server_rec * main_server) +{ + apr_status_t rv; + + /* Sanity check */ + if (!g_pm_read_pipe) + return APR_EPIPE; + + /* Wait for next command */ +#if APR_MAJOR_VERSION < 2 + rv = apr_wait_for_io_or_timeout(g_pm_read_pipe, NULL, FOR_READ); +#else + rv = apr_file_pipe_wait(g_pm_read_pipe, APR_WAIT_READ); +#endif + + /* Log any unexpect result */ + if (rv != APR_SUCCESS && !APR_STATUS_IS_TIMEUP(rv)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, main_server, + "mod_fcgid: error while waiting for message from pipe"); + return rv; + } + + /* Timeout */ + if (rv != APR_SUCCESS) + return rv; + + return apr_file_read_full(g_pm_read_pipe, command, sizeof(*command), + NULL); +} + +int procmgr_must_exit() +{ + return g_caughtSigTerm; +} + +apr_status_t procmgr_stop_procmgr(void *server) +{ + return APR_SUCCESS; +} diff --git a/modules/fcgid/fcgid_pm_win.c b/modules/fcgid/fcgid_pm_win.c new file mode 100644 index 0000000..9712dfb --- /dev/null +++ b/modules/fcgid/fcgid_pm_win.c @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_thread_proc.h" +#include "apr_strings.h" +#include "apr_queue.h" +#include "fcgid_pm.h" +#include "fcgid_pm_main.h" +#include "fcgid_conf.h" +#include "fcgid_spawn_ctl.h" +#define FCGID_MSGQUEUE_SIZE 10 + +static apr_thread_t *g_thread = NULL; +static apr_queue_t *g_msgqueue = NULL; +static apr_queue_t *g_notifyqueue = NULL; +static apr_thread_mutex_t *g_reqlock = NULL; +static apr_thread_t *g_wakeup_thread = NULL; +static int g_must_exit = 0; +static int g_wakeup_timeout = 0; + +static void *APR_THREAD_FUNC wakeup_thread(apr_thread_t * thd, void *data) +{ + while (!g_must_exit) { + /* Wake up every second to check g_must_exit flag */ + int i; + + for (i = 0; i < g_wakeup_timeout; i++) { + if (g_must_exit) + break; + apr_sleep(apr_time_from_sec(1)); + } + + /* Send a wake up message to procmgr_fetch_cmd() */ + if (!g_must_exit && g_msgqueue) + apr_queue_trypush(g_msgqueue, NULL); + } + return NULL; +} + +static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *data) +{ + server_rec *main_server = data; + + pm_main(main_server, main_server->process->pconf); + return NULL; +} + +apr_status_t procmgr_pre_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + return APR_SUCCESS; +} + +apr_status_t +procmgr_post_config(server_rec * main_server, apr_pool_t * pconf) +{ + apr_status_t rv; + fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, + &fcgid_module); + + /* Initialize spawn controler */ + spawn_control_init(main_server, pconf); + + /* Create a message queues */ + if ((rv = apr_queue_create(&g_msgqueue, FCGID_MSGQUEUE_SIZE, + pconf)) != APR_SUCCESS + || (rv = apr_queue_create(&g_notifyqueue, FCGID_MSGQUEUE_SIZE, + pconf)) != APR_SUCCESS) { + /* Fatal error */ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: can't create message queue"); + exit(1); + } + + /* Create request lock */ + if ((rv = apr_thread_mutex_create(&g_reqlock, + APR_THREAD_MUTEX_DEFAULT, + pconf)) != APR_SUCCESS) { + /* Fatal error */ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Can't create request mutex"); + exit(1); + } + + /* Calculate procmgr_fetch_cmd wake up interval */ + g_wakeup_timeout = min(sconf->error_scan_interval, + sconf->busy_scan_interval); + g_wakeup_timeout = min(sconf->idle_scan_interval, + g_wakeup_timeout); + if (g_wakeup_timeout == 0) + g_wakeup_timeout = 1; /* Make it reasonable */ + + /* Create process manager worker thread */ + if ((rv = apr_thread_create(&g_thread, NULL, worker_thread, + main_server, pconf)) != APR_SUCCESS) { + /* It's a fatal error */ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: can't create process manager thread"); + exit(1); + } + + /* Create wake up thread */ + /* XXX If there was a function such like apr_queue_pop_timedwait(), + then I don't need such an ugly thread to do the wake up job */ + if ((rv = apr_thread_create(&g_wakeup_thread, NULL, wakeup_thread, + NULL, pconf)) != APR_SUCCESS) { + /* It's a fatal error */ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: can't create wake up thread"); + exit(1); + } + + apr_pool_cleanup_register(pconf, main_server, procmgr_stop_procmgr, + apr_pool_cleanup_null); + + return APR_SUCCESS; +} + +void procmgr_init_spawn_cmd(fcgid_command * command, request_rec * r, + fcgid_cmd_conf *cmd_conf) +{ + fcgid_server_conf *sconf = + ap_get_module_config(r->server->module_config, &fcgid_module); + + /* no truncation should ever occur */ + AP_DEBUG_ASSERT(sizeof command->cgipath > strlen(cmd_conf->cgipath)); + apr_cpystrn(command->cgipath, cmd_conf->cgipath, sizeof command->cgipath); + AP_DEBUG_ASSERT(sizeof command->cmdline > strlen(cmd_conf->cmdline)); + apr_cpystrn(command->cmdline, cmd_conf->cmdline, sizeof command->cmdline); + + command->inode = (apr_ino_t) -1; + command->deviceid = (dev_t) -1; + command->uid = (uid_t) - 1; + command->gid = (gid_t) - 1; + command->userdir = 0; + command->vhost_id = sconf->vhost_id; + if (r->server->server_hostname) { + apr_cpystrn(command->server_hostname, r->server->server_hostname, + sizeof command->server_hostname); + } + else { + command->server_hostname[0] = '\0'; + } + + get_cmd_options(r, command->cgipath, &command->cmdopts, &command->cmdenv); +} + +apr_status_t procmgr_send_spawn_cmd(fcgid_command * command, + request_rec * r) +{ + if (g_thread && g_msgqueue && !g_must_exit + && g_reqlock && g_notifyqueue) { + apr_status_t rv; + + /* + Prepare the message send to another thread + destroy the message if I can't push to message + */ + fcgid_command *postcmd = + (fcgid_command *) malloc(sizeof(fcgid_command)); + if (!postcmd) + return APR_ENOMEM; + memcpy(postcmd, command, sizeof(*command)); + + /* Get request lock first */ + if ((rv = apr_thread_mutex_lock(g_reqlock)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't get request lock"); + return rv; + } + + /* Try push the message */ + if ((rv = apr_queue_push(g_msgqueue, postcmd)) != APR_SUCCESS) { + apr_thread_mutex_unlock(g_reqlock); + free(postcmd); + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't push request message"); + return rv; + } else { + /* Wait the respond from process manager */ + char *notifybyte = NULL; + + if ((rv = + apr_queue_pop(g_notifyqueue, + (void **)¬ifybyte)) != APR_SUCCESS) { + apr_thread_mutex_unlock(g_reqlock); + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't pop notify message"); + return rv; + } + } + + /* Release the lock now */ + if ((rv = apr_thread_mutex_unlock(g_reqlock)) != APR_SUCCESS) { + /* It's a fatal error */ + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't release request lock"); + exit(1); + } + } + + return APR_SUCCESS; +} + +apr_status_t procmgr_finish_notify(server_rec * main_server) +{ + apr_status_t rv; + char *notify = NULL; + + if ((rv = apr_queue_push(g_notifyqueue, notify)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: can't send spawn notify"); + } + + return rv; +} + +apr_status_t procmgr_fetch_cmd(fcgid_command * command, + server_rec * main_server) +{ + fcgid_command *peakcmd = NULL; + + if (!g_must_exit && g_msgqueue) { + if (apr_queue_pop(g_msgqueue, (void **)&peakcmd) == APR_SUCCESS) { + if (!peakcmd) + return APR_TIMEUP; /* This a wake up message */ + else { + /* Copy the command, and then free the memory */ + memcpy(command, peakcmd, sizeof(*peakcmd)); + free(peakcmd); + + return APR_SUCCESS; + } + } + } + + return APR_TIMEUP; +} + +apr_status_t +procmgr_child_init(server_rec * main_server, apr_pool_t * pchild) +{ + /* Noop on windows, but used by unix */ + + return APR_SUCCESS; +} + +int procmgr_must_exit() +{ + return g_must_exit; +} + +apr_status_t procmgr_stop_procmgr(void *server) +{ + apr_status_t status; + fcgid_server_conf *conf; + + /* Tell the world to die */ + g_must_exit = 1; + if (g_msgqueue) + apr_queue_push(g_msgqueue, NULL); + + /* Wait */ + if (g_thread && apr_thread_join(&status, g_thread) == APR_SUCCESS) { + /* Free the memory left in queue */ + fcgid_command *peakcmd = NULL; + + while (apr_queue_trypop(g_msgqueue, (void **)&peakcmd) == APR_SUCCESS) { + if (peakcmd) + free(peakcmd); + } + } + + /* Clean up the Job object if present */ + conf = ap_get_module_config(((server_rec*)server)->module_config, + &fcgid_module); + + if (conf->hJobObjectForAutoCleanup != NULL) { + CloseHandle(conf->hJobObjectForAutoCleanup); + } + + if (g_wakeup_thread) + return apr_thread_join(&status, g_wakeup_thread); + + return APR_SUCCESS; +} diff --git a/modules/fcgid/fcgid_proc.h b/modules/fcgid/fcgid_proc.h new file mode 100644 index 0000000..38619ec --- /dev/null +++ b/modules/fcgid/fcgid_proc.h @@ -0,0 +1,67 @@ +/* + * 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 FCGID_PM_PROC_H +#define FCGID_PM_PROC_H +#include "httpd.h" +#include "apr_pools.h" +#include "apr_file_io.h" +#include "fcgid_proctbl.h" + +typedef struct { + apr_table_t *proc_environ; + server_rec *main_server; + apr_pool_t *configpool; + char *cgipath; + uid_t uid; /* For suEXEC */ + gid_t gid; /* For suEXEC */ + int userdir; /* For suEXEC */ +} fcgid_proc_info; + +typedef struct { + int connect_timeout; /* in second */ + int communation_timeout; /* in second */ + void *ipc_handle_info; + request_rec *request; +} fcgid_ipc; + +apr_status_t proc_spawn_process(const char *cmdline, + fcgid_proc_info * procinfo, + fcgid_procnode * procnode); + +apr_status_t proc_kill_gracefully(fcgid_procnode * procnode, + server_rec * main_server); +apr_status_t proc_kill_force(fcgid_procnode * procnode, + server_rec * main_server); +apr_status_t proc_wait_process(server_rec * main_server, + fcgid_procnode * procnode); + +apr_status_t proc_connect_ipc(fcgid_procnode * procnode, + fcgid_ipc * ipc_handle); + +apr_status_t proc_read_ipc(fcgid_ipc * ipc_handle, const char *buffer, + apr_size_t * size); + +apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, + apr_bucket_brigade * output_brigade); + +apr_status_t proc_close_ipc(fcgid_ipc * ipc_handle); + +void proc_print_exit_info(fcgid_procnode * procnode, int exitcode, + apr_exit_why_e exitwhy, + server_rec * main_server); +#endif diff --git a/modules/fcgid/fcgid_proc_unix.c b/modules/fcgid/fcgid_proc_unix.c new file mode 100644 index 0000000..218f3f7 --- /dev/null +++ b/modules/fcgid/fcgid_proc_unix.c @@ -0,0 +1,879 @@ +/* + * 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 +#include +#include +#include /* For TCP_NODELAY */ +#include +#include +#define CORE_PRIVATE +#include "httpd.h" +#include "apr_version.h" +#include "apr_thread_proc.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_pools.h" +#include "apr_network_io.h" +#include "ap_mpm.h" +#include "http_config.h" +#include "mpm_common.h" +#include "util_script.h" +#include "unixd.h" +#include "mod_core.h" +#include "mod_cgi.h" +#include "apr_tables.h" +#include "fcgid_proc.h" +#include "fcgid_proctbl.h" +#include "fcgid_protocol.h" +#include "fcgid_conf.h" +#include "fcgid_pm.h" +#include "fcgid_spawn_ctl.h" + +#if MODULE_MAGIC_NUMBER_MAJOR < 20081201 +#define ap_unixd_config unixd_config +#endif + +#if APR_MAJOR_VERSION < 1 +#define APR_FPROT_UWRITE APR_UWRITE +#define APR_FPROT_UREAD APR_UREAD +#define APR_FPROT_UEXECUTE APR_UEXECUTE +#endif + +#define DEFAULT_FCGID_LISTENBACKLOG 5 + +typedef struct { + int handle_socket; +} fcgid_namedpipe_handle; + +static int g_process_counter = 0; + +static apr_status_t ap_unix_create_privileged_process(apr_proc_t *newproc, + const char *progname, + const char *const *args, + const char *const *env, + apr_procattr_t *attr, + ap_unix_identity_t *ugid, + apr_pool_t *p) +{ + int i = 0; + const char **newargs; + const char *newprogname; + const char *execuser, *execgroup; + const char *argv0; + + if (!ap_unixd_config.suexec_enabled) { + return apr_proc_create(newproc, progname, args, env, attr, p); + } + + argv0 = ap_strrchr_c(progname, '/'); + /* Allow suexec's "/" check to succeed */ + if (argv0 != NULL) { + argv0++; + } else { + argv0 = progname; + } + + + if (ugid->userdir) { + execuser = apr_psprintf(p, "~%ld", (long) ugid->uid); + } + else { + execuser = apr_psprintf(p, "%ld", (long) ugid->uid); + } + execgroup = apr_psprintf(p, "%ld", (long) ugid->gid); + + if (!execuser || !execgroup) { + return APR_ENOMEM; + } + + i = 0; + while (args[i]) { + i++; + } + /* allocate space for 4 new args, the input args, and a null terminator */ + newargs = apr_palloc(p, sizeof(char *) * (i + 4)); + newprogname = SUEXEC_BIN; + newargs[0] = SUEXEC_BIN; + newargs[1] = execuser; + newargs[2] = execgroup; + newargs[3] = apr_pstrdup(p, argv0); + + /* + ** using a shell to execute suexec makes no sense thus + ** we force everything to be APR_PROGRAM, and never + ** APR_SHELLCMD + */ + if (apr_procattr_cmdtype_set(attr, APR_PROGRAM) != APR_SUCCESS) { + return APR_EGENERAL; + } + + i = 1; + do { + newargs[i + 3] = args[i]; + } while (args[i++]); + + return apr_proc_create(newproc, newprogname, newargs, env, attr, p); +} + +static apr_status_t fcgid_create_privileged_process(apr_proc_t *newproc, + const char *progname, + const char *const *args, + const char *const *env, + apr_procattr_t *attr, + fcgid_proc_info *procinfo, + apr_pool_t *p) +{ + ap_unix_identity_t ugid; + + if (!ap_unixd_config.suexec_enabled + || (procinfo->uid == (uid_t) - 1 + && procinfo->gid == (gid_t) - 1)) { + return apr_proc_create(newproc, progname, args, env, attr, p); + } + + ugid.gid = procinfo->gid; + ugid.uid = procinfo->uid; + ugid.userdir = procinfo->userdir; + return ap_unix_create_privileged_process(newproc, progname, args, env, + attr, &ugid, p); +} + +static apr_status_t socket_file_cleanup(void *theprocnode) +{ + fcgid_procnode *procnode = (fcgid_procnode *) theprocnode; + + unlink(procnode->socket_path); + return APR_SUCCESS; +} + +static void log_setid_failure(const char *proc_type, + const char *id_type, + uid_t user_id) +{ + char errno_desc[120]; + char errmsg[240]; + + apr_strerror(errno, errno_desc, sizeof errno_desc); + apr_snprintf(errmsg, sizeof errmsg, + "(%d)%s: %s unable to set %s to %ld\n", + errno, errno_desc, proc_type, id_type, (long)user_id); + write(STDERR_FILENO, errmsg, strlen(errmsg)); +} + +/* When suexec is enabled, this runs in the forked child + * process prior to exec(). + */ +static apr_status_t exec_setuid_cleanup(void *dummy) +{ + if (seteuid(0) == -1) { + log_setid_failure("mod_fcgid child", "effective uid", 0); + _exit(1); + } + if (setuid(ap_unixd_config.user_id) == -1) { + log_setid_failure("mod_fcgid child", "uid", ap_unixd_config.user_id); + _exit(1); + } + return APR_SUCCESS; +} + +apr_status_t proc_spawn_process(const char *cmdline, fcgid_proc_info *procinfo, + fcgid_procnode *procnode) +{ + server_rec *main_server = procinfo->main_server; + fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, + &fcgid_module); + apr_status_t rv = APR_SUCCESS; + apr_file_t *file; + apr_proc_t tmpproc; + int omask, retcode, unix_socket; + char **proc_environ; + struct sockaddr_un unix_addr; + apr_procattr_t *procattr = NULL; + int len; + const char **wargv; + + /* Build wrapper args */ + apr_tokenize_to_argv(cmdline, (char ***)&wargv, procnode->proc_pool); + + /* + Create UNIX domain socket before spawn + */ + + /* Generate a UNIX domain socket file path */ + memset(&unix_addr, 0, sizeof(unix_addr)); + unix_addr.sun_family = AF_UNIX; + len = apr_snprintf(unix_addr.sun_path, sizeof(unix_addr.sun_path), + "%s/%" APR_PID_T_FMT ".%d", sconf->sockname_prefix, + getpid(), g_process_counter++); + + /* check for truncation of the socket path + * + * cheap but overly zealous check for sun_path overflow: if length of + * prepared string is at the limit, assume truncation + */ + if (len + 1 == sizeof(unix_addr.sun_path) + || len >= sizeof procnode->socket_path) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server, + "mod_fcgid: socket path length exceeds compiled-in limits"); + return APR_EGENERAL; + } + + apr_cpystrn(procnode->socket_path, unix_addr.sun_path, + sizeof(procnode->socket_path)); + + /* truncation already checked for in handler or FcgidWrapper parser */ + AP_DEBUG_ASSERT(wargv[0] != NULL); + AP_DEBUG_ASSERT(strlen(wargv[0]) < sizeof(procnode->executable_path)); + apr_cpystrn(procnode->executable_path, wargv[0], + sizeof(procnode->executable_path)); + + /* Unlink the file just in case */ + unlink(unix_addr.sun_path); + + /* Create the socket */ + if ((unix_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: couldn't create unix domain socket"); + return errno; + } + + /* Register cleanups to + * 1. Unlink the socket when the process exits + * 2. (suexec mode only, in the child cleanup) Switch to the configured uid + */ + if (ap_unixd_config.suexec_enabled) { + apr_pool_cleanup_register(procnode->proc_pool, + procnode, socket_file_cleanup, + exec_setuid_cleanup); + } + else { + apr_pool_cleanup_register(procnode->proc_pool, + procnode, socket_file_cleanup, + apr_pool_cleanup_null); + } + + /* Bind the socket */ + omask = umask(0077); + retcode = bind(unix_socket, (struct sockaddr *) &unix_addr, + sizeof(unix_addr)); + umask(omask); + if (retcode < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: couldn't bind unix domain socket %s", + unix_addr.sun_path); + close(unix_socket); + return errno; + } + + /* IPC directory permissions are safe, but avoid confusion */ + /* Not all flavors of unix use the current umask for AF_UNIX perms */ + + rv = apr_file_perms_set(unix_addr.sun_path, + APR_FPROT_UREAD|APR_FPROT_UWRITE|APR_FPROT_UEXECUTE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, main_server, + "mod_fcgid: Couldn't set permissions on unix domain socket %s", + unix_addr.sun_path); + return rv; + } + + /* Listen the socket */ + if (listen(unix_socket, DEFAULT_FCGID_LISTENBACKLOG) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: couldn't listen on unix domain socket"); + close(unix_socket); + return errno; + } + + /* Correct the file owner */ + if (!geteuid()) { + if (chown(unix_addr.sun_path, ap_unixd_config.user_id, -1) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "mod_fcgid: couldn't change owner of unix domain socket %s", + unix_addr.sun_path); + close(unix_socket); + return errno; + } + } + + { + int oldflags = fcntl(unix_socket, F_GETFD, 0); + + if (oldflags < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: fcntl F_GETFD failed"); + close(unix_socket); + return errno; + } + + oldflags |= FD_CLOEXEC; + if (fcntl(unix_socket, F_SETFD, oldflags) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: fcntl F_SETFD failed"); + close(unix_socket); + return errno; + } + } + + /* Build environment variables */ + proc_environ = ap_create_environment(procnode->proc_pool, + procinfo->proc_environ); + if (!proc_environ) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: can't build environment variables"); + close(unix_socket); + return APR_ENOMEM; + } + + /* Prepare the fork */ + if ((rv = apr_procattr_create(&procattr, procnode->proc_pool)) != APR_SUCCESS + || (rv = apr_procattr_child_err_set(procattr, + procinfo->main_server->error_log, + NULL)) != APR_SUCCESS + || (rv = apr_procattr_child_out_set(procattr, + procinfo->main_server->error_log, + NULL)) != APR_SUCCESS + || (rv = apr_procattr_dir_set(procattr, + ap_make_dirstr_parent(procnode->proc_pool, + wargv[0]))) != APR_SUCCESS + || (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) != APR_SUCCESS + || (rv = apr_os_file_put(&file, &unix_socket, 0, + procnode->proc_pool)) != APR_SUCCESS + || (rv = apr_procattr_child_in_set(procattr, file, NULL)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server, + "mod_fcgid: couldn't set child process attributes: %s", + unix_addr.sun_path); + close(unix_socket); + return rv; + } + + /* fork and exec now */ + /* Note, don't pass &(procnode->proc_id) to fcgid_create_privileged_process(), + * for it's a share memory address, both parent and child process may modify + * procnode->proc_id->pid, so sometimes it's 0 and sometimes it's >0 + */ + rv = fcgid_create_privileged_process(&tmpproc, wargv[0], wargv, + (const char *const *)proc_environ, + procattr, procinfo, + procnode->proc_pool); + + if (ap_unixd_config.suexec_enabled) { + /* Prior to creating the child process, a child cleanup was registered + * to switch the uid in the child. No-op the child cleanup for this + * pool so that it won't run again as other child processes are created. + * (The cleanup will be registered for the pool associated with those + * processes too.) + */ + apr_pool_child_cleanup_set(procnode->proc_pool, procnode, + socket_file_cleanup, apr_pool_cleanup_null); + } + + /* Close socket before try to connect to it */ + close(unix_socket); + procnode->proc_id = tmpproc; + + if (rv != APR_SUCCESS) { + memset(&procnode->proc_id, 0, sizeof(procnode->proc_id)); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server, + "mod_fcgid: can't run %s", wargv[0]); + } + + return rv; +} + +static apr_status_t proc_kill_internal(fcgid_procnode *procnode, int sig) +{ + /* su as root before sending signal, for suEXEC */ + apr_status_t rv; + + if (procnode->proc_id.pid == 0) { + /* procnode->proc_id.pid be 0 while fcgid_create_privileged_process() fail */ + return APR_SUCCESS; + } + + if (ap_unixd_config.suexec_enabled && seteuid(0) != 0) { + + /* can't gain privileges to send signal (should not occur); do NOT + * proceed, as something is broken with current identity + */ + log_setid_failure("mod_fcgid PM", "effective uid", 0); + _exit(1); + } + rv = apr_proc_kill(&(procnode->proc_id), sig); + if (ap_unixd_config.suexec_enabled && seteuid(ap_unixd_config.user_id) != 0) { + /* can't drop privileges after signalling (should not occur); do NOT + * proceed any further as euid(0)! + */ + log_setid_failure("mod_fcgid PM", "effective uid", ap_unixd_config.user_id); + _exit(1); + } + return rv; +} + +apr_status_t proc_kill_gracefully(fcgid_procnode *procnode, server_rec *main_server) +{ + return proc_kill_internal(procnode, SIGTERM); +} + +apr_status_t proc_kill_force(fcgid_procnode * procnode, + server_rec * main_server) +{ + return proc_kill_internal(procnode, SIGKILL); +} + +apr_status_t proc_wait_process(server_rec *main_server, fcgid_procnode *procnode) +{ + apr_status_t rv; + int exitcode; + apr_exit_why_e exitwhy; + + rv = apr_proc_wait(&(procnode->proc_id), &exitcode, &exitwhy, APR_NOWAIT); + if (rv == APR_CHILD_DONE || rv == APR_EGENERAL) { + /* Log why and how it die */ + proc_print_exit_info(procnode, exitcode, exitwhy, main_server); + + /* Register the death */ + register_termination(main_server, procnode); + + /* Destroy pool */ + apr_pool_destroy(procnode->proc_pool); + procnode->proc_pool = NULL; + memset(&procnode->proc_id, 0, sizeof(procnode->proc_id)); + + return APR_CHILD_DONE; + } + + return rv; +} + +static apr_status_t ipc_handle_cleanup(void *thesocket) +{ + fcgid_namedpipe_handle *handle_info = + (fcgid_namedpipe_handle *) thesocket; + + if (handle_info) { + if (handle_info->handle_socket != -1) { + close(handle_info->handle_socket); + handle_info->handle_socket = -1; + } + } + + return APR_SUCCESS; +} + +static apr_status_t set_socket_nonblock(int sd) +{ +#ifndef BEOS + int fd_flags; + + fd_flags = fcntl(sd, F_GETFL, 0); +#if defined(O_NONBLOCK) + fd_flags |= O_NONBLOCK; +#elif defined(O_NDELAY) + fd_flags |= O_NDELAY; +#elif defined(FNDELAY) + fd_flags |= FNDELAY; +#else +#error Please teach APR how to make sockets non-blocking on your platform. +#endif + if (fcntl(sd, F_SETFL, fd_flags) == -1) { + return errno; + } +#else + int on = 1; + if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0) + return errno; +#endif /* BEOS */ + return APR_SUCCESS; +} + +apr_status_t proc_connect_ipc(fcgid_procnode *procnode, fcgid_ipc *ipc_handle) +{ + fcgid_namedpipe_handle *handle_info; + struct sockaddr_un unix_addr; + apr_status_t rv; + + /* Alloc memory for unix domain socket */ + ipc_handle->ipc_handle_info + = (fcgid_namedpipe_handle *) apr_pcalloc(ipc_handle->request->pool, + sizeof + (fcgid_namedpipe_handle)); + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + handle_info->handle_socket = socket(AF_UNIX, SOCK_STREAM, 0); + apr_pool_cleanup_register(ipc_handle->request->pool, + handle_info, ipc_handle_cleanup, + apr_pool_cleanup_null); + + /* Connect to fastcgi server */ + memset(&unix_addr, 0, sizeof(unix_addr)); + unix_addr.sun_family = AF_UNIX; + + /* PM already made this check for truncation */ + AP_DEBUG_ASSERT(sizeof unix_addr.sun_path > strlen(procnode->socket_path)); + apr_cpystrn(unix_addr.sun_path, procnode->socket_path, + sizeof(unix_addr.sun_path)); + + /* I am the only one who connecting the server + So I don't have to worry about ECONNREFUSED(listen queue overflow) problem, + and I will never retry on error */ + if (connect(handle_info->handle_socket, (struct sockaddr *) &unix_addr, + sizeof(unix_addr)) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, apr_get_os_error(), + ipc_handle->request, + "mod_fcgid: can't connect unix domain socket: %s", + procnode->socket_path); + return APR_ECONNREFUSED; + } + + /* Set nonblock option */ + if ((rv = set_socket_nonblock(handle_info->handle_socket)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, ipc_handle->request, + "mod_fcgid: can't make unix domain socket nonblocking"); + return rv; + } + + return APR_SUCCESS; +} + +apr_status_t proc_close_ipc(fcgid_ipc * ipc_handle) +{ + apr_status_t rv; + + rv = apr_pool_cleanup_run(ipc_handle->request->pool, + ipc_handle->ipc_handle_info, + ipc_handle_cleanup); + ipc_handle->ipc_handle_info = NULL; + return rv; +} + +apr_status_t proc_read_ipc(fcgid_ipc *ipc_handle, const char *buffer, + apr_size_t *size) +{ + int retcode, unix_socket; + fcgid_namedpipe_handle *handle_info; + struct pollfd pollfds[1]; + + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + unix_socket = handle_info->handle_socket; + + do { + if ((retcode = read(unix_socket, (void *) buffer, *size)) > 0) { + *size = retcode; + return APR_SUCCESS; + } + } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); + if (retcode == -1 && !APR_STATUS_IS_EAGAIN(errno)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, + ipc_handle->request, + "mod_fcgid: error reading data from FastCGI server"); + return errno; + } + + /* I have to wait a while */ + + pollfds[0].fd = unix_socket; + pollfds[0].events = POLLIN; + do { + retcode = poll(pollfds, 1, ipc_handle->communation_timeout * 1000); + } while (retcode <= 0 && APR_STATUS_IS_EINTR(errno)); + if (retcode == -1) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, + ipc_handle->request, + "mod_fcgid: error polling unix domain socket"); + return errno; + } + else if (retcode == 0) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, + ipc_handle->request, + "mod_fcgid: read data timeout in %d seconds", + ipc_handle->communation_timeout); + return APR_ETIMEDOUT; + } + + do { + if ((retcode = read(unix_socket, (void *) buffer, *size)) > 0) { + *size = retcode; + return APR_SUCCESS; + } + } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); + + if (retcode == 0) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, + ipc_handle->request, + "mod_fcgid: error reading data, FastCGI server closed connection"); + return APR_EPIPE; + } + + ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, + ipc_handle->request, + "mod_fcgid: error reading data from FastCGI server"); + return errno; +} + +static apr_status_t socket_writev(fcgid_ipc *ipc_handle, + struct iovec *vec, int nvec, + int *writecnt) +{ + apr_status_t rv; + int retcode, unix_socket; + fcgid_namedpipe_handle *handle_info; + struct pollfd pollfds[1]; + + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + unix_socket = handle_info->handle_socket; + + /* Try nonblock write */ + do { + if ((retcode = writev(unix_socket, vec, nvec)) > 0) { + *writecnt = retcode; + return APR_SUCCESS; + } + } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); + rv = errno; + + if (APR_STATUS_IS_EAGAIN(rv)) { + /* poll() */ + pollfds[0].fd = unix_socket; + pollfds[0].events = POLLOUT; + do { + retcode = poll(pollfds, 1, ipc_handle->communation_timeout * 1000); + } while (retcode < 0 && APR_STATUS_IS_EINTR(errno)); + + if (retcode < 0) { + rv = errno; + } + else if (retcode == 0) { + rv = APR_TIMEUP; + } + else { + /* Write again */ + do { + if ((retcode = writev(unix_socket, vec, nvec)) > 0) { + *writecnt = retcode; + return APR_SUCCESS; + } + } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); + rv = errno; + } + } + + if (APR_STATUS_IS_EAGAIN(rv)) { + /* socket is writable, but we can't write the entire buffer; try to write a + * smaller amount, and if even that fails then sleep + */ + size_t to_write = vec[0].iov_len; + int slept = 0; + const apr_interval_time_t sleep_time = APR_USEC_PER_SEC / 4; + const int max_sleeps = 8; + + do { + if ((retcode = write(unix_socket, vec[0].iov_base, to_write)) > 0) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ipc_handle->request, + "wrote %d byte(s) and slept %d time(s) after EAGAIN", + retcode, slept); + *writecnt = retcode; + return APR_SUCCESS; + } + if (APR_STATUS_IS_EAGAIN(errno)) { + if (to_write == 1) { + apr_sleep(sleep_time); + ++slept; + } + else { + to_write /= 2; + } + } + } while ((APR_STATUS_IS_EINTR(errno) || APR_STATUS_IS_EAGAIN(errno)) + && slept < max_sleeps); + rv = errno; + } + + ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, + ipc_handle->request, + "mod_fcgid: error writing data to FastCGI server"); + return rv; +} + +static apr_status_t writev_it_all(fcgid_ipc *ipc_handle, + struct iovec *vec, int nvec) +{ + apr_size_t bytes_written = 0; + apr_status_t rv; + apr_size_t len = 0; + int i = 0; + int writecnt = 0; + + /* Calculate the total size */ + for (i = 0; i < nvec; i++) { + len += vec[i].iov_len; + } + + i = 0; + while (bytes_written != len) { + rv = socket_writev(ipc_handle, vec + i, nvec - i, &writecnt); + if (rv != APR_SUCCESS) + return rv; + bytes_written += writecnt; + + if (bytes_written < len) { + /* Skip over the vectors that have already been written */ + apr_size_t cnt = vec[i].iov_len; + + while (writecnt >= cnt && i + 1 < nvec) { + i++; + cnt += vec[i].iov_len; + } + + if (writecnt < cnt) { + /* Handle partial write of vec i */ + vec[i].iov_base = (char *) vec[i].iov_base + + (vec[i].iov_len - (cnt - writecnt)); + vec[i].iov_len = cnt - writecnt; + } + } + } + + return APR_SUCCESS; +} + +#define FCGID_VEC_COUNT 8 +apr_status_t proc_write_ipc(fcgid_ipc *ipc_handle, + apr_bucket_brigade *output_brigade) +{ + apr_status_t rv; + struct iovec vec[FCGID_VEC_COUNT]; + int nvec = 0; + apr_bucket *e; + + for (e = APR_BRIGADE_FIRST(output_brigade); + e != APR_BRIGADE_SENTINEL(output_brigade); + e = APR_BUCKET_NEXT(e)) { + apr_size_t len; + const char* base; + + if (APR_BUCKET_IS_METADATA(e)) { + continue; + } + + if ((rv = apr_bucket_read(e, &base, &len, + APR_BLOCK_READ)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, ipc_handle->request, + "mod_fcgid: can't read request from bucket"); + return rv; + } + + vec[nvec].iov_len = len; + vec[nvec].iov_base = (char*) base; + if (nvec == (FCGID_VEC_COUNT - 1)) { + /* It's time to write now */ + if ((rv = + writev_it_all(ipc_handle, vec, + FCGID_VEC_COUNT)) != APR_SUCCESS) + return rv; + nvec = 0; + } + else + nvec++; + } + + /* There are something left */ + if (nvec != 0) { + if ((rv = writev_it_all(ipc_handle, vec, nvec)) != APR_SUCCESS) + return rv; + } + + return APR_SUCCESS; +} + +void proc_print_exit_info(fcgid_procnode *procnode, int exitcode, + apr_exit_why_e exitwhy, server_rec *main_server) +{ + const char *diewhy = NULL; + char signal_info[HUGE_STRING_LEN]; + int signum = exitcode; + int loglevel = APLOG_INFO; + + memset(signal_info, 0, HUGE_STRING_LEN); + + /* Reasons to exit */ + switch (procnode->diewhy) { + case FCGID_DIE_KILLSELF: + diewhy = "normal exit"; + break; + case FCGID_DIE_IDLE_TIMEOUT: + diewhy = "idle timeout"; + break; + case FCGID_DIE_LIFETIME_EXPIRED: + diewhy = "lifetime expired"; + break; + case FCGID_DIE_BUSY_TIMEOUT: + diewhy = "busy timeout"; + break; + case FCGID_DIE_CONNECT_ERROR: + diewhy = "connect error"; + break; + case FCGID_DIE_COMM_ERROR: + diewhy = "communication error"; + break; + case FCGID_DIE_SHUTDOWN: + diewhy = "shutting down"; + break; + default: + loglevel = APLOG_ERR; + diewhy = "unknown"; + } + + /* Get signal info */ + if (APR_PROC_CHECK_SIGNALED(exitwhy)) { + switch (signum) { + case SIGTERM: + case SIGHUP: + case AP_SIG_GRACEFUL: + case SIGKILL: + apr_snprintf(signal_info, HUGE_STRING_LEN - 1, + "get stop signal %d", signum); + break; + + default: + loglevel = APLOG_ERR; + if (APR_PROC_CHECK_CORE_DUMP(exitwhy)) { + apr_snprintf(signal_info, HUGE_STRING_LEN - 1, + "get signal %d, possible coredump generated", + signum); + } else { + apr_snprintf(signal_info, HUGE_STRING_LEN - 1, + "get unexpected signal %d", signum); + } + } + } + else if (APR_PROC_CHECK_EXIT(exitwhy)) { + apr_snprintf(signal_info, HUGE_STRING_LEN - 1, + "terminated by calling exit(), return code: %d", + exitcode); + if (procnode->diewhy == FCGID_DIE_CONNECT_ERROR) + diewhy = "server exited"; + } + + /* Print log now */ + ap_log_error(APLOG_MARK, loglevel, 0, main_server, + "mod_fcgid: process %s(%" APR_PID_T_FMT ") exit(%s), %s", + procnode->executable_path, procnode->proc_id.pid, diewhy, signal_info); +} diff --git a/modules/fcgid/fcgid_proc_win.c b/modules/fcgid/fcgid_proc_win.c new file mode 100644 index 0000000..60b26a6 --- /dev/null +++ b/modules/fcgid/fcgid_proc_win.c @@ -0,0 +1,493 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "httpd.h" +#include "apr_thread_proc.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_pools.h" +#include "util_script.h" +#include "mod_core.h" +#include "mod_cgi.h" +#include "apr_tables.h" +#include "fcgid_proc.h" +#include "fcgid_proctbl.h" +#include "fcgid_protocol.h" +#include "fcgid_conf.h" +#include "fcgid_pm.h" +#include "fcgid_spawn_ctl.h" +#define SHUTDOWN_EVENT_NAME "_FCGI_SHUTDOWN_EVENT_" +#define FINISH_EVENT_DATA_NAME "finish_event" +#ifndef SIGKILL +#define SIGKILL 9 +#endif + +/* It's tested on WinNT ONLY, if it work on the other MS platform, let me know */ +#if WINVER < 0x0400 +#error It is tested on WinNT only +#endif + +typedef struct { + HANDLE handle_pipe; + OVERLAPPED overlap_read; + OVERLAPPED overlap_write; +} fcgid_namedpipe_handle; + +static int g_process_counter = 0; + +static apr_status_t close_finish_event(void *finishevent) +{ + HANDLE *finish_event = finishevent; + + CloseHandle(*finish_event); + return APR_SUCCESS; +} + +apr_status_t proc_spawn_process(const char *cmdline, fcgid_proc_info *procinfo, + fcgid_procnode *procnode) +{ + HANDLE *finish_event, listen_handle; + SECURITY_ATTRIBUTES SecurityAttributes; + fcgid_server_conf *sconf; + apr_procattr_t *proc_attr; + apr_status_t rv; + apr_file_t *file; + const char * const *proc_environ; + char sock_path[FCGID_PATH_MAX]; + const char **wargv; + + /* Build wrapper args */ + apr_tokenize_to_argv(cmdline, (char ***)&wargv, procnode->proc_pool); + + memset(&SecurityAttributes, 0, sizeof(SecurityAttributes)); + + /* Prepare finish event */ + finish_event = apr_palloc(procnode->proc_pool, sizeof(HANDLE)); + *finish_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (*finish_event == NULL + || !SetHandleInformation(*finish_event, HANDLE_FLAG_INHERIT, TRUE)) + { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: can't create mutex for subprocess"); + return APR_ENOLOCK; + } + apr_pool_cleanup_register(procnode->proc_pool, finish_event, + close_finish_event, apr_pool_cleanup_null); + + /* For proc_kill_gracefully() */ + apr_pool_userdata_set(finish_event, FINISH_EVENT_DATA_NAME, + NULL, procnode->proc_pool); + + /* Pass the finish event id to subprocess */ + apr_table_setn(procinfo->proc_environ, SHUTDOWN_EVENT_NAME, + apr_ltoa(procnode->proc_pool, (long) *finish_event)); + + /* Prepare the listen namedpipe file name (no check for truncation) */ + apr_snprintf(sock_path, sizeof sock_path, + "\\\\.\\pipe\\fcgidpipe-%lu.%d", + GetCurrentProcessId(), g_process_counter++); + + /* Prepare the listen namedpipe handle */ + SecurityAttributes.bInheritHandle = TRUE; + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.lpSecurityDescriptor = NULL; + listen_handle = CreateNamedPipe(sock_path, + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | + PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, + 8192, 8192, 0, &SecurityAttributes); + if (listen_handle == INVALID_HANDLE_VALUE) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: can't create namedpipe for subprocess"); + return APR_ENOSOCKET; + } + apr_cpystrn(procnode->socket_path, sock_path, sizeof(procnode->socket_path)); + apr_cpystrn(procnode->executable_path, wargv[0], + sizeof(procnode->executable_path)); + + /* Build environment variables */ + proc_environ = (const char * const *) + ap_create_environment(procnode->proc_pool, + procinfo->proc_environ); + if (!proc_environ) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: can't build environment variables"); + return APR_ENOMEM; + } + + /* Create process now */ + if ((rv = apr_procattr_create(&proc_attr, procnode->proc_pool)) + != APR_SUCCESS + || (rv = apr_procattr_dir_set(proc_attr, + ap_make_dirstr_parent(procnode->proc_pool, + wargv[0]))) != APR_SUCCESS + || (rv = apr_procattr_cmdtype_set(proc_attr, APR_PROGRAM)) + != APR_SUCCESS + || (rv = apr_procattr_detach_set(proc_attr, 1)) != APR_SUCCESS + || (rv = apr_procattr_io_set(proc_attr, APR_NO_PIPE, + APR_NO_FILE, APR_NO_FILE)) != APR_SUCCESS + || (rv = apr_os_file_put(&file, &listen_handle, 0, + procnode->proc_pool)) != APR_SUCCESS + || (rv = apr_procattr_child_in_set(proc_attr, file, NULL)) + != APR_SUCCESS) + { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, procinfo->main_server, + "mod_fcgid: can't create FastCGI process attribute"); + CloseHandle(listen_handle); + return APR_ENOPROC; + } + + /* fork and exec now */ + rv = apr_proc_create(&(procnode->proc_id), wargv[0], wargv, + proc_environ, + proc_attr, procnode->proc_pool); + + /* OK, I created the process, now put it back to idle list */ + CloseHandle(listen_handle); + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server, + "mod_fcgid: can't run %s", wargv[0]); + return rv; + } + + /* FcgidWin32PreventOrphans feature */ + sconf = ap_get_module_config(procinfo->main_server->module_config, + &fcgid_module); + + if (sconf->hJobObjectForAutoCleanup != NULL) { + /* Associate cgi process to current process */ + if (AssignProcessToJobObject(sconf->hJobObjectForAutoCleanup, + procnode->proc_id.hproc) == 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: unable to assign child process to " + "job object"); + } + } + + return APR_SUCCESS; +} + +apr_status_t proc_kill_gracefully(fcgid_procnode *procnode, + server_rec *main_server) +{ + HANDLE *finish_event = NULL; + + apr_pool_userdata_get((void **) &finish_event, + FINISH_EVENT_DATA_NAME, procnode->proc_pool); + + if (finish_event != NULL) + SetEvent(*finish_event); + return APR_SUCCESS; +} + +apr_status_t proc_kill_force(fcgid_procnode *procnode, + server_rec *main_server) +{ + return apr_proc_kill(&(procnode->proc_id), SIGKILL); +} + +apr_status_t proc_wait_process(server_rec *main_server, + fcgid_procnode *procnode) +{ + apr_status_t rv; + int exitcode; + apr_exit_why_e exitwhy; + + if ((rv = apr_proc_wait(&(procnode->proc_id), &exitcode, &exitwhy, + APR_NOWAIT)) == APR_CHILD_DONE) { + /* Log why and how it die */ + proc_print_exit_info(procnode, exitcode, exitwhy, main_server); + + /* Register the death */ + register_termination(main_server, procnode); + + /* Destroy pool */ + apr_pool_destroy(procnode->proc_pool); + procnode->proc_pool = NULL; + } + + return rv; +} + +static apr_status_t ipc_handle_cleanup(void *thehandle) +{ + fcgid_namedpipe_handle *handle = thehandle; + + /* Sanity check */ + if (handle) { + if (handle->handle_pipe != INVALID_HANDLE_VALUE) + CloseHandle(handle->handle_pipe); + if (handle->overlap_read.hEvent != NULL) + CloseHandle(handle->overlap_read.hEvent); + if (handle->overlap_write.hEvent != NULL) + CloseHandle(handle->overlap_write.hEvent); + handle->handle_pipe = INVALID_HANDLE_VALUE; + handle->overlap_read.hEvent = NULL; + handle->overlap_write.hEvent = NULL; + } + + return APR_SUCCESS; +} + +apr_status_t proc_connect_ipc(fcgid_procnode *procnode, fcgid_ipc *ipc_handle) +{ + /* Prepare the ipc struct */ + fcgid_namedpipe_handle *handle_info; + + ipc_handle->ipc_handle_info = + (fcgid_namedpipe_handle *) apr_pcalloc(ipc_handle->request->pool, + sizeof + (fcgid_namedpipe_handle)); + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + + /* Prepare OVERLAPPED struct for non-block I/O */ + handle_info->overlap_read.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + handle_info->overlap_write.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + handle_info->handle_pipe = INVALID_HANDLE_VALUE; + + apr_pool_cleanup_register(ipc_handle->request->pool, + handle_info, + ipc_handle_cleanup, apr_pool_cleanup_null); + + if (handle_info->overlap_read.hEvent == NULL + || handle_info->overlap_write.hEvent == NULL) + return APR_ENOMEM; + + /* Connect to name pipe */ + handle_info->handle_pipe = CreateFile(procnode->socket_path, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (handle_info->handle_pipe == INVALID_HANDLE_VALUE + && ipc_handle->connect_timeout != 0 + && GetLastError() == ERROR_PIPE_BUSY) + { + /* XXX - there appears to be a race, here + * Wait for pipe to be ready for connect, and try again + */ + if (WaitNamedPipe(procnode->socket_path, ipc_handle->connect_timeout)) + { + handle_info->handle_pipe = CreateFile(procnode->socket_path, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + } + } + + if (handle_info->handle_pipe == INVALID_HANDLE_VALUE) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) /* The process has exited */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ipc_handle->request, + "mod_fcgid: can't connect to named pipe, FastCGI" + " server %" APR_PID_T_FMT " has been terminated", + procnode->proc_id.pid); + else + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, apr_get_os_error(), + ipc_handle->request, + "mod_fcgid: can't connect to named pipe, FastCGI" + " server pid %" APR_PID_T_FMT, + procnode->proc_id.pid); + return APR_ESPIPE; + } + + /* Now named pipe connected */ + return APR_SUCCESS; +} + +apr_status_t proc_close_ipc(fcgid_ipc * ipc_handle) +{ + apr_status_t rv; + + rv = apr_pool_cleanup_run(ipc_handle->request->pool, + ipc_handle->ipc_handle_info, + ipc_handle_cleanup); + ipc_handle->ipc_handle_info = NULL; + return rv; +} + +apr_status_t proc_read_ipc(fcgid_ipc * ipc_handle, const char *buffer, + apr_size_t * size) +{ + apr_status_t rv; + fcgid_namedpipe_handle *handle_info; + DWORD bytesread; + + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + + if (ReadFile(handle_info->handle_pipe, (LPVOID) buffer, + *size, &bytesread, &handle_info->overlap_read)) { + *size = bytesread; + return APR_SUCCESS; + } else if ((rv = GetLastError()) != ERROR_IO_PENDING) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_FROM_OS_ERROR(rv), + ipc_handle->request, + "mod_fcgid: can't read from pipe"); + return rv; + } else { + /* it's ERROR_IO_PENDING */ + DWORD transferred; + DWORD dwWaitResult + = WaitForSingleObject(handle_info->overlap_read.hEvent, + ipc_handle->communation_timeout * 1000); + + if (dwWaitResult == WAIT_OBJECT_0) { + if (!GetOverlappedResult(handle_info->handle_pipe, + &handle_info->overlap_read, + &transferred, FALSE /* don't wait */ ) + || transferred == 0) { + rv = apr_get_os_error(); + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, + ipc_handle->request, + "mod_fcgid: get overlap result error"); + return rv; + } + + *size = transferred; + return APR_SUCCESS; + } else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ipc_handle->request, + "mod_fcgid: read timeout from pipe"); + return APR_ETIMEDOUT; + } + } +} + +apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, + apr_bucket_brigade * birgade_send) +{ + fcgid_namedpipe_handle *handle_info; + apr_bucket *bucket_request; + apr_status_t rv; + DWORD transferred; + + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + + for (bucket_request = APR_BRIGADE_FIRST(birgade_send); + bucket_request != APR_BRIGADE_SENTINEL(birgade_send); + bucket_request = APR_BUCKET_NEXT(bucket_request)) + { + const char *write_buf; + apr_size_t write_buf_len; + apr_size_t has_write; + + if (APR_BUCKET_IS_METADATA(bucket_request)) + continue; + + if ((rv = apr_bucket_read(bucket_request, &write_buf, &write_buf_len, + APR_BLOCK_READ)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, ipc_handle->request, + "mod_fcgid: can't read request from bucket"); + return rv; + } + + /* Write the buffer to fastcgi server */ + has_write = 0; + while (has_write < write_buf_len) { + DWORD byteswrite; + + if (WriteFile(handle_info->handle_pipe, + write_buf + has_write, + write_buf_len - has_write, + &byteswrite, &handle_info->overlap_write)) { + has_write += byteswrite; + continue; + } else if ((rv = GetLastError()) != ERROR_IO_PENDING) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, + APR_FROM_OS_ERROR(rv), ipc_handle->request, + "mod_fcgid: can't write to pipe"); + return rv; + } else { + /* + it's ERROR_IO_PENDING on write + */ + DWORD dwWaitResult = + WaitForSingleObject(handle_info->overlap_write.hEvent, + ipc_handle->communation_timeout * 1000); + if (dwWaitResult == WAIT_OBJECT_0) { + if (!GetOverlappedResult(handle_info->handle_pipe, + &handle_info->overlap_write, + &transferred, + FALSE /* don't wait */ ) + || transferred == 0) + { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, + apr_get_os_error(), ipc_handle->request, + "mod_fcgid: get overlap result error"); + return APR_ESPIPE; + } + has_write += transferred; + continue; + } else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, + ipc_handle->request, + "mod_fcgid: write timeout to pipe"); + return APR_ESPIPE; + } + } + } + } + + return APR_SUCCESS; +} + +void proc_print_exit_info(fcgid_procnode * procnode, int exitcode, + apr_exit_why_e exitwhy, server_rec * main_server) +{ + char *diewhy = NULL; + + /* Reasons to exit */ + switch (procnode->diewhy) { + case FCGID_DIE_KILLSELF: + if (exitwhy == APR_PROC_EXIT) + diewhy = "normal exit"; + else + diewhy = "access violation"; + break; + case FCGID_DIE_IDLE_TIMEOUT: + diewhy = "idle timeout"; + break; + case FCGID_DIE_LIFETIME_EXPIRED: + diewhy = "lifetime expired"; + break; + case FCGID_DIE_BUSY_TIMEOUT: + diewhy = "busy timeout"; + break; + case FCGID_DIE_CONNECT_ERROR: + diewhy = "connect error, server may has exited"; + break; + case FCGID_DIE_COMM_ERROR: + diewhy = "communication error"; + break; + case FCGID_DIE_SHUTDOWN: + diewhy = "shutting down"; + break; + default: + diewhy = "unknown"; + } + + /* Print log now */ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server, + "mod_fcgid: process %s(%" APR_PID_T_FMT ") exit(%s), return code %d", + procnode->executable_path, procnode->proc_id.pid, diewhy, exitcode); +} diff --git a/modules/fcgid/fcgid_proctbl.h b/modules/fcgid/fcgid_proctbl.h new file mode 100644 index 0000000..b188613 --- /dev/null +++ b/modules/fcgid/fcgid_proctbl.h @@ -0,0 +1,103 @@ +/* + * 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 FCGID_TABLE_H +#define FCGID_TABLE_H +#include +#include "httpd.h" +#include "apr_thread_proc.h" +#include "fcgid_global.h" +#include "fcgid_conf.h" + +/* Increase it if necessary */ +#define FCGID_MAX_APPLICATION (1024) + +/* FCGID_MAX_APPLICATION + 4 list headers */ +#define FCGID_PROC_TABLE_SIZE (FCGID_MAX_APPLICATION+4) + +/* + nNextIndex is for making a node list, there are four kind of list: + 1) free list: no process associate to this node + 2) busy list: a process is associated, and it's handling request + 3) idle list: a process is associated, and it's waiting request + 4) error list: a process is associated, and killing the process now +*/ +typedef struct { + /* only one of next_index or node_type is used, depending on context */ + int next_index; /* the next array index in the list */ + int node_type; /* the type of this node, used in fcgid_status_hook() only */ + apr_pool_t *proc_pool; /* pool for process */ + apr_proc_t proc_id; /* the process id */ + char executable_path[FCGID_PATH_MAX]; /* executable file path */ + char socket_path[FCGID_PATH_MAX]; /* cgi application socket path */ + apr_ino_t inode; /* cgi file inode */ + apr_dev_t deviceid; /* cgi file device id */ + char cmdline[FCGID_CMDLINE_MAX]; /* entire command line */ + gid_t gid; /* for suEXEC */ + uid_t uid; /* for suEXEC */ + int vhost_id; /* the vhost to which this process belongs (the server_rec + * addr fails with some mass-vhost mods which allocate + * them per-request) */ + apr_time_t start_time; /* the time of this process create */ + apr_time_t last_active_time; /* the time this process last active */ + int requests_handled; /* number of requests process has handled */ + char diewhy; /* why it die */ + fcgid_cmd_options cmdopts; /* context-specific configuration */ +} fcgid_procnode; + +/* Macros for diewhy */ +#define FCGID_DIE_KILLSELF 0 +#define FCGID_DIE_IDLE_TIMEOUT 1 +#define FCGID_DIE_LIFETIME_EXPIRED 2 +#define FCGID_DIE_BUSY_TIMEOUT 3 +#define FCGID_DIE_CONNECT_ERROR 4 +#define FCGID_DIE_COMM_ERROR 5 +#define FCGID_DIE_SHUTDOWN 6 + +typedef struct { + int must_exit; /* All processes using this share memory must exit */ +} fcgid_global_share; + +typedef struct { + fcgid_global_share global; + fcgid_procnode procnode_array[FCGID_PROC_TABLE_SIZE]; +} fcgid_share; + +apr_status_t proctable_child_init(server_rec * main_server, + apr_pool_t * pchild); +apr_status_t proctable_pre_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp); +apr_status_t proctable_post_config(server_rec * main_server, + apr_pool_t * pconf); + +fcgid_procnode *proctable_get_free_list(void); +fcgid_procnode *proctable_get_busy_list(void); +fcgid_procnode *proctable_get_idle_list(void); +fcgid_procnode *proctable_get_error_list(void); +fcgid_procnode *proctable_get_table_array(void); +size_t proctable_get_table_size(void); +fcgid_global_share *proctable_get_globalshare(void); + +void proctable_pm_lock(server_rec *s); +void proctable_pm_unlock(server_rec *s); +void proctable_lock(request_rec *r); +void proctable_unlock(request_rec *r); + +/* Just for debug */ +void proctable_print_debug_info(server_rec * main_server); + +#endif diff --git a/modules/fcgid/fcgid_proctbl_unix.c b/modules/fcgid/fcgid_proctbl_unix.c new file mode 100644 index 0000000..a38b081 --- /dev/null +++ b/modules/fcgid/fcgid_proctbl_unix.c @@ -0,0 +1,344 @@ +/* + * 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 "fcgid_proctbl.h" +#include "apr_version.h" +#include "apr_shm.h" +#include "apr_global_mutex.h" +#include "fcgid_global.h" +#include "fcgid_conf.h" +#include "fcgid_mutex.h" +#include + +static apr_shm_t *g_sharemem = NULL; +static apr_global_mutex_t *g_sharelock = NULL; +static const char *g_sharelock_name; +static const char *g_sharelock_mutex_type = "fcgid-proctbl"; +static fcgid_procnode *g_proc_array = NULL; /* Contain all process slot */ +static fcgid_procnode *g_free_list_header = NULL; /* Attach to no process list */ +static fcgid_procnode *g_busy_list_header = NULL; /* Attach to a working process list */ +static fcgid_procnode *g_idle_list_header = NULL; /* Attach to an idle process list */ +static fcgid_procnode *g_error_list_header = NULL; /* Attach to an error process list */ +static fcgid_share *_global_memory = NULL; +static fcgid_global_share *g_global_share = NULL; /* global information */ +static size_t g_table_size = FCGID_PROC_TABLE_SIZE; + +/* apr version 0.x not support apr_shm_remove, I have to copy it from apr version 1.x */ +#if (APR_MAJOR_VERSION < 1) +#ifdef HAVE_SYS_MMAN_H +#include +#endif +#ifdef HAVE_SYS_IPC_H +#include +#endif +#ifdef HAVE_SYS_MUTEX_H +#include +#endif +#ifdef HAVE_SYS_SHM_H +#include +#endif +#if !defined(SHM_R) +#define SHM_R 0400 +#endif +#if !defined(SHM_W) +#define SHM_W 0200 +#endif +#ifdef HAVE_SYS_FILE_H +#include +#endif + +static apr_status_t apr_shm_remove(const char *filename, apr_pool_t * pool) +{ +#if APR_USE_SHMEM_SHMGET + apr_status_t status; + apr_file_t *file; + key_t shmkey; + int shmid; +#endif + +#if APR_USE_SHMEM_MMAP_TMP + return apr_file_remove(filename, pool); +#endif +#if APR_USE_SHMEM_MMAP_SHM + if (shm_unlink(filename) == -1) { + return errno; + } + return APR_SUCCESS; +#endif +#if APR_USE_SHMEM_SHMGET + /* Presume that the file already exists; just open for writing */ + status = apr_file_open(&file, filename, APR_WRITE, + APR_OS_DEFAULT, pool); + if (status) { + return status; + } + + /* ftok() (on solaris at least) requires that the file actually + * exist before calling ftok(). */ + shmkey = ftok(filename, 1); + if (shmkey == (key_t) - 1) { + goto shm_remove_failed; + } + + apr_file_close(file); + + if ((shmid = shmget(shmkey, 0, SHM_R | SHM_W)) < 0) { + goto shm_remove_failed; + } + + /* Indicate that the segment is to be destroyed as soon + * as all processes have detached. This also disallows any + * new attachments to the segment. */ + if (shmctl(shmid, IPC_RMID, NULL) == -1) { + goto shm_remove_failed; + } + return apr_file_remove(filename, pool); + + shm_remove_failed: + status = errno; + /* ensure the file has been removed anyway. */ + apr_file_remove(filename, pool); + return status; +#endif + + /* No support for anonymous shm */ + return APR_ENOTIMPL; +} +#endif /* APR_MAJOR_VERSION<1 */ + +apr_status_t proctable_pre_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + return fcgid_mutex_register(g_sharelock_mutex_type, p); +} + +apr_status_t +proctable_post_config(server_rec * main_server, apr_pool_t * configpool) +{ + size_t shmem_size = sizeof(fcgid_share); + fcgid_procnode *ptmpnode = NULL; + int i; + apr_status_t rv; + fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, + &fcgid_module); + + /* Remove share memory first */ + apr_shm_remove(sconf->shmname_path, main_server->process->pconf); + + /* Create share memory */ + if ((rv = apr_shm_create(&g_sharemem, shmem_size, sconf->shmname_path, + main_server->process->pconf)) != APR_SUCCESS) + { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Can't create shared memory for size %" APR_SIZE_T_FMT " bytes", + shmem_size); + exit(1); + } + _global_memory = apr_shm_baseaddr_get(g_sharemem); + + /* Create global mutex */ + rv = fcgid_mutex_create(&g_sharelock, &g_sharelock_name, + g_sharelock_mutex_type, + main_server->process->pconf, main_server); + if (rv != APR_SUCCESS) { + exit(1); + } + + memset(_global_memory, 0, shmem_size); + g_proc_array = _global_memory->procnode_array; + g_global_share = &_global_memory->global; + + g_global_share->must_exit = 0; + + /* Init the array */ + g_idle_list_header = g_proc_array; + g_busy_list_header = g_idle_list_header + 1; + g_error_list_header = g_busy_list_header + 1; + g_free_list_header = g_error_list_header + 1; + ptmpnode = g_free_list_header; + for (i = 0; i < FCGID_MAX_APPLICATION; i++) { + ptmpnode->next_index = ptmpnode - g_proc_array + 1; + ptmpnode++; + } + + return APR_SUCCESS; +} + +apr_status_t +proctable_child_init(server_rec * main_server, apr_pool_t * configpool) +{ + apr_status_t rv; + + if ((rv = apr_global_mutex_child_init(&g_sharelock, + g_sharelock_name, + main_server->process->pconf)) != + APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: apr_global_mutex_child_init error"); + exit(1); + } + + return rv; +} + +static apr_status_t proctable_lock_internal(void) +{ + return apr_global_mutex_lock(g_sharelock); +} + +static apr_status_t proctable_unlock_internal(void) +{ + return apr_global_mutex_unlock(g_sharelock); +} + +fcgid_procnode *proctable_get_free_list(void) +{ + return g_free_list_header; +} + +fcgid_procnode *proctable_get_busy_list(void) +{ + return g_busy_list_header; +} + +fcgid_procnode *proctable_get_idle_list(void) +{ + return g_idle_list_header; +} + +fcgid_procnode *proctable_get_table_array(void) +{ + return g_proc_array; +} + +fcgid_procnode *proctable_get_error_list(void) +{ + return g_error_list_header; +} + +fcgid_global_share *proctable_get_globalshare(void) +{ + return g_global_share; +} + +size_t proctable_get_table_size(void) +{ + return g_table_size; +} + +void proctable_lock(request_rec *r) +{ + /* Lock error is a fatal error */ + apr_status_t rv; + + if ((rv = proctable_lock_internal()) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't lock process table in pid %" + APR_PID_T_FMT, + getpid()); + exit(1); + } +} + +void proctable_unlock(request_rec *r) +{ + /* Lock error is a fatal error */ + apr_status_t rv; + + if ((rv = proctable_unlock_internal()) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't unlock process table in pid %" + APR_PID_T_FMT, + getpid()); + exit(1); + } +} + +void proctable_pm_lock(server_rec *s) +{ + apr_status_t rv; + + if (g_global_share->must_exit) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, + "mod_fcgid: server is restarted, pid %" APR_PID_T_FMT + " must exit", + getpid()); + kill(getpid(), SIGTERM); + } + + /* Lock error is a fatal error */ + if ((rv = proctable_lock_internal()) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, + "mod_fcgid: can't lock process table in PM, pid %" + APR_PID_T_FMT, + getpid()); + exit(1); + } +} + +void proctable_pm_unlock(server_rec *s) +{ + /* Lock error is a fatal error */ + apr_status_t rv; + + if ((rv = proctable_unlock_internal()) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, + "mod_fcgid: can't unlock process table in PM, pid %" + APR_PID_T_FMT, + getpid()); + exit(1); + } +} + +void proctable_print_debug_info(server_rec * main_server) +{ + int freecount = 0; + fcgid_procnode *current_node; + + for (current_node = &g_proc_array[g_free_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) + freecount++; + + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: total node count: %d, free node count: %d", + FCGID_MAX_APPLICATION, freecount); + + for (current_node = &g_proc_array[g_idle_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: idle node index: %ld", + (long)(current_node - g_proc_array)); + } + + for (current_node = &g_proc_array[g_busy_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: busy node index: %ld", + (long)(current_node - g_proc_array)); + } + + for (current_node = &g_proc_array[g_error_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: error node index: %ld", + (long)(current_node - g_proc_array)); + } +} diff --git a/modules/fcgid/fcgid_proctbl_win.c b/modules/fcgid/fcgid_proctbl_win.c new file mode 100644 index 0000000..f2c361b --- /dev/null +++ b/modules/fcgid/fcgid_proctbl_win.c @@ -0,0 +1,214 @@ +/* + * 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 "fcgid_proctbl.h" +#include "apr_shm.h" +#include "fcgid_global.h" + +static apr_thread_mutex_t *g_sharelock = NULL; + +static fcgid_procnode *g_proc_array = NULL; /* Contain all process slot */ +static fcgid_procnode *g_free_list_header = NULL; /* Attach to no process list */ +static fcgid_procnode *g_busy_list_header = NULL; /* Attach to a working process list */ +static fcgid_procnode *g_idle_list_header = NULL; /* Attach to an idle process list */ +static fcgid_procnode *g_error_list_header = NULL; /* Attach to an error process list */ +static fcgid_share *_global_memory = NULL; +static fcgid_global_share *g_global_share = NULL; /* global information */ +static size_t g_table_size = FCGID_PROC_TABLE_SIZE; + +apr_status_t proctable_pre_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + return APR_SUCCESS; +} + +apr_status_t +proctable_post_config(server_rec * main_server, apr_pool_t * pconf) +{ + long shmem_size = sizeof(fcgid_share); + fcgid_procnode *ptmpnode = NULL; + int i; + apr_status_t rv = APR_SUCCESS; + + if ((rv = apr_thread_mutex_create(&g_sharelock, + APR_THREAD_MUTEX_DEFAULT, + pconf)) != APR_SUCCESS) { + /* Fatal error */ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Can't create global mutex"); + exit(1); + } + + /* There is only one process in WinNT mpm, share memory is not necessary */ + _global_memory = (fcgid_share *) apr_pcalloc(pconf, shmem_size); + + g_proc_array = _global_memory->procnode_array; + g_global_share = &_global_memory->global; + + g_global_share->must_exit = 0; + + /* Init the array */ + g_idle_list_header = g_proc_array; + g_busy_list_header = g_idle_list_header + 1; + g_error_list_header = g_busy_list_header + 1; + g_free_list_header = g_error_list_header + 1; + ptmpnode = g_free_list_header; + for (i = 0; i < FCGID_MAX_APPLICATION; i++) { + ptmpnode->next_index = ptmpnode - g_proc_array + 1; + ptmpnode++; + } + + return APR_SUCCESS; +} + +apr_status_t +proctable_child_init(server_rec * main_server, apr_pool_t * pchild) +{ + return APR_SUCCESS; +} + +static apr_status_t proctable_lock_internal(void) +{ + return apr_thread_mutex_lock(g_sharelock); +} + +static apr_status_t proctable_unlock_internal(void) +{ + return apr_thread_mutex_unlock(g_sharelock); +} + +fcgid_procnode *proctable_get_free_list(void) +{ + return g_free_list_header; +} + +fcgid_procnode *proctable_get_busy_list(void) +{ + return g_busy_list_header; +} + +fcgid_procnode *proctable_get_idle_list(void) +{ + return g_idle_list_header; +} + +fcgid_procnode *proctable_get_table_array(void) +{ + return g_proc_array; +} + +fcgid_procnode *proctable_get_error_list(void) +{ + return g_error_list_header; +} + +fcgid_global_share *proctable_get_globalshare(void) +{ + return g_global_share; +} + +size_t proctable_get_table_size(void) +{ + return g_table_size; +} + +void proctable_lock(request_rec *r) +{ + /* Lock error is a fatal error */ + apr_status_t rv; + + if ((rv = proctable_lock_internal()) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't lock process table"); + exit(1); + } +} + +void proctable_unlock(request_rec *r) +{ + /* Lock error is a fatal error */ + apr_status_t rv; + + if ((rv = proctable_unlock_internal()) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, + "mod_fcgid: can't unlock process table"); + exit(1); + } +} + +void proctable_pm_lock(server_rec * s) +{ + /* Lock error is a fatal error */ + apr_status_t rv; + + if ((rv = proctable_lock_internal()) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, + "mod_fcgid: can't lock process table in PM"); + exit(1); + } +} + +void proctable_pm_unlock(server_rec * s) +{ + /* Lock error is a fatal error */ + apr_status_t rv; + + if ((rv = proctable_unlock_internal()) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, + "mod_fcgid: can't unlock process table in PM"); + exit(1); + } +} + +void proctable_print_debug_info(server_rec * main_server) +{ + int freecount = 0; + fcgid_procnode *current_node; + + for (current_node = &g_proc_array[g_free_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) + freecount++; + + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: total node count: %d, free node count: %d", + FCGID_MAX_APPLICATION, freecount); + + for (current_node = &g_proc_array[g_idle_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: idle node index: %d", + current_node - g_proc_array); + } + + for (current_node = &g_proc_array[g_busy_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: busy node index: %d", + current_node - g_proc_array); + } + + for (current_node = &g_proc_array[g_error_list_header->next_index]; + current_node != g_proc_array; + current_node = &g_proc_array[current_node->next_index]) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, + "mod_fcgid: error node index: %d", + current_node - g_proc_array); + } +} diff --git a/modules/fcgid/fcgid_protocol.c b/modules/fcgid/fcgid_protocol.c new file mode 100644 index 0000000..308f27d --- /dev/null +++ b/modules/fcgid/fcgid_protocol.c @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_pools.h" +#include "fcgid_global.h" +#include "fcgid_protocol.h" + +static size_t init_environment(char *buf, char **envp) +{ + char *spliter; + apr_size_t namelen, valuelen; + char *cur_buf = buf; + size_t buffer_size = 0; + + for (; *envp != NULL; envp++) { + spliter = strchr(*envp, '='); + if (spliter == NULL) + continue; + + namelen = spliter - *envp; + valuelen = strlen(spliter + 1); + + /* Put name length to buffer */ + if (namelen < 0x80) { + if (!buf) + buffer_size++; + else + *cur_buf++ = (unsigned char) namelen; + } else { + if (!buf) + buffer_size += 4; + else { + *cur_buf++ = (unsigned char) ((namelen >> 24) | 0x80); + *cur_buf++ = (unsigned char) (namelen >> 16); + *cur_buf++ = (unsigned char) (namelen >> 8); + *cur_buf++ = (unsigned char) namelen; + } + } + + /* Put value length to buffer */ + if (valuelen < 0x80) { + if (!buf) + buffer_size++; + else + *cur_buf++ = (unsigned char) valuelen; + } else { + if (!buf) + buffer_size += 4; + else { + *cur_buf++ = (unsigned char) ((valuelen >> 24) | 0x80); + *cur_buf++ = (unsigned char) (valuelen >> 16); + *cur_buf++ = (unsigned char) (valuelen >> 8); + *cur_buf++ = (unsigned char) valuelen; + } + } + + /* Now the name and body buffer */ + if (!buf) { + buffer_size += namelen; + buffer_size += valuelen; + } else { + memcpy(cur_buf, *envp, namelen); + cur_buf += namelen; + memcpy(cur_buf, spliter + 1, valuelen); + cur_buf += valuelen; + } + } + return buffer_size; +} + +static void +init_begin_request_body(int role, + FCGI_BeginRequestBody * begin_request_body) +{ + begin_request_body->roleB1 = (unsigned char) (((role >> 8) & 0xff)); + begin_request_body->roleB0 = (unsigned char) (role & 0xff); + begin_request_body->flags = 0; + memset(begin_request_body->reserved, 0, + sizeof(begin_request_body->reserved)); +} + +int +init_header(int type, int requestId, apr_size_t contentLength, + apr_size_t paddingLength, FCGI_Header * header) +{ + if (contentLength > 65535 || paddingLength > 255) + return 0; + header->version = FCGI_VERSION_1; + header->type = (unsigned char) type; + header->requestIdB1 = (unsigned char) ((requestId >> 8) & 0xff); + header->requestIdB0 = (unsigned char) (requestId & 0xff); + header->contentLengthB1 = + (unsigned char) ((contentLength >> 8) & 0xff); + header->contentLengthB0 = (unsigned char) ((contentLength) & 0xff); + header->paddingLength = (unsigned char) paddingLength; + header->reserved = 0; + return 1; +} + +int +build_begin_block(int role, request_rec * r, + apr_bucket_alloc_t * alloc, + apr_bucket_brigade * request_brigade) +{ + /* Alloc memory for begin request header & body */ + FCGI_Header *begin_request_header = + apr_bucket_alloc(sizeof(FCGI_Header), alloc); + FCGI_BeginRequestBody *begin_request_body = + apr_bucket_alloc(sizeof(FCGI_BeginRequestBody), alloc); + apr_bucket *bucket_header = + apr_bucket_heap_create((const char *) begin_request_header, + sizeof(*begin_request_header), + apr_bucket_free, + alloc); + apr_bucket *bucket_body = + apr_bucket_heap_create((const char *) begin_request_body, + sizeof(*begin_request_body), + apr_bucket_free, + alloc); + + /* Initialize begin request header and body */ + if (!init_header(FCGI_BEGIN_REQUEST, 1, sizeof(FCGI_BeginRequestBody), + 0, begin_request_header)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: can't init begin request header"); + return 0; + } + init_begin_request_body(role, begin_request_body); + + /* Append the header and body to request brigade */ + APR_BRIGADE_INSERT_TAIL(request_brigade, bucket_header); + APR_BRIGADE_INSERT_TAIL(request_brigade, bucket_body); + + return 1; +} + +int +build_env_block(request_rec * r, char **envp, + apr_bucket_alloc_t * alloc, + apr_bucket_brigade * request_brigade) +{ + /* Get the size of the destination buffer */ + apr_size_t bufsize = init_environment(NULL, envp); + + /* Alloc memory for environment header and body */ + FCGI_Header *env_request_header = + apr_bucket_alloc(sizeof(FCGI_Header), alloc); + FCGI_Header *env_empty_header = + apr_bucket_alloc(sizeof(FCGI_Header), alloc); + char *buf = apr_bucket_alloc(bufsize, alloc); + apr_bucket *bucket_header = apr_bucket_heap_create((const char *) + env_request_header, + sizeof + (*env_request_header), + apr_bucket_free, + alloc); + apr_bucket *bucket_body = apr_bucket_heap_create(buf, bufsize, + apr_bucket_free, + alloc); + apr_bucket *bucket_empty_header = apr_bucket_heap_create((const char *) + env_empty_header, + sizeof + (*env_empty_header), + apr_bucket_free, + alloc); + + /* Initialize header and body */ + if (!init_header(FCGI_PARAMS, 1, bufsize, 0, env_request_header) + || !init_header(FCGI_PARAMS, 1, 0, 0, env_empty_header)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "mod_fcgid: can't init env request header"); + return 0; + } + init_environment(buf, envp); + + /* Append the header and body to request brigade */ + APR_BRIGADE_INSERT_TAIL(request_brigade, bucket_header); + APR_BRIGADE_INSERT_TAIL(request_brigade, bucket_body); + APR_BRIGADE_INSERT_TAIL(request_brigade, bucket_empty_header); + + return 1; +} diff --git a/modules/fcgid/fcgid_protocol.h b/modules/fcgid/fcgid_protocol.h new file mode 100644 index 0000000..6184a72 --- /dev/null +++ b/modules/fcgid/fcgid_protocol.h @@ -0,0 +1,113 @@ +/* + * 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 FCGID_PROTOCOL_H +#define FCGID_PROTOCOL_H + +/* + * Listening socket file number + */ +#define FCGI_LISTENSOCK_FILENO 0 + +typedef struct { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} FCGI_Header; + +/* + * Number of bytes in a FCGI_Header. Future versions of the protocol + * will not reduce this number. + */ +#define FCGI_HEADER_LEN 8 + +/* + * Value for version component of FCGI_Header + */ +#define FCGI_VERSION_1 1 + +/* + * Values for type component of FCGI_Header + */ +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 +#define FCGI_UNKNOWN_TYPE 11 +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +/* + * Value for requestId component of FCGI_Header + */ +#define FCGI_NULL_REQUEST_ID 0 + +typedef struct { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} FCGI_BeginRequestBody; + +/* + * Mask for flags component of FCGI_BeginRequestBody + */ +#define FCGI_KEEP_CONN 1 + +/* + * Values for role component of FCGI_BeginRequestBody + */ +#define FCGI_RESPONDER 1 +#define FCGI_AUTHORIZER 2 +#define FCGI_FILTER 3 + +/* + * Values for protocolStatus component of FCGI_EndRequestBody + */ +#define FCGI_REQUEST_COMPLETE 0 +#define FCGI_CANT_MPX_CONN 1 +#define FCGI_OVERLOADED 2 +#define FCGI_UNKNOWN_ROLE 3 + +/* + * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records + */ +#define FCGI_MAX_CONNS "FCGI_MAX_CONNS" +#define FCGI_MAX_REQS "FCGI_MAX_REQS" +#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" + +int init_header(int type, int requestId, apr_size_t contentLength, + apr_size_t paddingLength, FCGI_Header * header); + +int build_begin_block(int role, request_rec * r, + apr_bucket_alloc_t * alloc, + apr_bucket_brigade * request_brigade); + +int build_env_block(request_rec * r, char **envp, + apr_bucket_alloc_t * alloc, + apr_bucket_brigade * request_brigade); +#endif diff --git a/modules/fcgid/fcgid_spawn_ctl.c b/modules/fcgid/fcgid_spawn_ctl.c new file mode 100644 index 0000000..2d0b39c --- /dev/null +++ b/modules/fcgid/fcgid_spawn_ctl.c @@ -0,0 +1,253 @@ +/* + * 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 "fcgid_spawn_ctl.h" +#include "fcgid_conf.h" + +#include "apr_strings.h" + +#define REGISTER_LIFE 1 +#define REGISTER_DEATH 2 + +struct fcgid_stat_node { + apr_ino_t inode; + dev_t deviceid; + uid_t uid; + gid_t gid; + const char *cmdline; + int vhost_id; + int score; + int process_counter; + int max_class_process_count; + int min_class_process_count; + apr_time_t last_stat_time; + struct fcgid_stat_node *next; +}; + +static apr_pool_t *g_stat_pool = NULL; +static struct fcgid_stat_node *g_stat_list_header = NULL; +static int g_total_process; + +static void +register_life_death(server_rec * main_server, + fcgid_procnode * procnode, int life_or_death) +{ + struct fcgid_stat_node *previous_node, *current_node; + fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, + &fcgid_module); + apr_time_t now = apr_time_now(); + + if (!g_stat_pool || !procnode) + abort(); + + /* Can I find the node base on inode, device id and cmdline? */ + previous_node = g_stat_list_header; + for (current_node = previous_node; + current_node != NULL; current_node = current_node->next) { + if (current_node->inode == procnode->inode + && current_node->deviceid == procnode->deviceid + && !strcmp(current_node->cmdline, procnode->cmdline) + && current_node->vhost_id == procnode->vhost_id + && current_node->uid == procnode->uid + && current_node->gid == procnode->gid) + break; + previous_node = current_node; + } + + if (!current_node) { /* I can't find it, create one */ + if (life_or_death == REGISTER_DEATH) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, main_server, + "stat node not found for exiting process %s", + procnode->cmdline); + return; + } + current_node = apr_pcalloc(g_stat_pool, sizeof(*current_node)); + current_node->deviceid = procnode->deviceid; + current_node->inode = procnode->inode; + current_node->cmdline = apr_pstrdup(g_stat_pool, procnode->cmdline); + current_node->vhost_id = procnode->vhost_id; + current_node->uid = procnode->uid; + current_node->gid = procnode->gid; + current_node->last_stat_time = now; + current_node->score = 0; + current_node->process_counter = 0; + current_node->max_class_process_count = + procnode->cmdopts.max_class_process_count; + current_node->min_class_process_count = + procnode->cmdopts.min_class_process_count; + current_node->next = NULL; + + /* append it to stat list for next search */ + if (!previous_node) + g_stat_list_header = current_node; + else + previous_node->next = current_node; + } + + /* Increase the score first */ + if (life_or_death == REGISTER_LIFE) { + current_node->score += sconf->spawn_score; + current_node->process_counter++; + } else { + current_node->score += sconf->termination_score; + current_node->process_counter--; + } + + /* Decrease the score based on elapsed time */ + current_node->score -= + sconf->time_score * + (int)(apr_time_sec(now) - apr_time_sec(current_node->last_stat_time)); + + /* Make score reasonable */ + if (current_node->score < 0) + current_node->score = 0; + + current_node->last_stat_time = now; +} + +void spawn_control_init(server_rec * main_server, apr_pool_t * configpool) +{ + apr_status_t rv; + + if ((rv = apr_pool_create(&g_stat_pool, configpool)) != APR_SUCCESS) { + /* Fatal error */ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: can't create stat pool"); + exit(1); + } +} + +void +register_termination(server_rec * main_server, fcgid_procnode * procnode) +{ + register_life_death(main_server, procnode, REGISTER_DEATH); + g_total_process--; +} + +void register_spawn(server_rec * main_server, fcgid_procnode * procnode) +{ + register_life_death(main_server, procnode, REGISTER_LIFE); + g_total_process++; +} + +/* + * Spawn control strategy: + * 1. Add FcgidSpawnScore to score if application is terminated + * 2. Add FcgidTerminationScore to score if application is spawned + * 3. Subtract FcgidTimeScore from score each second while score is positive + * 4. Ignore spawn request if score is higher than FcgidSpawnScoreUpLimit + * 5. Ignore spawn request if total process count higher than + * FcgidSpawnScoreUpLimit + * 6. Ignore spawn request if process count of this class is higher than + * FcgidMaxProcessesPerClass + */ +int is_spawn_allowed(server_rec * main_server, fcgid_command * command) +{ + struct fcgid_stat_node *current_node; + fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, + &fcgid_module); + + if (!command || !g_stat_pool) + return 1; + + /* Total process count higher than up limit? */ + if (g_total_process >= sconf->max_process_count) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server, + "mod_fcgid: %s total process count %d >= %d, skip the spawn request", + command->cgipath, g_total_process, sconf->max_process_count); + return 0; + } + + /* Can I find the node base on inode, device id and cmdline? */ + for (current_node = g_stat_list_header; + current_node != NULL; current_node = current_node->next) { + if (current_node->inode == command->inode + && current_node->deviceid == command->deviceid + && !strcmp(current_node->cmdline, command->cmdline) + && current_node->vhost_id == command->vhost_id + && current_node->uid == command->uid + && current_node->gid == command->gid) + break; + } + + if (!current_node) { + /* There are no existing processes for this class, so obviously + * no class-specific limits have been exceeded. + */ + return 1; + } + else { + apr_time_t now = apr_time_now(); + + current_node->score -= + sconf->time_score * + (int)(apr_time_sec(now) - apr_time_sec(current_node->last_stat_time)); + current_node->last_stat_time = now; + if (current_node->score < 0) + current_node->score = 0; + + /* Score is higher than up limit? */ + if (current_node->score >= sconf->spawnscore_uplimit) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server, + "mod_fcgid: %s spawn score %d >= %d, skip the spawn request", + command->cgipath, current_node->score, + sconf->spawnscore_uplimit); + return 0; + } + + /* + * Process count of this class higher than up limit? + */ + if (current_node->process_counter >= current_node->max_class_process_count) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server, + "mod_fcgid: too many %s processes (current:%d, max:%d), skip the spawn request", + command->cgipath, current_node->process_counter, + current_node->max_class_process_count); + return 0; + } + + return 1; + } +} + +int is_kill_allowed(server_rec * main_server, fcgid_procnode * procnode) +{ + struct fcgid_stat_node *current_node; + + if (!g_stat_pool || !procnode) + return 0; + + /* Can I find the node base on inode, device id and cmdline? */ + for (current_node = g_stat_list_header; + current_node != NULL; current_node = current_node->next) { + if (current_node->inode == procnode->inode + && current_node->deviceid == procnode->deviceid + && !strcmp(current_node->cmdline, procnode->cmdline) + && current_node->vhost_id == procnode->vhost_id + && current_node->uid == procnode->uid + && current_node->gid == procnode->gid) + break; + } + + if (current_node) { + /* Found the node */ + if (current_node->process_counter <= current_node->min_class_process_count) + return 0; + } + + return 1; +} diff --git a/modules/fcgid/fcgid_spawn_ctl.h b/modules/fcgid/fcgid_spawn_ctl.h new file mode 100644 index 0000000..281c65d --- /dev/null +++ b/modules/fcgid/fcgid_spawn_ctl.h @@ -0,0 +1,30 @@ +/* + * 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 FCGID_SPAWN_CONTROL_H +#define FCGID_SPAWN_CONTROL_H +#include "fcgid_proctbl.h" +#include "fcgid_pm.h" + +void spawn_control_init(server_rec * main_server, apr_pool_t * configpool); +void register_termination(server_rec * main_server, + fcgid_procnode * procnode); +void register_spawn(server_rec * main_server, fcgid_procnode * procnode); +int is_spawn_allowed(server_rec * main_server, fcgid_command * command); +int is_kill_allowed(server_rec * main_server, fcgid_procnode * procnode); + +#endif diff --git a/modules/fcgid/mod_fcgid.c b/modules/fcgid/mod_fcgid.c new file mode 100644 index 0000000..c429b1b --- /dev/null +++ b/modules/fcgid/mod_fcgid.c @@ -0,0 +1,972 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_request.h" +#include "http_protocol.h" +#include "ap_mmn.h" +#include "apr_lib.h" +#include "apr_buckets.h" +#include "apr_strings.h" +#include "apr_thread_proc.h" +#include "mod_cgi.h" +#include "mod_status.h" +#include "util_script.h" +#include "fcgid_global.h" +#include "fcgid_pm.h" +#include "fcgid_proctbl.h" +#include "fcgid_conf.h" +#include "fcgid_spawn_ctl.h" +#include "fcgid_bridge.h" +#include "fcgid_filter.h" +#include "fcgid_protocol.h" +#include "fcgid_proc.h" + +static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) * cgi_build_command; +static ap_filter_rec_t *fcgid_filter_handle; +static int g_php_fix_pathinfo_enable = 0; + +enum fcgid_procnode_type { + FCGID_PROCNODE_TYPE_IDLE, + FCGID_PROCNODE_TYPE_BUSY, + FCGID_PROCNODE_TYPE_ERROR, +}; + +enum fcgid_auth_check_mode { + FCGID_AUTH_CHECK_AUTHN, + FCGID_AUTH_CHECK_AUTHZ, + FCGID_AUTH_CHECK_ACCESS +}; + +/* Stolen from mod_cgi.c */ +/* KLUDGE --- for back-compatibility, we don't have to check ExecCGI + * in ScriptAliased directories, which means we need to know if this + * request came through ScriptAlias or not... so the Alias module + * leaves a note for us. + */ + +static int is_scriptaliased(request_rec * r) +{ + const char *t = apr_table_get(r->notes, "alias-forced-type"); + + return t && (!strcasecmp(t, "cgi-script")); +} + +static apr_status_t +default_build_command(const char **cmd, const char ***argv, + request_rec * r, apr_pool_t * p, + cgi_exec_info_t * e_info) +{ + int numwords, x, idx; + char *w; + const char *args = NULL; + + if (e_info->process_cgi) { + *cmd = r->filename; + /* Do not process r->args if they contain an '=' assignment + */ + if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) { + args = r->args; + } + } + + if (!args) { + numwords = 1; + } else { + /* count the number of keywords */ + for (x = 0, numwords = 2; args[x]; x++) { + if (args[x] == '+') { + ++numwords; + } + } + } + /* Everything is - 1 to account for the first parameter + * which is the program name. + */ + if (numwords > APACHE_ARG_MAX - 1) { + numwords = APACHE_ARG_MAX - 1; /* Truncate args to prevent overrun */ + } + *argv = apr_palloc(p, (numwords + 2) * sizeof(char *)); + (*argv)[0] = *cmd; + for (x = 1, idx = 1; x < numwords; x++) { + w = ap_getword_nulls(p, &args, '+'); + ap_unescape_url(w); + (*argv)[idx++] = ap_escape_shell_cmd(p, w); + } + (*argv)[idx] = NULL; + + return APR_SUCCESS; +} + +/* http2env stolen from util_script.c */ +static char *http2env(apr_pool_t *a, const char *w) +{ + char *res = (char *)apr_palloc(a, sizeof("HTTP_") + strlen(w)); + char *cp = res; + char c; + + *cp++ = 'H'; + *cp++ = 'T'; + *cp++ = 'T'; + *cp++ = 'P'; + *cp++ = '_'; + + while ((c = *w++) != 0) { + if (!apr_isalnum(c)) { + *cp++ = '_'; + } + else { + *cp++ = apr_toupper(c); + } + } + *cp = 0; + + return res; +} + +static void fcgid_add_cgi_vars(request_rec * r) +{ + apr_array_header_t *passheaders = get_pass_headers(r); + + if (passheaders != NULL) { + const char **hdr = (const char **) passheaders->elts; + int hdrcnt = passheaders->nelts; + int i; + + for (i = 0; i < hdrcnt; i++, ++hdr) { + const char *val = apr_table_get(r->headers_in, *hdr); + + if (val) { + /* no munging of header name to create envvar name; + * consistent with legacy mod_fcgid behavior and mod_fastcgi + * prior to 2.4.7 + */ + apr_table_setn(r->subprocess_env, *hdr, val); + /* standard munging of header name (upcase, HTTP_, etc.) */ + apr_table_setn(r->subprocess_env, http2env(r->pool, *hdr), val); + } + } + } + + /* Work around cgi.fix_pathinfo = 1 in php.ini */ + if (g_php_fix_pathinfo_enable) { + char *merge_path; + apr_table_t *e = r->subprocess_env; + + /* "DOCUMENT_ROOT"/"SCRIPT_NAME" -> "SCRIPT_NAME" */ + const char *doc_root = apr_table_get(e, "DOCUMENT_ROOT"); + const char *script_name = apr_table_get(e, "SCRIPT_NAME"); + + if (doc_root && script_name + && apr_filepath_merge(&merge_path, doc_root, script_name, 0, + r->pool) == APR_SUCCESS) { + apr_table_setn(e, "SCRIPT_NAME", merge_path); + } + } +} + +static int fcgid_handler(request_rec * r) +{ + cgi_exec_info_t e_info; + const char *command; + const char **argv; + apr_status_t rv; + int http_retcode; + fcgid_cmd_conf *wrapper_conf; + + if (strcmp(r->handler, "fcgid-script")) + return DECLINED; + + if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r)) + return HTTP_FORBIDDEN; + + if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) && + r->path_info && *r->path_info) + return HTTP_NOT_FOUND; + + e_info.process_cgi = 1; + e_info.cmd_type = APR_PROGRAM; + e_info.detached = 0; + e_info.in_pipe = APR_CHILD_BLOCK; + e_info.out_pipe = APR_CHILD_BLOCK; + e_info.err_pipe = APR_CHILD_BLOCK; + e_info.prog_type = RUN_AS_CGI; + e_info.bb = NULL; + e_info.ctx = NULL; + e_info.next = NULL; + + wrapper_conf = get_wrapper_info(r->filename, r); + + /* Check for existence of requested file, unless we use a virtual wrapper. */ + if (wrapper_conf == NULL || !wrapper_conf->virtual) { + if (r->finfo.filetype == 0) + return HTTP_NOT_FOUND; + + if (r->finfo.filetype == APR_DIR) + return HTTP_FORBIDDEN; + } + + /* Build the command line */ + if (wrapper_conf) { + if ((rv = + default_build_command(&command, &argv, r, r->pool, + &e_info)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + "mod_fcgid: don't know how to spawn wrapper child process: %s", + r->filename); + return HTTP_INTERNAL_SERVER_ERROR; + } + } else { + if ((rv = cgi_build_command(&command, &argv, r, r->pool, + &e_info)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + "mod_fcgid: don't know how to spawn child process: %s", + r->filename); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Check request like "http://localhost/cgi-bin/a.exe/defghi" */ + if (r->finfo.inode == 0 && r->finfo.device == 0) { + if ((rv = + apr_stat(&r->finfo, command, APR_FINFO_IDENT, + r->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, + "mod_fcgid: can't get %s file info", command); + return HTTP_NOT_FOUND; + } + } + + /* Dummy up a wrapper configuration, using the requested file as + * both the executable path and command-line. + */ + wrapper_conf = apr_pcalloc(r->pool, sizeof(*wrapper_conf)); + + if (strlen(command) >= fcgid_min(FCGID_PATH_MAX, FCGID_CMDLINE_MAX)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "mod_fcgid: Executable path length exceeds compiled-in limit: %s", + command); + return HTTP_INTERNAL_SERVER_ERROR; + } + + wrapper_conf->cgipath = apr_pstrdup(r->pool, command); + wrapper_conf->cmdline = wrapper_conf->cgipath; + wrapper_conf->inode = r->finfo.inode; + wrapper_conf->deviceid = r->finfo.device; + } + + ap_add_common_vars(r); + ap_add_cgi_vars(r); + fcgid_add_cgi_vars(r); + + /* Remove hop-by-hop headers handled by http + */ + apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE"); + apr_table_unset(r->subprocess_env, "HTTP_TE"); + apr_table_unset(r->subprocess_env, "HTTP_TRAILER"); + apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING"); + apr_table_unset(r->subprocess_env, "HTTP_UPGRADE"); + + /* Connection hop-by-hop header to prevent the CGI from hanging */ + apr_table_set(r->subprocess_env, "HTTP_CONNECTION", "close"); + + /* Insert output filter */ + ap_add_output_filter_handle(fcgid_filter_handle, NULL, r, + r->connection); + + http_retcode = bridge_request(r, FCGI_RESPONDER, wrapper_conf); + return (http_retcode == HTTP_OK ? OK : http_retcode); +} + +static int fcgidsort(fcgid_procnode **e1, fcgid_procnode **e2) +{ + int cmp = strcmp((*e1)->executable_path, (*e2)->executable_path); + + if (cmp != 0) + return cmp; + if ((*e1)->gid != (*e2)->gid) + return (*e1)->gid > (*e2)->gid ? 1 : -1; + if ((*e1)->uid != (*e2)->uid) + return (*e1)->uid > (*e2)->uid ? 1 : -1; + cmp = strcmp((*e1)->cmdline, (*e2)->cmdline); + if (cmp != 0) + return cmp; + if ((*e1)->vhost_id != (*e2)->vhost_id) + return (*e1)->vhost_id > (*e2)->vhost_id ? 1 : -1; + if ((*e1)->diewhy != (*e2)->diewhy) + return (*e1)->diewhy > (*e2)->diewhy ? 1 : -1; + if ((*e1)->node_type != (*e2)->node_type) + return (*e1)->node_type > (*e2)->node_type ? 1 : -1; + return 0; +} + +static char *get_state_desc(fcgid_procnode *node) +{ + if (node->node_type == FCGID_PROCNODE_TYPE_IDLE) + return "Ready"; + else if (node->node_type == FCGID_PROCNODE_TYPE_BUSY) + return "Working"; + else { + switch (node->diewhy) { + case FCGID_DIE_KILLSELF: + return "Exiting(normal exit)"; + case FCGID_DIE_IDLE_TIMEOUT: + return "Exiting(idle timeout)"; + case FCGID_DIE_LIFETIME_EXPIRED: + return "Exiting(lifetime expired)"; + case FCGID_DIE_BUSY_TIMEOUT: + return "Exiting(busy timeout)"; + case FCGID_DIE_CONNECT_ERROR: + return "Exiting(connect error)"; + case FCGID_DIE_COMM_ERROR: + return "Exiting(communication error)"; + case FCGID_DIE_SHUTDOWN: + return "Exiting(shutting down)"; + default: + return "Exiting"; + } + } +} + +/* fcgid Extension to mod_status */ +static int fcgid_status_hook(request_rec *r, int flags) +{ + fcgid_procnode **ar = NULL, *current_node; + int num_ent, index; + apr_ino_t last_inode = 0; + apr_dev_t last_deviceid = 0; + gid_t last_gid = 0; + uid_t last_uid = 0; + const char *last_cmdline = ""; + apr_time_t now; + int last_vhost_id = -1; + const char *basename, *tmpbasename; + fcgid_procnode *proc_table = proctable_get_table_array(); + fcgid_procnode *error_list_header = proctable_get_error_list(); + fcgid_procnode *idle_list_header = proctable_get_idle_list(); + fcgid_procnode *busy_list_header = proctable_get_busy_list(); + + if ((flags & AP_STATUS_SHORT) || (proc_table == NULL)) + return OK; + + proctable_lock(r); + + /* Get element count */ + num_ent = 0; + current_node = &proc_table[busy_list_header->next_index]; + while (current_node != proc_table) { + num_ent++; + current_node = &proc_table[current_node->next_index]; + } + current_node = &proc_table[idle_list_header->next_index]; + while (current_node != proc_table) { + num_ent++; + current_node = &proc_table[current_node->next_index]; + } + current_node = &proc_table[error_list_header->next_index]; + while (current_node != proc_table) { + num_ent++; + current_node = &proc_table[current_node->next_index]; + } + + /* Create an array for qsort() */ + if (num_ent != 0) { + ar = (fcgid_procnode **)apr_palloc(r->pool, num_ent * sizeof(fcgid_procnode*)); + index = 0; + current_node = &proc_table[busy_list_header->next_index]; + while (current_node != proc_table) { + ar[index] = apr_palloc(r->pool, sizeof(fcgid_procnode)); + *ar[index] = *current_node; + ar[index++]->node_type = FCGID_PROCNODE_TYPE_BUSY; + current_node = &proc_table[current_node->next_index]; + } + current_node = &proc_table[idle_list_header->next_index]; + while (current_node != proc_table) { + ar[index] = apr_palloc(r->pool, sizeof(fcgid_procnode)); + *ar[index] = *current_node; + ar[index++]->node_type = FCGID_PROCNODE_TYPE_IDLE; + current_node = &proc_table[current_node->next_index]; + } + current_node = &proc_table[error_list_header->next_index]; + while (current_node != proc_table) { + ar[index] = apr_palloc(r->pool, sizeof(fcgid_procnode)); + *ar[index] = *current_node; + ar[index++]->node_type = FCGID_PROCNODE_TYPE_ERROR; + current_node = &proc_table[current_node->next_index]; + } + } + proctable_unlock(r); + + now = apr_time_now(); + + /* Sort the array */ + if (num_ent != 0) + qsort((void *)ar, num_ent, sizeof(fcgid_procnode *), + (int (*)(const void *, const void *))fcgidsort); + + /* Output */ + ap_rputs("
\n

mod_fcgid status:

\n", r); + ap_rprintf(r, "Total FastCGI processes: %d\n", num_ent); + for (index = 0; index < num_ent; index++) { + current_node = ar[index]; + if (current_node->inode != last_inode || current_node->deviceid != last_deviceid + || current_node->gid != last_gid || current_node->uid != last_uid + || strcmp(current_node->cmdline, last_cmdline) + || current_node->vhost_id != last_vhost_id) { + if (index != 0) + ap_rputs("\n\n", r); + + /* Print executable path basename */ + tmpbasename = ap_strrchr_c(current_node->executable_path, '/'); + if (tmpbasename != NULL) + tmpbasename++; + basename = ap_strrchr_c(tmpbasename, '\\'); + if (basename != NULL) + basename++; + else + basename = tmpbasename; + ap_rprintf(r, "
\nProcess: %s  (%s)
\n", + basename, current_node->cmdline); + + /* Create a new table for this process info */ + ap_rputs("\n\n" + "" + "" + "\n", r); + + last_inode = current_node->inode; + last_deviceid = current_node->deviceid; + last_gid = current_node->gid; + last_uid = current_node->uid; + last_cmdline = current_node->cmdline; + last_vhost_id = current_node->vhost_id; + } + + ap_rprintf(r, "", + current_node->proc_id.pid, + apr_time_sec(now - current_node->start_time), + apr_time_sec(now - current_node->last_active_time), + current_node->requests_handled, + get_state_desc(current_node)); + } + if (num_ent != 0) { + ap_rputs("
PidActiveIdleAccessesState
%" APR_PID_T_FMT "%" APR_TIME_T_FMT "%" APR_TIME_T_FMT "%d%s
\n\n", r); + ap_rputs("
\n" + "Active and Idle are time active and time since\n" + "last request, in seconds.\n", r); + } + + return OK; +} + +static int mod_fcgid_modify_auth_header(void *subprocess_env, + const char *key, const char *val) +{ + /* When the application gives a 200 response, the server ignores response + headers whose names aren't prefixed with Variable- prefix, and ignores + any response content */ + if (strncasecmp(key, "Variable-", 9) == 0) + apr_table_setn(subprocess_env, key + 9, val); + return 1; +} + +static int mod_fcgid_check_auth(request_rec *r, + enum fcgid_auth_check_mode auth_check_mode) +{ + int res = 0; + const char *password = NULL; + apr_table_t *saved_subprocess_env = NULL; + fcgid_cmd_conf *auth_cmd_info = NULL; + int authoritative; + const char *auth_role = NULL; + const char *role_log_msg = NULL; + const char *user_log_msg = ""; + + /* Because we don't function as authn/z providers, integration with + * the standard httpd authn/z modules is somewhat problematic. + * + * With httpd 2.4 in particular, our hook functions may be + * circumvented by mod_authz_core's check_access_ex hook, unless + * Require directives specify that user-based authn/z is needed. + * + * Even then, APR_HOOK_MIDDLE may cause our authentication hook to be + * ordered after mod_auth_basic's check_authn hook, in which case it + * will be skipped unless AuthBasicAuthoritative is Off and no authn + * provider recognizes the user or outright denies the request. + * + * Also, when acting as an authenticator, we don't have a mechanism to + * set r->user based on the script response, so scripts can't implement + * a private authentication scheme; instead we use ap_get_basic_auth_pw() + * and only support Basic HTTP authentication. + * + * It is possible to act reliably as both authenticator and authorizer + * if mod_authn_core is loaded to support AuthType and AuthName, but + * mod_authz_core and mod_auth_basic are not loaded. However, in this + * case the Require directive is not available, which defeats many + * common configuration tropes. + */ + + switch (auth_check_mode) { + case FCGID_AUTH_CHECK_AUTHN: + auth_cmd_info = get_authenticator_info(r, &authoritative); + auth_role = "AUTHENTICATOR"; + role_log_msg = "Authentication"; + break; + + case FCGID_AUTH_CHECK_AUTHZ: + auth_cmd_info = get_authorizer_info(r, &authoritative); + auth_role = "AUTHORIZER"; + role_log_msg = "Authorization"; + break; + + case FCGID_AUTH_CHECK_ACCESS: + auth_cmd_info = get_access_info(r, &authoritative); + auth_role = "ACCESS_CHECKER"; + role_log_msg = "Access check"; + break; + } + + /* Is this auth check command enabled? */ + if (auth_cmd_info == NULL) + return DECLINED; + + /* Get the user password */ + if (auth_check_mode == FCGID_AUTH_CHECK_AUTHN + && (res = ap_get_basic_auth_pw(r, &password)) != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "mod_fcgid: authenticator requires " + "basic HTTP auth credentials"); + return res; + } + + if (auth_check_mode != FCGID_AUTH_CHECK_ACCESS) { + user_log_msg = apr_psprintf(r->pool, " of user %s", r->user); + } + + /* Save old process environment */ + saved_subprocess_env = apr_table_copy(r->pool, r->subprocess_env); + + /* Add some environment variables */ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + fcgid_add_cgi_vars(r); + if (auth_check_mode == FCGID_AUTH_CHECK_AUTHN) { + apr_table_setn(r->subprocess_env, "REMOTE_PASSWD", password); + } + apr_table_setn(r->subprocess_env, "FCGI_APACHE_ROLE", auth_role); + + /* Drop the variables CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED, + * SCRIPT_NAME and most Hop-By-Hop headers - EXCEPT we will pass + * PROXY_AUTH to allow CGI to perform proxy auth for httpd + */ + apr_table_unset(r->subprocess_env, "CONTENT_LENGTH"); + apr_table_unset(r->subprocess_env, "PATH_INFO"); + apr_table_unset(r->subprocess_env, "PATH_TRANSLATED"); + apr_table_unset(r->subprocess_env, "SCRIPT_NAME"); + apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE"); + apr_table_unset(r->subprocess_env, "HTTP_TE"); + apr_table_unset(r->subprocess_env, "HTTP_TRAILER"); + apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING"); + apr_table_unset(r->subprocess_env, "HTTP_UPGRADE"); + + /* Connection hop-by-hop header to prevent the CGI from hanging */ + apr_table_set(r->subprocess_env, "HTTP_CONNECTION", "close"); + + /* Handle the request */ + res = bridge_request(r, FCGI_AUTHORIZER, auth_cmd_info); + + /* Restore r->subprocess_env */ + r->subprocess_env = saved_subprocess_env; + + if (res == OK && r->status == HTTP_OK + && apr_table_get(r->headers_out, "Location") == NULL) { + /* Pass */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_fcgid: %s%s to access %s succeeded", + role_log_msg, user_log_msg, r->uri); + + /* Modify headers: An Authorizer application's 200 response may include headers + whose names are prefixed with Variable-. */ + apr_table_do(mod_fcgid_modify_auth_header, r->subprocess_env, + r->err_headers_out, NULL); + + return OK; + } + else { + const char *add_err_msg = ""; + + /* Print error info first */ + if (res != OK) { + add_err_msg = + apr_psprintf(r->pool, "; error or unexpected condition " + "while parsing response (%d)", res); + } + else if (r->status == HTTP_OK) { + add_err_msg = "; internal redirection not allowed"; + } + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "mod_fcgid: %s%s to access %s failed, reason: " + "script returned status %d%s", + role_log_msg, user_log_msg, r->uri, r->status, + add_err_msg); + + /* Handle error */ + if (!authoritative) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_fcgid: not authoritative"); + return DECLINED; + } + else { + if (auth_check_mode != FCGID_AUTH_CHECK_ACCESS) { + ap_note_basic_auth_failure(r); + } + return (res == OK) ? HTTP_UNAUTHORIZED : res; + } + } +} + +static int mod_fcgid_authenticator(request_rec *r) +{ + return mod_fcgid_check_auth(r, FCGID_AUTH_CHECK_AUTHN); +} + +static int mod_fcgid_authorizer(request_rec *r) +{ + return mod_fcgid_check_auth(r, FCGID_AUTH_CHECK_AUTHZ); +} + +static int mod_fcgid_check_access(request_rec *r) +{ + return mod_fcgid_check_auth(r, FCGID_AUTH_CHECK_ACCESS); +} + +static void initialize_child(apr_pool_t * pchild, server_rec * main_server) +{ + apr_status_t rv; + + if ((rv = proctable_child_init(main_server, pchild)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Can't initialize shared memory or mutex in child"); + return; + } + + if ((rv = procmgr_child_init(main_server, pchild)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Can't initialize process manager"); + return; + } + + return; +} + +static int +fcgid_init(apr_pool_t * config_pool, apr_pool_t * plog, apr_pool_t * ptemp, + server_rec * main_server) +{ + const char *userdata_key = "fcgid_init"; + apr_status_t rv; + void *dummy = NULL; + fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, + &fcgid_module); + + ap_add_version_component(config_pool, MODFCGID_PRODUCT); + + g_php_fix_pathinfo_enable = sconf->php_fix_pathinfo_enable; + + /* Initialize process manager only once */ + apr_pool_userdata_get(&dummy, userdata_key, + main_server->process->pool); + if (!dummy) { + apr_pool_userdata_set((const void *)1, userdata_key, + apr_pool_cleanup_null, + main_server->process->pool); + return OK; + } + + /* Initialize share memory and share lock */ + if ((rv = + proctable_post_config(main_server, config_pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Can't initialize shared memory or mutex"); + return rv; + } + + /* Initialize process manager */ + if ((rv = + procmgr_post_config(main_server, config_pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, + "mod_fcgid: Can't initialize process manager"); + return rv; + } + + /* This is the means by which unusual (non-unix) os's may find alternate + * means to run a given command (e.g. shebang/registry parsing on Win32) + */ + cgi_build_command = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command); + if (!cgi_build_command) { + cgi_build_command = default_build_command; + } + + return APR_SUCCESS; +} + +static const command_rec fcgid_cmds[] = { + AP_INIT_TAKE1("FcgidAccessChecker", set_access_info, NULL, + ACCESS_CONF | OR_FILEINFO, + "a absolute access checker file path"), + AP_INIT_FLAG("FcgidAccessCheckerAuthoritative", + set_access_authoritative, NULL, ACCESS_CONF | OR_FILEINFO, + "Set to 'off' to allow access control to be passed along to lower modules upon failure"), + AP_INIT_TAKE1("FcgidAuthenticator", set_authenticator_info, NULL, + ACCESS_CONF | OR_FILEINFO, + "a absolute authenticator file path"), + AP_INIT_FLAG("FcgidAuthenticatorAuthoritative", + set_authenticator_authoritative, NULL, + ACCESS_CONF | OR_FILEINFO, + "Set to 'off' to allow authentication to be passed along to lower modules upon failure"), + AP_INIT_TAKE1("FcgidAuthorizer", set_authorizer_info, NULL, + ACCESS_CONF | OR_FILEINFO, + "a absolute authorizer file path"), + AP_INIT_FLAG("FcgidAuthorizerAuthoritative", + set_authorizer_authoritative, NULL, + ACCESS_CONF | OR_FILEINFO, + "Set to 'off' to allow authorization to be passed along to lower modules upon failure"), + AP_INIT_TAKE1("FcgidBusyScanInterval", set_busy_scan_interval, NULL, + RSRC_CONF, + "scan interval for busy timeout process"), + AP_INIT_TAKE1("FcgidBusyTimeout", set_busy_timeout, NULL, RSRC_CONF, + "a fastcgi application will be killed after handling a request for BusyTimeout"), + AP_INIT_RAW_ARGS("FcgidCmdOptions", set_cmd_options, NULL, RSRC_CONF, + "set processing options for a FastCGI command"), + AP_INIT_TAKE12("FcgidInitialEnv", add_default_env_vars, NULL, RSRC_CONF, + "an environment variable name and optional value to pass to FastCGI."), + AP_INIT_TAKE1("FcgidMaxProcessesPerClass", + set_max_class_process, + NULL, RSRC_CONF, + "Max process count of one class of fastcgi application"), + AP_INIT_TAKE1("FcgidMinProcessesPerClass", + set_min_class_process, + NULL, RSRC_CONF, + "Min process count of one class of fastcgi application"), + AP_INIT_TAKE1("FcgidErrorScanInterval", set_error_scan_interval, NULL, + RSRC_CONF, + "scan interval for exited process"), + AP_INIT_TAKE1("FcgidIdleScanInterval", set_idle_scan_interval, NULL, + RSRC_CONF, + "scan interval for idle timeout process"), + AP_INIT_TAKE1("FcgidIdleTimeout", set_idle_timeout, NULL, RSRC_CONF, + "an idle fastcgi application will be killed after IdleTimeout"), + AP_INIT_TAKE1("FcgidIOTimeout", set_ipc_comm_timeout, NULL, RSRC_CONF, + "Communication timeout to fastcgi server"), + AP_INIT_TAKE1("FcgidConnectTimeout", set_ipc_connect_timeout, NULL, + RSRC_CONF, + "Connect timeout to fastcgi server"), + AP_INIT_TAKE1("FcgidMaxProcesses", set_max_process, NULL, RSRC_CONF, + "Max total process count"), + AP_INIT_TAKE1("FcgidMaxRequestInMem", set_max_mem_request_len, NULL, + RSRC_CONF, + "The part of HTTP request which greater than this limit will swap to disk"), + AP_INIT_TAKE1("FcgidMaxRequestLen", set_max_request_len, NULL, RSRC_CONF, + "Max HTTP request length in byte"), + AP_INIT_TAKE1("FcgidMaxRequestsPerProcess", set_max_requests_per_process, + NULL, RSRC_CONF, + "Max requests handled by each fastcgi application"), + AP_INIT_TAKE1("FcgidOutputBufferSize", set_output_buffersize, NULL, + RSRC_CONF, + "CGI output buffer size"), + AP_INIT_TAKE1("FcgidPassHeader", add_pass_headers, NULL, RSRC_CONF, + "Header name which will be passed to FastCGI as environment variable."), + AP_INIT_TAKE1("FcgidFixPathinfo", + set_php_fix_pathinfo_enable, + NULL, RSRC_CONF, + "Set 1, if cgi.fix_pathinfo=1 in php.ini"), + AP_INIT_TAKE1("FcgidProcessLifeTime", set_proc_lifetime, NULL, RSRC_CONF, + "fastcgi application lifetime"), + AP_INIT_TAKE1("FcgidProcessTableFile", set_shmpath, NULL, RSRC_CONF, + "fastcgi shared memory file path"), + AP_INIT_TAKE1("FcgidIPCDir", set_socketpath, NULL, RSRC_CONF, + "fastcgi socket file path"), + AP_INIT_TAKE1("FcgidSpawnScore", set_spawn_score, NULL, RSRC_CONF, + "Score of spawn"), + AP_INIT_TAKE1("FcgidSpawnScoreUpLimit", set_spawnscore_uplimit, NULL, + RSRC_CONF, + "Spawn score up limit"), + AP_INIT_TAKE1("FcgidTerminationScore", set_termination_score, NULL, + RSRC_CONF, + "Score of termination"), + AP_INIT_TAKE1("FcgidTimeScore", set_time_score, NULL, + RSRC_CONF, + "Score of passage of time (in seconds)"), + AP_INIT_TAKE123("FcgidWrapper", set_wrapper_config, NULL, + RSRC_CONF | ACCESS_CONF | OR_FILEINFO, + "The CGI wrapper file an optional URL suffix and an optional flag"), + AP_INIT_TAKE1("FcgidZombieScanInterval", set_zombie_scan_interval, NULL, + RSRC_CONF, + "scan interval for zombie process"), +#ifdef WIN32 + AP_INIT_FLAG("FcgidWin32PreventOrphans", + set_win32_prevent_process_orphans, NULL, RSRC_CONF, + "Prevented fcgi process orphaning during Apache worker " + "abrupt shutdowns [see documentation]"), +#endif + + /* The following directives are all deprecated in favor + * of a consistent use of the Fcgid prefix. + * Add all new command above this line. + */ + AP_INIT_TAKE1("BusyScanInterval", set_busy_scan_interval, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidBusyScanInterval' instead"), + AP_INIT_TAKE1("BusyTimeout", set_busy_timeout, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidBusyTimeout' instead"), + AP_INIT_TAKE12("DefaultInitEnv", add_default_env_vars, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidInitialEnv' instead"), + AP_INIT_TAKE1("DefaultMaxClassProcessCount", + set_max_class_process, + NULL, RSRC_CONF, + "Deprecated - Use 'FcgidMaxProcessesPerClass' instead"), + AP_INIT_TAKE1("DefaultMinClassProcessCount", + set_min_class_process, + NULL, RSRC_CONF, + "Deprecated - Use 'FcgidMinProcessesPerClass' instead"), + AP_INIT_TAKE1("ErrorScanInterval", set_error_scan_interval, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidErrorScanInterval' instead"), + AP_INIT_TAKE1("FastCgiAccessChecker", set_access_info, NULL, + ACCESS_CONF | OR_FILEINFO, + "Deprecated - Use 'FcgidAccessChecker' instead"), + AP_INIT_FLAG("FastCgiAccessCheckerAuthoritative", + set_access_authoritative, NULL, ACCESS_CONF | OR_FILEINFO, + "Deprecated - Use 'FcgidAccessCheckerAuthoritative' instead"), + AP_INIT_TAKE1("FastCgiAuthenticator", set_authenticator_info, NULL, + ACCESS_CONF | OR_FILEINFO, + "Deprecated - Use 'FcgidAuthenticator' instead"), + AP_INIT_FLAG("FastCgiAuthenticatorAuthoritative", + set_authenticator_authoritative, NULL, + ACCESS_CONF | OR_FILEINFO, + "Deprecated - Use 'FcgidAuthenticatorAuthoritative' instead"), + AP_INIT_TAKE1("FastCgiAuthorizer", set_authorizer_info, NULL, + ACCESS_CONF | OR_FILEINFO, + "Deprecated - Use 'FcgidAuthorizer' instead"), + AP_INIT_FLAG("FastCgiAuthorizerAuthoritative", + set_authorizer_authoritative, NULL, + ACCESS_CONF | OR_FILEINFO, + "Deprecated - Use 'FcgidAuthorizerAuthoritative' instead"), + AP_INIT_TAKE123("FCGIWrapper", set_wrapper_config, NULL, + RSRC_CONF | ACCESS_CONF | OR_FILEINFO, + "Deprecated - Use 'FcgidWrapper' instead"), + AP_INIT_TAKE1("IdleScanInterval", set_idle_scan_interval, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidIdleScanInterval' instead"), + AP_INIT_TAKE1("IdleTimeout", set_idle_timeout, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidIdleTimeout' instead"), + AP_INIT_TAKE1("IPCCommTimeout", set_ipc_comm_timeout, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidIOTimeout' instead"), + AP_INIT_TAKE1("IPCConnectTimeout", set_ipc_connect_timeout, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidConnectTimeout' instead"), + AP_INIT_TAKE1("MaxProcessCount", set_max_process, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidMaxProcesses' instead"), + AP_INIT_TAKE1("MaxRequestInMem", set_max_mem_request_len, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidMaxRequestInMem' instead"), + AP_INIT_TAKE1("MaxRequestLen", set_max_request_len, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidMaxRequestLen' instead"), + AP_INIT_TAKE1("MaxRequestsPerProcess", set_max_requests_per_process, + NULL, RSRC_CONF, + "Deprecated - Use 'FcgidMaxRequestsPerProcess' instead"), + AP_INIT_TAKE1("OutputBufferSize", set_output_buffersize, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidOutputBufferSize' instead"), + AP_INIT_TAKE1("PassHeader", add_pass_headers, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidPassHeader' instead"), + AP_INIT_TAKE1("PHP_Fix_Pathinfo_Enable", + set_php_fix_pathinfo_enable, + NULL, RSRC_CONF, + "Deprecated - Use 'FcgidFixPathinfo' instead"), + AP_INIT_TAKE1("ProcessLifeTime", set_proc_lifetime, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidProcessLifeTime' instead"), + AP_INIT_TAKE1("SharememPath", set_shmpath, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidProcessTableFile' instead"), + AP_INIT_TAKE1("SocketPath", set_socketpath, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidIPCDir' instead"), + AP_INIT_TAKE1("SpawnScore", set_spawn_score, NULL, RSRC_CONF, + "Deprecated - Use 'FcgidSpawnScore' instead"), + AP_INIT_TAKE1("SpawnScoreUpLimit", set_spawnscore_uplimit, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidSpawnScoreUpLimit' instead"), + AP_INIT_TAKE1("TerminationScore", set_termination_score, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidTerminationScore' instead"), + AP_INIT_TAKE1("TimeScore", set_time_score, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidTimeScore' instead"), + AP_INIT_TAKE1("ZombieScanInterval", set_zombie_scan_interval, NULL, + RSRC_CONF, + "Deprecated - Use 'FcgidZombieScanInterval' instead"), + {NULL} +}; + +static int fcgid_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + apr_status_t rv; + + APR_OPTIONAL_HOOK(ap, status_hook, fcgid_status_hook, NULL, NULL, + APR_HOOK_MIDDLE); + + rv = procmgr_pre_config(pconf, plog, ptemp); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = proctable_pre_config(pconf, plog, ptemp); + if (rv != APR_SUCCESS) { + return rv; + } + + return OK; +} + +static void register_hooks(apr_pool_t * p) +{ + ap_hook_pre_config(fcgid_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(fcgid_init, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(initialize_child, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(fcgid_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_user_id(mod_fcgid_authenticator, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_auth_checker(mod_fcgid_authorizer, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_access_checker(mod_fcgid_check_access, NULL, NULL, + APR_HOOK_MIDDLE); + + /* Insert fcgid output filter */ + fcgid_filter_handle = + ap_register_output_filter("FCGID_OUT", + fcgid_filter, + NULL, AP_FTYPE_RESOURCE - 10); +} + +module AP_MODULE_DECLARE_DATA fcgid_module = { + STANDARD20_MODULE_STUFF, + create_fcgid_dir_config, /* create per-directory config structure */ + merge_fcgid_dir_config, /* merge per-directory config structures */ + create_fcgid_server_config, /* create per-server config structure */ + merge_fcgid_server_config, /* merge per-server config structures */ + fcgid_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; diff --git a/modules/fcgid/mod_fcgid.dep b/modules/fcgid/mod_fcgid.dep new file mode 100644 index 0000000..b74ffc4 --- /dev/null +++ b/modules/fcgid/mod_fcgid.dep @@ -0,0 +1,119 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_fcgid.mak + +.\fcgid_bridge.c : \ + ".\fcgid_bridge.h"\ + ".\fcgid_bucket.h"\ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_pm.h"\ + ".\fcgid_proc.h"\ + ".\fcgid_proctbl.h"\ + ".\fcgid_protocol.h"\ + ".\fcgid_spawn_ctl.h"\ + + +.\fcgid_bucket.c : \ + ".\fcgid_bridge.h"\ + ".\fcgid_bucket.h"\ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_proc.h"\ + ".\fcgid_proctbl.h"\ + ".\fcgid_protocol.h"\ + + +.\fcgid_conf.c : \ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + + +!IF "$(CFG)" == "mod_fcgid - Win32 Release" + +!ELSEIF "$(CFG)" == "mod_fcgid - Win32 Debug" + +!ENDIF + +.\fcgid_filter.c : \ + ".\fcgid_bucket.h"\ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_filter.h"\ + ".\fcgid_global.h"\ + ".\fcgid_proc.h"\ + ".\fcgid_proctbl.h"\ + + +.\fcgid_pm_main.c : \ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_pm.h"\ + ".\fcgid_pm_main.h"\ + ".\fcgid_proc.h"\ + ".\fcgid_proctbl.h"\ + ".\fcgid_spawn_ctl.h"\ + + +.\fcgid_pm_win.c : \ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_pm.h"\ + ".\fcgid_pm_main.h"\ + ".\fcgid_proctbl.h"\ + ".\fcgid_spawn_ctl.h"\ + + +.\fcgid_proc_win.c : \ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_pm.h"\ + ".\fcgid_proc.h"\ + ".\fcgid_proctbl.h"\ + ".\fcgid_protocol.h"\ + ".\fcgid_spawn_ctl.h"\ + + +.\fcgid_proctbl_win.c : \ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_proctbl.h"\ + + +.\fcgid_protocol.c : \ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_protocol.h"\ + + +.\fcgid_spawn_ctl.c : \ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + ".\fcgid_pm.h"\ + ".\fcgid_proctbl.h"\ + ".\fcgid_spawn_ctl.h"\ + + +.\mod_fcgid.c : \ + ".\fcgid_bridge.h"\ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_filter.h"\ + ".\fcgid_global.h"\ + ".\fcgid_pm.h"\ + ".\fcgid_proctbl.h"\ + ".\fcgid_protocol.h"\ + ".\fcgid_spawn_ctl.h"\ + + +.\mod_fcgid.rc : \ + ".\fcgid_conf.h"\ + ".\fcgid_config.h"\ + ".\fcgid_global.h"\ + diff --git a/modules/fcgid/mod_fcgid.dsp b/modules/fcgid/mod_fcgid.dsp new file mode 100644 index 0000000..7d11c53 --- /dev/null +++ b/modules/fcgid/mod_fcgid.dsp @@ -0,0 +1,222 @@ +# Microsoft Developer Studio Project File - Name="mod_fcgid" - 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_fcgid - 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_fcgid.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_fcgid.mak" CFG="mod_fcgid - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_fcgid - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_fcgid - 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_fcgid - 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 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "$(APACHE2_HOME)/include" /Fd"Release\mod_fcgid_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" /I "../../srclib/apr/include" /I "$(APACHE2_HOME)/include" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /machine:I386 /out:"Release/mod_fcgid.so" +# ADD LINK32 libhttpd.lib libaprutil-1.lib libapr-1.lib kernel32.lib /nologo /subsystem:windows /dll /debug /incremental:no /machine:I386 /out:"Release/mod_fcgid.so" /libpath:"..\..\Release" /libpath:"..\..\srclib\apr\Release" /libpath:"..\..\srclib\apr-util\Release" /libpath:"$(APACHE2_HOME)/lib" /base:"0x46430000" /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_fcgid.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_fcgid - 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 /GX /Od /Zi /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /GX /Od /Zi /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "$(APACHE2_HOME)/include" /Fd"Debug\mod_fcgid_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" /I "../../srclib/apr/include" /I "$(APACHE2_HOME)/include" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /machine:I386 /out:"Debug/mod_fcgid.so" +# ADD LINK32 libhttpd.lib libaprutil-1.lib libapr-1.lib kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /machine:I386 /libpath:"..\..\Debug" /libpath:"..\..\srclib\apr\Debug" /libpath:"..\..\srclib\apr-util\Debug" /libpath:"$(APACHE2_HOME)/lib" /out:"Debug/mod_fcgid.so" /base:"0x46430000" +# Begin Special Build Tool +TargetPath=.\Debug\mod_fcgid.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_fcgid - Win32 Release" +# Name "mod_fcgid - Win32 Debug" +# Begin Source File + +SOURCE=.\fcgid_bridge.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_bridge.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_bucket.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_bucket.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_conf.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_conf.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_filter.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_filter.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_global.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_pm.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_pm_main.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_pm_main.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_pm_win.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_proc.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_proc_win.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_proctbl.h +# End Source File +# Begin Source File + +SOURCE=.\fcgid_proctbl_win.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_protocol.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_spawn_ctl.c +# End Source File +# Begin Source File + +SOURCE=.\fcgid_spawn_ctl.h +# End Source File +# Begin Source File + +SOURCE=.\mod_fcgid.c +# End Source File +# Begin Source File + +SOURCE=.\mod_fcgid.rc +# End Source File +# Begin Source File + +SOURCE=.\fcgid_config.h +# End Source File +# Begin Source File + +SOURCE=".\fcgid_config.h.in" + +!IF "$(CFG)" == "mod_fcgid - Win32 Release" + +# Begin Custom Build - Generating fcgid_config.h +InputPath=".\fcgid_config.h.in" + +".\fcgid_config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + echo /* No configuration */ > .\fcgid_config.h + +# End Custom Build + +!ELSEIF "$(CFG)" == "mod_fcgid - Win32 Debug" + +# Begin Custom Build - Generating fcgid_config.h +InputPath=".\fcgid_config.h.in" + +".\fcgid_config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + echo /* No configuration */ > .\fcgid_config.h + +# End Custom Build + +!ENDIF + +# End Source File +# End Target +# End Project diff --git a/modules/fcgid/mod_fcgid.mak b/modules/fcgid/mod_fcgid.mak new file mode 100644 index 0000000..466cccb --- /dev/null +++ b/modules/fcgid/mod_fcgid.mak @@ -0,0 +1,351 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_fcgid.dsp +!IF "$(CFG)" == "" +CFG=mod_fcgid - Win32 Release +!MESSAGE No configuration specified. Defaulting to mod_fcgid - Win32 Release. +!ENDIF + +!IF "$(CFG)" != "mod_fcgid - Win32 Release" && "$(CFG)" != "mod_fcgid - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_fcgid.mak" CFG="mod_fcgid - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_fcgid - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_fcgid - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_fcgid - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +ALL : "$(OUTDIR)\mod_fcgid.so" "$(DS_POSTBUILD_DEP)" + + +CLEAN : + -@erase "$(INTDIR)\fcgid_bridge.obj" + -@erase "$(INTDIR)\fcgid_bucket.obj" + -@erase "$(INTDIR)\fcgid_conf.obj" + -@erase "$(INTDIR)\fcgid_filter.obj" + -@erase "$(INTDIR)\fcgid_pm_main.obj" + -@erase "$(INTDIR)\fcgid_pm_win.obj" + -@erase "$(INTDIR)\fcgid_proc_win.obj" + -@erase "$(INTDIR)\fcgid_proctbl_win.obj" + -@erase "$(INTDIR)\fcgid_protocol.obj" + -@erase "$(INTDIR)\fcgid_spawn_ctl.obj" + -@erase "$(INTDIR)\mod_fcgid.obj" + -@erase "$(INTDIR)\mod_fcgid.res" + -@erase "$(INTDIR)\mod_fcgid_src.idb" + -@erase "$(INTDIR)\mod_fcgid_src.pdb" + -@erase "$(OUTDIR)\mod_fcgid.exp" + -@erase "$(OUTDIR)\mod_fcgid.lib" + -@erase "$(OUTDIR)\mod_fcgid.pdb" + -@erase "$(OUTDIR)\mod_fcgid.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "$(APACHE2_HOME)/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_fcgid_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_fcgid.res" /i "../../srclib/apr/include" /i "$(APACHE2_HOME)/include" /d "NDEBUG" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_fcgid.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=libhttpd.lib libaprutil-1.lib libapr-1.lib kernel32.lib /nologo /base:"0x46430000" /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_fcgid.pdb" /debug /out:"$(OUTDIR)\mod_fcgid.so" /implib:"$(OUTDIR)\mod_fcgid.lib" /libpath:"..\..\Release" /libpath:"..\..\srclib\apr\Release" /libpath:"..\..\srclib\apr-util\Release" /libpath:"$(APACHE2_HOME)/lib" /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\fcgid_bridge.obj" \ + "$(INTDIR)\fcgid_bucket.obj" \ + "$(INTDIR)\fcgid_conf.obj" \ + "$(INTDIR)\fcgid_filter.obj" \ + "$(INTDIR)\fcgid_pm_main.obj" \ + "$(INTDIR)\fcgid_pm_win.obj" \ + "$(INTDIR)\fcgid_proc_win.obj" \ + "$(INTDIR)\fcgid_proctbl_win.obj" \ + "$(INTDIR)\fcgid_protocol.obj" \ + "$(INTDIR)\fcgid_spawn_ctl.obj" \ + "$(INTDIR)\mod_fcgid.obj" \ + "$(INTDIR)\mod_fcgid.res" + +"$(OUTDIR)\mod_fcgid.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_fcgid.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_fcgid.so" + if exist .\Release\mod_fcgid.so.manifest mt.exe -manifest .\Release\mod_fcgid.so.manifest -outputresource:.\Release\mod_fcgid.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_fcgid - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +ALL : ".\fcgid_config.h" "$(OUTDIR)\mod_fcgid.so" "$(DS_POSTBUILD_DEP)" + + +CLEAN : + -@erase "$(INTDIR)\fcgid_bridge.obj" + -@erase "$(INTDIR)\fcgid_bucket.obj" + -@erase "$(INTDIR)\fcgid_conf.obj" + -@erase "$(INTDIR)\fcgid_filter.obj" + -@erase "$(INTDIR)\fcgid_pm_main.obj" + -@erase "$(INTDIR)\fcgid_pm_win.obj" + -@erase "$(INTDIR)\fcgid_proc_win.obj" + -@erase "$(INTDIR)\fcgid_proctbl_win.obj" + -@erase "$(INTDIR)\fcgid_protocol.obj" + -@erase "$(INTDIR)\fcgid_spawn_ctl.obj" + -@erase "$(INTDIR)\mod_fcgid.obj" + -@erase "$(INTDIR)\mod_fcgid.res" + -@erase "$(INTDIR)\mod_fcgid_src.idb" + -@erase "$(INTDIR)\mod_fcgid_src.pdb" + -@erase "$(OUTDIR)\mod_fcgid.exp" + -@erase "$(OUTDIR)\mod_fcgid.lib" + -@erase "$(OUTDIR)\mod_fcgid.pdb" + -@erase "$(OUTDIR)\mod_fcgid.so" + -@erase ".\fcgid_config.h" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /GX /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "$(APACHE2_HOME)/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_fcgid_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_fcgid.res" /i "../../srclib/apr/include" /i "$(APACHE2_HOME)/include" /d "_DEBUG" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_fcgid.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=libhttpd.lib libaprutil-1.lib libapr-1.lib kernel32.lib /nologo /base:"0x46430000" /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_fcgid.pdb" /debug /out:"$(OUTDIR)\mod_fcgid.so" /implib:"$(OUTDIR)\mod_fcgid.lib" /libpath:"..\..\Debug" /libpath:"..\..\srclib\apr\Debug" /libpath:"..\..\srclib\apr-util\Debug" /libpath:"$(APACHE2_HOME)/lib" +LINK32_OBJS= \ + "$(INTDIR)\fcgid_bridge.obj" \ + "$(INTDIR)\fcgid_bucket.obj" \ + "$(INTDIR)\fcgid_conf.obj" \ + "$(INTDIR)\fcgid_filter.obj" \ + "$(INTDIR)\fcgid_pm_main.obj" \ + "$(INTDIR)\fcgid_pm_win.obj" \ + "$(INTDIR)\fcgid_proc_win.obj" \ + "$(INTDIR)\fcgid_proctbl_win.obj" \ + "$(INTDIR)\fcgid_protocol.obj" \ + "$(INTDIR)\fcgid_spawn_ctl.obj" \ + "$(INTDIR)\mod_fcgid.obj" \ + "$(INTDIR)\mod_fcgid.res" + +"$(OUTDIR)\mod_fcgid.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_fcgid.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_fcgid.so" + if exist .\Debug\mod_fcgid.so.manifest mt.exe -manifest .\Debug\mod_fcgid.so.manifest -outputresource:.\Debug\mod_fcgid.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_fcgid.dep") +!INCLUDE "mod_fcgid.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_fcgid.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_fcgid - Win32 Release" || "$(CFG)" == "mod_fcgid - Win32 Debug" +SOURCE=.\fcgid_bridge.c + +"$(INTDIR)\fcgid_bridge.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_bucket.c + +"$(INTDIR)\fcgid_bucket.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_conf.c + +"$(INTDIR)\fcgid_conf.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=".\fcgid_config.h.in" + +!IF "$(CFG)" == "mod_fcgid - Win32 Release" + +InputPath=".\fcgid_config.h.in" + +".\fcgid_config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + < .\fcgid_config.h +<< + + +!ELSEIF "$(CFG)" == "mod_fcgid - Win32 Debug" + +InputPath=".\fcgid_config.h.in" + +".\fcgid_config.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + < .\fcgid_config.h +<< + + +!ENDIF + +SOURCE=.\fcgid_filter.c + +"$(INTDIR)\fcgid_filter.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_pm_main.c + +"$(INTDIR)\fcgid_pm_main.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_pm_win.c + +"$(INTDIR)\fcgid_pm_win.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_proc_win.c + +"$(INTDIR)\fcgid_proc_win.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_proctbl_win.c + +"$(INTDIR)\fcgid_proctbl_win.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_protocol.c + +"$(INTDIR)\fcgid_protocol.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\fcgid_spawn_ctl.c + +"$(INTDIR)\fcgid_spawn_ctl.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\mod_fcgid.c + +"$(INTDIR)\mod_fcgid.obj" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + + +SOURCE=.\mod_fcgid.rc + +"$(INTDIR)\mod_fcgid.res" : $(SOURCE) "$(INTDIR)" ".\fcgid_config.h" + $(RSC) $(RSC_PROJ) $(SOURCE) + + + +!ENDIF + diff --git a/modules/fcgid/mod_fcgid.rc b/modules/fcgid/mod_fcgid.rc new file mode 100644 index 0000000..781bfd7 --- /dev/null +++ b/modules/fcgid/mod_fcgid.rc @@ -0,0 +1,56 @@ +#define VERSION_ONLY + +#include "fcgid_conf.h" + +#define ASF_LICENSE \ + "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\r\n\r\n" \ + "http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n" \ + "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." + +/* macro for Win32 .rc files using numeric csv representation */ +#define MODFCGID_REVISION_CSV MODFCGID_VERSION_MAJOR ##, \ + ##MODFCGID_VERSION_MINOR ##, \ + ##MODFCGID_VERSION_SUBVER + +1 VERSIONINFO + FILEVERSION MODFCGID_REVISION_CSV,200 + PRODUCTVERSION MODFCGID_REVISION_CSV,200 + FILEFLAGSMASK 0x3fL +#if defined(_DEBUG) + FILEFLAGS 0x01L +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", ASF_LICENSE "\0" + VALUE "CompanyName", "Apache Software Foundation\0" + VALUE "FileDescription", "fcgid_module for Apache\0" + VALUE "FileVersion", MODFCGID_REVISION "\0" + VALUE "InternalName", "mod_fcgid.so\0" + VALUE "LegalCopyright", MODFCGID_COPYRIGHT "\0" + VALUE "OriginalFilename", "mod_fcgid.so\0" + VALUE "ProductName", "Apache HTTP Server Project\0" + VALUE "ProductVersion", MODFCGID_REVISION "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/modules/fcgid/modules.mk.apxs b/modules/fcgid/modules.mk.apxs new file mode 100644 index 0000000..51ad05d --- /dev/null +++ b/modules/fcgid/modules.mk.apxs @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# this is used/needed by the APACHE2 build system +# +mod_fcgid.la: mod_fcgid.slo fcgid_bridge.slo fcgid_conf.slo fcgid_pm_main.slo fcgid_protocol.slo fcgid_spawn_ctl.slo fcgid_proctbl_unix.slo fcgid_pm_unix.slo fcgid_proc_unix.slo fcgid_bucket.slo fcgid_filter.slo fcgid_mutex_unix.slo + $(SH_LINK) -rpath $(libexecdir) -module -avoid-version mod_fcgid.lo fcgid_bridge.lo fcgid_conf.lo fcgid_pm_main.lo fcgid_protocol.lo fcgid_spawn_ctl.lo fcgid_proctbl_unix.lo fcgid_pm_unix.lo fcgid_proc_unix.lo fcgid_bucket.lo fcgid_filter.lo fcgid_mutex_unix.lo +DISTCLEAN_TARGETS = modules.mk +static = +shared = mod_fcgid.la +