Import upstream 0.7.1
[manu/suphp.git] / src / apache / mod_suphp.c
1 /*
2     suPHP - (c)2002-2005 Sebastian Marsching <sebastian@marsching.com>
3
4     This file is part of suPHP.
5
6     suPHP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     suPHP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with suPHP; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21
22 #define CORE_PRIVATE
23
24 #include "httpd.h"
25 #include "http_config.h"
26 #include "http_request.h"
27 #include "http_core.h"
28 #include "http_protocol.h"
29 #include "http_main.h"
30 #include "http_log.h"
31 #include "util_script.h"
32
33
34 #define SUPHP_CONFIG_MODE_SERVER 1
35 #define SUPHP_CONFIG_MODE_DIRECTORY 2
36
37 #define SUPHP_ENGINE_OFF 0
38 #define SUPHP_ENGINE_ON 1
39 #define SUPHP_ENGINE_UNDEFINED 2
40
41 #ifndef SUPHP_PATH_TO_SUPHP
42 #define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
43 #endif
44
45
46 /* Module declaration */
47 module MODULE_VAR_EXPORT suphp_module;
48
49
50 /* Configuration structure */
51 typedef struct {
52     int engine; // Status of suPHP_Engine
53     char *php_config;
54     int cmode;  // Server of directory configuration?
55 #ifdef SUPHP_USE_USERGROUP
56     char *target_user;
57     char *target_group;
58 #endif
59     table *handlers;
60     char *php_path;
61 } suphp_conf;
62
63
64 /* Configuration mergers/creators */
65
66 static void *suphp_create_dir_config(pool *p, char *dir) {
67     suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
68     cfg->php_config = NULL;
69     cfg->engine = SUPHP_ENGINE_UNDEFINED;
70     cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
71     
72 #ifdef SUPHP_USE_USERGROUP
73     cfg->target_user = NULL;
74     cfg->target_group = NULL;
75 #endif
76
77     /* Create table with 0 initial elements */
78     /* This size may be increased for performance reasons */
79     cfg->handlers = ap_make_table(p, 0);
80     
81     return (void *) cfg;
82 }
83
84 static void *suphp_merge_dir_config(pool *p, void *base, void *overrides) {
85     suphp_conf *parent = (suphp_conf *) base;
86     suphp_conf *child = (suphp_conf *) overrides;
87     suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
88
89     merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
90     
91     if (child->php_config)
92         merged->php_config = ap_pstrdup(p, child->php_config);
93     else if (parent->php_config)
94         merged->php_config = ap_pstrdup(p, parent->php_config);
95     else 
96         merged->php_config = NULL;
97     
98     if (child->engine != SUPHP_ENGINE_UNDEFINED)
99         merged->engine = child->engine;
100     else
101         merged->engine = parent->engine;
102
103 #ifdef SUPHP_USE_USERGROUP
104     if (child->target_user)
105         merged->target_user = ap_pstrdup(p, child->target_user);
106     else if (parent->target_user)
107         merged->target_user = ap_pstrdup(p, parent->target_user);
108     else
109         merged->target_user = NULL;
110
111     if (child->target_group)
112         merged->target_group = ap_pstrdup(p, child->target_group);
113     else if (parent->target_group)
114         merged->target_group = ap_pstrdup(p, parent->target_group);
115     else
116         merged->target_group = NULL;
117 #endif
118     
119     merged->handlers = ap_overlay_tables(p, child->handlers, parent->handlers);
120
121     return (void *) merged;
122 }
123
124
125 static void *suphp_create_server_config(pool *p, server_rec *s) {
126     suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
127     
128     cfg->engine = SUPHP_ENGINE_UNDEFINED;
129     cfg->php_path = NULL;
130     cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
131     
132 #ifdef SUPHP_USE_USERGROUP
133     cfg->target_user = NULL;
134     cfg->target_group = NULL;
135 #endif
136     
137     /* Create table with 0 initial elements */
138     /* This size may be increased for performance reasons */
139     cfg->handlers = ap_make_table(p, 0);
140
141     return (void *) cfg;
142 }
143
144
145 static void *suphp_merge_server_config(pool *p, void *base, void *overrides) {
146     suphp_conf *parent = (suphp_conf *) base;
147     suphp_conf *child = (suphp_conf *) overrides;
148     suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
149     
150     if (child->engine != SUPHP_ENGINE_UNDEFINED)
151         merged->engine = child->engine;
152     else
153         merged->engine = parent->engine;
154     
155     if (child->php_path != NULL)
156         merged->php_path = ap_pstrdup(p, child->php_path);
157     else
158         merged->php_path = ap_pstrdup(p, parent->php_path);
159
160 #ifdef SUPHP_USE_USERGROUP
161     if (child->target_user)
162         merged->target_user = ap_pstrdup(p, child->target_user);
163     else if (parent->target_user)
164         merged->target_user = ap_pstrdup(p, parent->target_user);
165     else
166         merged->target_user = NULL;
167
168     if (child->target_group)
169         merged->target_group = ap_pstrdup(p, child->target_group);
170     else if (parent->target_group)
171         merged->target_group = ap_pstrdup(p, parent->target_group);
172     else
173         merged->target_group = NULL;
174 #endif
175     
176      merged->handlers = ap_overlay_tables(p, child->handlers, parent->handlers);
177
178     return (void *) merged;
179 }
180
181
182 /* Command handlers */
183
184 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
185                                            int flag) {
186     suphp_conf *cfg;
187
188     if (mconfig)
189         cfg = (suphp_conf *) mconfig;
190     else
191         cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config,
192                                                   &suphp_module);
193     
194     if (flag)
195         cfg->engine = SUPHP_ENGINE_ON;
196     else
197         cfg->engine = SUPHP_ENGINE_OFF;
198
199     return NULL;
200 }
201
202
203 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
204                                            const char *arg) {
205     suphp_conf *cfg = (suphp_conf *) mconfig;
206     
207     cfg->php_config = ap_pstrdup(cmd->pool, arg);
208     
209     return NULL;
210 }
211
212
213 #ifdef SUPHP_USE_USERGROUP
214 static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig,
215                                                const char *arg1, 
216                                                const char *arg2) {
217     suphp_conf *cfg;
218
219     if (mconfig)
220         cfg = (suphp_conf *) mconfig;
221     else
222         cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
223     
224     cfg->target_user = ap_pstrdup(cmd->pool, arg1);
225     cfg->target_group = ap_pstrdup(cmd->pool, arg2);
226
227     return NULL;
228 }
229 #endif
230
231
232 static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
233                                                 const char *arg) {
234     suphp_conf *cfg;
235     if (mconfig)
236         cfg = (suphp_conf *) mconfig;
237     else
238         cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
239
240     // Mark active handlers with '1'
241     ap_table_set(cfg->handlers, arg, "1");
242
243     return NULL;
244 }
245
246 static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd, 
247                                                    void *mconfig, 
248                                                    const char *arg) {
249     suphp_conf *cfg;
250     if (mconfig)
251         cfg = (suphp_conf *) mconfig;
252     else
253         cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
254     
255     // Mark deactivated handlers with '0'
256     ap_table_set(cfg->handlers, arg, "0");
257
258     return NULL;
259 }
260
261
262 static const char *suphp_handle_cmd_phppath(cmd_parms *cmd, void* mconfig, const char *arg)
263 {
264     suphp_conf *cfg;
265     
266     cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);
267     
268     cfg->php_path = ap_pstrdup(cmd->pool, arg);
269     
270     return NULL;
271 }
272
273
274 /* Command table */
275
276 static const command_rec suphp_cmds[] = {
277     {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF|ACCESS_CONF,
278      FLAG, "Whether suPHP is on or off, default is off"},
279     {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1, 
280      "Where the php.ini resides, default is the PHP default"},
281 #ifdef SUPHP_USE_USERGROUP
282     {"suPHP_UserGroup", suphp_handle_cmd_user_group, NULL, 
283      RSRC_CONF|ACCESS_CONF, TAKE2, "User and group scripts shall be run as"},
284 #endif 
285     {"suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF | ACCESS_CONF,
286      ITERATE, "Tells mod_suphp to handle these MIME-types"},
287     {"suphp_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF | ACCESS_CONF,
288      ITERATE, "Tells mod_suphp not to handle these MIME-types"},
289     {"suPHP_PHPPath", suphp_handle_cmd_phppath, NULL, RSRC_CONF, TAKE1, "Path to the PHP binary used to render source view"},
290     {NULL}
291 };
292
293
294 /* Helper function which is called when spawning child process */
295
296 int suphp_source_child(void *rp, child_info *cinfo) {
297     request_rec *r = (request_rec *) rp;
298     suphp_conf *conf;
299     pool *p = r->main ? r->main->pool : r->pool;
300     char **argv, **env;
301
302     conf = ap_get_module_config(r->server->module_config, &suphp_module);
303     
304     /* We want to log output written to stderr */
305     ap_error_log2stderr(r->server);
306
307     /* prepare argv for new process */
308     
309     argv = ap_palloc(p, 4 * sizeof(char *));
310     argv[0] = ap_pstrdup(p, conf->php_path);
311     argv[1] = "-s";
312     argv[2] = ap_pstrdup(p, r->filename);
313     argv[3] = NULL;
314
315     /* prepare environment */
316
317     env = ap_create_environment(p, r->subprocess_env);
318
319     /* We cannot use ap_call_exec because of the interference with suExec */
320     /* So we do everything ourselves */
321  
322     /* mandatory cleanup before execution */
323     ap_cleanup_for_exec();
324     
325     execve(ap_pstrdup(p, conf->php_path), argv, env);
326
327     /* We are still here? Okay - exec failed */
328     ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
329                  conf->php_path);
330     exit(0);
331     /* NOT REACHED */
332     return (0);
333 }
334
335
336 int suphp_child(void *rp, child_info *cinfo) {
337     request_rec *r = (request_rec *) rp;
338     core_dir_config *core_conf;
339     pool *p = r->main ? r->main->pool : r->pool;
340      
341     char **argv, **env;
342
343     core_conf = (core_dir_config *) ap_get_module_config(
344         r->per_dir_config, &core_module);
345     
346     /* We want to log output written to stderr */
347     ap_error_log2stderr(r->server);
348
349     /* prepare argv for new process */
350     
351     argv = ap_palloc(p, 2 * sizeof(char *));
352     argv[0] = SUPHP_PATH_TO_SUPHP;
353     argv[1] = NULL;
354
355     /* prepare environment */
356
357     env = ap_create_environment(p, r->subprocess_env);
358
359     /* We cannot use ap_call_exec because of the interference with suExec */
360     /* So we do everything ourselves */
361  
362     /* Set resource limits from core config */
363    
364 #ifdef RLIMIT_CPU
365     if (core_conf->limit_cpu != NULL) {
366         if ((setrlimit(RLIMIT_CPU, core_conf->limit_cpu)) != 0) {
367             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
368                          "setrlimit: failed to set CPU usage limit");
369         }
370     }
371 #endif /* RLIMIT_CPU */
372 #ifdef RLIMIT_NPROC
373     if (core_conf->limit_nproc != NULL) {
374         if ((setrlimit(RLIMIT_NPROC, core_conf->limit_nproc)) != 0) {
375             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
376                          "setrlimit: failed to set process limit");
377         }
378     }
379 #endif /* RLIMIT_NPROC */
380 #ifdef RLIMIT_AS
381     if (core_conf->limit_mem != NULL) {
382         if ((setrlimit(RLIMIT_AS, core_conf->limit_mem)) != 0) {
383             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
384                          "setrlimit: failed to set memory limit");
385         }
386     }
387 #endif /* RLIMIT_VMEM */
388 #ifdef RLIMIT_DATA
389     if (core_conf->limit_mem != NULL) {
390         if ((setrlimit(RLIMIT_DATA, core_conf->limit_mem)) != 0) {
391             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
392                          "setrlimit: failed to set memory limit");
393         }
394     }
395 #endif /* RLIMIT_VMEM */
396 #ifdef RLIMIT_VMEM
397     if (core_conf->limit_mem != NULL) {
398         if ((setrlimit(RLIMIT_VMEM, core_conf->limit_mem)) != 0) {
399             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
400                          "setrlimit: failed to set memory limit");
401         }
402     }
403 #endif /* RLIMIT_VMEM */
404
405     /* mandatory cleanup before execution */
406     ap_cleanup_for_exec();
407     
408     execve(SUPHP_PATH_TO_SUPHP, argv, env);
409
410     /* We are still here? Okay - exec failed */
411     ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
412                  SUPHP_PATH_TO_SUPHP);
413     exit(0);
414     /* NOT REACHED */
415     return (0);
416 }
417
418
419 /* Handlers */
420
421 static int suphp_source_handler(request_rec *r) {
422     suphp_conf *conf;
423     int rv;
424     pool *p;
425     int fd;
426     BUFF *script_in, *script_out, *script_err;
427     char buffer[HUGE_STRING_LEN];
428     
429     if (strcmp(r->method, "GET")) {
430         return DECLINED;
431     }
432     
433     conf = ap_get_module_config(r->server->module_config, &suphp_module);
434     if (conf->php_path == NULL) {
435         return DECLINED;
436     }
437     
438     p = r->main ? r->main->pool : r->pool;
439     
440     fd = open(r->filename, O_NOCTTY, O_RDONLY);
441     if (fd != -1) {
442         close(fd);
443     } else if (errno == EACCES) {
444         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
445                       r->filename);
446         return HTTP_FORBIDDEN;
447     } else if (errno == ENOENT || errno == ENOTDIR) {
448         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
449                       r->filename);
450         return HTTP_NOT_FOUND;
451     } else {
452         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could open file: %s",
453                       r->filename);
454         return HTTP_NOT_FOUND;
455     }
456     
457     /* Fork child process */
458     
459     if (!ap_bspawn_child(p, suphp_source_child, (void *) r, kill_after_timeout,
460                          &script_in, &script_out, &script_err)) {
461         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
462                       "couldn't spawn child process for: %s", r->filename);
463         return HTTP_INTERNAL_SERVER_ERROR;
464     }
465     
466     /* Read request body */
467     
468     if (ap_should_client_block(r)) {
469         char buffer[HUGE_STRING_LEN];
470         int len_read;
471
472         ap_hard_timeout("reading request body", r);
473
474         while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
475             ap_reset_timeout(r);
476             // Ignore input
477         }
478         
479         ap_bflush(script_in);
480         ap_kill_timeout(r);
481     }
482
483     ap_bclose(script_in);
484     
485     /* Transfer output from PHP to client */
486
487     if (script_out) {
488         /* Output headers and body */
489
490         r->content_type = "text/html";
491         ap_send_http_header(r);
492         if (!r->header_only) {
493             ap_send_fb(script_out, r);
494         }
495         ap_bclose(script_out);
496         /* Errors have already been logged by child */
497         ap_bclose(script_err);
498     }
499     
500     return OK;
501
502 }
503
504
505 static int suphp_handler(request_rec *r) {
506     suphp_conf *sconf;
507     suphp_conf *dconf;
508
509 #ifdef SUPHP_USE_USERGROUP
510     char *ud_user = NULL;
511     char *ud_group = NULL;
512 #endif
513     
514     struct stat finfo;
515     
516     int rv;
517     
518     char *auth_user = NULL;
519     char *auth_pass = NULL;
520
521     pool *p;
522
523     BUFF *script_in, *script_out, *script_err;
524     
525     const char *handler;
526
527     sconf = ap_get_module_config(r->server->module_config, &suphp_module);
528     dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
529
530     p = r->main ? r->main->pool : r->pool;
531
532     /* only handle request if mod_suphp is active for this handler */
533     /* check only first byte of value (second has to be \0) */
534     if (r->handler != NULL) {
535         handler = r->handler;
536     } else {
537         handler = r->content_type;
538     }
539     if ((ap_table_get(dconf->handlers, handler) == NULL)) {
540         if ((ap_table_get(sconf->handlers, handler) == NULL)
541             || (*(ap_table_get(sconf->handlers, handler)) == '0')) {
542             return DECLINED;
543         }
544     } else if (*(ap_table_get(dconf->handlers, handler)) == '0') {
545         return DECLINED;
546     }
547
548     /* check if suPHP is enabled for this request */
549
550     if (((sconf->engine != SUPHP_ENGINE_ON)
551          && (dconf->engine != SUPHP_ENGINE_ON))
552         || ((sconf->engine == SUPHP_ENGINE_ON)
553             && (dconf->engine == SUPHP_ENGINE_OFF)))
554         return DECLINED;
555
556     /* check if file is existing and accessible */
557     
558     rv = stat(ap_pstrdup(p, r->filename), &finfo);
559     if (rv == 0) {
560         ; /* do nothing */
561     } else if (errno == EACCES) {
562         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
563                       r->filename);
564         return HTTP_FORBIDDEN;
565     } else if (errno == ENOENT || errno == ENOTDIR) {
566         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
567                       r->filename);
568         return HTTP_NOT_FOUND;
569     } else {
570         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could not get fileinfo: %s",
571                       r->filename);
572         return HTTP_NOT_FOUND;
573     }
574
575 #ifdef SUPHP_USE_USERGROUP
576     if ((sconf->target_user == NULL || sconf->target_group == NULL)
577         && (dconf->target_user == NULL || dconf->target_group == NULL)) {
578         
579         /* Identify mod_userdir request
580            As Apache 1.3 does not yet provide a clean way to see
581            whether a request was handled by mod_userdir, we assume
582            this is true for any request beginning with ~ */
583
584         int ud_success = 0; /* set to 1 on success */
585
586         if (!strncmp("/~", r->uri, 2)) {
587             char *username = ap_pstrdup(r->pool, r->uri + 2);
588             char *pos = strchr(username, '/');
589             if (pos) {
590                 *pos = 0;
591                 if (strlen(username)) {
592                     struct passwd *pw;
593                     struct group *gr;
594                     gid_t gid;
595                     char *grpname;
596                     if ((pw = getpwnam(username)) != NULL) {
597                         gid = pw->pw_gid;
598                         
599                         if ((gr = getgrgid(gid)) != NULL) {
600                             grpname = gr->gr_name;
601                         } else {
602                             if ((grpname = ap_palloc(r->pool, 16)) == NULL) {
603                                 return HTTP_INTERNAL_SERVER_ERROR;
604                             }
605                             ap_snprintf(grpname, 16, "#%ld", (long) gid);
606                         }
607                         
608                         ud_user = username;
609                         ud_group = grpname;
610                         ud_success = 1;
611                     }
612                 }
613             }       
614         }
615         
616         if (!ud_success) {
617             /* This is not a userdir request and user/group are not
618                set, so log the error and return */
619             ap_log_rerror(APLOG_MARK, APLOG_ERR, r, 
620                           "No user or group set - set suPHP_UserGroup");
621             return HTTP_INTERNAL_SERVER_ERROR;
622         }
623     }
624 #endif /* SUPHP_USE_USERGROUP */
625
626
627     /* prepare environment for new process */
628     
629     ap_add_common_vars(r);
630     ap_add_cgi_vars(r);
631
632     ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
633     ap_table_unset(r->subprocess_env, "SUPHP_AUTH_USER");
634     ap_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
635     
636 #ifdef SUPHP_USE_USERGROUP
637     ap_table_unset(r->subprocess_env, "SUPHP_USER");
638     ap_table_unset(r->subprocess_env, "SUPHP_GROUP");
639 #endif /* SUPHP_USE_USERGROUP */
640
641     if (dconf->php_config) {
642         ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
643     }
644
645     ap_table_set(r->subprocess_env, "SUPHP_HANDLER", handler);
646
647     if (r->headers_in) {
648         const char *auth;
649         auth = ap_table_get(r->headers_in, "Authorization");
650         if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
651             char *user;
652             char *pass;
653             user = ap_pbase64decode(p, auth + 6);
654             if (user) {
655                 pass = strchr(user, ':');
656                 if (pass) {
657                     *pass++ = '\0';
658                     auth_user = ap_pstrdup(p, user);
659                     auth_pass = ap_pstrdup(p, pass);
660                 }
661             }
662         }
663     }
664     
665     if (auth_user && auth_pass) {
666         ap_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
667         ap_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
668     }
669
670 #ifdef SUPHP_USE_USERGROUP
671     if (dconf->target_user) {
672         ap_table_set(r->subprocess_env, "SUPHP_USER", dconf->target_user);
673     } else if (sconf->target_user) {
674         ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
675     } else {
676         ap_table_set(r->subprocess_env, "SUPHP_USER", ud_user);
677     }
678
679     if (dconf->target_group) {
680         ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
681     } else if (sconf->target_group) {
682         ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
683     } else {
684         ap_table_set(r->subprocess_env, "SUPHP_GROUP", ud_group);
685     }
686 #endif /* SUPHP_USE_USERGROUP */
687     
688     /* Fork child process */
689     
690     if (!ap_bspawn_child(p, suphp_child, (void *) r, kill_after_timeout,
691                          &script_in, &script_out, &script_err)) {
692         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
693                       "couldn't spawn child process for: %s", r->filename);
694         return HTTP_INTERNAL_SERVER_ERROR;
695     }
696     
697     /* Transfer request body to script */
698     
699     if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
700         /* Call failed, return status */
701         return rv;
702     }
703     
704     if (ap_should_client_block(r)) {
705         char buffer[HUGE_STRING_LEN];
706         int len_read;
707
708         ap_hard_timeout("reading request body", r);
709
710         while ((len_read = ap_get_client_block(r, buffer, HUGE_STRING_LEN)) 
711                > 0) {
712             ap_reset_timeout(r);
713             if (ap_bwrite(script_in, buffer, len_read) < len_read) {
714                 /* silly script stopped reading, soak up remaining message */
715                 while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
716                     /* dump it */
717                 }
718                 break;
719             }
720         }
721         
722         ap_bflush(script_in);
723         ap_kill_timeout(r);
724     }
725
726     ap_bclose(script_in);
727     
728     /* Transfer output from script to client */
729
730     if (script_out) {
731         const char *location;
732         char hbuffer[MAX_STRING_LEN];
733         char buffer[HUGE_STRING_LEN];
734         
735         rv = ap_scan_script_header_err_buff(r, script_out, hbuffer);
736         if (rv == HTTP_NOT_MODIFIED) {
737             return rv;
738         } else if (rv) {
739             return HTTP_INTERNAL_SERVER_ERROR;
740         }
741         
742         location = ap_table_get(r->headers_out, "Location");
743         if (location && r->status == 200) {
744             /* Soak up all the script output */
745             ap_hard_timeout("reading from script", r);
746             while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
747                 continue;
748             }
749             ap_kill_timeout(r);
750             ap_bclose(script_out);
751             ap_bclose(script_err);
752         
753             if (location[0] == '/') { 
754                 /* Redirect has always GET method */
755                 r->method = ap_pstrdup(p, "GET");
756                 r->method_number = M_GET;
757                 
758                 /* Remove Content-Length - redirect should not read  *
759                  * request body                                      */
760                 ap_table_unset(r->headers_in, "Content-Length");
761                 
762                 /* Do the redirect */
763                 ap_internal_redirect_handler(location, r);
764                 return OK;
765             } else {
766                 /* Script did not set status 302 - so it does not want *
767                  * to send its own body. Simply set redirect status    */
768                 return REDIRECT;
769             }
770         }
771             
772         /* Output headers and body */
773
774         ap_send_http_header(r);
775         if (!r->header_only) {
776             ap_send_fb(script_out, r);
777         }
778         ap_bclose(script_out);
779         /* Errors have already been logged by child */
780         ap_bclose(script_err);
781     }
782     
783     return OK;
784 }
785
786
787 /* Handlers table */
788
789 static handler_rec suphp_handlers[] = {
790     {"*", suphp_handler},
791     {"x-httpd-php-source", suphp_source_handler},
792     {"application/x-httpd-php-source", suphp_source_handler},
793     {NULL}
794 };
795
796 /* Module definition */
797
798 module MODULE_VAR_EXPORT suphp_module = {
799     STANDARD_MODULE_STUFF,
800     NULL, /* initializer */
801     suphp_create_dir_config,     /* create directory config */
802     suphp_merge_dir_config,      /* merge directory config */
803     suphp_create_server_config,  /* create server config */
804     suphp_merge_server_config,   /* merge server config */
805     suphp_cmds,                  /* command table */
806     suphp_handlers,              /* content handlers */
807     NULL,                        /* URI-to-filename translation */
808     NULL,                        /* check/validate user_id */
809     NULL,                        /* check user_id is valid *here* */
810     NULL,                        /* check access by host address */
811     NULL,                        /* MIME type checker/setter */
812     NULL,                        /* fixups */
813     NULL,                        /* logger */
814     NULL,                        /* header parser */
815     NULL,                        /* process initialaization */
816     NULL,                        /* process exit/cleanup */
817     NULL                         /* post read_request handling */
818 };