0.6.1-1 release
[manu/suphp.git] / src / apache2 / 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 #include "apr.h"
22 #include "apr_strings.h"
23 #include "apr_thread_proc.h"
24 #include "apr_buckets.h"
25
26 #define CORE_PRIVATE
27
28 #include "httpd.h"
29 #include "http_config.h"
30 #include "http_core.h"
31 #include "http_log.h"
32
33 #include "util_script.h"
34 #include "util_filter.h"
35
36
37 module AP_MODULE_DECLARE_DATA suphp_module;
38
39
40 /*********************
41   Auxiliary functions
42  *********************/
43
44 static int suphp_bucket_read(apr_bucket *b, char *buf, int len)
45 {
46     const char *dst_end = buf + len - 1;
47     char * dst = buf;
48     apr_status_t rv;
49     const char *bucket_data;
50     apr_size_t bucket_data_len;
51     const char *src;
52     const char *src_end;
53     int count = 0;
54     
55     if (APR_BUCKET_IS_EOS(b))
56         return -1;
57        
58     rv = apr_bucket_read(b, &bucket_data, &bucket_data_len, APR_BLOCK_READ);
59     if (!APR_STATUS_IS_SUCCESS(rv) || (bucket_data_len == 0))
60     {
61         return 0;
62     }
63     src = bucket_data;
64     src_end = bucket_data + bucket_data_len;
65     while ((src < src_end) && (dst < dst_end))
66     {
67         *dst = *src;
68      dst++;
69      src++;
70      count++;
71     }
72     *dst = 0;
73     return count;
74 }
75
76
77 static void suphp_log_script_err(request_rec *r, apr_file_t *script_err)
78 {
79     char argsbuffer[HUGE_STRING_LEN];
80     char *newline;
81
82     while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
83                          script_err) == APR_SUCCESS) {
84         newline = strchr(argsbuffer, '\n');
85         if (newline) {
86             *newline = '\0';
87         }
88         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
89                       "%s", argsbuffer);
90     }
91 }
92
93
94 /**************************
95   Configuration processing
96  **************************/
97
98 #define SUPHP_CONFIG_MODE_SERVER 1
99 #define SUPHP_CONFIG_MODE_DIRECTORY 2
100
101 #define SUPHP_ENGINE_OFF 0
102 #define SUPHP_ENGINE_ON 1
103 #define SUPHP_ENGINE_UNDEFINED 2
104
105 #ifndef SUPHP_PATH_TO_SUPHP
106 #define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
107 #endif
108
109 typedef struct {
110     int engine; // Status of suPHP_Engine
111     char *php_config;
112     int cmode;  // Server of directory configuration?
113 #ifdef SUPHP_USE_USERGROUP
114     char *target_user;
115     char *target_group;
116 #endif
117     apr_table_t *handlers;
118 } suphp_conf;
119
120
121 static void *suphp_create_dir_config(apr_pool_t *p, char *dir)
122 {
123     suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
124     
125     cfg->php_config = NULL;
126     cfg->engine = SUPHP_ENGINE_UNDEFINED;
127     cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
128
129 #ifdef SUPHP_USE_USERGROUP
130     cfg->target_user = NULL;
131     cfg->target_group = NULL;
132 #endif
133     
134     /* Create table with 0 initial elements */
135     /* This size may be increased for performance reasons */
136     cfg->handlers = apr_table_make(p, 0);
137     
138     return (void *) cfg;
139 }
140
141
142 static void *suphp_merge_dir_config(apr_pool_t *p, void *base, 
143                                     void *overrides)
144 {
145     suphp_conf *parent = (suphp_conf *) base;
146     suphp_conf *child = (suphp_conf *) overrides;
147     suphp_conf *merged = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
148     
149     merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
150     
151     if (child->php_config)
152         merged->php_config = apr_pstrdup(p, child->php_config);
153     else if (parent->php_config)
154         merged->php_config = apr_pstrdup(p, parent->php_config);
155     else
156         merged->php_config = NULL;
157     
158     if (child->engine != SUPHP_ENGINE_UNDEFINED)
159         merged->engine = child->engine;
160     else
161         merged->engine = parent->engine;
162
163 #ifdef SUPHP_USE_USERGROUP
164     if (child->target_user)
165         merged->target_user = apr_pstrdup(p, child->target_user);
166     else if (parent->target_user)
167         merged->target_user = apr_pstrdup(p, parent->target_user);
168     else
169         merged->target_user = NULL;
170         
171     if (child->target_group)
172         merged->target_group = apr_pstrdup(p, child->target_group);
173     else if (parent->target_group)
174         merged->target_group = apr_pstrdup(p, parent->target_group);
175     else
176         merged->target_group = NULL;
177 #endif
178     
179     merged->handlers = apr_table_overlay(p, child->handlers, parent->handlers);
180     
181     return (void *) merged;  
182 }
183
184
185 static void *suphp_create_server_config(apr_pool_t *p, server_rec *s)
186 {
187     suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
188     
189     cfg->engine = SUPHP_ENGINE_UNDEFINED;
190     cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
191     
192     return (void *) cfg;
193 }
194
195
196 static void *suphp_merge_server_config(apr_pool_t *p, void *base,
197                                        void *overrides)
198 {
199     suphp_conf *parent = (suphp_conf *) base;
200     suphp_conf *child = (suphp_conf *) overrides;
201     suphp_conf *merged = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
202     
203     if (child->engine != SUPHP_ENGINE_UNDEFINED)
204         merged->engine = child->engine;
205     else
206         merged->engine = parent->engine;
207
208 #ifdef SUPHP_USE_USERGROUP
209     if (child->target_user)
210         merged->target_user = apr_pstrdup(p, child->target_user);
211     else if (parent->target_user)
212         merged->target_user = apr_pstrdup(p, parent->target_user);
213     else
214         merged->target_user = NULL;
215         
216     if (child->target_group)
217         merged->target_group = apr_pstrdup(p, child->target_group);
218     else if (parent->target_group)
219         merged->target_group = apr_pstrdup(p, parent->target_group);
220     else
221         merged->target_group = NULL;
222 #endif
223     
224     return (void*) merged;
225 }
226
227
228 /******************
229   Command handlers
230  ******************/
231
232 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
233                                            int flag)
234 {
235     server_rec *s = cmd->server;
236     suphp_conf *cfg;
237     
238     if (mconfig)
239         cfg = (suphp_conf *) mconfig;
240     else
241         cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
242     
243     if (flag)
244         cfg->engine = SUPHP_ENGINE_ON;
245     else
246         cfg->engine = SUPHP_ENGINE_OFF;
247     
248     return NULL;
249 }
250
251
252 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
253                                            const char *arg)
254 {
255     server_rec *s = cmd->server;
256     suphp_conf *cfg;
257     
258     if (mconfig)
259         cfg = (suphp_conf *) mconfig;
260     else
261         cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
262     
263     cfg->php_config = apr_pstrdup(cmd->pool, arg);
264     
265     return NULL;
266 }
267
268
269 #ifdef SUPHP_USE_USERGROUP
270 static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig,
271                                            const char *arg1, const char *arg2)
272 {
273     suphp_conf *cfg = (suphp_conf *) mconfig;
274     
275     cfg->target_user = apr_pstrdup(cmd->pool, arg1);
276     cfg->target_group = apr_pstrdup(cmd->pool, arg2);
277     
278     return NULL;
279 }
280 #endif
281
282
283 static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
284                                              const char *arg)
285 {
286     suphp_conf *cfg = (suphp_conf *) mconfig;
287     // Mark active handler with '1'
288     apr_table_set(cfg->handlers, arg, "1");
289
290     return NULL;
291 }
292
293
294 static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd, 
295                                                    void *mconfig, 
296                                                    const char *arg)
297 {
298     suphp_conf *cfg = (suphp_conf *) mconfig;
299     // Mark deactivated handler with '0'
300     apr_table_set(cfg->handlers, arg, "0");
301
302     return NULL;
303 }
304
305
306 static const command_rec suphp_cmds[] =
307 {
308     AP_INIT_FLAG("suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF | ACCESS_CONF,
309                  "Whether suPHP is on or off, default is off"),
310     AP_INIT_TAKE1("suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS,
311                   "Wheres the php.ini resides, default is the PHP default"),
312 #ifdef SUPHP_USE_USERGROUP
313     AP_INIT_TAKE2("suPHP_UserGroup", suphp_handle_cmd_user_group, NULL, RSRC_CONF | ACCESS_CONF,
314                   "User and group scripts shall be run as"),
315 #endif
316     AP_INIT_ITERATE("suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"),
317     AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"),
318     {NULL}
319 };
320
321
322 /******************
323   Hooks / handlers
324  ******************/
325
326 static int suphp_handler(request_rec *r)
327 {
328     apr_pool_t *p;
329     suphp_conf *sconf;
330     suphp_conf *dconf;
331     core_dir_config *core_conf;
332     
333     apr_finfo_t finfo;
334     
335     apr_procattr_t *procattr;
336     
337     apr_proc_t *proc;
338     
339     char **argv;
340     char **env;
341     apr_status_t rv;
342     int len = 0;
343 #if MAX_STRING_LEN < 1024
344     char strbuf[1024];
345 #else
346     char strbuf[MAX_STRING_LEN];
347 #endif
348     int nph = 0;
349     int eos_reached = 0;
350     char *auth_user = NULL;
351     char *auth_pass = NULL;
352     
353     apr_bucket_brigade *bb;
354     apr_bucket *b;
355     
356     /* load configuration */
357     
358     p = r->main ? r->main->pool : r->pool;
359     sconf = ap_get_module_config(r->server->module_config, &suphp_module);
360     dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
361     core_conf = (core_dir_config *) ap_get_module_config(r->per_dir_config, &core_module);
362     
363     /* only handle request if mod_suphp is active for this handler */
364     /* check only first byte of value (second has to be \0) */
365     if ((apr_table_get(dconf->handlers, r->handler) == NULL)
366         || (*(apr_table_get(dconf->handlers, r->handler)) == '0'))
367         return DECLINED;
368     
369     /* check if suPHP is enabled for this request */
370     
371     if (((sconf->engine != SUPHP_ENGINE_ON)
372         && (dconf->engine != SUPHP_ENGINE_ON))
373         || ((sconf->engine == SUPHP_ENGINE_ON)
374         && (dconf->engine == SUPHP_ENGINE_OFF)))
375         return DECLINED;
376     
377     /* check if file is existing and acessible */
378     
379     rv = apr_stat(&finfo, apr_pstrdup(p, r->filename), APR_FINFO_NORM, p);
380     
381     if (rv == APR_SUCCESS)
382         ; /* do nothing */
383     else if (rv == EACCES)
384     {
385         return HTTP_FORBIDDEN;
386         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "access to %s denied", r->filename);
387     }
388     else if (rv == ENOENT)
389     {
390         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "File does not exist: %s", r->filename);
391         return HTTP_NOT_FOUND;
392     }
393     else
394     {
395         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "could not get fileinfo: %s", r->filename);
396         return HTTP_NOT_FOUND;
397     }
398     
399     if (!(r->finfo.protection & APR_UREAD))
400     {
401         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Insufficient permissions: %s", r->filename);
402         return HTTP_FORBIDDEN;
403     }
404     
405 #ifdef SUPHP_USE_USERGROUP
406     if ((sconf->target_user == NULL || sconf->target_group == NULL)
407         && (dconf->target_user == NULL || dconf->target_group == NULL))
408     {
409         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
410                       "No user or group set - set suPHP_UserGroup");
411         return HTTP_INTERNAL_SERVER_ERROR;
412     }
413 #endif
414         
415     /* prepare argv for new process */
416     
417     argv = apr_palloc(p, 2 * sizeof(char *));
418     argv[0] = SUPHP_PATH_TO_SUPHP;
419     argv[1] = NULL;
420     
421     /* prepare environment for new process */
422     
423     ap_add_common_vars(r);
424     ap_add_cgi_vars(r);
425     
426     apr_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
427     apr_table_unset(r->subprocess_env, "SUPHP_AUTH_USER");
428     apr_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
429     
430 #ifdef SUPHP_USE_USERGROUP
431     apr_table_unset(r->subprocess_env, "SUPHP_USER");
432     apr_table_unset(r->subprocess_env, "SUPHP_GROUP");
433 #endif
434     
435     if (dconf->php_config)
436     {
437         apr_table_setn(r->subprocess_env, "SUPHP_PHP_CONFIG", apr_pstrdup(p, dconf->php_config));
438     }
439     
440     apr_table_setn(r->subprocess_env, "SUPHP_HANDLER", r->handler);
441     
442     if (r->headers_in)
443     {
444         const char *auth = NULL;
445         auth = apr_table_get(r->headers_in, "Authorization");
446         if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0)
447         {
448             char *user;
449             char *pass;
450             user = ap_pbase64decode(p, auth + 6);
451             if (user)
452             {
453                 pass = strchr(user, ':');
454                 if (pass)
455                 {
456                     *pass++ = '\0';
457                     auth_user = apr_pstrdup(r->pool, user);
458                     auth_pass = apr_pstrdup(r->pool, pass);
459                 }
460             }
461         }
462     }
463     
464     if (auth_user && auth_pass)
465     {
466         apr_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
467         apr_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
468     }
469
470 #ifdef SUPHP_USE_USERGROUP
471     if (dconf->target_user)
472     {
473         apr_table_setn(r->subprocess_env, "SUPHP_USER",
474                        apr_pstrdup(r->pool, dconf->target_user));
475     }
476     else
477     {
478         apr_table_setn(r->subprocess_env, "SUPHP_USER",
479                        apr_pstrdup(r->pool, sconf->target_user));
480     }
481     
482     if (dconf->target_group)
483     {
484         apr_table_setn(r->subprocess_env, "SUPHP_GROUP",
485                        apr_pstrdup(r->pool, dconf->target_group));
486     }
487     else
488     {
489         apr_table_setn(r->subprocess_env, "SUPHP_GROUP",
490                        apr_pstrdup(r->pool, sconf->target_group));
491     }
492 #endif
493     
494     env = ap_create_environment(p, r->subprocess_env);
495         
496     /* set attributes for new process */
497     
498     if (((rv = apr_procattr_create(&procattr, p)) != APR_SUCCESS)
499         || ((rv = apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)) != APR_SUCCESS)
500         || ((rv = apr_procattr_dir_set(procattr, ap_make_dirstr_parent(r->pool, r->filename))) != APR_SUCCESS)
501     
502     /* set resource limits */
503
504 #ifdef RLIMIT_CPU
505         || ((rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, core_conf->limit_cpu)) != APR_SUCCESS)
506 #endif
507 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
508         || ((rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, core_conf->limit_mem)) != APR_SUCCESS)
509 #endif
510 #ifdef RLIMIT_NPROC
511         || ((apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, core_conf->limit_nproc)) != APR_SUCCESS)
512 #endif
513
514         || ((apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) != APR_SUCCESS)
515         || ((apr_procattr_error_check_set(procattr, 1)) != APR_SUCCESS)
516         || ((apr_procattr_detach_set(procattr, 0)) != APR_SUCCESS))
517     {
518         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
519                       "couldn't set child process attributes: %s", r->filename);
520         return HTTP_INTERNAL_SERVER_ERROR;
521     }
522     
523     /* create new process */
524     
525
526     proc = apr_pcalloc(p, sizeof(*proc));
527     rv = apr_proc_create(proc, SUPHP_PATH_TO_SUPHP, (const char *const *)argv, (const char *const *)env, procattr, p);
528     if (rv != APR_SUCCESS)
529     {
530        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
531                      "couldn't create child process: %s for %s", SUPHP_PATH_TO_SUPHP, r->filename);
532        return HTTP_INTERNAL_SERVER_ERROR;
533     }
534     apr_pool_note_subprocess(p, proc, APR_KILL_AFTER_TIMEOUT);
535
536     if (!proc->out)
537         return APR_EBADF;
538     apr_file_pipe_timeout_set(proc->out, r->server->timeout);
539     
540     if (!proc->in)
541         return APR_EBADF;
542     apr_file_pipe_timeout_set(proc->in, r->server->timeout);
543     
544     if (!proc->err)
545         return APR_EBADF;
546     apr_file_pipe_timeout_set(proc->err, r->server->timeout);
547     
548     /* send request body to script */
549     
550     bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
551     do
552     {
553         apr_bucket *bucket;
554         rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
555         
556         if (rv != APR_SUCCESS)
557         {
558             return rv;
559         }
560         
561         APR_BRIGADE_FOREACH(bucket, bb)
562         {
563             const char *data;
564             apr_size_t len;
565             int child_stopped_reading = 0;
566             
567             if (APR_BUCKET_IS_EOS(bucket))
568             {
569                 eos_reached = 1;
570                 break;
571             }
572             
573             if (APR_BUCKET_IS_FLUSH(bucket) || child_stopped_reading)
574             {
575                 continue;
576             }
577             
578             apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
579             
580             rv = apr_file_write_full(proc->in, data, len, NULL);
581             if (rv != APR_SUCCESS)
582             {
583                 child_stopped_reading = 1;
584             }
585         }
586         apr_brigade_cleanup(bb);
587     }
588     while (!eos_reached);
589     
590     apr_file_flush(proc->in);
591     apr_file_close(proc->in);
592     
593     /* get output from script and check if non-parsed headers are used */
594     
595     b = apr_bucket_pipe_create(proc->out, r->connection->bucket_alloc);
596     APR_BRIGADE_INSERT_TAIL(bb, b);
597     
598     len = 8;
599     if ((suphp_bucket_read(b, strbuf, len) == len)
600         && !(strcmp(strbuf, "HTTP/1.0") && strcmp(strbuf, "HTTP/1.1")))
601     {
602         nph = 1;
603     }
604     
605     b = apr_bucket_eos_create(r->connection->bucket_alloc);
606     APR_BRIGADE_INSERT_TAIL(bb, b);
607     
608     if (proc->out && !nph)
609     {
610         /* normal cgi headers, so we have to create the real headers by hand */
611         
612         int ret;
613         const char *location;
614         
615         ret = ap_scan_script_header_err_brigade(r, bb, strbuf);
616         if (ret == HTTP_NOT_MODIFIED)
617         {
618             return ret;
619         }
620         else if (ret != APR_SUCCESS)
621         {
622             suphp_log_script_err(r, proc->err);
623             
624             /* ap_scan_script_header_err_brigade does logging itself,
625                so simply return                                       */
626                
627             return HTTP_INTERNAL_SERVER_ERROR;
628         }
629         
630         location = apr_table_get(r->headers_out, "Location");
631         if (location && location[0] == '/' && r->status == 200)
632         {
633             /* empty brigade (script output) and modify headers */
634             
635             const char *buf;
636             apr_size_t blen;
637             APR_BRIGADE_FOREACH(b, bb)
638             {
639                 if (APR_BUCKET_IS_EOS(b))
640                     break;
641                 if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS)
642                     break;
643             }
644             apr_brigade_destroy(bb);
645             suphp_log_script_err(r, proc->err);
646             r->method = apr_pstrdup(r->pool, "GET");
647             r->method_number = M_GET;
648             apr_table_unset(r->headers_in, "Content-Length");
649             
650             ap_internal_redirect_handler(location, r);
651             return OK;
652         }
653         else if (location && r->status == 200)
654         {
655             /* empty brigade (script output) */
656             const char *buf;
657             apr_size_t blen;
658             APR_BRIGADE_FOREACH(b, bb)
659             {
660                 if (APR_BUCKET_IS_EOS(b))
661                     break;
662                 if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS)
663                     break;
664             }
665             apr_brigade_destroy(bb);
666             return HTTP_MOVED_TEMPORARILY;
667         }
668         
669         /* send output to browser (through filters) */
670         
671         rv = ap_pass_brigade(r->output_filters, bb);
672         
673         /* write errors to logfile */
674         
675         if (rv == APR_SUCCESS && !r->connection->aborted)
676             suphp_log_script_err(r, proc->err);
677         
678         apr_file_close(proc->err);
679     }
680     
681     if (proc->out && nph)
682     {
683         /* use non-parsed headers (direct output) */
684         
685         struct ap_filter_t *cur;
686         
687         /* get rid of output filters */
688         
689         cur = r->proto_output_filters;
690         while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION)
691         {
692             cur = cur->next;
693         }
694         r->output_filters = r->proto_output_filters = cur;
695         
696         /* send output to browser (directly) */
697         
698         rv = ap_pass_brigade(r->output_filters, bb);
699         
700         /* log errors */
701         if (rv == APR_SUCCESS && !r->connection->aborted)
702             suphp_log_script_err(r, proc->err);
703             
704         apr_file_close(proc->err);
705     }
706
707     return OK;
708 }
709
710 static void suphp_register_hooks(apr_pool_t *p)
711 {
712     ap_hook_handler(suphp_handler, NULL, NULL, APR_HOOK_MIDDLE);
713 }
714
715
716 /********************
717   Module declaration
718  ********************/
719  
720 module AP_MODULE_DECLARE_DATA suphp_module =
721 {
722     STANDARD20_MODULE_STUFF,
723     suphp_create_dir_config,
724     suphp_merge_dir_config,
725     suphp_create_server_config,
726     suphp_merge_server_config,
727     suphp_cmds,
728     suphp_register_hooks
729 };