[svn-inject] Installing original source of suphp
[manu/suphp.git] / src / apache / mod_suphp.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 /*
60  * mod_suphp is based on mod_cgi from the original Apache sources
61  * Code for authorization environment variables is partly taken from mod_php
62  *
63  * mod_suphp was written by Sebastian Marsching <sebastian@marsching.com>
64  * Feel free to contact me if you have bug-reports or suggestions
65  */
66
67 #include "httpd.h"
68 #include "http_config.h"
69 #include "http_request.h"
70 #include "http_core.h"
71 #include "http_protocol.h"
72 #include "http_main.h"
73 #include "http_log.h"
74 #include "util_script.h"
75 #include "http_conf_globals.h"
76
77 #ifndef PATH_TO_SUPHP
78 #define PATH_TO_SUPHP "/usr/sbin/suphp"
79 #endif
80
81 module MODULE_VAR_EXPORT suphp_module;
82
83 /* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
84  * in ScriptAliased directories, which means we need to know if this
85  * request came through ScriptAlias or not... so the Alias module
86  * leaves a note for us.
87  */
88
89 static int is_scriptaliased(request_rec *r)
90 {
91     const char *t = ap_table_get(r->notes, "alias-forced-type");
92     return t && (!strcasecmp(t, "cgi-script"));
93 }
94
95
96 /*
97  * We have to use an own version of ap_call_exec() because suExec
98  * does not allow to execute setuid-root programs
99  */
100
101 static char **suphp_create_argv(pool *p, char *path, char *user, char *group,
102                           char *av0, const char *args)
103 {
104     int x, numwords;
105     char **av;
106     char *w;
107     int idx = 0;
108
109     /* count the number of keywords */
110
111     for (x = 0, numwords = 1; args[x]; x++) {
112         if (args[x] == '+') {
113             ++numwords;
114         }
115     }
116
117     if (numwords > APACHE_ARG_MAX - 5) {
118         numwords = APACHE_ARG_MAX - 5;  /* Truncate args to prevent overrun */
119     }
120     av = (char **) ap_palloc(p, (numwords + 5) * sizeof(char *));
121
122     if (path) {
123         av[idx++] = path;
124     }
125     if (user) {
126         av[idx++] = user;
127     }
128     if (group) {
129         av[idx++] = group;
130     }
131
132     av[idx++] = av0;
133
134     for (x = 1; x <= numwords; x++) {
135         w = ap_getword_nulls(p, &args, '+');
136         ap_unescape_url(w);
137         av[idx++] = ap_escape_shell_cmd(p, w);
138     }
139     av[idx] = NULL;
140     return av;
141 }
142
143 int suphp_call_exec(request_rec *r, child_info *pinfo, char *argv0,
144                              char **env, int shellcmd)
145 {
146     int pid = 0;
147
148 /*
149
150 #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
151     defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
152
153     core_dir_config *conf;
154     conf = (core_dir_config *) ap_get_module_config(r->per_dir_config,
155                                                     &core_module);
156
157 #endif
158
159 #ifdef RLIMIT_CPU
160     if (conf->limit_cpu != NULL) {
161         if ((setrlimit(RLIMIT_CPU, conf->limit_cpu)) != 0) {
162             ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
163                          "setrlimit: failed to set CPU usage limit");
164         }
165     }
166 #endif
167 #ifdef RLIMIT_NPROC
168     if (conf->limit_nproc != NULL) {
169         if ((setrlimit(RLIMIT_NPROC, conf->limit_nproc)) != 0) {
170             ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
171                          "setrlimit: failed to set process limit");
172         }
173     }
174 #endif
175 #if defined(RLIMIT_AS)
176     if (conf->limit_mem != NULL) {
177         if ((setrlimit(RLIMIT_AS, conf->limit_mem)) != 0) {
178             ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
179                          "setrlimit(RLIMIT_AS): failed to set memory "
180                          "usage limit");
181         }
182     }
183 #elif defined(RLIMIT_DATA)
184     if (conf->limit_mem != NULL) {
185         if ((setrlimit(RLIMIT_DATA, conf->limit_mem)) != 0) {
186             ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
187                          "setrlimit(RLIMIT_DATA): failed to set memory "
188                          "usage limit");
189         }
190     }
191 #elif defined(RLIMIT_VMEM)
192     if (conf->limit_mem != NULL) {
193         if ((setrlimit(RLIMIT_VMEM, conf->limit_mem)) != 0) {
194             ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
195                          "setrlimit(RLIMIT_VMEM): failed to set memory "
196                          "usage limit");
197         }
198     }
199 #endif
200
201 */
202
203 //          execve("/usr/sbin/suphp",
204 //                 suphp_create_argv(r->pool, NULL, NULL, NULL, "suphp", r->args),
205 //                 env);
206             execle(PATH_TO_SUPHP, "suphp", NULL, env); 
207     return (pid);
208 }
209
210
211
212 /* Configuration stuff */
213
214 #define DEFAULT_LOGBYTES 10385760
215 #define DEFAULT_BUFBYTES 1024
216
217 #define CONFIG_MODE_SERVER 1
218 #define CONFIG_MODE_DIRECTORY 2
219 #define CONFIG_MODE_COMBO 3     /* Shouldn't ever happen. */
220
221 #define SUPHP_ENGINE_OFF 0
222 #define SUPHP_ENGINE_ON 1
223 #define SUPHP_ENGINE_UNDEFINED 2
224
225 typedef struct {
226     int cmode;
227     char *logname;
228     long logbytes;
229     int bufbytes;
230     int engine;
231     char *php_config;
232 } suphp_conf;
233
234 static void *suphp_create_dir_config(pool *p, char *dirspec)
235 {
236  suphp_conf *cfg;
237  cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
238  cfg->php_config = NULL;
239  cfg->cmode = CONFIG_MODE_DIRECTORY;
240  return (void *) cfg;
241 }
242
243 static void *suphp_merge_dir_config(pool *p, void *base_conf, void *new_conf)
244 {
245  suphp_conf *parent = (suphp_conf *) base_conf;
246  suphp_conf *child = (suphp_conf *) new_conf;
247  suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
248  
249  merged->php_config = child->php_config ? child->php_config : parent->php_config;
250  
251  return merged;
252 }
253
254 static void *suphp_create_server_config(pool *p, server_rec *s)
255 {
256     suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
257
258     cfg->logname = NULL;
259     cfg->logbytes = DEFAULT_LOGBYTES;
260     cfg->bufbytes = DEFAULT_BUFBYTES;
261     cfg->engine = SUPHP_ENGINE_UNDEFINED;
262
263     return (void *) cfg;
264 }
265
266 static void *suphp_merge_server_config(pool *p, void *base_conf, void *new_conf)
267 {
268     suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
269     suphp_conf *parent = (suphp_conf *) base_conf;
270     suphp_conf *child = (suphp_conf *) new_conf;
271     
272     if (child->logname)
273     {
274      merged->logname = child->logname;
275      merged->logbytes = child->logbytes;
276      merged->bufbytes = child->bufbytes;
277     }
278     else
279     {
280      merged->logname = parent->logname;
281      merged->logbytes = parent->logbytes;
282      merged->bufbytes = parent->bufbytes;
283     }
284
285     if (child->engine != SUPHP_ENGINE_UNDEFINED)
286      merged->engine = child->engine;
287     else
288      merged->engine = parent->engine;
289     
290     return (void *) merged;
291 }
292
293 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig, int flag)
294 {
295  suphp_conf *cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);
296  
297  if (flag)
298   cfg->engine = SUPHP_ENGINE_ON;
299  else
300   cfg->engine = SUPHP_ENGINE_OFF;
301  
302  return NULL;
303 }
304
305 static const char *suphp_handle_cmd_config(cmd_parms *parms, void *mconfig, const char *arg)
306 {
307  suphp_conf *cfg = (suphp_conf *) mconfig;
308  cfg->php_config = (char*)ap_pstrdup(parms->pool, arg);
309  return NULL;
310 }
311
312 static const command_rec suphp_cmds[] =
313 {
314     {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF, FLAG, "Whether PHP is on or off, default is off"},
315     {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1, "Where the php.ini resists, default is to use PHP's default configuration"},
316     {NULL}
317 };
318
319 static int log_scripterror(request_rec *r, suphp_conf * conf, int ret,
320                            int show_errno, char *error)
321 {
322     FILE *f;
323     struct stat finfo;
324
325     ap_log_rerror(APLOG_MARK, show_errno|APLOG_ERR, r, 
326                 "%s: %s", error, r->filename);
327
328     if (!conf->logname ||
329         ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
330          &&   (finfo.st_size > conf->logbytes)) ||
331          ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
332                       "a")) == NULL)) {
333         return ret;
334     }
335
336     /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
337     fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
338             r->args ? "?" : "", r->args ? r->args : "", r->protocol);
339     /* "%% 500 /usr/local/apache/cgi-bin */
340     fprintf(f, "%%%% %d %s\n", ret, r->filename);
341
342     fprintf(f, "%%error\n%s\n", error);
343
344     ap_pfclose(r->pool, f);
345     return ret;
346 }
347
348 static int log_script(request_rec *r, suphp_conf * conf, int ret,
349                   char *dbuf, const char *sbuf, BUFF *script_in, BUFF *script_err)
350 {
351     array_header *hdrs_arr = ap_table_elts(r->headers_in);
352     table_entry *hdrs = (table_entry *) hdrs_arr->elts;
353     char argsbuffer[HUGE_STRING_LEN];
354     FILE *f;
355     int i;
356     struct stat finfo;
357
358     if (!conf->logname ||
359         ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
360          &&   (finfo.st_size > conf->logbytes)) ||
361          ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
362                       "a")) == NULL)) {
363         /* Soak up script output */
364         while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
365             continue;
366 #if defined(WIN32) || defined(NETWARE)
367         /* Soak up stderr and redirect it to the error log.
368          * Script output to stderr is already directed to the error log
369          * on Unix, thanks to the magic of fork().
370          */
371         while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
372             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
373                           "%s", argsbuffer);            
374         }
375 #else
376         while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
377             continue;
378 #endif
379         return ret;
380     }
381
382     /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
383     fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
384             r->args ? "?" : "", r->args ? r->args : "", r->protocol);
385     /* "%% 500 /usr/local/apache/cgi-bin" */
386     fprintf(f, "%%%% %d %s\n", ret, r->filename);
387
388     fputs("%request\n", f);
389     for (i = 0; i < hdrs_arr->nelts; ++i) {
390         if (!hdrs[i].key)
391             continue;
392         fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
393     }
394     if ((r->method_number == M_POST || r->method_number == M_PUT)
395         && *dbuf) {
396         fprintf(f, "\n%s\n", dbuf);
397     }
398
399     fputs("%response\n", f);
400     hdrs_arr = ap_table_elts(r->err_headers_out);
401     hdrs = (table_entry *) hdrs_arr->elts;
402
403     for (i = 0; i < hdrs_arr->nelts; ++i) {
404         if (!hdrs[i].key)
405             continue;
406         fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
407     }
408
409     if (sbuf && *sbuf)
410         fprintf(f, "%s\n", sbuf);
411
412     if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
413         fputs("%stdout\n", f);
414         fputs(argsbuffer, f);
415         while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
416             fputs(argsbuffer, f);
417         fputs("\n", f);
418     }
419
420     if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
421         fputs("%stderr\n", f);
422         fputs(argsbuffer, f);
423         while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
424             fputs(argsbuffer, f);
425         fputs("\n", f);
426     }
427
428     ap_bclose(script_in);
429     ap_bclose(script_err);
430
431     ap_pfclose(r->pool, f);
432     return ret;
433 }
434
435 /****************************************************************
436  *
437  * Actual suPHP handling...
438  */
439
440
441 struct suphp_child_stuff {
442 #ifdef TPF
443     TPF_FORK_CHILD t;
444 #endif
445     request_rec *r;
446     int nph;
447     int debug;
448     char *argv0;
449 };
450
451 static int suphp_child(void *child_stuff, child_info *pinfo)
452 {
453     struct suphp_child_stuff *cld = (struct suphp_child_stuff *) child_stuff;
454     request_rec *r = cld->r;
455     char *argv0 = cld->argv0;
456     int child_pid;
457     const char *authorization=NULL;
458     char *tmp=NULL;
459     char *auth_user=NULL;
460     char *auth_password=NULL;
461     suphp_conf *cfg = (suphp_conf *) ap_get_module_config(r->per_dir_config, &suphp_module);
462
463 #ifdef DEBUG_SUPHP
464 #ifdef OS2
465     /* Under OS/2 need to use device con. */
466     FILE *dbg = fopen("con", "w");
467 #else
468     FILE *dbg = fopen("/dev/tty", "w");
469 #endif
470     int i;
471 #endif
472
473     char **env;
474
475     RAISE_SIGSTOP(SUPHP_CHILD);
476 #ifdef DEBUG_SUPHP
477     fprintf(dbg, "Attempting to exec %s as %ssuPHP child (argv0 = %s)\n",
478             r->filename, cld->nph ? "NPH " : "", argv0);
479 #endif
480
481     /*
482      * Set environment variables with authorization information like mod_php
483      */
484     
485     if (r->headers_in)
486     {
487      authorization = ap_table_get(r->headers_in, "Authorization");
488     }
489     if (authorization 
490         && !strcasecmp(ap_getword(r->pool, &authorization, ' '), "Basic"))
491     {
492      tmp = ap_uudecode(r->pool, authorization);
493      auth_user = ap_getword_nulls_nc(r->pool, &tmp, ':');
494 /*
495      if (auth_user)
496      {
497       auth_user = estrdup(auth_user);
498      }
499 */
500      auth_password = tmp;
501 /*
502      if (auth_password)
503      {
504       auth_password = estrdup(auth_password);
505      }
506 */
507      if (auth_user)
508      {
509       ap_table_setn(r->subprocess_env, "PHP_AUTH_USER", auth_user);
510      }
511      if (auth_password)
512      {
513       ap_table_setn(r->subprocess_env, "PHP_AUTH_PW", auth_password);
514      }
515     }
516     
517     if (cfg->php_config)
518     {
519      ap_table_setn(r->subprocess_env, "PHP_CONFIG", cfg->php_config);
520     }
521      
522     ap_add_cgi_vars(r);
523     env = ap_create_environment(r->pool, r->subprocess_env);
524
525 #ifdef DEBUG_SUPHP
526     fprintf(dbg, "Environment: \n");
527     for (i = 0; env[i]; ++i)
528         fprintf(dbg, "'%s'\n", env[i]);
529 #endif
530
531
532     ap_chdir_file(r->filename);
533     if (!cld->debug)
534         ap_error_log2stderr(r->server);
535
536     /* Transumute outselves into the script.
537      * NB only ISINDEX scripts get decoded arguments.
538      */
539
540 #ifdef TPF
541     return (0);
542 #else
543     ap_cleanup_for_exec();
544
545     child_pid = suphp_call_exec(r, pinfo, argv0, env, 0);
546     return (child_pid);
547
548     /* Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
549      * EARTH-shattering kaboom!
550      *
551      * Oh, well.  Muddle through as best we can...
552      *
553      * Note that only stderr is available at this point, so don't pass in
554      * a server to aplog_error.
555      */
556
557     ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed", r->filename);
558     exit(0);
559     /* NOT REACHED */
560     return (0);
561 #endif  /* TPF */
562 }
563
564 static int suphp_handler(request_rec *r)
565 {
566     int retval, nph, dbpos = 0;
567     char *argv0, *dbuf = NULL;
568     BUFF *script_out, *script_in, *script_err;
569     char argsbuffer[HUGE_STRING_LEN];
570     int is_included = !strcmp(r->protocol, "INCLUDED");
571     void *sconf = r->server->module_config;
572     suphp_conf *conf =
573     (suphp_conf *) ap_get_module_config(sconf, &suphp_module);
574     suphp_conf *mconf = (suphp_conf *) ap_get_module_config(r->server->module_config, &suphp_module);
575
576     struct suphp_child_stuff cld;
577
578     if ((mconf->engine == SUPHP_ENGINE_OFF) || (mconf->engine == SUPHP_ENGINE_UNDEFINED))
579      return DECLINED;
580     
581     if (r->method_number == M_OPTIONS) {
582         /* 99 out of 100 CGI scripts, this is all they support */
583         r->allowed |= (1 << M_GET);
584         r->allowed |= (1 << M_POST);
585         return DECLINED;
586     }
587
588     if ((argv0 = strrchr(r->filename, '/')) != NULL)
589         argv0++;
590     else
591         argv0 = r->filename;
592
593 //    nph = !(strncmp(argv0, "nph-", 4));
594     nph = 0;
595
596 //    if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
597 //      return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
598 //                             "Options ExecCGI is off in this directory");
599 //    if (nph && is_included)
600 //      return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
601 //                             "attempt to include NPH CGI script");
602 //
603     if (r->finfo.st_mode == 0)
604         return log_scripterror(r, conf, NOT_FOUND, APLOG_NOERRNO,
605                                "script not found or unable to stat");
606     if (S_ISDIR(r->finfo.st_mode))
607         return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
608                                "attempt to invoke directory as script");
609
610     if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
611         return retval;
612
613     ap_add_common_vars(r);
614     cld.argv0 = argv0;
615     cld.r = r;
616     cld.nph = nph;
617     cld.debug = conf->logname ? 1 : 0;
618 #ifdef TPF
619     cld.t.filename = r->filename;
620     cld.t.subprocess_env = r->subprocess_env;
621     cld.t.prog_type = FORK_FILE;
622 #endif   /* TPF */
623
624 #ifdef CHARSET_EBCDIC
625     /* The included MIME headers must ALWAYS be in text/ebcdic format.
626      * Only after reading the MIME headers, we check the Content-Type
627      * and switch to the necessary conversion mode.
628      * Until then (and in case an nph- script was called), use the
629      * configured default conversion:
630      */
631     ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out);
632 #endif /*CHARSET_EBCDIC*/
633
634     /*
635      * we spawn out of r->main if it's there so that we can avoid
636      * waiting for free_proc_chain to cleanup in the middle of an
637      * SSI request -djg
638      */
639     if (!ap_bspawn_child(r->main ? r->main->pool : r->pool, suphp_child,
640                          (void *) &cld, kill_after_timeout,
641                          &script_out, &script_in, &script_err)) {
642         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
643                     "couldn't spawn child process: %s", r->filename);
644         return HTTP_INTERNAL_SERVER_ERROR;
645     }
646
647     /* Transfer any put/post args, CERN style...
648      * Note that we already ignore SIGPIPE in the core server.
649      */
650
651     if (ap_should_client_block(r)) {
652         int dbsize, len_read;
653
654         if (conf->logname) {
655             dbuf = ap_pcalloc(r->pool, conf->bufbytes + 1);
656             dbpos = 0;
657         }
658
659         ap_hard_timeout("copy script args", r);
660
661         while ((len_read =
662                 ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) {
663             if (conf->logname) {
664                 if ((dbpos + len_read) > conf->bufbytes) {
665                     dbsize = conf->bufbytes - dbpos;
666                 }
667                 else {
668                     dbsize = len_read;
669                 }
670                 memcpy(dbuf + dbpos, argsbuffer, dbsize);
671                 dbpos += dbsize;
672             }
673             ap_reset_timeout(r);
674             if (ap_bwrite(script_out, argsbuffer, len_read) < len_read) {
675                 /* silly script stopped reading, soak up remaining message */
676                 while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) {
677                     /* dump it */
678                 }
679                 break;
680             }
681         }
682
683         ap_bflush(script_out);
684
685         ap_kill_timeout(r);
686     }
687
688     ap_bclose(script_out);
689
690     /* Handle script return... */
691     if (script_in && !nph) {
692         const char *location;
693         char sbuf[MAX_STRING_LEN];
694         int ret;
695
696         if ((ret = ap_scan_script_header_err_buff(r, script_in, sbuf))) {
697             return log_script(r, conf, ret, dbuf, sbuf, script_in, script_err);
698         }
699
700         location = ap_table_get(r->headers_out, "Location");
701
702         if (location && location[0] == '/' && r->status == 200) {
703
704             /* Soak up all the script output */
705             ap_hard_timeout("read from script", r);
706             while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
707                 continue;
708             }
709 #if defined(WIN32) || defined(NETWARE)
710             /* Soak up stderr and redirect it to the error log.
711              * Script output to stderr is already directed to the error log
712              * on Unix, thanks to the magic of fork().
713              */
714             while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
715                 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
716                               "%s", argsbuffer);            
717             }
718 #else
719             while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
720                 continue;
721             }
722 #endif
723             ap_kill_timeout(r);
724
725
726             /* This redirect needs to be a GET no matter what the original
727              * method was.
728              */
729             r->method = ap_pstrdup(r->pool, "GET");
730             r->method_number = M_GET;
731
732             /* We already read the message body (if any), so don't allow
733              * the redirected request to think it has one.  We can ignore 
734              * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
735              */
736             ap_table_unset(r->headers_in, "Content-Length");
737
738             ap_internal_redirect_handler(location, r);
739             return OK;
740         }
741         else if (location && r->status == 200) {
742             /* XX Note that if a script wants to produce its own Redirect
743              * body, it now has to explicitly *say* "Status: 302"
744              */
745             return REDIRECT;
746         }
747
748         ap_send_http_header(r);
749         if (!r->header_only) {
750             ap_send_fb(script_in, r);
751         }
752         ap_bclose(script_in);
753
754         ap_soft_timeout("soaking script stderr", r);
755 #if defined(WIN32) || defined(NETWARE)
756         /* Script output to stderr is already directed to the error log
757          * on Unix, thanks to the magic of fork().
758          */
759         while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
760             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r, 
761                           "%s", argsbuffer);            
762         }
763 #else
764         while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
765             continue;
766         }
767 #endif
768         ap_kill_timeout(r);
769         ap_bclose(script_err);
770     }
771
772     if (script_in && nph) {
773         ap_send_fb(script_in, r);
774     }
775
776     return OK;                  /* NOT r->status, even if it has changed. */
777 }
778
779 static const handler_rec suphp_handlers[] =
780 {
781     {"x-httpd-php", suphp_handler},
782     {NULL}
783 };
784
785 module MODULE_VAR_EXPORT suphp_module =
786 {
787     STANDARD_MODULE_STUFF,
788     NULL,                       /* initializer */
789     suphp_create_dir_config,    /* dir config creater */
790     suphp_merge_dir_config,     /* dir merger --- default is to override */
791     suphp_create_server_config, /* server config */
792     suphp_merge_server_config,  /* merge server config */
793     suphp_cmds,                 /* command table */
794     suphp_handlers,             /* handlers */
795     NULL,                       /* filename translation */
796     NULL,                       /* check_user_id */
797     NULL,                       /* check auth */
798     NULL,                       /* check access */
799     NULL,                       /* type_checker */
800     NULL,                       /* fixups */
801     NULL,                       /* logger */
802     NULL,                       /* header parser */
803     NULL,                       /* child_init */
804     NULL,                       /* child_exit */
805     NULL                        /* post read-request */
806 };