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!)
  ! &lt;Location /examples/echo&gt;
  ! SetHandler fastcgi-script
  ! &lt;/Location&gt;
  ! 
  ! # Start a FastCGI application that's accessible from other machines
  ! AppClass $FastCGI/examples/echo.fcg -port 8978
  ! &lt;Location /examples/echo.fcg&gt;
  ! SetHandler fastcgi-script
  ! &lt/Location&gt;
  ! 
  ! # 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
  ! &lt;Location /examples/remote-echo&gt;
  ! SetHandler fastcgi-script
  ! &lt/Location&gt;
  ! 
  ! # 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 }
    };
    
  
  
  

Reply via email to