randy 97/01/19 11:22:34 Modified: htdocs/manual/mod mod_fastcgi.html Log: Update docs. Note that I added a comment about the included patch being reversed and added the <!--#include for our footer. Submitted by: Stanley Gambarin Revision Changes Path 1.5 +321 -76 apache/htdocs/manual/mod/mod_fastcgi.html Index: mod_fastcgi.html =================================================================== RCS file: /export/home/cvs/apache/htdocs/manual/mod/mod_fastcgi.html,v retrieving revision 1.4 retrieving revision 1.5 diff -C3 -r1.4 -r1.5 *** mod_fastcgi.html 1996/12/02 18:14:05 1.4 --- mod_fastcgi.html 1997/01/19 19:21:51 1.5 *************** *** 12,84 **** </head> <body> ! <!--#include virtual="header.html" --> ! <h1>Module mod_fastcgi</h1> - This module is contained in the <code>mod_fastcgi.c</code> file. It - provides a high-performance alternative to CGI for writing Web server - applications in a variety of languages, including Perl, C, C++, - Java, and Python.<p> - - Any request for a file with the MIME type - <code>application/x-httpd-fcgi</code> will be processed by - <code>mod_fastcgi</code>. For the request to succeed, the server's - configuration must have started the application (executable file) - using the <code>AppClass</code> directive. - <p>This module is included optionally in Apache 1.2 and later. ! <h2>Summary</h2> ! FastCGI is a high-performance alternative to CGI. ! FastCGI gets its speed by having ! the Web server keep the application processes running between requests. So, ! unlike CGI, you do not have the overhead of starting up a new process and ! doing application initialization (e.g. connecting ! to a database) each time somebody requests a document. The ! processes start up with the Web server and keep on running.<p> FastCGI applications communicate with a Web server using a simple communications protocol. A single full-duplex connection communicates the environment variables and <code>stdin</code> data to the application, and <code>stdout</code> and <code>stderr</code> data to ! the Web server.<p> For more information on FastCGI, including freely available FastCGI server modules and application libraries, go to the <A ! HREF="http://www.fastcgi.com/">FastCGI home page (http://www.fastcgi.com/)</A>.<p> <h2>Directives</h2> <ul> <li> <a HREF="#AppClass">AppClass</a> <li> <a HREF="#FastCgiIpcDir">FastCgiIpcDir</a> </ul> <hr> <A name="AppClass"><h2>AppClass</h2></A> ! <strong>Syntax:</strong> AppClass exec-path <em>[-processes N] [-listen-queue-depth N] [-restart-delay N] [-priority N] [-initial-env name=value]<br></em> <strong>Context:</strong> srm.conf<br> <strong>Module:</strong> mod_fastcgi<p> The <code>AppClass</code> directive starts one or more FastCGI application processes, using the executable file ! <code>exec-path</code>. <code>mod_fastcgi</code> will restart these ! processes should they die.<p> ! ! When a client requests the file <code>exec-path</code>, ! the request is handled first by the <code>mod_fastcgi</code> module. ! <code>mod_fastcgi</code> communicates the request to a process ! in the application class, which generates the response. ! <code>mod_fastcgi</code> relays this response back to the client.<p> The optional parameters to the <code>AppClass</code> directive are as follows: --- 12,165 ---- </head> <body> ! <IMG SRC="http://www.apache.org/images/apache_sub.gif" ALT=""> ! <!--/%hypertext --> ! <h1>Module mod_fastcgi</h1> ! This module is contained in the <code>mod_fastcgi.c</code> file, and ! is not compiled into the server by default. ! To use <code>mod_fastcgi</code> you first copy ! <code>src/mod_fastcgi.c</code> from this kit into your Apache server's ! source directory. Then you add the following line to the server build ! Configuration file: ! <pre> ! Module fastcgi_module mod_fastcgi.o ! </pre> ! <p> ! FastCGI provides a high-performance alternative to CGI for writing Web ! server applications in a variety of languages, including Perl, C, C++, ! Java, and Python. FastCGI gets its speed by having keeping ! application processes running between requests. So, unlike CGI, you do ! not have the overhead of starting up a new process and doing ! application initialization (e.g. connecting to a database) each time ! somebody requests a document.<p> FastCGI applications communicate with a Web server using a simple communications protocol. A single full-duplex connection communicates the environment variables and <code>stdin</code> data to the application, and <code>stdout</code> and <code>stderr</code> data to ! the Web server. An application can reside on a different ! machine from the Web server, allowing applications to scale beyond ! a single box and providing easier integration with existing systems.<p> For more information on FastCGI, including freely available FastCGI server modules and application libraries, go to the <A ! HREF="http://www.fastcgi.com/">FastCGI website (http://www.fastcgi.com/)</A>.<p> + + <h2>Summary</h2> + + In order to configure FastCGI applications, you need a combination of + <code>mod_fastcgi</code> directives and other directives provided by + the Apache server.<p> + + You use the + <code>AppClass</code> directive to start the FastCGI + applications that you want to be managed by this Web server. The + applications are managed in the sense that the server (a) logs an + error message when a managed process dies and (b) attempts to + restart managed processes that die.<p> + + You use one or both of the + <code>AppClass</code> and <code>ExternalAppClass</code> directives + to define an association between a pathname and the connection + information for a FastCGI application. Connection + information is either the pathname of + a Unix domain socket or the IP address and port number of a + TCP port. The difference between the two directives + is that a single <code>AppClass</code> + directive both starts an application and sets up the association + for communicating with it, while <code>ExternalAppClass</code> only + defines the association. In the case of <code>AppClass</code>, + the pathname used in the association is always the pathname of the + application's executable; with <code>ExternalAppClass</code> the pathname + is arbitrary.<p> + + In order for an HTTP request to be processed by <code>mod_fastcgi</code> + the request's handler must be <code>fastcgi-handler</code> + or the request's MIME type must be + <code>application/x-httpd-fcgi</code>. Apache + provides several ways to set the handler and MIME type of + a request: + + <ul> + <li> <code>SetHandler</code> (in the context of a <code>Location</code> + or <code>Directory</code> section or <code>.htaccess</code> + file) can associate the handler + <code>fastcgi-handler</code> with + a specific file, or all the files in a directory.<p> + <li> <code>AddHandler</code> can associate the handler + <code>fastcgi-handler</code> with + files based on file extension.<p> + <li> <code>ForceType</code> (in the context of a <code>Location</code> + or <code>Directory</code> section or <code>.htaccess</code> + file) can associate the MIME type + <code>application/x-httpd-fcgi</code> with + a specific file, or all the files in a directory.<p> + <li> <code>AddType</code> can associate the MIME type + <code>application/x-httpd-fcgi</code> with + files based on file extension.<p> + </ul> + + Refer to the documentation for <code>mod_mime</code> + for more information on these directives, and to the documentation + of the Apache core features for information on <code>Location</code> + and <code>Directory</code> sections.<p> + + <code>mod_fastcgi</code> handles requests as follows: + + <ul> + <li> <code>mod_fastcgi</code> retrieves the connection + information associated with the requested pathname. + <li> if no connection information is associated with the pathname, + the server returns <code>404 Not Found</code>. + <li> <code>mod_fastcgi</code> connects to the FastCGI application process. + <li> if the connection attempt fails, + the server returns <code>500 Server Error</code>. + <li> <code>mod_fastcgi</code> transmits the request to the + FastCGI application process, which generates a response. + <li> <code>mod_fastcgi</code> receives the application's + response and transforms + it into an HTTP response. The server sends + this response back to the client. + </ul> + + The configuration examples below show some valid ways to configure + <code>mod_fastcgi</code>. + + <h2>Directives</h2> <ul> <li> <a HREF="#AppClass">AppClass</a> <li> + <a HREF="#ExternalAppClass">ExternalAppClass</a> + <li> <a HREF="#FastCgiIpcDir">FastCgiIpcDir</a> </ul> <hr> <A name="AppClass"><h2>AppClass</h2></A> ! <strong>Syntax:</strong> AppClass path-name <em>[-processes N] [-listen-queue-depth N] [-restart-delay N] [-priority N] + [-port N] [-socket sock-name] [-initial-env name=value]<br></em> <strong>Context:</strong> srm.conf<br> <strong>Module:</strong> mod_fastcgi<p> The <code>AppClass</code> directive starts one or more FastCGI application processes, using the executable file ! <code>path-name</code>. Should any of these processes die, ! <code>mod_fastcgi</code> will write an error log entry and restart ! the faulty process.<p> The optional parameters to the <code>AppClass</code> directive are as follows: *************** *** 113,118 **** --- 194,220 ---- values are not allowed.<p> <li> + <B>port:</B> TCP port number that the FastCGI application + process will listen on. Using this + option makes the application accessible from the another + machine (as well as from this one.) An argument to the + <code>port</code> option should be a number + between 1 and 65535.<p> + + <li> + <B>socket:</B> pathname of the Unix domain socket that + the FastCGI application process will listen on. The + module creates this socket within the directory + specified by <code>FastCgiIpcDir</code>. Using this option + makes the application accessible to other Web servers or + applications (e.g. <code>cgi-fcgi</code>) on the same machine + or via multiple pathnames on this Web server + (using <code>ExternalAppClass</code>.) + If neither the <code>-socket</code> nor the <code>-port</code> + options are given, the Web server generates a Unix domain + socket name itself.<p> + + <li> <B>initial-env:</B> a name-value pair in the initial environment passed to the application processes. The argument has the form <code>name=value</code>, with no whitespace allowed. *************** *** 121,129 **** initial environment is empty (no name-value pairs.)<p> </ul> <p> ! Errors possible in the <code>AppClass</code> directive include syntax errors, arguments out of range, and the file ! <code>exec-path</code> being non-existent or not executable.<p> <A name="FastCgiIpcDir"><h2>FastCgiIpcDir</h2></A> --- 223,283 ---- initial environment is empty (no name-value pairs.)<p> </ul> <p> ! The <code>-socket</code> and <code>-port</code> options are ! mutually exclusive. ! <code>path-name</code> ! must not equal the <code>path-name</code> supplied to an ! earlier <code>AppClass</code> or <code>ExternalAppClass</code> ! directive. Other errors ! possible in the <code>AppClass</code> directive include syntax errors, arguments out of range, and the file ! <code>path-name</code> being non-existent or not executable. ! <p> ! ! ! <A name="ExternalAppClass"><h2>ExternalAppClass</h2></A> ! <strong>Syntax:</strong> ExternalAppClass path-name ! <em>[-host host:port] ! [-socket sock-name]<br></em> ! <strong>Context:</strong> srm.conf<br> ! <strong>Module:</strong> mod_fastcgi<p> ! ! The <code>ExternalAppClass</code> directive provides a connection a ! FastCGI application process that is listening to a specified TCP port ! or a UNIX domain socket. <code>ExternalAppClass</code> is most commonly ! used to communicate with a FastCGI application running on a different ! machine. This directive implies nothing about how the FastCGI application ! is managed (started, restarted, etc.). <code>path-name</code> ! simply provides an identifier for the connection.<p> ! ! The optional parameters to the <code>ExternalAppClass</code> directive ! are as follows: ! <ul> ! <li> ! <B>host:</B> the host and port of the machine on which a FastCGI ! process is running. The argument should be a single string, ! containing the host IP address (either as ! a hostname to be resolved via DNS or as a "dotted quad" ! such as "123.96.248.182"), followed by a colon (":"), ! followed by a port number ! (an integer between 1 and 65535.)<p> ! ! <li> ! <B>socket:</B> full pathname of a Unix Domain socket accessible ! from this Web server. Most commonly, the FastCGI ! application listening on this socket would have been started by an ! <code>AppClass</code> directive with the <code>-socket</code> ! option.<p> ! </ul> ! <p> ! Exactly one of the <code>port</code> and <code>socket</code> ! parameters must be supplied. <code>path-name</code> ! must not equal the <code>path-name</code> supplied to an ! earlier <code>AppClass</code> or <code>ExternalAppClass</code> ! directive. Other ! errors possible in the <code>ExternalAppClass</code> ! directive include syntax errors and arguments out of range. ! <p> <A name="FastCgiIpcDir"><h2>FastCgiIpcDir</h2></A> *************** *** 145,151 **** To avoid this problem place a <code>FastCgiIpcDir</code> directive before the <code>AppClass</code> directives in your server ! configuration. Specify a directory that's readable, writable, and searchable by the account you use for your Web server, but otherwise not accessible to anyone.<p> --- 299,305 ---- To avoid this problem place a <code>FastCgiIpcDir</code> directive before the <code>AppClass</code> directives in your server ! configuration. Specify a directory that's readable, writeable, and searchable by the account you use for your Web server, but otherwise not accessible to anyone.<p> *************** *** 177,183 **** corruption problem. A corrupted error log makes it difficult to debug problems on your Web server. You should apply the following patch to Apache 1.1.1 in order to eliminate the possibility of ! this problem: <pre> % diff -c alloc.c alloc.c.orig *** alloc.c Mon Sep 23 17:45:34 1996 --- 331,338 ---- corruption problem. A corrupted error log makes it difficult to debug problems on your Web server. You should apply the following patch to Apache 1.1.1 in order to eliminate the possibility of ! this problem: (Note: This patch is reversed and must be applied ! with a <em>-R</em> flag to <em>patch</em>) <pre> % diff -c alloc.c alloc.c.orig *** alloc.c Mon Sep 23 17:45:34 1996 *************** *** 219,255 **** <li> The <code>ScriptAlias</code> directive takes priority over the ! <code>AddType</code> directive; a file located in a directory that ! is the target of <code>ScriptAlias</code>directive has type ! <code>application/x-httpd-cgi</code> and is handled by ! <code>mod_cgi</code>. So don't put FastCGI applications in your ! <code>/cgi-bin/</code> directory -- they won't work properly!<p> ! ! <li> ! <code>mod_fastcgi</code> becomes confused if you put a slash ! at the end of your <code>DocumentRoot</code>. The symptom ! is that the request handler won't find the applications that ! you have defined using <code>AppClass</code>.<p> ! ! <li> ! <code>mod_fastcgi</code> does not know about environment ! variables defined by the optional module <code>mod_env</code>. ! Use the <code>-initial-env</code> option to <code>AppClass</code>.<p> <li> ! <code>mod_fastcgi</code> does not implement TCP/IP ! connections to FastCGI applications, only Unix Domain socket ! connections. To connect to remote FastCGI applications ! run the <code>cgi-fcgi</code> program as a CGI script. ! See the ! <a href="http://www.fastcgi.com/kit/doc/cgi-fcgi.1"><code>cgi-fcgi</code> ! manpage</a> for more information.<p> </ul> ! <h2>Example</h2> What follows is a minimal httpd.conf for Apache 1.1.1 and FastCGI Developer's Kit 1.5. Use this configuration for initial testing --- 374,474 ---- <li> The <code>ScriptAlias</code> directive takes priority over the ! <code>AddType</code> directive: A file located in a directory that ! is the target of <code>ScriptAlias</code> is always ! handled by the handler <code>cgi-handler</code> (<code>mod_cgi</code>.) ! So don't put FastCGI applications in a <code>ScriptAlias</code> ! directory -- the applications won't work properly!<p> ! ! <li> ! The optional module <code>mod_env</code> provides two directives ! (<code>PassEnv</code> and <code>SetEnv</code>) that are designed ! for passing environment variables to CGI scripts. These ! directives also work for passing per-request environment variables ! to FastCGI applications. To pass initial environment variables ! you must use the <code>-initial-env</code> option to <code>AppClass</code>.<p> <li> ! <code>mod_fastcgi</code> does not implement the Authorizer or Filter ! roles described in the FastCGI specification. However, you can ! approximate the Filter role using Apache's <code>Action</code> ! directive to route requests to a FastCGI Responder. ! See the documentation for <code>mod_actions</code> for information ! on the <code>Action</code> directive.<p> ! ! <li> ! See the <a href="README"><code>README</code></a> file for a complete ! list of known bugs in this version of <code>mod_fastcgi</code>.<p> </ul> ! <h2>Notes on CGI response headers</h2> ! ! You may have noticed that <code>mod_fastcgi</code> makes no ! provision for non-parsed-header scripts. There's a good reason for ! this: <code>mod_fastcgi</code> does not restrict the functionality ! of parsed-header scripts in any way. A parsed-header script ! can do anything that an "nph" script can do, and some things ! that an "nph" script can't do.<p> ! ! To exploit the power of parsed-header scripts you need ! to understand the three standard CGI/1.1 ! response headers:<p> ! ! <ul> ! <li><code>Status:</code> an HTTP status line, e.g. ! <code>"206 Partial Content"</code>.<p> ! <li><code>Location:</code> a URL or absolute path to content.<p> ! <li><code>Content-type:</code> the media type of script-generated ! response content.<p> ! </ul> ! ! Only certain combinations of these headers make sense:<p> ! ! <ul> ! <li>Each header may appear at most once in a response.<p> ! <li>The <code>Status</code> and <code>Location</code> headers ! are mutually exclusive, i.e. at most one may appear ! in a response. If neither appears, the effect ! is as if <code>Status: 200 OK</code> appeared, and ! a <code>Content-type</code> header must appear.<p> ! <li>A <code>Content-type</code> header means that ! the script will generate response content (e.g. ! an HTML document), and the ! absence of a <code>Content-type</code> header means ! that the script will not generate response content.<p> ! <li>A <code>Location</code> header may only be generated ! in response to a <code>GET</code> or <code>HEAD</code> ! request.<p> ! </ul> ! ! The <code>Location</code> header specifies a redirect. ! There are two kinds. If the <code>Location</code> value ! is a full URL (e.g. <code>http://www.fastcgi.com/servers/apache</code>), ! the effect is to generate an HTTP 302 response. If the ! <code>Location</code> value is an absolute path ! (e.g. <code>/servers/apache</code>), the effect is to ! execute a recursive request for that path within the server, ! and return the response of that request.<p> ! ! <code>Location</code> is optionally accompanied by ! <code>Content-type</code>. If the <code>Location</code> value ! is a full URL and no <code>Content-type</code> is specified, ! the Web server provides standardized content (of type ! <code>text/html</code>); if a <code>Content-type</code> is specified, ! the script provides the content. If the <code>Location</code> value ! is an absolute path then script-generated <code>Content-type</code> and ! content are ignored.<p> ! ! <code>mod_fastcgi</code> performs buffering of script-provided ! content, but content is not allowed to linger in buffers ! for more than a fraction of a second. Therefore "server push" ! scripts work correctly.<p> ! ! ! <h2>Configuration example</h2> What follows is a minimal httpd.conf for Apache 1.1.1 and FastCGI Developer's Kit 1.5. Use this configuration for initial testing *************** *** 275,288 **** Save the resulting file as <code>$APACHE/conf/httpd.conf</code>.<p> <li> ! Build Apache 1.1.1 with mod_fastcgi. This creates the <code>httpd</code> executable.<p> Build the FastCGI Developer's Kit 1.5. This creates the <code>echo</code> executable that you are going to run as a ! FastCGI application, and makes the <code>echo.fcg</code> link ! to this application. This link gives it a distinctive MIME type ! so that <code>mod_fastcgi</code> will handle it.<p> <li> In a shell, cd to <code>$APACHE</code> and start httpd: --- 494,505 ---- Save the resulting file as <code>$APACHE/conf/httpd.conf</code>.<p> <li> ! Build Apache 1.1.1 with <code>mod_fastcgi</code>. This creates the <code>httpd</code> executable.<p> Build the FastCGI Developer's Kit 1.5. This creates the <code>echo</code> executable that you are going to run as a ! FastCGI application.<p> <li> In a shell, cd to <code>$APACHE</code> and start httpd: *************** *** 293,303 **** <li> Use a browser to access the URL <pre> ! http://$YOUR_HOST:5556/examples/echo.fcg </pre> where <code>$YOUR_HOST</code> is the IP address of the host running httpd. Look for <code>STATE=TEXAS</code> in the ! initial environment that <code>echo.fcg</code> displays.<p> </ol> <pre> --- 510,521 ---- <li> Use a browser to access the URL <pre> ! http://$YOUR_HOST:5556/examples/echo </pre> where <code>$YOUR_HOST</code> is the IP address of the host running httpd. Look for <code>STATE=TEXAS</code> in the ! initial environment that <code>echo</code> displays. The ! request counter should increment each time you reload the page.<p> </ol> <pre> *************** *** 310,315 **** --- 528,540 ---- # Not starting httpd as root, so Port must be larger than 1023 Port 5556 + # This is what you'd add to the config if the server is to be + # started as root. Don't do this until you've verified that the + # server works when started as non-root! Don't use user/group nobody; + # define a new user and group specifically for running the server. + # User httpd + # Group httpd + # Configure just one idle httpd child process, to simplify debugging StartServers 1 MinSpareServers 1 *************** *** 322,342 **** ScoreBoardFile logs/httpd.scoreboard # Tell httpd where to get documents - # XXX: No slash allowed at the end of DocumentRoot DocumentRoot $FASTCGI - # Tell Apache that mod_fastcgi should handle files ending in .fcg - AddType application/x-httpd-fcgi .fcg - # This is how you'd place the Unix-domain socket files in the logs # directory (you'd probably want to create a subdirectory for them.) ! # Don't do this until you've verified that the server works with ! # the socket files stored locally, in /tmp. # FastCgiIpcDir $APACHE/logs ! # Start the echo.fcg application (echo.fcg is a sym-link to echo, ! # created by $FASTCGI/examples/Makefile.) ! AppClass $FASTCGI/examples/echo.fcg -initial-env STATE=TEXAS # End of httpd.conf </pre> --- 547,588 ---- ScoreBoardFile logs/httpd.scoreboard # Tell httpd where to get documents DocumentRoot $FASTCGI # This is how you'd place the Unix-domain socket files in the logs # directory (you'd probably want to create a subdirectory for them.) ! # Don't do this until you've verified that everything works with ! # the socket files stored locally, in /tmp! # FastCgiIpcDir $APACHE/logs ! # Start the echo app ! AppClass $FASTCGI/examples/echo -initial-env STATE=TEXAS ! ! # Have mod_fastcgi handle requests for the echo app ! # (otherwise the server will return the app's binary as a file!) ! <Location /examples/echo> ! SetHandler fastcgi-script ! </Location> ! ! # Start a FastCGI application that's accessible from other machines ! AppClass $FastCGI/examples/echo.fcg -port 8978 ! <Location /examples/echo.fcg> ! SetHandler fastcgi-script ! </Location> ! ! # Connect to "remote" app started above. Since the app is actually ! # local, communication will take place using TCP loopback. ! # To test true remote operation, start one copy of this ! # Web server on one machine, then start another copy with ! # "localhost" in the line below changed to the host name of the first machine. ! ExternalAppClass $FASTCGI/examples/remote-echo -host localhost:8978 ! <Location /examples/remote-echo> ! SetHandler fastcgi-script ! </Location> ! ! # This is how you'd have mod_fastcgi handle any request for a file ! # whose name ends in .fcg: ! # AddHandler fastcgi-script fcg # End of httpd.conf </pre> *************** *** 344,347 **** <!--#include virtual="footer.html" --> </BODY> </HTML> - --- 590,592 ----
Modified: src CHANGES mod_fastcgi.c Log: Update the supplied mod_fastcgi.c to the current 1.4.3 version. Submitted by: Stanley Gambarin Revision Changes Path 1.124 +3 -0 apache/src/CHANGES Index: CHANGES =================================================================== RCS file: /export/home/cvs/apache/src/CHANGES,v retrieving revision 1.123 retrieving revision 1.124 diff -C3 -r1.123 -r1.124 *** CHANGES 1997/01/16 17:22:54 1.123 --- CHANGES 1997/01/19 19:22:31 1.124 *************** *** 1,5 **** --- 1,8 ---- Changes with Apache 1.2b5 + *) Update mod_fastcgi.c to version 1.4.3 as distributed by + Open Market. + *) Fixed bug in modules/Makefile that wouldn't allow building in more than one subdirectory (or cleaning, either). [Jeremy Laidman] 1.5 +1188 -502 apache/src/mod_fastcgi.c Index: mod_fastcgi.c =================================================================== RCS file: /export/home/cvs/apache/src/mod_fastcgi.c,v retrieving revision 1.4 retrieving revision 1.5 diff -C3 -r1.4 -r1.5 *** mod_fastcgi.c 1996/12/28 00:04:52 1.4 --- mod_fastcgi.c 1997/01/19 19:22:31 1.5 *************** *** 1,8 **** /* * mod_fastcgi.c -- * ! * Apache server module for FastCGI ! * * * * Copyright (c) 1995-1996 Open Market, Inc. --- 1,7 ---- /* * mod_fastcgi.c -- * ! * Apache server module for FastCGI. * * * Copyright (c) 1995-1996 Open Market, Inc. *************** *** 21,56 **** */ /* - * TODO list - * - * * Test cleanup of FastCGI processes more thoroughly. - * We still have a problem getting them all killed off - * for the config file re-read. How many processes can - * be killed off (reliably) in 3 seconds? This would - * work much better if the process manager had its own - * process group. - * - * * On Solaris, the process manager gets killed when you re-link - * the server, so you always need to move the executable out of the - * build directory before you use it. Is there any way the process - * manager can handle this exception? - * - * * Implement application timeouts. Request timeouts are done. - * - * * Investigate using more Apache core code for response header - * generation, etc. - * - * * Investigate using the optional module mod_env for setting - * initial environment variables (suggested by Michael Smith). - * - * * [Major] Support running FastCGI apps without AppClass. - * Design notes below. A side-effect of implementing - * this feature is that the module will correctly handle - * a DocumentRoot with a slash at the end. - * - */ - - /* * Module design notes. * * 1. Restart cleanup. --- 20,25 ---- *************** *** 225,230 **** --- 194,219 ---- free(ptr); } } + + static char *StringCopy(char *str) + { + int strLen = strlen(str); + char *newString = Malloc(strLen + 1); + memcpy(newString, str, strLen); + newString[strLen] = '\000'; + return newString; + } + + static char *StrError(int errorCode) + { + /* No strerror prototype on SunOS? */ + char *msg = (char *) strerror(errno); + if(msg == NULL) { + msg = "errno out of range"; + } + ASSERT(strlen(msg) < 100); + return msg; + } /*-----------------------Include fastcgi.h definitions ----------*/ *************** *** 262,268 **** * The length of the FastCGI packet header. */ #define FCGI_HEADER_LEN 8 - #define FCGI_MAX_LENGTH 0xffff --- 251,256 ---- *************** *** 567,572 **** --- 555,561 ---- * Change the length of a dynamic string. This can cause the * string to either grow or shrink, depending on the value of * length. + * * Input: * Tcl_DString *dsPtr; Structure describing dynamic string * int length; New length for dynamic string. *************** *** 618,623 **** --- 607,613 ---- * * Frees up any memory allocated for the dynamic string and * reinitializes the string to an empty state. + * * Input: * Tcl_DString *dsPtr; Structure describing dynamic string * *************** *** 728,738 **** --- 718,736 ---- typedef void *OS_IpcAddress; typedef struct _OS_IpcAddr { + int addrType; /* one of TYPE_* below */ + int port; /* port used for TCP connections */ DString bindPath; /* Path used for the socket bind point */ struct sockaddr *serverAddr; /* server address (for connect) */ int addrLen; /* length of server address (for connect) */ } OS_IpcAddr; + /* + * Values of addrType field. + */ + #define TYPE_UNKNOWN 0 /* Uninitialized address type */ + #define TYPE_LOCAL 1 /* Local IPC: UNIX domain stream socket */ + #define TYPE_TCP 2 /* TCP stream socket */ /* *---------------------------------------------------------------------- *************** *** 749,757 **** --- 747,758 ---- * *---------------------------------------------------------------------- */ + OS_IpcAddress OS_InitIpcAddr(void) { OS_IpcAddr *ipcAddrPtr = (OS_IpcAddr *)Malloc(sizeof(OS_IpcAddr)); + ipcAddrPtr->addrType = TYPE_UNKNOWN; + ipcAddrPtr->port = -1; DStringInit(&ipcAddrPtr->bindPath); ipcAddrPtr->serverAddr = NULL; ipcAddrPtr->addrLen = 0; *************** *** 775,796 **** *---------------------------------------------------------------------- */ ! static int OS_BuildSockAddrUn(char *bindPath, ! struct sockaddr_un *servAddrPtr, ! int *servAddrLen) { int bindPathLen = strlen(bindPath); #ifdef HAVE_SOCKADDR_UN_SUN_LEN /* 4.3BSD Reno and later: BSDI */ ! if(bindPathLen >= sizeof(servAddrPtr->sun_path)) return -1; #else /* 4.3 BSD Tahoe: Solaris, HPUX, DEC, ... */ ! if(bindPathLen > sizeof(servAddrPtr->sun_path)) return -1; #endif memset((char *) servAddrPtr, 0, sizeof(*servAddrPtr)); servAddrPtr->sun_family = AF_UNIX; memcpy(servAddrPtr->sun_path, bindPath, bindPathLen); #ifdef HAVE_SOCKADDR_UN_SUN_LEN /* 4.3BSD Reno and later: BSDI */ *servAddrLen = sizeof(servAddrPtr->sun_len) + sizeof(servAddrPtr->sun_family) --- 776,801 ---- *---------------------------------------------------------------------- */ ! static int OS_BuildSockAddrUn( ! char *bindPath, ! struct sockaddr_un *servAddrPtr, ! int *servAddrLen) { int bindPathLen = strlen(bindPath); #ifdef HAVE_SOCKADDR_UN_SUN_LEN /* 4.3BSD Reno and later: BSDI */ ! if(bindPathLen >= sizeof(servAddrPtr->sun_path)) { return -1; + } #else /* 4.3 BSD Tahoe: Solaris, HPUX, DEC, ... */ ! if(bindPathLen > sizeof(servAddrPtr->sun_path)) { return -1; + } #endif memset((char *) servAddrPtr, 0, sizeof(*servAddrPtr)); servAddrPtr->sun_family = AF_UNIX; memcpy(servAddrPtr->sun_path, bindPath, bindPathLen); + #ifdef HAVE_SOCKADDR_UN_SUN_LEN /* 4.3BSD Reno and later: BSDI */ *servAddrLen = sizeof(servAddrPtr->sun_len) + sizeof(servAddrPtr->sun_family) *************** *** 807,844 **** * * OS_CreateLocalIpcFd -- * ! * This procedure is responsible for creating the listener socket ! * on Unix for local process communication. It will create a Unix ! * domain socket, bind it, and return a file descriptor to it to the ! * caller. * * Results: ! * Listener socket created. This call returns either a valid ! * file descriptor or -1 on error. * *---------------------------------------------------------------------- */ ! typedef char *MakeSocketNameProc(Tcl_DString *dsPtr); ! int OS_CreateLocalIpcFd(OS_IpcAddress ipcAddress, int listenQueueDepth, ! uid_t uid, gid_t gid, MakeSocketNameProc makeSocketName) { OS_IpcAddr *ipcAddrPtr = (OS_IpcAddr *)ipcAddress; - char bindPathExt[32]; struct sockaddr_un *addrPtr = NULL; int listenSock = -1; - makeSocketName(&ipcAddrPtr->bindPath); /* * Build the domain socket address. */ addrPtr = (struct sockaddr_un *) Malloc(sizeof(struct sockaddr_un)); ipcAddrPtr->serverAddr = (struct sockaddr *) addrPtr; ! ! if (OS_BuildSockAddrUn(DStringValue(&ipcAddrPtr->bindPath), addrPtr, ! &ipcAddrPtr->addrLen)) { goto GET_IPC_ERROR_EXIT; } /* * Create the listening socket to be used by the fcgi server. --- 812,856 ---- * * OS_CreateLocalIpcFd -- * ! * This procedure is responsible for creating the listener socket ! * on Unix for local process communication. It will create a Unix ! * domain socket, bind it, and return a file descriptor to it to the ! * caller. * * Results: ! * Valid file descriptor or -1 on error. ! * ! * Side effects: ! * *ipcAddress initialized. * *---------------------------------------------------------------------- */ ! typedef char *MakeSocketNameProc(char *name, int extension, Tcl_DString *dsPtr); ! int OS_CreateLocalIpcFd( ! OS_IpcAddress ipcAddress, ! int listenQueueDepth, ! uid_t uid, ! gid_t gid, ! MakeSocketNameProc makeSocketName, ! char *name, ! int extension) { OS_IpcAddr *ipcAddrPtr = (OS_IpcAddr *)ipcAddress; struct sockaddr_un *addrPtr = NULL; int listenSock = -1; + ASSERT(ipcAddrPtr->addrType == TYPE_UNKNOWN); /* * Build the domain socket address. */ addrPtr = (struct sockaddr_un *) Malloc(sizeof(struct sockaddr_un)); ipcAddrPtr->serverAddr = (struct sockaddr *) addrPtr; ! if (OS_BuildSockAddrUn(makeSocketName(name, extension, &ipcAddrPtr->bindPath), ! addrPtr, &ipcAddrPtr->addrLen)) { goto GET_IPC_ERROR_EXIT; } + ipcAddrPtr->addrType = TYPE_LOCAL; /* * Create the listening socket to be used by the fcgi server. *************** *** 851,864 **** /* * Bind the listening socket and set it to listen. */ if(OS_Bind(listenSock, ipcAddrPtr->serverAddr, ipcAddrPtr->addrLen) < 0 || OS_Listen(listenSock, listenQueueDepth) < 0) { goto GET_IPC_ERROR_EXIT; } #ifndef __EMX__ ! /* OS/2 dosen't support changing ownership. */ chown(DStringValue(&ipcAddrPtr->bindPath), uid, gid); #endif chmod(DStringValue(&ipcAddrPtr->bindPath), S_IRUSR | S_IWUSR); return listenSock; --- 863,882 ---- /* * Bind the listening socket and set it to listen. */ + if((unlink(DStringValue(&ipcAddrPtr->bindPath)) < 0) + && (errno != ENOENT)) { + goto GET_IPC_ERROR_EXIT; + } if(OS_Bind(listenSock, ipcAddrPtr->serverAddr, ipcAddrPtr->addrLen) < 0 || OS_Listen(listenSock, listenQueueDepth) < 0) { goto GET_IPC_ERROR_EXIT; } + #ifndef __EMX__ ! /* OS/2 dosen't support changing ownership. */ chown(DStringValue(&ipcAddrPtr->bindPath), uid, gid); #endif + chmod(DStringValue(&ipcAddrPtr->bindPath), S_IRUSR | S_IWUSR); return listenSock; *************** *** 868,873 **** --- 886,893 ---- if(addrPtr != NULL) { free(addrPtr); ipcAddrPtr->serverAddr = NULL; + ipcAddrPtr->addrType = TYPE_UNKNOWN; + ipcAddrPtr->addrLen = 0; } return -1; } *************** *** 900,923 **** /* *---------------------------------------------------------------------- * ! * OS_CleanupFcgiProgram -- * ! * Perform OS cleanup on a server managed process. ! * XXX: odd name, really more like inverse of OS_CreateLocalIpcFd * * Results: * None. * ! * Side effects: ! * Domain socket bindPath is unlinked. * *---------------------------------------------------------------------- ! */ ! void OS_CleanupFcgiProgram(OS_IpcAddress ipcAddress) { ! OS_IpcAddr *ipcAddrPtr = (OS_IpcAddr *)ipcAddress; ! unlink(DStringValue(&ipcAddrPtr->bindPath)); } /* XXX: where does this number come from? */ #define ht_openmax (128) --- 920,1124 ---- /* *---------------------------------------------------------------------- * ! * OS_CreateRemoteIpcFd -- ! * ! * This procedure is responsible for creating a listener socket ! * for remote process communication. It will create a TCP socket, ! * bind it, and return a file descriptor to the caller. ! * ! * Results: ! * Valid file descriptor or -1 on error. ! * ! *---------------------------------------------------------------------- ! */ ! ! int OS_CreateRemoteIpcFd( ! OS_IpcAddress ipcAddress, ! int portIn, ! int listenQueueDepth) ! { ! OS_IpcAddr *ipcAddrPtr = (OS_IpcAddr *) ipcAddress; ! struct sockaddr_in *addrPtr = (struct sockaddr_in *) ! Malloc(sizeof(struct sockaddr_in)); ! int resultSock = -1; ! int flag = 1; ! ! ASSERT(ipcAddrPtr->addrType == TYPE_UNKNOWN); ! ipcAddrPtr->addrType = TYPE_TCP; ! ipcAddrPtr->port = portIn; ! ipcAddrPtr->addrLen = sizeof(struct sockaddr_in); ! ! memset((char *) addrPtr, 0, sizeof(struct sockaddr_in)); ! addrPtr->sin_family = AF_INET; ! addrPtr->sin_addr.s_addr = htonl(INADDR_ANY); ! addrPtr->sin_port = htons(portIn); ! ipcAddrPtr->serverAddr = (struct sockaddr *) addrPtr; ! ! if((resultSock = OS_Socket(ipcAddrPtr->serverAddr->sa_family, ! SOCK_STREAM, 0)) < 0) { ! goto GET_IPC_ERROR_EXIT; ! } ! ! if(setsockopt(resultSock, SOL_SOCKET, SO_REUSEADDR, ! (char *) &flag, sizeof(flag)) < 0) { ! goto GET_IPC_ERROR_EXIT; ! } ! ! /* ! * Bind the listening socket and set it to listen ! */ ! if(OS_Bind(resultSock, ipcAddrPtr->serverAddr, ipcAddrPtr->addrLen) < 0 ! || OS_Listen(resultSock, listenQueueDepth) < 0) { ! goto GET_IPC_ERROR_EXIT; ! } ! ! return resultSock; ! ! GET_IPC_ERROR_EXIT: ! if(resultSock != -1) { ! OS_Close(resultSock); ! } ! if(addrPtr != NULL) { ! Free(addrPtr); ! ipcAddrPtr->serverAddr = NULL; ! ipcAddrPtr->port = -1; ! ipcAddrPtr->addrType = TYPE_UNKNOWN; ! ipcAddrPtr->addrLen = 0; ! } ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * ResolveHostname -- ! * ! * Given a hostname string (aegaen.openmarket.com) or an ASCII ! * "dotted decimal" IP address (199.170.183.5), convert to ! * IP address. * ! * NOTE: This routine will block as the hostname is resolved, and ! * should only be used in startup or debugging code. * * Results: + * Returns -1 if error, and the number of resolved addresses (one + * or more), if success. + * + * Side effects: * None. * ! *---------------------------------------------------------------------- ! */ ! ! int ResolveHostname(char *hostname, struct in_addr *addr) ! { ! struct hostent *hp; ! int count; ! ! addr->s_addr = inet_addr(hostname); ! if(addr->s_addr == (unsigned int) -1) { ! if((hp = gethostbyname(hostname)) == NULL) { ! return -1; ! } ! ! memcpy((char *) addr, hp->h_addr, hp->h_length); ! count = 0; ! while(hp->h_addr_list[count] != 0) { ! count++; ! } ! ! return count; ! } ! return 1; ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * OS_CreateLocalIpcAddr -- ! * ! * This procedure is responsible for creating a Unix domain address ! * to be used to connect to a fcgi server not managed by the Web ! * server. ! * ! * Results: ! * Unix Domain socket is created. This call returns 0 on success ! * or -1 on error. ! * ! * Side effects: ! * OS_IpcAddress structure is allocated and returned to the caller. ! * 'errno' will set on errors (-1 is returned). * *---------------------------------------------------------------------- ! */ ! ! int OS_CreateLocalIpcAddr( ! OS_IpcAddress ipcAddress, ! MakeSocketNameProc makeSocketName, ! char *name, ! int extension) { ! OS_IpcAddr *ipcAddrPtr = (OS_IpcAddr *) ipcAddress; ! struct sockaddr_un* addrPtr = NULL; ! ASSERT(ipcAddrPtr->addrType == TYPE_UNKNOWN); ! ASSERT(name != NULL); ! ! /* ! * Build the domain socket address. ! */ ! addrPtr = (struct sockaddr_un *) Malloc(sizeof(struct sockaddr_un)); ! ipcAddrPtr->serverAddr = (struct sockaddr *) addrPtr; ! if(OS_BuildSockAddrUn(makeSocketName(name, extension, &ipcAddrPtr->bindPath), ! addrPtr, &ipcAddrPtr->addrLen)) { ! goto GET_IPC_ADDR_ERROR; ! } ! ipcAddrPtr->addrType = TYPE_LOCAL; ! return 0; ! ! GET_IPC_ADDR_ERROR: ! if(addrPtr != NULL) { ! free(addrPtr); ! ipcAddrPtr->serverAddr = NULL; ! } ! return -1; ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * OS_CreateInetIpc -- ! * ! * This procedure is responsible for creating an OS_IpcAddr version ! * of hostname:port to be used for communications via TCP. ! * ! * Results: ! * AF_INET socket created. ! * ! * Side effects: ! * OS_IpcAddress structure is allocated and returned to the caller. ! * ! *---------------------------------------------------------------------- ! */ ! void OS_CreateInetIpc( ! OS_IpcAddress ipcAddress, ! struct in_addr *hostIn, ! int portIn) ! { ! OS_IpcAddr *ipcAddrPtr = (OS_IpcAddr *) ipcAddress; ! struct sockaddr_in *addrPtr; ! ! ASSERT(ipcAddrPtr->addrType == TYPE_UNKNOWN); ! ipcAddrPtr->addrType = TYPE_TCP; ! ipcAddrPtr->port = portIn; ! ! addrPtr = (struct sockaddr_in *) Malloc(sizeof(struct sockaddr_in)); ! memset(addrPtr, 0, sizeof(struct sockaddr_in)); ! ipcAddrPtr->addrLen = sizeof(struct sockaddr_in); ! addrPtr->sin_family = AF_INET; ! addrPtr->sin_port = htons(portIn); ! memcpy(&addrPtr->sin_addr.s_addr, hostIn, sizeof(struct in_addr)); ! ipcAddrPtr->serverAddr = (struct sockaddr *) addrPtr; } + /* XXX: where does this number come from? */ #define ht_openmax (128) *************** *** 931,950 **** * * Results: * 0 for successful fork, -1 for failed fork. ! * Child process exits in case of failure, with exit ! * status = errno of failed system call. * * Side effects: * Child process created. * *---------------------------------------------------------------------- */ ! static int OS_ExecFcgiProgram(pid_t *childPid, int listenFd, int priority, ! char *programName, char **envPtr) { int i; DString dirName; ! char *dnEnd; /* * Fork the fcgi process. */ --- 1132,1163 ---- * * Results: * 0 for successful fork, -1 for failed fork. ! * ! * In case the child fails before or in the exec, the child ! * obtains the error log by calling getErrLog, logs ! * the error, and exits with exit status = errno of ! * the failed system call. * * Side effects: * Child process created. * *---------------------------------------------------------------------- */ ! typedef FILE *GetErrLog(void); ! ! static int OS_ExecFcgiProgram( ! pid_t *childPid, ! int listenFd, ! int priority, ! char *programName, ! char **envPtr, ! GetErrLog *getErrLog) { int i; DString dirName; ! char *dnEnd, *failedSysCall; ! FILE *errorLogFile; ! /* * Fork the fcgi process. */ *************** *** 954,969 **** } else if(*childPid != 0) { return 0; } /* * We're the child; no return. */ if(!geteuid() && setuid(user_id) == -1) { ! exit(errno); } if(listenFd != FCGI_LISTENSOCK_FILENO) { OS_Dup2(listenFd, FCGI_LISTENSOCK_FILENO); OS_Close(listenFd); } DStringInit(&dirName); dnEnd = strrchr(programName, '/'); if(dnEnd == NULL) { --- 1167,1185 ---- } else if(*childPid != 0) { return 0; } + /* * We're the child; no return. */ if(!geteuid() && setuid(user_id) == -1) { ! failedSysCall = "setuid"; ! goto ErrorExit; } if(listenFd != FCGI_LISTENSOCK_FILENO) { OS_Dup2(listenFd, FCGI_LISTENSOCK_FILENO); OS_Close(listenFd); } + DStringInit(&dirName); dnEnd = strrchr(programName, '/'); if(dnEnd == NULL) { *************** *** 972,1004 **** DStringAppend(&dirName, programName, dnEnd - programName); } if(chdir(DStringValue(&dirName)) < 0) { ! exit(errno); } DStringFree(&dirName); #ifndef __EMX__ ! /* OS/2 dosen't support nice() */ if(priority != 0) { ! if(nice (priority) == -1) { ! exit(errno); } } ! #endif /* * Close any file descriptors we may have gotten from the parent * process. The only FD left open is the FCGI listener socket. */ for(i=0; i < ht_openmax; i++) { ! if(i != FCGI_LISTENSOCK_FILENO) OS_Close(i); } ! if(envPtr != NULL) { ! execle(programName, programName, NULL, envPtr); ! } else { ! execl(programName, programName, NULL); ! } /* ! * exec failed ! */ exit(errno); } --- 1188,1240 ---- DStringAppend(&dirName, programName, dnEnd - programName); } if(chdir(DStringValue(&dirName)) < 0) { ! failedSysCall = "chdir"; ! goto ErrorExit; } DStringFree(&dirName); + #ifndef __EMX__ ! /* OS/2 dosen't support nice() */ if(priority != 0) { ! if(nice(priority) == -1) { ! failedSysCall = "nice"; ! goto ErrorExit; } } ! #endif ! /* * Close any file descriptors we may have gotten from the parent * process. The only FD left open is the FCGI listener socket. */ for(i=0; i < ht_openmax; i++) { ! if(i != FCGI_LISTENSOCK_FILENO) { OS_Close(i); + } } ! do { ! if(envPtr != NULL) { ! execle(programName, programName, NULL, envPtr); ! failedSysCall = "execle"; ! } else { ! execl(programName, programName, NULL); ! failedSysCall = "execl"; ! } ! } while(errno == EINTR); ! ! ErrorExit: /* ! * We had to close all files but the FCGI listener socket in order to ! * exec the application. So if we want to report exec errors (we do!) ! * we must wait until now to open the log file. ! */ ! errorLogFile = getErrLog(); ! fprintf(errorLogFile, ! "[%s] mod_fastcgi: %s pid %d syscall %s failed" ! " before entering app, errno = %s.\n", ! get_time(), programName, getpid(), failedSysCall, ! strerror(errno)); ! fflush(errorLogFile); exit(errno); } *************** *** 1061,1066 **** --- 1297,1303 ---- *---------------------------------------------------------------------- */ #define WS_SET_errno(x) errno = x + int WS_Access(const char *path, int mode, uid_t uid, gid_t gid) { struct stat statBuf; *************** *** 1068,1090 **** struct group *grp; struct passwd *usr; ! if(stat(path, &statBuf) < 0) return -1; /* * If the user owns this file, check the owner bits. */ if(uid == statBuf.st_uid) { WS_SET_errno(EACCES); ! if((mode & R_OK) && !(statBuf.st_mode & S_IRUSR)) goto no_access; ! ! if((mode & W_OK) && !(statBuf.st_mode & S_IWUSR)) goto no_access; ! ! if((mode & X_OK) && !(statBuf.st_mode & S_IXUSR)) goto no_access; ! return 0; } --- 1305,1328 ---- struct group *grp; struct passwd *usr; ! if(stat(path, &statBuf) < 0) { return -1; + } /* * If the user owns this file, check the owner bits. */ if(uid == statBuf.st_uid) { WS_SET_errno(EACCES); ! if((mode & R_OK) && !(statBuf.st_mode & S_IRUSR)) { goto no_access; ! } ! if((mode & W_OK) && !(statBuf.st_mode & S_IWUSR)) { goto no_access; ! } ! if((mode & X_OK) && !(statBuf.st_mode & S_IXUSR)) { goto no_access; ! } return 0; } *************** *** 1110,1134 **** * user is a member of that group, apply the group permissions. */ grp = getgrgid(statBuf.st_gid); ! if(grp == NULL) return -1; usr = getpwuid(uid); ! if(usr == NULL) return -1; for(names = grp->gr_mem; *names != NULL; names++) { if(!strcmp(*names, usr->pw_name)) { WS_SET_errno(EACCES); ! if((mode & R_OK) && !(statBuf.st_mode & S_IRGRP)) goto no_access; ! ! if((mode & W_OK) && !(statBuf.st_mode & S_IWGRP)) goto no_access; ! ! if((mode & X_OK) && !(statBuf.st_mode & S_IXGRP)) goto no_access; ! return 0; } } --- 1348,1374 ---- * user is a member of that group, apply the group permissions. */ grp = getgrgid(statBuf.st_gid); ! if(grp == NULL) { return -1; + } usr = getpwuid(uid); ! if(usr == NULL) { return -1; + } for(names = grp->gr_mem; *names != NULL; names++) { if(!strcmp(*names, usr->pw_name)) { WS_SET_errno(EACCES); ! if((mode & R_OK) && !(statBuf.st_mode & S_IRGRP)) { goto no_access; ! } ! if((mode & W_OK) && !(statBuf.st_mode & S_IWGRP)) { goto no_access; ! } ! if((mode & X_OK) && !(statBuf.st_mode & S_IXGRP)) { goto no_access; ! } return 0; } } *************** *** 1173,1178 **** --- 1413,1419 ---- * *---------------------------------------------------------------------- */ + void BufferCheck(Buffer *bufPtr) { ASSERT(bufPtr->size > 0); *************** *** 1203,1208 **** --- 1444,1450 ---- * *---------------------------------------------------------------------- */ + void BufferReset(Buffer *bufPtr) { bufPtr->length = 0; *************** *** 1224,1229 **** --- 1466,1472 ---- * *---------------------------------------------------------------------- */ + Buffer *BufferCreate(int size) { Buffer *bufPtr; *************** *** 1249,1254 **** --- 1492,1498 ---- * *---------------------------------------------------------------------- */ + void BufferDelete(Buffer *bufPtr) { BufferCheck(bufPtr); *************** *** 1270,1275 **** --- 1514,1520 ---- * *---------------------------------------------------------------------- */ + int BufferRead(Buffer *bufPtr, int fd) { int len; *************** *** 1283,1290 **** len = OS_Read(fd, bufPtr->end, len); if(len > 0) { bufPtr->end += len; ! if(bufPtr->end >= (bufPtr->data + bufPtr->size)) bufPtr->end -= bufPtr->size; bufPtr->length += len; } } --- 1528,1536 ---- len = OS_Read(fd, bufPtr->end, len); if(len > 0) { bufPtr->end += len; ! if(bufPtr->end >= (bufPtr->data + bufPtr->size)) { bufPtr->end -= bufPtr->size; + } bufPtr->length += len; } } *************** *** 1325,1336 **** if(len > MAX_WRITE) { len = MAX_WRITE; } if(len > 0) { len = OS_Write(fd, bufPtr->begin, len); if(len > 0) { bufPtr->begin += len; ! if(bufPtr->begin >= (bufPtr->data + bufPtr->size)) bufPtr->begin -= bufPtr->size; bufPtr->length -= len; } } --- 1571,1584 ---- if(len > MAX_WRITE) { len = MAX_WRITE; } + if(len > 0) { len = OS_Write(fd, bufPtr->begin, len); if(len > 0) { bufPtr->begin += len; ! if(bufPtr->begin >= (bufPtr->data + bufPtr->size)) { bufPtr->begin -= bufPtr->size; + } bufPtr->length -= len; } } *************** *** 1356,1361 **** --- 1604,1610 ---- * *---------------------------------------------------------------------- */ + void BufferPeekToss(Buffer *bufPtr, char **beginPtr, int *countPtr) { BufferCheck(bufPtr); *************** *** 1380,1385 **** --- 1629,1635 ---- * *---------------------------------------------------------------------- */ + void BufferToss(Buffer *bufPtr, int count) { BufferCheck(bufPtr); *************** *** 1387,1394 **** bufPtr->length -= count; bufPtr->begin += count; ! if(bufPtr->begin >= bufPtr->data + bufPtr->size) bufPtr->begin -= bufPtr->size; } /* --- 1637,1645 ---- bufPtr->length -= count; bufPtr->begin += count; ! if(bufPtr->begin >= bufPtr->data + bufPtr->size) { bufPtr->begin -= bufPtr->size; + } } /* *************** *** 1410,1415 **** --- 1661,1667 ---- * *---------------------------------------------------------------------- */ + void BufferPeekExpand(Buffer *bufPtr, char **endPtr, int *countPtr) { BufferCheck(bufPtr); *************** *** 1436,1441 **** --- 1688,1694 ---- * *---------------------------------------------------------------------- */ + void BufferExpand(Buffer *bufPtr, int count) { BufferCheck(bufPtr); *************** *** 1443,1450 **** bufPtr->length += count; bufPtr->end += count; ! if(bufPtr->end >= bufPtr->data + bufPtr->size) bufPtr->end -= bufPtr->size; BufferCheck(bufPtr); } --- 1696,1704 ---- bufPtr->length += count; bufPtr->end += count; ! if(bufPtr->end >= bufPtr->data + bufPtr->size) { bufPtr->end -= bufPtr->size; + } BufferCheck(bufPtr); } *************** *** 1464,1469 **** --- 1718,1724 ---- * *---------------------------------------------------------------------- */ + int BufferAddData(Buffer *bufPtr, char *data, int datalen) { char *end; *************** *** 1471,1481 **** int canCopy; /* Number of bytes to copy in a given op. */ ASSERT(data != NULL); ! if(datalen == 0) return 0; ASSERT(datalen > 0); - BufferCheck(bufPtr); end = bufPtr->data + bufPtr->size; --- 1726,1736 ---- int canCopy; /* Number of bytes to copy in a given op. */ ASSERT(data != NULL); ! if(datalen == 0) { return 0; + } ASSERT(datalen > 0); BufferCheck(bufPtr); end = bufPtr->data + bufPtr->size; *************** *** 1489,1496 **** bufPtr->length += canCopy; bufPtr->end += canCopy; copied += canCopy; ! if (bufPtr->end >= end) bufPtr->end = bufPtr->data; datalen -= canCopy; /* --- 1744,1752 ---- bufPtr->length += canCopy; bufPtr->end += canCopy; copied += canCopy; ! if (bufPtr->end >= end) { bufPtr->end = bufPtr->data; + } datalen -= canCopy; /* *************** *** 1522,1527 **** --- 1778,1784 ---- * *---------------------------------------------------------------------- */ + int BufferAdd(Buffer *bufPtr, char *str) { return BufferAddData(bufPtr, str, strlen(str)); *************** *** 1542,1547 **** --- 1799,1805 ---- * *---------------------------------------------------------------------- */ + int BufferGetData(Buffer *bufPtr, char *data, int datalen) { char *end; *************** *** 1563,1570 **** bufPtr->length -= canCopy; bufPtr->begin += canCopy; copied += canCopy; ! if (bufPtr->begin >= end) bufPtr->begin = bufPtr->data; /* * If there's more to go, copy the second part starting from the --- 1821,1829 ---- bufPtr->length -= canCopy; bufPtr->begin += canCopy; copied += canCopy; ! if (bufPtr->begin >= end) { bufPtr->begin = bufPtr->data; + } /* * If there's more to go, copy the second part starting from the *************** *** 1599,1604 **** --- 1858,1864 ---- * *---------------------------------------------------------------------- */ + void BufferMove(Buffer *toPtr, Buffer *fromPtr, int len) { int fromLen, toLen, toMove; *************** *** 1621,1628 **** toMove = min(toMove, len); ASSERT(toMove >= 0); ! if(toMove == 0) return; memcpy(toPtr->end, fromPtr->begin, toMove); BufferToss(fromPtr, toMove); --- 1881,1889 ---- toMove = min(toMove, len); ASSERT(toMove >= 0); ! if(toMove == 0) { return; + } memcpy(toPtr->end, fromPtr->begin, toMove); BufferToss(fromPtr, toMove); *************** *** 1647,1652 **** --- 1908,1914 ---- * *---------------------------------------------------------------------- */ + void BufferDStringAppend(DString *strPtr, Buffer *bufPtr, int len) { int fromLen; *************** *** 1668,1675 **** /* * Done with the generic stuff. Here starts the FastCGI stuff. */ - - typedef request_rec WS_Request; #define FCGI_MAGIC_TYPE "application/x-httpd-fcgi" --- 1930,1935 ---- *************** *** 1677,1682 **** --- 1937,1943 ---- #define FCGI_DEFAULT_RESTART_DELAY 5 #define FCGI_MAX_PROCESSES 20 #define FCGI_ERRMSG_LEN 200 + /* * If the exec of a FastCGI app fails, this is the minimum number * of seconds to wait before retrying the exec. *************** *** 1722,1727 **** --- 1983,1996 ---- int numFailures; /* num restarts due to exit failure */ OS_IpcAddress ipcAddr; /* IPC Address of FCGI app server class. * Used to connect to an app server. */ + int directive; /* AppClass or ExternalAppClass */ + DString bindname; /* Name used to create a socket */ + DString host; /* Hostname for externally managed + * FastCGI application processes */ + int port; /* Port number either for externally + * managed FastCGI applications or for + * server managed FastCGI applications, + * where server became application mngr. */ int listenFd; /* Listener socket of FCGI app server * class. Passed to app server process * at process creation. */ *************** *** 1736,1741 **** --- 2005,2012 ---- int freeOnZero; /* Deferred free; free this structure * when refCount = 0. Not used * by Apache. */ + int affinity; /* Session affinity. Not used by + * Apache server. */ int restartTimerQueued; /* = TRUE = restart timer queued. * Not used by Apache. */ int keepConnection; /* = 1 = maintain connection to app. */ *************** *** 1745,1750 **** --- 2016,2028 ---- struct _FastCgiServerInfo *next; } FastCgiServerInfo; + /* + * Value of directive field. + */ + #define APP_CLASS_UNKNOWN 0 + #define APP_CLASS_STANDARD 1 + #define APP_CLASS_EXTERNAL 2 + /* * FastCgiInfo holds the state of a particular FastCGI request. */ *************** *** 1773,1778 **** --- 2051,2065 ---- int requestId; int eofSent; } FastCgiInfo; + + /* + * Values of parseHeader field + */ + #define SCAN_CGI_READING_HEADERS 1 + #define SCAN_CGI_FINISHED 0 + #define SCAN_CGI_BAD_HEADER -1 + #define SCAN_CGI_INT_REDIRECT -2 + #define SCAN_CGI_SRV_REDIRECT -3 /* * Global variables *************** *** 1797,1821 **** * * FastCgiIpcDirCmd -- * *---------------------------------------------------------------------- */ ! static char *FastCgiIpcDirCmd(cmd_parms *cmd, void *dummy, char *arg) { uid_t uid; gid_t gid; int len; ASSERT(arg != NULL); len = strlen(arg); ASSERT(len > 0); if(*arg != '/') { return "FastCgiIpcDir: Directory path must be absolute\n"; } uid = (user_id == (uid_t) -1) ? geteuid() : user_id; gid = (group_id == (gid_t) -1) ? getegid() : group_id; if(WS_Access(arg, R_OK | W_OK | X_OK, uid, gid)) { return "FastCgiIpcDir: Need read/write/exec permission on directory\n"; } ipcDir = Malloc(len + 1); strcpy(ipcDir, arg); while(len > 1 && ipcDir[len-1] == '/') { --- 2084,2118 ---- * * FastCgiIpcDirCmd -- * + * Sets up the directory into which Unix domain sockets + * that are used for local communication will be deposited. + * + * Results: + * NULL or an error message + * *---------------------------------------------------------------------- */ ! ! const char *FastCgiIpcDirCmd(cmd_parms *cmd, void *dummy, char *arg) { uid_t uid; gid_t gid; int len; + ASSERT(arg != NULL); len = strlen(arg); ASSERT(len > 0); if(*arg != '/') { return "FastCgiIpcDir: Directory path must be absolute\n"; } + uid = (user_id == (uid_t) -1) ? geteuid() : user_id; gid = (group_id == (gid_t) -1) ? getegid() : group_id; if(WS_Access(arg, R_OK | W_OK | X_OK, uid, gid)) { return "FastCgiIpcDir: Need read/write/exec permission on directory\n"; } + ipcDir = Malloc(len + 1); strcpy(ipcDir, arg); while(len > 1 && ipcDir[len-1] == '/') { *************** *** 1832,1861 **** * * Appends a socket path name to an empty DString. * The name is formed from the directory specified to ! * the FastCgiIpcDir directive, followed by ! * "OM_WS_N.pid" where N is a sequence number and pid ! * is the current process ID. * * Results: * The value of the socket path name. * * Side effects: ! * Appends to the DString, increments the sequence number. * *---------------------------------------------------------------------- */ static int bindPathExtInt = 1; ! char *MakeSocketName(Tcl_DString *dsPtr) { char bindPathExt[32]; ASSERT(DStringLength(dsPtr) == 0); DStringAppend(dsPtr, ipcDir, -1); ! DStringAppend(dsPtr, "/OM_WS_", -1); ! sprintf(bindPathExt, "%d.%d", bindPathExtInt, (int)getpid()); ! bindPathExtInt++; ! return DStringAppend(dsPtr, bindPathExt, -1); ! } /* *---------------------------------------------------------------------- --- 2129,2170 ---- * * Appends a socket path name to an empty DString. * The name is formed from the directory specified to ! * the FastCgiIpcDir directive, followed by either ! * (1) the name parameter, if it is not NULL ! * (2) "OM_WS_N.pid" where N is a sequence number and pid ! * is the current process ID. * * Results: * The value of the socket path name. * * Side effects: ! * Appends to the DString. If name != NULL, increments the ! * sequence number. * *---------------------------------------------------------------------- */ static int bindPathExtInt = 1; ! char *MakeSocketName(char *name, int extension, Tcl_DString *dsPtr) { char bindPathExt[32]; ASSERT(DStringLength(dsPtr) == 0); DStringAppend(dsPtr, ipcDir, -1); ! DStringAppend(dsPtr, "/", -1); ! if(name != NULL) { ! DStringAppend(dsPtr, name, -1); ! } else { ! DStringAppend(dsPtr, "OM_WS_", -1); ! sprintf(bindPathExt, "%d.%d", bindPathExtInt, (int)getpid()); ! DStringAppend(dsPtr, bindPathExt, -1); ! bindPathExtInt++; ! } ! if(extension != -1) { ! sprintf(bindPathExt, ".%d", extension); ! DStringAppend(dsPtr, bindPathExt, -1); ! } ! return DStringValue(dsPtr); ! } /* *---------------------------------------------------------------------- *************** *** 1873,1881 **** --- 2182,2192 ---- * *---------------------------------------------------------------------- */ + FastCgiServerInfo *LookupFcgiServerInfo(char *ePath) { FastCgiServerInfo *info; + for(info = fastCgiServers; info != NULL; info = info->next) { const char *execPath = DStringValue(&info->execPath); if(execPath != NULL && strcmp(execPath, ePath) == 0) { *************** *** 1906,1911 **** --- 2217,2223 ---- * *---------------------------------------------------------------------- */ + static FastCgiServerInfo *CreateFcgiServerInfo(int numInstances, char *ePath) { FastCgiServerInfo *serverInfoPtr = NULL; *************** *** 1929,1938 **** --- 2241,2255 ---- serverInfoPtr->numRestarts = 0; serverInfoPtr->numFailures = 0; serverInfoPtr->ipcAddr = OS_InitIpcAddr(); + serverInfoPtr->directive = APP_CLASS_UNKNOWN; + DStringInit(&serverInfoPtr->host); + DStringInit(&serverInfoPtr->bindname); + serverInfoPtr->port = -1; serverInfoPtr->processPriority = 0; serverInfoPtr->listenFd = -1; serverInfoPtr->reqRefCount = 0; serverInfoPtr->freeOnZero = FALSE; + serverInfoPtr->affinity = FALSE; serverInfoPtr->restartTimerQueued = FALSE; serverInfoPtr->keepConnection = FALSE; serverInfoPtr->fcgiFd = -1; *************** *** 1990,1998 **** /* * Clean up server info structure resources. */ - OS_CleanupFcgiProgram(serverInfoPtr->ipcAddr); OS_FreeIpcAddr(serverInfoPtr->ipcAddr); DStringFree(&serverInfoPtr->execPath); if(serverInfoPtr->listenFd != -1) { OS_Close(serverInfoPtr->listenFd); serverInfoPtr->listenFd = -1; --- 2307,2318 ---- /* * Clean up server info structure resources. */ OS_FreeIpcAddr(serverInfoPtr->ipcAddr); DStringFree(&serverInfoPtr->execPath); + DStringFree(&serverInfoPtr->host); + DStringFree(&serverInfoPtr->bindname); + serverInfoPtr->port = -1; + serverInfoPtr->directive = APP_CLASS_UNKNOWN; if(serverInfoPtr->listenFd != -1) { OS_Close(serverInfoPtr->listenFd); serverInfoPtr->listenFd = -1; *************** *** 2023,2028 **** --- 2343,2371 ---- /* *---------------------------------------------------------------------- * + * CleanupPreviousConfig -- + * + * This routine is called by each directive in the module. + * If the directive is the first directive in the reading of + * a new configuration, the routine cleans up from any previous + * reading of a configuration by this process. + * + *---------------------------------------------------------------------- + */ + + static void CleanupPreviousConfig(void) + { + if(!readingConfig) { + while(fastCgiServers != NULL) { + FreeFcgiServerInfo(fastCgiServers); + } + readingConfig = TRUE; + } + } + + /* + *---------------------------------------------------------------------- + * * ParseApacheRawArgs -- * * Turns an Apache RAW_ARGS input into argc and argv. *************** *** 2042,2047 **** --- 2385,2391 ---- * *---------------------------------------------------------------------- */ + char **ParseApacheRawArgs(char *rawArgs, int *argcPtr) { char *input, *p; *************** *** 2059,2064 **** --- 2403,2409 ---- } input = Malloc(strlen(rawArgs) + 1); strcpy(input, rawArgs); + /* * Make one pass over the input, null-terminating each argument and * computing argc. Then allocate argv, with argc entries. argc *************** *** 2076,2081 **** --- 2421,2427 ---- break; } *p++ = '\0'; + /* * Look for a non-whitespace character. */ *************** *** 2085,2090 **** --- 2431,2437 ---- } } argv = Malloc(sizeof(char *) * argc); + /* * Make a second pass over the input to fill in argv. */ *************** *** 2106,2154 **** /* *---------------------------------------------------------------------- * ! * AppClassCmd -- ! * ! * Implements the FastCGI AppClass configuration directive. This ! * command adds a fast cgi program class which will be used by the ! * httpd parent to start/stop/maintain fast cgi server apps. ! * ! * AppClass <exec-path> [-processes N] \ ! * [-restart-delay N] [-priority N] \ ! * [-initial-env name1=value1] \ ! * [-initial-env name2=value2] ! * ! * Default values: * ! * o numProcesses will be set to 1 ! * o restartDelay will be set to 5 which means the application will not ! * be restarted any earlier than 5 seconds from when it was last ! * invoked. If the application has been up for longer than 5 seconds ! * and it fails, a single copy will be restarted immediately. Other ! * restarts within that group will be inhibited until the restart-delay ! * has elapsed. ! * o affinity will be set to FALSE (ie. no process affinity) if not ! * specified. * * Results: ! * TCL_OK or TCL_ERROR. * * Side effects: ! * Registers a new AppClass handler for FastCGI. * *---------------------------------------------------------------------- */ ! static char *AppClassCmd(cmd_parms *cmd, void *dummy, char *arg) { ! int argc; ! char **argv = NULL; ! char *execPath; ! FastCgiServerInfo *serverInfoPtr = NULL; ! int i, n; ! uid_t uid; ! gid_t gid; ! char *cvtPtr; ! char **envHead = NULL; ! char **envPtr; int envCount; char *namePtr; char *valuePtr; --- 2453,2633 ---- /* *---------------------------------------------------------------------- * ! * ConfigureLocalServer -- * ! * Configure a FastCGI server for local communication using ! * Unix domain sockets. This is used by ExternalAppClass directive ! * to configure connection point for "-socket" option. * * Results: ! * 0 on successful configure or -1 if there was an error * * Side effects: ! * New FastCGI structure is allocated. * *---------------------------------------------------------------------- */ ! ! static int ConfigureLocalServer( ! char *path, ! int affinity, ! int numInstances, ! FastCgiServerInfo *serverInfoPtr) { ! FcgiProcessInfo *processInfoPtr; ! int i; ! ! serverInfoPtr->affinity = affinity; ! serverInfoPtr->maxProcesses = numInstances; ! ! if(serverInfoPtr->affinity == FALSE) { ! if(OS_CreateLocalIpcAddr(serverInfoPtr->ipcAddr, MakeSocketName, path, -1) != 0) { ! return -1; ! } ! } else { ! processInfoPtr = serverInfoPtr->procInfo; ! for(i = 0; i < numInstances; i++) { ! if(OS_CreateLocalIpcAddr(processInfoPtr->ipcAddr, MakeSocketName, path, i+1) != 0) { ! return -1; ! } ! processInfoPtr++; ! } ! } ! return 0; ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * ConfigureTCPServer -- ! * ! * Configure a FastCGI server for communication using TCP. This is ! * used by ExternalAppClass directive to configure connection point ! * for "-host" option. The remote host is specified as 'host:port', ! * as in 'aegean.openmarket.com:666'. ! * ! * Results: ! * 0 on successful configure or -1 if there was an error ! * ! * Side effects: ! * New FastCGI structure is allocated and modifies hostSpec. ! * ! *---------------------------------------------------------------------- ! */ ! ! static int ConfigureTCPServer( ! char *hostSpec, ! int affinity, ! int numInstances, ! FastCgiServerInfo *serverInfoPtr) ! { ! FcgiProcessInfo *processInfoPtr; ! struct in_addr host; ! long port; ! char *p, *cvptr; ! int i, numHosts; ! ! /* ! * Parse the host specification string into host and port components. ! */ ! p = strchr(hostSpec, ':'); ! if(p == NULL) { ! return -1; ! } ! *p = '\0'; ! *p++; ! ! if((numHosts = ResolveHostname(hostSpec, &host)) < 0) { ! return -1; ! } ! ! /* ! * If the address lookup resolves to more than one host, this is ! * an error. The proper way to handle this is for the creator of ! * the server configuration file to specify the IP address in dotted ! * decimal notation. This will insure the proper host routing (as ! * long as someone doesn't have multiple machines with the same IP ! * address which is not legal and we can't do anything about that). ! */ ! if(numHosts > 1) { ! return -1; ! } ! ! port = strtol(p, &cvptr, 10); ! if(*cvptr != '\0' || port < 1 || port > 65535) { ! return -1; ! } ! ! /* ! * Create an info structure for the Fast CGI server (TCP type). ! */ ! DStringAppend(&serverInfoPtr->host, hostSpec, -1); ! serverInfoPtr->port = (int)port; ! serverInfoPtr->affinity = affinity; ! serverInfoPtr->maxProcesses = numInstances; ! ! if(serverInfoPtr->affinity == FALSE) { ! OS_CreateInetIpc(serverInfoPtr->ipcAddr, &host, (int)port); ! } else { ! processInfoPtr = serverInfoPtr->procInfo; ! for(i = 0; i < numInstances; i++) { ! OS_CreateInetIpc(processInfoPtr->ipcAddr, &host, (int)(port + i)); ! processInfoPtr++; ! } ! } ! return 0; ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * AppClassCmd -- ! * ! * Implements the FastCGI AppClass configuration directive. This ! * command adds a fast cgi program class which will be used by the ! * httpd parent to start/stop/maintain fast cgi server apps. ! * ! * AppClass <exec-path> [-processes N] \ ! * [-restart-delay N] [-priority N] \ ! * [-port N] [-socket sock-name] \ ! * [-initial-env name1=value1] \ ! * [-initial-env name2=value2] ! * ! * Default values: ! * ! * o numProcesses will be set to 1 ! * o restartDelay will be set to 5 which means the application will not ! * be restarted any earlier than 5 seconds from when it was last ! * invoked. If the application has been up for longer than 5 seconds ! * and it fails, a single copy will be restarted immediately. Other ! * restarts within that group will be inhibited until the restart-delay ! * has elapsed. ! * o affinity will be set to FALSE (ie. no process affinity) if not ! * specified. ! * o if both -socket and -port are omitted, server generates a name for the ! * socket used in connection. ! * ! * Results: ! * NULL or an error message. ! * ! * Side effects: ! * Registers a new AppClass handler for FastCGI. ! * ! *---------------------------------------------------------------------- ! */ ! ! const char *AppClassCmd(cmd_parms *cmd, void *dummy, char *arg) ! { ! int argc; ! char **argv = NULL; ! char *execPath; ! FastCgiServerInfo *serverInfoPtr = NULL; ! int i, n; ! uid_t uid; ! gid_t gid; ! char *cvtPtr; ! char **envHead = NULL; ! char **envPtr; int envCount; char *namePtr; char *valuePtr; *************** *** 2156,2161 **** --- 2635,2642 ---- int restartDelay = FCGI_DEFAULT_RESTART_DELAY; int processPriority = 0; int listenQueueDepth = FCGI_DEFAULT_LISTEN_Q; + char *bindname = NULL; + int portNumber = -1; int affinity = FALSE; char *errMsg = Malloc(1024); *************** *** 2164,2188 **** * server restart, clean up structures created by the previous * sequence of AppClassCmds. */ ! if(!readingConfig) { ! while(fastCgiServers != NULL) { ! /* ! * Let FreeFcgiServerInfo know that it should not try ! * to kill the application processes (they were killed ! * by SIGHUP). ! */ ! for(i = 0; i < fastCgiServers->maxProcesses; i++) { ! fastCgiServers->procInfo[i].pid = -1; ! } ! FreeFcgiServerInfo(fastCgiServers); ! } ! readingConfig = TRUE; ! } ! /* ! * Parse the raw arguments into tokens. ! * argv[0] is empty and argv[1] is the exec path. ! * Validate the exec path. ! */ argv = ParseApacheRawArgs(arg, &argc); if(argc < 2) { sprintf(errMsg, "AppClass: Too few args\n"); --- 2645,2657 ---- * server restart, clean up structures created by the previous * sequence of AppClassCmds. */ ! CleanupPreviousConfig(); ! ! /* ! * Parse the raw arguments into tokens. ! * argv[0] is empty and argv[1] is the exec path. ! * Validate the exec path. ! */ argv = ParseApacheRawArgs(arg, &argc); if(argc < 2) { sprintf(errMsg, "AppClass: Too few args\n"); *************** *** 2202,2207 **** --- 2671,2677 ---- sprintf(errMsg, "AppClass: Could not access file %s\n", execPath); goto ErrorReturn; } + /* * You'd like to create the server info structure now, but * you can't because you don't know numProcesses. So *************** *** 2257,2262 **** --- 2727,2750 ---- } listenQueueDepth = n; continue; + } else if((strcmp(argv[i], "-port") == 0)) { + if((i + 1) == argc) { + goto MissingValueReturn; + } + i++; + n = strtol(argv[i], &cvtPtr, 10); + if(*cvtPtr != '\0' || n < 1) { + goto BadValueReturn; + } + portNumber = n; + continue; + } else if((strcmp(argv[i], "-socket") == 0)) { + if((i + 1) == argc) { + goto MissingValueReturn; + } + i++; + bindname = argv[i]; + continue; } else if((strcmp(argv[i], "-initial-env") == 0)) { if((i + 1) == argc) { goto MissingValueReturn; *************** *** 2280,2285 **** --- 2768,2779 ---- goto ErrorReturn; } } /* for */ + + if((bindname != NULL) && (portNumber != -1)) { + sprintf(errMsg, + "AppClass: -port and -socket options are mutually exclusive"); + goto ErrorReturn; + } serverInfoPtr = CreateFcgiServerInfo(numProcesses, execPath); ASSERT(serverInfoPtr != NULL); DStringAppend(&serverInfoPtr->execPath, execPath, -1); *************** *** 2287,2329 **** serverInfoPtr->restartDelay = restartDelay; serverInfoPtr->processPriority = processPriority; serverInfoPtr->listenQueueDepth = listenQueueDepth; serverInfoPtr->envp = envHead; /* * Set envHead to NULL so that if there is an error below we don't * free the environment structure twice. */ envHead = NULL; /* * Create an IPC path for the AppClass. */ if(affinity == FALSE) { ! int listenFd = OS_CreateLocalIpcFd(serverInfoPtr->ipcAddr, ! serverInfoPtr->listenQueueDepth, uid, gid, MakeSocketName); if(listenFd < 0) { ! sprintf(errMsg, "AppClass: could not create local IPC socket\n"); goto ErrorReturn; ! } else { ! serverInfoPtr->listenFd = listenFd; ! /* ! * Propagate listenFd to each process so that process manager ! * doesn't have to understand affinity. ! */ ! for(i = 0; i < serverInfoPtr->maxProcesses; i++) { ! serverInfoPtr->procInfo[i].listenFd = listenFd; ! } } } Free(argv[1]); Free(argv); Free(errMsg); return NULL; ! MissingValueReturn: sprintf(errMsg, "AppClass: missing value for %s\n", argv[i]); goto ErrorReturn; ! BadValueReturn: sprintf(errMsg, "AppClass: bad value \"%s\" for %s\n", argv[i], argv[i-1]); goto ErrorReturn; ! ErrorReturn: if(serverInfoPtr != NULL) { FreeFcgiServerInfo(serverInfoPtr); } --- 2781,2840 ---- serverInfoPtr->restartDelay = restartDelay; serverInfoPtr->processPriority = processPriority; serverInfoPtr->listenQueueDepth = listenQueueDepth; + if(bindname != NULL) { + DStringAppend(&serverInfoPtr->bindname, bindname, -1); + } + serverInfoPtr->port = portNumber; serverInfoPtr->envp = envHead; + serverInfoPtr->directive = APP_CLASS_STANDARD; + /* * Set envHead to NULL so that if there is an error below we don't * free the environment structure twice. */ envHead = NULL; + /* * Create an IPC path for the AppClass. */ if(affinity == FALSE) { ! int listenFd; ! if(serverInfoPtr->port == -1) { ! /* local IPC */ ! listenFd = OS_CreateLocalIpcFd(serverInfoPtr->ipcAddr, ! serverInfoPtr->listenQueueDepth, uid, gid, ! MakeSocketName, bindname, -1); ! } else { ! /* TCP/IP */ ! listenFd = OS_CreateRemoteIpcFd(serverInfoPtr->ipcAddr, ! serverInfoPtr->port, serverInfoPtr->listenQueueDepth); ! } ! if(listenFd < 0) { ! sprintf(errMsg, "AppClass: could not create IPC socket\n"); goto ErrorReturn; ! } ! serverInfoPtr->listenFd = listenFd; ! /* ! * Propagate listenFd to each process so that process manager ! * doesn't have to understand affinity. ! */ ! for(i = 0; i < serverInfoPtr->maxProcesses; i++) { ! serverInfoPtr->procInfo[i].listenFd = listenFd; } } Free(argv[1]); Free(argv); Free(errMsg); return NULL; ! ! MissingValueReturn: sprintf(errMsg, "AppClass: missing value for %s\n", argv[i]); goto ErrorReturn; ! BadValueReturn: sprintf(errMsg, "AppClass: bad value \"%s\" for %s\n", argv[i], argv[i-1]); goto ErrorReturn; ! ErrorReturn: if(serverInfoPtr != NULL) { FreeFcgiServerInfo(serverInfoPtr); } *************** *** 2340,2345 **** --- 2851,3001 ---- /* *---------------------------------------------------------------------- * + * ExternalAppClassCmd -- + * + * Implements the FastCGI ExternalAppClass configuration directive. + * This command adds a fast cgi program class which will be used by the + * httpd parent to connect to the fastcgi process which is not managed + * by the web server and may be running on the local or remote machine. + * + * ExternalAppClass <name> [-host hostname:port] \ + * [-socket socket_path] + * + * + * Results: + * NULL or an error message. + * + * Side effects: + * Registers a new ExternalAppClass handler for FastCGI. + * + *---------------------------------------------------------------------- + */ + + const char *ExternalAppClassCmd(cmd_parms *cmd, void *dummy, char *arg) + { + int argc; + char **argv = NULL; + char *className = NULL; + char *hostPort = NULL; + char *localPath = NULL; + FastCgiServerInfo *serverInfoPtr = NULL; + int configResult = -1; + int i; + char *errMsg = Malloc(1024); + + /* + * If this is the first call to ExternalAppClassCmd since a + * server restart, clean up structures created by the previous + * sequence of ExternalAppClassCmds. + */ + CleanupPreviousConfig(); + + /* + * Parse the raw arguments into tokens. + * argv[0] is empty and argv[1] is the symbolic + * name of the connection. Note that this name + * is not used for anything but the lookup of the + * proper server. + */ + argv = ParseApacheRawArgs(arg, &argc); + if(argc < 3) { + sprintf(errMsg, "ExternalAppClass: Too few args\n"); + goto ErrorReturn; + } + className = argv[1]; + serverInfoPtr = LookupFcgiServerInfo(className); + if(serverInfoPtr != NULL) { + sprintf(errMsg, + "ExternalAppClass: Redefinition of previously \ + defined class %s\n", + className); + goto ErrorReturn; + } + + /* + * Parse out the command line arguments. + */ + for(i = 2; i < argc; i++) { + if((strcmp(argv[i], "-host") == 0)) { + if((i + 1) == argc) { + goto MissingValueReturn; + } + i++; + hostPort = argv[i]; + continue; + } else if((strcmp(argv[i], "-socket") == 0)) { + if((i+1) == argc) { + goto MissingValueReturn; + } + i++; + localPath = argv[i]; + continue; + } else { + sprintf(errMsg, "ExternalAppClass: Unknown option %s\n", argv[i]); + goto ErrorReturn; + } + } /* for */ + + /* + * Check out that we do not have any conflicts + */ + if(((hostPort != NULL) && (localPath != NULL)) || + ((hostPort == NULL) && (localPath == NULL))) { + sprintf(errMsg, "ExternalAppClass: Conflict of arguments -port \ + and -socket.\n"); + goto ErrorReturn; + } + + /* + * The following code will have to change when Apache will + * begin to support connections with affinity. Note that the + * className becomes an execPath member of the serverInfo + * structure and it used just for lookups. I also put in values + * for affinity and numInstances in order to keep most of the + * common code in sync. + */ + serverInfoPtr = CreateFcgiServerInfo(1, className); + ASSERT(serverInfoPtr != NULL); + DStringAppend(&serverInfoPtr->execPath, className, -1); + serverInfoPtr->directive = APP_CLASS_EXTERNAL; + + if(hostPort != NULL) { + configResult = ConfigureTCPServer(hostPort, FALSE, + 1, serverInfoPtr); + } else { + configResult = ConfigureLocalServer(localPath, FALSE, + 1, serverInfoPtr); + } + + if(configResult == 0) { + return NULL; + } else { + sprintf(errMsg, "ExternalAppClass: Unable to configure server\n"); + goto ErrorReturn; + } + + MissingValueReturn: + sprintf(errMsg, "ExternalAppClass: missing value for %s\n", argv[i]); + goto ErrorReturn; + BadValueReturn: + sprintf(errMsg, "ExternalAppClass: bad value \"%s\" for %s\n", + argv[i], argv[i-1]); + goto ErrorReturn; + ErrorReturn: + if(serverInfoPtr != NULL) { + FreeFcgiServerInfo(serverInfoPtr); + } + if(argv != NULL) { + Free(argv[1]); + Free(argv); + } + return errMsg; + } + + + /* + *---------------------------------------------------------------------- + * * Code related to the FastCGI process manager. * *---------------------------------------------------------------------- *************** *** 2374,2379 **** --- 3030,3055 ---- static char *errorLogPathname = NULL; static sigset_t signalsToBlock; + static FILE *FastCgiProcMgrGetErrLog(void) + { + FILE *errorLogFile = NULL; + if(errorLogPathname != NULL) { + /* + * errorLogFile = fopen(errorLogPathname, "a"), + * but work around faulty implementations of fopen (SunOS) + */ + int fd = open(errorLogPathname, O_WRONLY | O_APPEND | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if(fd >= 0) { + errorLogFile = fdopen(fd, "a"); + } + } + if(errorLogFile == NULL) { + errorLogFile = fopen("/dev/null", "a"); + } + return errorLogFile; + } + static void FastCgiProcMgrSignalHander(int signo) { if(signo == SIGTERM) { *************** *** 2386,2397 **** --- 3062,3075 ---- static int CaughtSigTerm(void) { int result; + /* * Start of critical region for caughtSigTerm */ sigprocmask(SIG_BLOCK, &signalsToBlock, NULL); result = caughtSigTerm; sigprocmask(SIG_UNBLOCK, &signalsToBlock, NULL); + /* * End of critical region for caughtSigTerm */ *************** *** 2408,2429 **** int waitStatus, status, callWaitPid; sigset_t sigMask; pid_t myPid = getpid(); ! FILE *errorLogFile = NULL; - if(errorLogPathname != NULL) { - /* - * errorLogFile = fopen(errorLogPathname, "a"), - * but work around faulty implementations of fopen (SunOS) - */ - int fd = open(errorLogPathname, O_WRONLY | O_APPEND | O_CREAT, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - if (fd >= 0) { - errorLogFile = fdopen(fd, "a"); - } - } - if(errorLogFile == NULL) { - errorLogFile = fopen("/dev/null", "a"); - } /* * If the Apache parent process is running as root, * consider reducing privileges now. --- 3086,3093 ---- int waitStatus, status, callWaitPid; sigset_t sigMask; pid_t myPid = getpid(); ! FILE *errorLogFile = FastCgiProcMgrGetErrLog(); /* * If the Apache parent process is running as root, * consider reducing privileges now. *************** *** 2431,2440 **** if(geteuid() == 0 && setuid(user_id) == -1) { fprintf(errorLogFile, "[%s] mod_fastcgi: Unable to change uid\n", ! get_time()); fflush(errorLogFile); exit(1); } /* * Set up to handle SIGTERM, SIGCHLD, and SIGALRM. */ --- 3095,3106 ---- if(geteuid() == 0 && setuid(user_id) == -1) { fprintf(errorLogFile, "[%s] mod_fastcgi: Unable to change uid\n", ! " exiting\n", ! get_time()); fflush(errorLogFile); exit(1); } + /* * Set up to handle SIGTERM, SIGCHLD, and SIGALRM. */ *************** *** 2449,2454 **** --- 3115,3121 ---- ASSERT(OS_Signal(SIGTERM, FastCgiProcMgrSignalHander) != SIG_ERR); ASSERT(OS_Signal(SIGCHLD, FastCgiProcMgrSignalHander) != SIG_ERR); ASSERT(OS_Signal(SIGALRM, FastCgiProcMgrSignalHander) != SIG_ERR); + /* * s->procInfo[i].pid == 0 means we've never tried to start this one. */ *************** *** 2458,2463 **** --- 3125,3131 ---- s->procInfo[i].pid = 0; } } + /* * Loop until SIGTERM */ *************** *** 2466,2477 **** int sleepSeconds = INT_MAX; pid_t childPid; int waitStatus; /* * Examine each configured AppClass for a process that needs * starting. Compute the earliest time when the start should ! * be attempted, starting it now if the time has passed. */ for(s = fastCgiServers; s != NULL; s = s->next) { for(i = 0; i < s->maxProcesses; i++) { if(s->procInfo[i].pid <= 0) { time_t restartTime = s->restartTime + s->restartDelay; --- 3134,3151 ---- int sleepSeconds = INT_MAX; pid_t childPid; int waitStatus; + /* * Examine each configured AppClass for a process that needs * starting. Compute the earliest time when the start should ! * be attempted, starting it now if the time has passed. Also, ! * remember that we do NOT need to restart externally managed ! * FastCGI applications. */ for(s = fastCgiServers; s != NULL; s = s->next) { + if(s->directive == APP_CLASS_EXTERNAL) { + continue; + } for(i = 0; i < s->maxProcesses; i++) { if(s->procInfo[i].pid <= 0) { time_t restartTime = s->restartTime + s->restartDelay; *************** *** 2490,2496 **** s->procInfo[i].listenFd, s->processPriority, DStringValue(&s->execPath), ! s->envp); if(status != 0) { fprintf(errorLogFile, "[%s] mod_fastcgi: AppClass %s" --- 3164,3171 ---- s->procInfo[i].listenFd, s->processPriority, DStringValue(&s->execPath), ! s->envp, ! FastCgiProcMgrGetErrLog); if(status != 0) { fprintf(errorLogFile, "[%s] mod_fastcgi: AppClass %s" *************** *** 2511,2517 **** " restarted with pid %d.\n", get_time(), DStringValue(&s->execPath), ! s->procInfo[i].pid); fflush(errorLogFile); } ASSERT(s->procInfo[i].pid > 0); --- 3186,3192 ---- " restarted with pid %d.\n", get_time(), DStringValue(&s->execPath), ! (int)s->procInfo[i].pid); fflush(errorLogFile); } ASSERT(s->procInfo[i].pid > 0); *************** *** 2521,2527 **** } } } ! /* * Start of critical region for caughtSigChld and caughtSigTerm. */ sigprocmask(SIG_BLOCK, &signalsToBlock, NULL); --- 3196,3203 ---- } } } ! ! /* * Start of critical region for caughtSigChld and caughtSigTerm. */ sigprocmask(SIG_BLOCK, &signalsToBlock, NULL); *************** *** 2541,2546 **** --- 3217,3223 ---- callWaitPid = caughtSigChld; caughtSigChld = FALSE; sigprocmask(SIG_UNBLOCK, &signalsToBlock, NULL); + /* * End of critical region for caughtSigChld and caughtSigTerm. */ *************** *** 2550,2555 **** --- 3227,3233 ---- */ continue; } + /* * We've caught SIGCHLD, so poll for signal notifications * using waitpid. If a child has died, write a log message *************** *** 2564,2569 **** --- 3242,3250 ---- break; } for(s = fastCgiServers; s != NULL; s = s->next) { + if(s->directive == APP_CLASS_EXTERNAL) { + continue; + } for(i = 0; i < s->maxProcesses; i++) { if(s->procInfo[i].pid == childPid) { goto ChildFound; *************** *** 2576,2589 **** fprintf(errorLogFile, "[%s] mod_fastcgi: AppClass %s pid %d terminated" " by calling exit with status = %d.\n", ! get_time(), DStringValue(&s->execPath), childPid, WEXITSTATUS(waitStatus)); } else { ASSERT(WIFSIGNALED(waitStatus)); fprintf(errorLogFile, "[%s] mod_fastcgi: AppClass %s pid %d terminated" " due to uncaught signal %d.\n", ! get_time(), DStringValue(&s->execPath), childPid, WTERMSIG(waitStatus)); } s->procInfo[i].pid = -1; --- 3257,3270 ---- fprintf(errorLogFile, "[%s] mod_fastcgi: AppClass %s pid %d terminated" " by calling exit with status = %d.\n", ! get_time(), DStringValue(&s->execPath), (int)childPid, WEXITSTATUS(waitStatus)); } else { ASSERT(WIFSIGNALED(waitStatus)); fprintf(errorLogFile, "[%s] mod_fastcgi: AppClass %s pid %d terminated" " due to uncaught signal %d.\n", ! get_time(), DStringValue(&s->execPath), (int)childPid, WTERMSIG(waitStatus)); } s->procInfo[i].pid = -1; *************** *** 2591,2601 **** fflush(errorLogFile); } /* for (;;) */ } /* for (;;) */ ! ProcessSigTerm: /* * Kill off the children, then exit. */ for(s = fastCgiServers; s != NULL; s = s->next) { for(i = 0; i < s->maxProcesses; i++) { if(s->procInfo[i].pid > 0) { kill(s->procInfo[i].pid, SIGTERM); --- 3272,3286 ---- fflush(errorLogFile); } /* for (;;) */ } /* for (;;) */ ! ! ProcessSigTerm: /* * Kill off the children, then exit. */ for(s = fastCgiServers; s != NULL; s = s->next) { + if(s->directive == APP_CLASS_EXTERNAL) { + continue; + } for(i = 0; i < s->maxProcesses; i++) { if(s->procInfo[i].pid > 0) { kill(s->procInfo[i].pid, SIGTERM); *************** *** 2624,2630 **** void ModFastCgiInit(server_rec *s, pool *p) { if(s->error_fname != NULL) { ! errorLogPathname = server_root_relative(p, s->error_fname); } if(fastCgiServers != NULL) { ASSERT(readingConfig); --- 3309,3315 ---- void ModFastCgiInit(server_rec *s, pool *p) { if(s->error_fname != NULL) { ! errorLogPathname = StringCopy(server_root_relative(p, s->error_fname)); } if(fastCgiServers != NULL) { ASSERT(readingConfig); *************** *** 2696,2704 **** * *---------------------------------------------------------------------- */ ! static void MakeBeginRequestBody(int role, ! int keepConnection, ! FCGI_BeginRequestBody *body) { ASSERT((role >> 16) == 0); body->roleB1 = (role >> 8) & 0xff; --- 3381,3391 ---- * *---------------------------------------------------------------------- */ ! ! static void MakeBeginRequestBody( ! int role, ! int keepConnection, ! FCGI_BeginRequestBody *body) { ASSERT((role >> 16) == 0); body->roleB1 = (role >> 8) & 0xff; *************** *** 2721,2726 **** --- 3408,3414 ---- * *---------------------------------------------------------------------- */ + static void SendBeginRequest(FastCgiInfo *infoPtr) { FCGI_BeginRequestBody body; *************** *** 2756,2766 **** * *---------------------------------------------------------------------- */ static void FCGIUtil_BuildNameValueHeader( int nameLen, int valueLen, unsigned char *headerBuffPtr, ! int *headerLenPtr) { unsigned char *startHeaderBuffPtr = headerBuffPtr; ASSERT(nameLen >= 0); --- 3444,3456 ---- * *---------------------------------------------------------------------- */ + static void FCGIUtil_BuildNameValueHeader( int nameLen, int valueLen, unsigned char *headerBuffPtr, ! int *headerLenPtr) ! { unsigned char *startHeaderBuffPtr = headerBuffPtr; ASSERT(nameLen >= 0); *************** *** 2800,2805 **** --- 3490,3496 ---- * *---------------------------------------------------------------------- */ + static void SendEnvironment(WS_Request *reqPtr, FastCgiInfo *infoPtr) { int headerLen, nameLen, valueLen; *************** *** 2837,2843 **** } SendPacketHeader(infoPtr, FCGI_PARAMS, 0); } - /* *---------------------------------------------------------------------- --- 3528,3533 ---- *************** *** 2859,2864 **** --- 3549,3555 ---- * *---------------------------------------------------------------------- */ + static void ClientToCgiBuffer(FastCgiInfo *infoPtr) { int movelen; *************** *** 2871,2876 **** --- 3562,3568 ---- if(infoPtr->eofSent) { return; } + /* * If there's some client data and room for at least one byte * of data in the output buffer (after protocol overhead), then *************** *** 2883,2888 **** --- 3575,3581 ---- SendPacketHeader(infoPtr, FCGI_STDIN, movelen); BufferMove(infoPtr->outbufPtr, infoPtr->reqInbufPtr, movelen); } + /* * If all the client data has been sent, and there's room * in the output buffer, indicate EOF. *************** *** 2914,2919 **** --- 3607,3613 ---- * *---------------------------------------------------------------------- */ + static int CgiToClientBuffer(FastCgiInfo *infoPtr) { FCGI_Header header; *************** *** 2924,2931 **** * State #1: looking for the next complete packet header. */ if(infoPtr->gotHeader == FALSE) { ! if(BufferLength(infoPtr->inbufPtr) < sizeof(FCGI_Header)) return OK; BufferGetData(infoPtr->inbufPtr, (char *) &header, sizeof(FCGI_Header)); /* --- 3618,3626 ---- * State #1: looking for the next complete packet header. */ if(infoPtr->gotHeader == FALSE) { ! if(BufferLength(infoPtr->inbufPtr) < sizeof(FCGI_Header)) { return OK; + } BufferGetData(infoPtr->inbufPtr, (char *) &header, sizeof(FCGI_Header)); /* *************** *** 2941,2946 **** --- 3636,3642 ---- infoPtr->gotHeader = TRUE; infoPtr->paddingLen = header.paddingLength; } + /* * State #2: got a header, and processing packet bytes. */ *************** *** 2949,2964 **** switch(infoPtr->packetType) { case FCGI_STDOUT: if(len > 0) { ! if(infoPtr->parseHeader) ! BufferDStringAppend(infoPtr->header, ! infoPtr->inbufPtr, len); ! else { ! len = min(BufferFree(infoPtr->reqOutbufPtr), len); ! if (len > 0) ! BufferMove(infoPtr->reqOutbufPtr, ! infoPtr->inbufPtr, len); ! else ! return OK; } infoPtr->dataLen -= len; } --- 3645,3667 ---- switch(infoPtr->packetType) { case FCGI_STDOUT: if(len > 0) { ! switch(infoPtr->parseHeader) { ! case SCAN_CGI_READING_HEADERS: ! BufferDStringAppend(infoPtr->header, ! infoPtr->inbufPtr, len); ! break; ! case SCAN_CGI_FINISHED: ! len = min(BufferFree(infoPtr->reqOutbufPtr), len); ! if(len > 0) { ! BufferMove(infoPtr->reqOutbufPtr, ! infoPtr->inbufPtr, len); ! } else { ! return OK; ! } ! break; ! default: ! /* Toss data on the floor */ ! break; } infoPtr->dataLen -= len; } *************** *** 2973,2979 **** case FCGI_END_REQUEST: if(!infoPtr->readingEndRequestBody) { if(infoPtr->dataLen != sizeof(FCGI_EndRequestBody)) { - infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN); sprintf(infoPtr->errorMsg, "mod_fastcgi: FastCGI protocol error -" " FCGI_END_REQUEST record body size %d !=" --- 3676,3681 ---- *************** *** 2993,2999 **** /* * XXX: What to do with FCGI_OVERLOADED? */ - infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN); sprintf(infoPtr->errorMsg, "mod_fastcgi: FastCGI protocol error -" " FCGI_END_REQUEST record protocolStatus %d !=" --- 3695,3700 ---- *************** *** 3021,3026 **** --- 3722,3728 ---- infoPtr->dataLen -= len; break; } /* switch */ + /* * Discard padding, then start looking for * the next header. *************** *** 3052,3057 **** --- 3754,3764 ---- * If the end of the string is reached, return a pointer to the * end of the string. * + * If the FIRST character(s) in the line are '\n' or "\r\n", the + * first character is replaced with a NULL and next character + * past the newline is returned. NOTE: this condition supercedes + * the processing of RFC-822 continuation lines. + * * If continuation is set to 'TRUE', then it parses a (possible) * sequence of RFC-822 continuation lines. * *************** *** 3063,3074 **** * *---------------------------------------------------------------------- */ char *ScanLine(char *start, int continuation) { char *p = start; char *end = start; ! if(*p != '\n') { if(continuation) { while(*p != '\0') { if(*p == '\n' && p[1] != ' ' && p[1] != '\t') --- 3770,3784 ---- * *---------------------------------------------------------------------- */ + char *ScanLine(char *start, int continuation) { char *p = start; char *end = start; ! if(p[0] == '\r' && p[1] == '\n') { /* If EOL in 1st 2 chars */ ! p++; /* point to \n and stop */ ! } else if(*p != '\n') { if(continuation) { while(*p != '\0') { if(*p == '\n' && p[1] != ' ' && p[1] != '\t') *************** *** 3076,3290 **** p++; } } else { ! while(*p != '\0' && *p != '\n') p++; } } end = p; ! if(*end != '\0') end++; /* * Trim any trailing whitespace. */ ! while(isspace(p[-1]) && p > start) p--; *p = '\0'; return end; } - /* - *---------------------------------------------------------------------- - * - * HTTPTime -- - * - * Return the current time, in HTTP time format. - * - * Results: - * Returns pointer to time string, in static allocated array. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - #define TIMEBUFSIZE 256 - char *HTTPTime(struct tm *when) - { - static char str[TIMEBUFSIZE]; - - strftime(str, TIMEBUFSIZE-1, "%A, %d-%b-%y %T GMT", when); - return str; - } - #undef TIMEBUFSIZE - /* *---------------------------------------------------------------------- * ! * SendHeader -- ! * ! * Queue an HTTP header line for sending back to the client. ! * If this is an old HTTP request (HTTP/0.9) or an inner (nested) ! * request, ignore and return. ! * ! * NOTE: This routine assumes that the sent data will fit in ! * remaining space in the output buffer. ! * ! * Results: ! * None. * ! * Side effects: ! * Header information queued to be sent to client. * ! *---------------------------------------------------------------------- ! */ ! void SendHeader(FastCgiInfo *reqPtr, ...) ! { ! va_list argList; ! char *str; ! ! va_start(argList, reqPtr); ! while (TRUE) { ! str = va_arg(argList, char *); ! if(str == NULL) ! break; ! BufferAdd(reqPtr->reqOutbufPtr, str); ! ASSERT(BufferFree(reqPtr->reqOutbufPtr) > 0); ! } ! BufferAdd(reqPtr->reqOutbufPtr, "\r\n"); /* terminate */ ! ASSERT(BufferFree(reqPtr->reqOutbufPtr) > 0); ! va_end(argList); ! } ! ! /* ! *---------------------------------------------------------------------- * ! * AddHeaders -- * * Results: ! * None. * * Side effects: ! * None. * *---------------------------------------------------------------------- */ - void AddHeaders(FastCgiInfo *reqPtr, char *str) - { - BufferAdd(reqPtr->reqOutbufPtr, str); - } - - /* - *---------------------------------------------------------------------- - * - * BeginHeader -- - * - * Begin a standard HTTP header: emits opening message - * (i.e. "200 OK") and standard header matter. - * - * Results: - * None. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - void BeginHeader(FastCgiInfo *reqPtr, char *msg) - { - time_t now; - - ASSERT(BufferLength(reqPtr->reqOutbufPtr) == 0); - - now = time(NULL); - SendHeader(reqPtr, SERVER_PROTOCOL, " ", msg, NULL); - SendHeader(reqPtr, "Date: ", HTTPTime(gmtime(&now)), NULL); - SendHeader(reqPtr, "Server: ", SERVER_VERSION, NULL); - SendHeader(reqPtr, "MIME-version: 1.0", NULL); ! /* ! * We shouldn't have run out of space in the output buffer. ! */ ! ASSERT(BufferFree(reqPtr->reqOutbufPtr) > 0); ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * EndHeader -- ! * ! * Marks the end of the HTTP header: sends a blank line. ! * ! * Results: ! * None. ! * ! * Side effects: ! * None. ! * ! *---------------------------------------------------------------------- ! */ ! void EndHeader(FastCgiInfo *reqPtr) { ! SendHeader(reqPtr, "", NULL); ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * ComposeURL -- ! * ! * XXX ! * ! *---------------------------------------------------------------------- ! */ ! #define HostInfo(x) x->server ! #define HostName(x) x->server->server_hostname ! #define HostPort(x) x->server->port ! ! void ComposeURL(WS_Request *reqPtr, char *url, DString *result) ! { ! if(url[0] == '/') { ! char portStr[10]; ! if (HostInfo(reqPtr) && HostName(reqPtr)) { ! sprintf(portStr, "%d", HostPort(reqPtr)); ! DStringAppend(result, "http://", -1); ! DStringAppend(result, HostName(reqPtr), -1); ! DStringAppend(result, ":", -1); ! DStringAppend(result, portStr, -1); ! } ! } ! DStringAppend(result, url, -1); ! } ! ! /* ! *---------------------------------------------------------------------- ! * ! * ScanCGIHeader -- ! * ! * Scans the CGI header data to see if the CGI program has sent a ! * complete header. If it has, then parse the header and queue ! * the HTTP response header information back to the client. ! * ! * Results: ! * TRUE if completed without error, FALSE otherwise. ! * ! * Side effects: ! * Sets 'reqPtr->parseHeader' to TRUE if the header was parsed ! * successfully. ! * ! *---------------------------------------------------------------------- ! */ ! int ScanCGIHeader(WS_Request *reqPtr, FastCgiInfo *infoPtr) ! { ! DString status; ! DString headers; ! char *line, *next, *p, *data, *name; ! int len, flag, sent; ! int bufFree; ! ASSERT(infoPtr->parseHeader == TRUE); /* * Do we have the entire header? Scan for the blank line that --- 3786,3854 ---- p++; } } else { ! while(*p != '\0' && *p != '\n') { p++; + } } } end = p; ! if(*end != '\0') { end++; + } /* * Trim any trailing whitespace. */ ! while(isspace(p[-1]) && p > start) { p--; + } *p = '\0'; return end; } /* *---------------------------------------------------------------------- * ! * ScanCGIHeader -- * ! * Call with reqPtr->parseHeader == SCAN_CGI_READING_HEADERS ! * and initial script output in infoPtr->header. * ! * If the initial script output does not include the header ! * terminator ("\r\n\r\n") ScanCGIHeader returns with no side ! * effects, to be called again when more script output ! * has been appended to infoPtr->header. * ! * If the initial script output includes the header terminator, ! * ScanCGIHeader parses the headers and determines whether or ! * not the remaining script output will be sent to the client. ! * If so, ScanCGIHeader sends the HTTP response headers to the ! * client and copies any non-header script output to the output ! * buffer reqOutbuf. * * Results: ! * none. * * Side effects: ! * May set reqPtr->parseHeader to: ! * SCAN_CGI_FINISHED -- headers parsed, returning script response ! * SCAN_CGI_BAD_HEADER -- malformed header from script ! * (specific message placed in infoPtr->errorMsg.) ! * SCAN_CGI_INT_REDIRECT -- handler should perform internal redirect ! * SCAN_CGI_SRV_REDIRECT -- handler should return REDIRECT * *---------------------------------------------------------------------- */ ! void ScanCGIHeader(WS_Request *reqPtr, FastCgiInfo *infoPtr) { ! char *p, *next, *name, *value, *location; ! int len, flag; ! int hasContentType, hasStatus, hasLocation; ! ASSERT(infoPtr->parseHeader == SCAN_CGI_READING_HEADERS); /* * Do we have the entire header? Scan for the blank line that *************** *** 3308,3416 **** } /* ! * Return (to be called later when we have more data) if we don't have ! * and entire header. */ ! if(flag < 2) ! return TRUE; ! ! infoPtr->parseHeader = FALSE; ! DStringInit(&status); ! DStringAppend(&status, "200 OK", -1); ! DStringInit(&headers); next = DStringValue(infoPtr->header); for(;;) { ! next = ScanLine(line = next, TRUE); ! if(*line == '\0') { break; } ! if((p = strpbrk(line, " \t")) != NULL) { ! data = p + 1; ! *p = '\0'; ! } else { ! data = ""; } ! while(isspace(*data)) { ! data++; } /* ! * Handle "Location:" and "Status:" specially. ! * All other headers get passed through unmodified. */ ! if(!strcasecmp(line, "Location:")) { ! DStringTrunc(&status, 0); ! DStringAppend(&status, "302 Redirect", -1); ! DStringAppend(&headers, "Location: ", -1); /* ! * This is a deviation from the CGI/1.1 spec. ! * ! * The spec says that "virtual paths" should be treated ! * as if the client had accessed the file in the first ! * place. This usually breaks relative references in the ! * referenced document. ! * ! * XXX: do more research on this? */ ! ComposeURL(reqPtr, data, &headers); ! DStringAppend(&headers, "\r\n", -1); ! } else if(!strcasecmp(line, "Status:")) { ! DStringTrunc(&status, 0); ! DStringAppend(&status, data, -1); } else { ! if(data != NULL) { ! DStringAppend(&headers, line, -1); ! DStringAppend(&headers, " ", 1); ! DStringAppend(&headers, data, -1); ! DStringAppend(&headers, "\r\n", -1); } } } - /* ! * We're done scanning the CGI script's header output. Now ! * we have to write to the client: status, CGI header, and ! * any over-read CGI output. */ ! BeginHeader(infoPtr, DStringValue(&status)); ! DStringFree(&status); ! ! AddHeaders(infoPtr, DStringValue(&headers)); ! DStringFree(&headers); ! EndHeader(infoPtr); ! len = next - DStringValue(infoPtr->header); len = DStringLength(infoPtr->header) - len; ASSERT(len >= 0); ! bufFree = BufferFree(infoPtr->reqOutbufPtr); ! ! if (bufFree < len) { ! /* should the same fix be made in core server? */ ! int bufLen = BufferLength(infoPtr->reqOutbufPtr); ! Buffer *newBuf = BufferCreate(len + bufLen); ! BufferMove(newBuf, infoPtr->reqOutbufPtr, bufLen); ! BufferDelete(infoPtr->reqOutbufPtr); ! infoPtr->reqOutbufPtr = newBuf; ! } ! bufFree = BufferFree(infoPtr->reqOutbufPtr); ! if(bufFree == 0) ! goto fail; ! /* ! * Only send the body for methods other than HEAD. */ ! if(!infoPtr->reqPtr->header_only) { ! if(len > 0) { ! sent = BufferAddData(infoPtr->reqOutbufPtr, next, len); ! if(sent != len) ! goto fail; ! } } ! return TRUE; ! fail: ! return FALSE; } /* --- 3872,4041 ---- } /* ! * Return (to be called later when we have more data) ! * if we don't have an entire header. */ ! if(flag < 2) { ! return; ! } ! /* ! * Parse all the headers. ! */ ! infoPtr->parseHeader = SCAN_CGI_FINISHED; ! hasContentType = hasStatus = hasLocation = FALSE; next = DStringValue(infoPtr->header); for(;;) { ! next = ScanLine(name = next, TRUE); ! if(*name == '\0') { break; } ! if((p = strchr(name, ':')) == NULL) { ! goto BadHeader; ! } ! value = p + 1; ! while(p != name && isspace(*(p - 1))) { ! p--; } ! if(p == name) { ! goto BadHeader; } + *p = '\0'; + if(strpbrk(name, " \t") != NULL) { + *p = ' '; + goto BadHeader; + } + while(isspace(*value)) { + value++; + } + /* ! * name is the trimmed header name and value the ! * trimmed header value. Perform checks, then record value ! * in the request data structure. */ ! if(!strcasecmp(name, "Content-type")) { ! if(hasContentType) { ! goto DuplicateNotAllowed; ! } ! hasContentType = TRUE; ! reqPtr->content_type = pstrdup(reqPtr->pool, value); ! } else if(!strcasecmp(name, "Status")) { ! int statusValue = strtol(value, NULL, 10); ! if(hasStatus) { ! goto DuplicateNotAllowed; ! } else if(statusValue < 0) { ! goto BadStatusValue; ! } ! hasStatus = TRUE; ! reqPtr->status = statusValue; ! reqPtr->status_line = pstrdup(reqPtr->pool, value); ! } else if(!strcasecmp(name, "Location")) { ! if(hasLocation) { ! goto DuplicateNotAllowed; ! } ! hasLocation = TRUE; ! table_set(reqPtr->headers_out, "Location", value); ! } else { /* ! * Don't merge headers. If the script wants them ! * merged, the script can do the merging. */ ! table_add(reqPtr->err_headers_out, name, value); ! } ! } ! /* ! * Who responds, this handler or Apache? ! */ ! if(hasLocation) { ! location = table_get(reqPtr->headers_out, "Location"); ! if(location[0] == '/') { ! /* ! * Location is an absolute path. This handler will ! * consume all script output, then have Apache perform an ! * internal redirect. ! */ ! infoPtr->parseHeader = SCAN_CGI_INT_REDIRECT; ! return; } else { ! /* ! * Location is an absolute URL. If the script didn't ! * produce a Content-type header, this handler will ! * consume all script output and then have Apache generate ! * its standard redirect response. Otherwise this handler ! * will transmit the script's response. ! */ ! if(!hasContentType) { ! infoPtr->parseHeader = SCAN_CGI_SRV_REDIRECT; ! return; ! } else { ! reqPtr->status = REDIRECT; ! if (!hasStatus) { ! reqPtr->status_line = ! pstrdup(reqPtr->pool, "302 Moved Temporarily"); ! } } } } /* ! * We're responding. Send headers, buffer excess script output. */ ! send_http_header(reqPtr); ! if(reqPtr->header_only) { ! return; ! } len = next - DStringValue(infoPtr->header); len = DStringLength(infoPtr->header) - len; ASSERT(len >= 0); + if(BufferFree(infoPtr->reqOutbufPtr) < len) { + /* + * XXX: Since headers don't pass through reqOutbuf anymore, + * the following code appears unnecessary. But does Open Market + * server have a lurking problem here? + */ + int bufLen = BufferLength(infoPtr->reqOutbufPtr); + Buffer *newBuf = BufferCreate(len + bufLen); + BufferMove(newBuf, infoPtr->reqOutbufPtr, bufLen); + BufferDelete(infoPtr->reqOutbufPtr); + infoPtr->reqOutbufPtr = newBuf; + } + ASSERT(BufferFree(infoPtr->reqOutbufPtr) >= len); + if(len > 0) { + int sent = BufferAddData(infoPtr->reqOutbufPtr, next, len); + ASSERT(sent == len); + } + return; ! BadHeader: /* ! * Log an informative message, but only log first line of ! * a multi-line header */ ! if((p = strpbrk(name, "\r\n")) != NULL) { ! *p = '\0'; } ! Free(infoPtr->errorMsg); ! infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN + strlen(name)); ! sprintf(infoPtr->errorMsg, ! "mod_fastcgi: Malformed response header from app: '%s'", name); ! goto ErrorReturn; ! ! DuplicateNotAllowed: ! sprintf(infoPtr->errorMsg, ! "mod_fastcgi: Duplicate CGI response header '%s'" ! " not allowed", name); ! goto ErrorReturn; ! ! BadStatusValue: ! Free(infoPtr->errorMsg); ! infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN + strlen(value)); ! sprintf(infoPtr->errorMsg, ! "mod_fastcgi: Invalid Status value '%s'", value); ! goto ErrorReturn; ! ErrorReturn: ! infoPtr->parseHeader = SCAN_CGI_BAD_HEADER; ! return; } /* *************** *** 3437,3442 **** --- 4062,4068 ---- * *---------------------------------------------------------------------- */ + static void FillOutbuf(WS_Request *reqPtr, FastCgiInfo *infoPtr) { char *end; *************** *** 3480,3489 **** --- 4106,4117 ---- * *---------------------------------------------------------------------- */ + static void DrainReqOutbuf(WS_Request *reqPtr, FastCgiInfo *infoPtr) { char *begin; int count; + BufferPeekToss(infoPtr->reqOutbufPtr, &begin, &count); if(count == 0) { return; *************** *** 3507,3512 **** --- 4135,4149 ---- * done with the request. This avoids the FastCGI application * receiving SIGPIPE. * + * If the FastCGI application sends a bad header, FastCGIDoWork + * continues reading from the application but sends no response + * to the client (returns SERVER_ERROR.) + * + * If the FastCGI application requests an internal redirect, + * or requests a redirect response without returning content, + * FastCGIDoWork sends no response and returns OK; the variable + * infoPtr->parseHeader tells the story. + * * Results: * OK or SERVER_ERROR * *************** *** 3515,3520 **** --- 4152,4158 ---- * *---------------------------------------------------------------------- */ + static int FastCgiDoWork(WS_Request *reqPtr, FastCgiInfo *infoPtr) { struct timeval timeOut, *timeOutPtr; *************** *** 3535,3540 **** --- 4173,4179 ---- if(!infoPtr->eofSent) { FillOutbuf(reqPtr, infoPtr); } + /* * To avoid deadlock, don't do a blocking select to write to * the FastCGI application without selecting to read from the *************** *** 3597,3626 **** if(infoPtr->exitStatusSet) { keepReadingFromFcgiApp = FALSE; } ! if(infoPtr->parseHeader) { ! if(!ScanCGIHeader(reqPtr, infoPtr)) { ! goto BadHeader; ! } } } /* while */ ! if(infoPtr->parseHeader) { ! goto BadHeader; } ! bflush(reqPtr->connection->client); ! bgetopt(reqPtr->connection->client, BO_BYTECT, &reqPtr->bytes_sent); ! /* ! * XXX: This would be the place to check the error status ! * of the connection to the client, if indeed we care. ! */ ! return OK; ! BadHeader: ! infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN); sprintf(infoPtr->errorMsg, ! "mod_fastcgi: Malformed CGI or HTTP header from FastCGI app"); return SERVER_ERROR; ! AppIoError: /* No strerror prototype on SunOS? */ fromStrerror = (char *) strerror(errno); infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN + strlen(fromStrerror)); sprintf(infoPtr->errorMsg, "mod_fastcgi: OS error '%s' while communicating with app", --- 4236,4273 ---- if(infoPtr->exitStatusSet) { keepReadingFromFcgiApp = FALSE; } ! if(infoPtr->parseHeader == SCAN_CGI_READING_HEADERS) { ! ScanCGIHeader(reqPtr, infoPtr); } } /* while */ ! switch(infoPtr->parseHeader) { ! case SCAN_CGI_FINISHED: ! bflush(reqPtr->connection->client); ! bgetopt(reqPtr->connection->client, ! BO_BYTECT, &reqPtr->bytes_sent); ! return OK; ! case SCAN_CGI_READING_HEADERS: ! goto UnterminatedHeader; ! case SCAN_CGI_BAD_HEADER: ! return SERVER_ERROR; ! case SCAN_CGI_INT_REDIRECT: ! case SCAN_CGI_SRV_REDIRECT: ! return OK; ! default: ! ASSERT(FALSE); } ! ! UnterminatedHeader: sprintf(infoPtr->errorMsg, ! "mod_fastcgi: Unterminated CGI response headers," ! " %d bytes received from app", ! DStringLength(infoPtr->header)); return SERVER_ERROR; ! ! AppIoError: /* No strerror prototype on SunOS? */ fromStrerror = (char *) strerror(errno); + Free(infoPtr->errorMsg); infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN + strlen(fromStrerror)); sprintf(infoPtr->errorMsg, "mod_fastcgi: OS error '%s' while communicating with app", *************** *** 3629,3642 **** } /* ! * Cleans up data associated with a request. */ void FcgiCleanUp(FastCgiInfo *infoPtr) { if(infoPtr == NULL) { return; } if(DStringLength(infoPtr->errorOut) > 0) { fprintf(infoPtr->reqPtr->server->error_log, "[%s] mod_fastcgi: stderr output from %s: '%s'\n", get_time(), infoPtr->reqPtr->filename, --- 4276,4306 ---- } /* ! *---------------------------------------------------------------------- ! * ! * FcgiCleanUp -- ! * ! * Cleanup the resources ! * ! * Results: ! * none. ! * ! * Side effects: ! * Free memory. ! * ! *---------------------------------------------------------------------- */ + void FcgiCleanUp(FastCgiInfo *infoPtr) { if(infoPtr == NULL) { return; } if(DStringLength(infoPtr->errorOut) > 0) { + /* + * Would like to call log_reason here, but log_reason + * says "access failed" which isn't necessarily so. + */ fprintf(infoPtr->reqPtr->server->error_log, "[%s] mod_fastcgi: stderr output from %s: '%s'\n", get_time(), infoPtr->reqPtr->filename, *************** *** 3671,3676 **** --- 4335,4341 ---- * *---------------------------------------------------------------------- */ + static int FastCgiHandler(WS_Request *reqPtr) { FastCgiServerInfo *serverInfoPtr; *************** *** 3679,3694 **** --- 4344,4362 ---- char *msg = NULL; int status; + no2slash(reqPtr->filename); serverInfoPtr = LookupFcgiServerInfo(reqPtr->filename); if (serverInfoPtr == NULL) { log_reason("mod_fastcgi: No AppClass directive for requested file", reqPtr->filename, reqPtr); return NOT_FOUND; } + status = setup_client_block(reqPtr, REQUEST_CHUNKED_ERROR); if(status != OK) { return status; } + /* * Allocate and initialize FastCGI private data to augment the request * structure. *************** *** 3700,3707 **** infoPtr->gotHeader = FALSE; infoPtr->reqInbufPtr = BufferCreate(SERVER_BUFSIZE); infoPtr->reqOutbufPtr = BufferCreate(SERVER_BUFSIZE); ! infoPtr->errorMsg = NULL; ! infoPtr->parseHeader = TRUE; infoPtr->header = (DString *) malloc(sizeof(DString)); infoPtr->errorOut = (DString *) malloc(sizeof(DString)); infoPtr->reqPtr = reqPtr; --- 4368,4375 ---- infoPtr->gotHeader = FALSE; infoPtr->reqInbufPtr = BufferCreate(SERVER_BUFSIZE); infoPtr->reqOutbufPtr = BufferCreate(SERVER_BUFSIZE); ! infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN); ! infoPtr->parseHeader = SCAN_CGI_READING_HEADERS; infoPtr->header = (DString *) malloc(sizeof(DString)); infoPtr->errorOut = (DString *) malloc(sizeof(DString)); infoPtr->reqPtr = reqPtr; *************** *** 3718,3729 **** --- 4386,4399 ---- SendBeginRequest(infoPtr); SendEnvironment(reqPtr, infoPtr); + /* * Read as much as possible from the client now, before connecting * to the FastCGI application. */ soft_timeout("read script input or send script output", reqPtr); FillOutbuf(reqPtr, infoPtr); + /* * Open a connection to the FastCGI application. */ *************** *** 3736,3774 **** ipcAddrPtr->addrLen) < 0) { goto ConnectionErrorReturn; } ! if((status = FastCgiDoWork(reqPtr, infoPtr)) != OK) { goto ErrorReturn; } ! kill_timeout(reqPtr); ! FcgiCleanUp(infoPtr); ! return OK; ! ConnectionErrorReturn: msg = (char *) strerror(errno); if (msg == NULL) { msg = "errno out of range"; } infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN + strlen(msg)); sprintf(infoPtr->errorMsg, "mod_fastcgi: Could not connect to application," " OS error '%s'", msg); ! ErrorReturn: log_reason(infoPtr->errorMsg, reqPtr->filename, reqPtr); FcgiCleanUp(infoPtr); return SERVER_ERROR; } command_rec fastcgi_cmds[] = { { "FastCgiIpcDir", FastCgiIpcDirCmd, NULL, RSRC_CONF, TAKE1, NULL }, ! { "AppClass", AppClassCmd, NULL, RSRC_CONF, RAW_ARGS, ! NULL }, { NULL } }; handler_rec fastcgi_handlers[] = { { FCGI_MAGIC_TYPE, FastCgiHandler }, { NULL } }; --- 4406,4460 ---- ipcAddrPtr->addrLen) < 0) { goto ConnectionErrorReturn; } ! status = FastCgiDoWork(reqPtr, infoPtr); ! kill_timeout(reqPtr); ! if(status != OK) { goto ErrorReturn; + }; + switch(infoPtr->parseHeader) { + case SCAN_CGI_INT_REDIRECT: + internal_redirect_handler( + table_get(reqPtr->headers_out, "Location"), reqPtr); + break; + case SCAN_CGI_SRV_REDIRECT: + status = REDIRECT; + break; } ! goto CleanupReturn; ! ! ConnectionErrorReturn: msg = (char *) strerror(errno); if (msg == NULL) { msg = "errno out of range"; } + Free(infoPtr->errorMsg); infoPtr->errorMsg = Malloc(FCGI_ERRMSG_LEN + strlen(msg)); sprintf(infoPtr->errorMsg, "mod_fastcgi: Could not connect to application," " OS error '%s'", msg); ! ErrorReturn: log_reason(infoPtr->errorMsg, reqPtr->filename, reqPtr); FcgiCleanUp(infoPtr); return SERVER_ERROR; + + CleanupReturn: + FcgiCleanUp(infoPtr); + return status; } command_rec fastcgi_cmds[] = { { "FastCgiIpcDir", FastCgiIpcDirCmd, NULL, RSRC_CONF, TAKE1, NULL }, ! { "AppClass", AppClassCmd, NULL, RSRC_CONF, RAW_ARGS, NULL }, ! { "ExternalAppClass", ExternalAppClassCmd, NULL, RSRC_CONF, RAW_ARGS, NULL }, { NULL } }; handler_rec fastcgi_handlers[] = { { FCGI_MAGIC_TYPE, FastCgiHandler }, + { "fastcgi-script", FastCgiHandler }, { NULL } };