Import upstream 0.7.1
[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 #include "apr_poll.h"
26
27 #define CORE_PRIVATE
28
29 #include "httpd.h"
30 #include "http_config.h"
31 #include "http_core.h"
32 #include "http_log.h"
33
34 #include "util_script.h"
35 #include "util_filter.h"
36
37 /* needed for get_suexec_identity hook */
38 #include "unixd.h"
39
40 module AP_MODULE_DECLARE_DATA suphp_module;
41
42
43 /*********************
44   Auxiliary functions
45  *********************/
46
47 static apr_status_t suphp_log_script_err(request_rec *r, apr_file_t *script_err)
48 {
49     char argsbuffer[HUGE_STRING_LEN];
50     char *newline;
51     apr_status_t rv;
52     
53     while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN,
54                          script_err)) == APR_SUCCESS) {
55         newline = strchr(argsbuffer, '\n');
56         if (newline) {
57             *newline = '\0';
58         }
59         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
60                       "%s", argsbuffer);
61     }
62     
63     return rv;
64 }
65
66 char *suphp_brigade_read(apr_pool_t *p, apr_bucket_brigade *bb, int bytes)
67 {
68     char *target_buf;
69     char *next_byte;
70     char *last_byte;
71     apr_bucket *b;
72     
73     if (bytes == 0) {
74         return NULL;
75     }
76     
77     target_buf = (char *) apr_palloc(p, bytes + 1);
78     next_byte = target_buf;
79     last_byte = target_buf + bytes;
80     
81     for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
82         char *buf;
83         apr_size_t size;
84         apr_size_t i;
85         if (apr_bucket_read(b, &buf, &size, APR_BLOCK_READ) == APR_SUCCESS) {
86             for (i = 0; i < size; i++) {
87                 *next_byte = *buf;
88                 next_byte++;
89                 buf++;
90                 if (next_byte == last_byte) {
91                     *next_byte = 0;
92                     return target_buf;
93                 }
94             }
95         }
96     }
97     next_byte = 0;
98     return target_buf;
99 }
100
101
102 /**************************
103   Configuration processing
104  **************************/
105
106 #define SUPHP_CONFIG_MODE_SERVER 1
107 #define SUPHP_CONFIG_MODE_DIRECTORY 2
108
109 #define SUPHP_ENGINE_OFF 0
110 #define SUPHP_ENGINE_ON 1
111 #define SUPHP_ENGINE_UNDEFINED 2
112
113 #ifndef SUPHP_PATH_TO_SUPHP
114 #define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
115 #endif
116
117 typedef struct {
118     int engine; // Status of suPHP_Engine
119     char *php_config;
120     int cmode;  // Server of directory configuration?
121 #ifdef SUPHP_USE_USERGROUP
122     char *target_user;
123     char *target_group;
124 #endif
125     apr_table_t *handlers;
126     char *php_path;
127 } suphp_conf;
128
129
130 static void *suphp_create_dir_config(apr_pool_t *p, char *dir)
131 {
132     suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
133     
134     cfg->php_config = NULL;
135     cfg->engine = SUPHP_ENGINE_UNDEFINED;
136     cfg->php_path = NULL;
137     cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
138
139 #ifdef SUPHP_USE_USERGROUP
140     cfg->target_user = NULL;
141     cfg->target_group = NULL;
142 #endif
143     
144     /* Create table with 0 initial elements */
145     /* This size may be increased for performance reasons */
146     cfg->handlers = apr_table_make(p, 0);
147     
148     return (void *) cfg;
149 }
150
151
152 static void *suphp_merge_dir_config(apr_pool_t *p, void *base, 
153                                     void *overrides)
154 {
155     suphp_conf *parent = (suphp_conf *) base;
156     suphp_conf *child = (suphp_conf *) overrides;
157     suphp_conf *merged = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
158     
159     merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
160     
161     if (child->php_config)
162         merged->php_config = apr_pstrdup(p, child->php_config);
163     else if (parent->php_config)
164         merged->php_config = apr_pstrdup(p, parent->php_config);
165     else
166         merged->php_config = NULL;
167     
168     if (child->engine != SUPHP_ENGINE_UNDEFINED)
169         merged->engine = child->engine;
170     else
171         merged->engine = parent->engine;
172
173 #ifdef SUPHP_USE_USERGROUP
174     if (child->target_user)
175         merged->target_user = apr_pstrdup(p, child->target_user);
176     else if (parent->target_user)
177         merged->target_user = apr_pstrdup(p, parent->target_user);
178     else
179         merged->target_user = NULL;
180         
181     if (child->target_group)
182         merged->target_group = apr_pstrdup(p, child->target_group);
183     else if (parent->target_group)
184         merged->target_group = apr_pstrdup(p, parent->target_group);
185     else
186         merged->target_group = NULL;
187 #endif
188     
189     merged->handlers = apr_table_overlay(p, child->handlers, parent->handlers);
190     
191     return (void *) merged;  
192 }
193
194
195 static void *suphp_create_server_config(apr_pool_t *p, server_rec *s)
196 {
197     suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
198     
199     cfg->engine = SUPHP_ENGINE_UNDEFINED;
200     cfg->php_path = NULL;
201     cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
202     
203     /* Create table with 0 initial elements */
204     /* This size may be increased for performance reasons */
205     cfg->handlers = apr_table_make(p, 0);
206     
207     return (void *) cfg;
208 }
209
210
211 static void *suphp_merge_server_config(apr_pool_t *p, void *base,
212                                        void *overrides)
213 {
214     suphp_conf *parent = (suphp_conf *) base;
215     suphp_conf *child = (suphp_conf *) overrides;
216     suphp_conf *merged = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
217     
218     if (child->engine != SUPHP_ENGINE_UNDEFINED)
219         merged->engine = child->engine;
220     else
221         merged->engine = parent->engine;
222     
223     if (child->php_path != NULL)
224         merged->php_path = apr_pstrdup(p, child->php_path);
225     else
226         merged->php_path = apr_pstrdup(p, parent->php_path);
227     
228 #ifdef SUPHP_USE_USERGROUP
229     if (child->target_user)
230         merged->target_user = apr_pstrdup(p, child->target_user);
231     else if (parent->target_user)
232         merged->target_user = apr_pstrdup(p, parent->target_user);
233     else
234         merged->target_user = NULL;
235         
236     if (child->target_group)
237         merged->target_group = apr_pstrdup(p, child->target_group);
238     else if (parent->target_group)
239         merged->target_group = apr_pstrdup(p, parent->target_group);
240     else
241         merged->target_group = NULL;
242 #endif
243     
244     merged->handlers = apr_table_overlay(p, child->handlers, parent->handlers);
245     
246     return (void*) merged;
247 }
248
249
250 /******************
251   Command handlers
252  ******************/
253
254 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
255                                            int flag)
256 {
257     server_rec *s = cmd->server;
258     suphp_conf *cfg;
259     
260     if (mconfig)
261         cfg = (suphp_conf *) mconfig;
262     else
263         cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
264     
265     if (flag)
266         cfg->engine = SUPHP_ENGINE_ON;
267     else
268         cfg->engine = SUPHP_ENGINE_OFF;
269     
270     return NULL;
271 }
272
273
274 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
275                                            const char *arg)
276 {
277     server_rec *s = cmd->server;
278     suphp_conf *cfg;
279     
280     if (mconfig)
281         cfg = (suphp_conf *) mconfig;
282     else
283         cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
284     
285     cfg->php_config = apr_pstrdup(cmd->pool, arg);
286     
287     return NULL;
288 }
289
290
291 #ifdef SUPHP_USE_USERGROUP
292 static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig,
293                                            const char *arg1, const char *arg2)
294 {
295     suphp_conf *cfg = (suphp_conf *) mconfig;
296     
297     cfg->target_user = apr_pstrdup(cmd->pool, arg1);
298     cfg->target_group = apr_pstrdup(cmd->pool, arg2);
299     
300     return NULL;
301 }
302 #endif
303
304
305 static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
306                                              const char *arg)
307 {
308     suphp_conf *cfg;
309     if (mconfig)
310         cfg = (suphp_conf *) mconfig;
311     else
312         cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);
313     
314     // Mark active handler with '1'
315     apr_table_set(cfg->handlers, arg, "1");
316
317     return NULL;
318 }
319
320
321 static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd, 
322                                                    void *mconfig, 
323                                                    const char *arg)
324 {
325     suphp_conf *cfg;
326     if (mconfig)
327         cfg = (suphp_conf *) mconfig;
328     else
329         cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);
330     
331     // Mark deactivated handler with '0'
332     apr_table_set(cfg->handlers, arg, "0");
333
334     return NULL;
335 }
336
337
338 static const char *suphp_handle_cmd_phppath(cmd_parms *cmd, void* mconfig, const char *arg)
339 {
340     server_rec *s = cmd->server;
341     suphp_conf *cfg;
342     
343     cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
344     
345     cfg->php_path = apr_pstrdup(cmd->pool, arg);
346     
347     return NULL;
348 }
349
350
351 static const command_rec suphp_cmds[] =
352 {
353     AP_INIT_FLAG("suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF | ACCESS_CONF,
354                  "Whether suPHP is on or off, default is off"),
355     AP_INIT_TAKE1("suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS,
356                   "Wheres the php.ini resides, default is the PHP default"),
357 #ifdef SUPHP_USE_USERGROUP
358     AP_INIT_TAKE2("suPHP_UserGroup", suphp_handle_cmd_user_group, NULL, RSRC_CONF | ACCESS_CONF,
359                   "User and group scripts shall be run as"),
360 #endif
361     AP_INIT_ITERATE("suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"),
362     AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"),
363     AP_INIT_TAKE1("suPHP_PHPPath", suphp_handle_cmd_phppath, NULL, RSRC_CONF, "Path to the PHP binary used to render source view"),
364     {NULL}
365 };
366
367 /*****************************************
368   Code for reading script's stdout/stderr
369   based on mod_cgi's code
370  *****************************************/
371
372 #if APR_FILES_AS_SOCKETS
373
374 static const apr_bucket_type_t bucket_type_suphp;
375
376 struct suphp_bucket_data {
377     apr_pollset_t *pollset;
378     request_rec *r;
379 };
380
381 static apr_bucket *suphp_bucket_create(request_rec *r, apr_file_t *out, apr_file_t *err, apr_bucket_alloc_t *list)
382 {
383     apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
384     apr_status_t rv;
385     apr_pollfd_t fd;
386     struct suphp_bucket_data *data = apr_palloc(r->pool, sizeof(*data));
387     
388     APR_BUCKET_INIT(b);
389     b->free = apr_bucket_free;
390     b->list = list;
391     b->type = &bucket_type_suphp;
392     b->length = (apr_size_t) (-1);
393     b->start = (-1);
394     
395     /* Create the pollset */
396     rv = apr_pollset_create(&data->pollset, 2, r->pool, 0);
397     AP_DEBUG_ASSERT(rv == APR_SUCCESS);
398     
399     fd.desc_type = APR_POLL_FILE;
400     fd.reqevents = APR_POLLIN;
401     fd.p = r->pool;
402     fd.desc.f = out; /* script's stdout */
403     fd.client_data = (void *) 1;
404     rv = apr_pollset_add(data->pollset, &fd);
405     AP_DEBUG_ASSERT(rv == APR_SUCCESS);
406     
407     fd.desc.f = err; /* script's stderr */
408     fd.client_data = (void *) 2;
409     rv = apr_pollset_add(data->pollset, &fd);
410     AP_DEBUG_ASSERT(rv == APR_SUCCESS);
411     
412     data->r = r;
413     b->data = data;
414     return b;
415 }
416
417 static apr_bucket *suphp_bucket_dup(struct suphp_bucket_data *data, apr_bucket_alloc_t *list)
418 {
419     apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
420     APR_BUCKET_INIT(b);
421     b->free = apr_bucket_free;
422     b->list = list;
423     b->type = &bucket_type_suphp;
424     b->length = (apr_size_t) (-1);
425     b->start = (-1);
426     b->data = data;
427     return b;
428 }
429
430 /* This utility method is needed, because APR's implementation for the
431    pipe bucket cannot handle or special bucket type                    */
432 static apr_status_t suphp_read_fd(apr_bucket *b, apr_file_t *fd, const char **str, apr_size_t *len)
433 {
434     char *buf;
435     apr_status_t rv;
436     
437     *str = NULL;
438     *len = APR_BUCKET_BUFF_SIZE;
439     buf = apr_bucket_alloc(*len, b->list);
440     
441     rv = apr_file_read(fd, buf, len);
442     
443     if (*len > 0) {
444         /* Got data */
445         struct suphp_bucket_data *data = b->data;
446         apr_bucket_heap *h;
447         
448         /* Change the current bucket to refer to what we read
449            and append the pipe bucket after it                */
450         b = apr_bucket_heap_make(b, buf, *len, apr_bucket_free);
451         /* Here, b->data is the new heap bucket data */
452         h = b->data;
453         h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
454         *str = buf;
455         APR_BUCKET_INSERT_AFTER(b, suphp_bucket_dup(data, b->list));
456     } else {
457         /* Got no data */
458         apr_bucket_free(buf);
459         b = apr_bucket_immortal_make(b, "", 0);
460         /* Here, b->data is the reference to the empty string */
461         *str = b->data;
462     }
463     return rv;
464 }
465
466 /* Poll on stdout and stderr to make sure the process does not block
467    because of a full system (stderr) buffer                          */
468 static apr_status_t suphp_bucket_read(apr_bucket *b, const char **str, apr_size_t *len, apr_read_type_e block) {
469   struct suphp_bucket_data *data = b->data;
470   apr_interval_time_t timeout;
471   apr_status_t rv;
472   int gotdata = 0;
473   
474   timeout = (block == APR_NONBLOCK_READ) ? 0 : data->r->server->timeout;
475   
476   do {
477       const apr_pollfd_t *results;
478       apr_int32_t num;
479       
480       rv = apr_pollset_poll(data->pollset, timeout, &num, &results);
481       if (APR_STATUS_IS_TIMEUP(rv)) {
482           return (timeout == 0) ? APR_EAGAIN : rv;
483       } else if (APR_STATUS_IS_EINTR(rv)) {
484           continue;
485       } else if (rv != APR_SUCCESS) {
486           ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, "Poll failed waiting for suPHP child process");
487           return rv;
488       }
489       
490       while (num > 0) {
491         if (results[0].client_data == (void *) 1) {
492             /* handle stdout */
493             rv = suphp_read_fd(b, results[0].desc.f, str, len);
494             if (APR_STATUS_IS_EOF(rv)) {
495               rv = APR_SUCCESS;
496             }
497             gotdata = 1;
498         } else {
499             /* handle stderr */
500             apr_status_t rv2 = suphp_log_script_err(data->r, results[0].desc.f);
501             if (APR_STATUS_IS_EOF(rv2)) {
502                 apr_pollset_remove(data->pollset, &results[0]);
503             }
504         }
505         num--;
506         results++;
507       }
508   } while (!gotdata);
509   
510   return rv;
511 }
512
513 static const apr_bucket_type_t bucket_type_suphp = {
514     "SUPHP", 5, APR_BUCKET_DATA,
515     apr_bucket_destroy_noop,
516     suphp_bucket_read,
517     apr_bucket_setaside_notimpl,
518     apr_bucket_split_notimpl,
519     apr_bucket_copy_notimpl
520 };
521
522 #endif
523
524 static void suphp_discard_output(apr_bucket_brigade *bb) {
525   apr_bucket *b;
526   const char *buf;
527   apr_size_t len;
528   apr_status_t rv;
529   for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
530       if (APR_BUCKET_IS_EOS(b)) {
531           break;
532       }
533       rv = apr_bucket_read(b, &buf, &len, APR_BLOCK_READ);
534       if (rv != APR_SUCCESS) {
535           break;
536       }
537   }
538 }
539
540
541 /******************
542   Hooks / handlers
543  ******************/
544
545 static int suphp_script_handler(request_rec *r);
546 static int suphp_source_handler(request_rec *r);
547
548 static int suphp_handler(request_rec *r)
549 {
550     suphp_conf *sconf, *dconf;
551     
552     sconf = ap_get_module_config(r->server->module_config, &suphp_module);
553     dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
554     
555     /* only handle request if mod_suphp is active for this handler */
556     /* check only first byte of value (second has to be \0) */
557     if (apr_table_get(dconf->handlers, r->handler) != NULL)
558     {
559         if (*(apr_table_get(dconf->handlers, r->handler)) != '0')
560         {
561             return suphp_script_handler(r);
562         }
563     }
564     else
565     {
566         if ((apr_table_get(sconf->handlers, r->handler) != NULL)
567         && (*(apr_table_get(sconf->handlers, r->handler)) != '0'))
568         {
569             return suphp_script_handler(r);
570         }
571     }
572     
573     if (!strcmp(r->handler, "x-httpd-php-source") 
574     || !strcmp(r->handler, "application/x-httpd-php-source"))
575     {
576         return suphp_source_handler(r);
577     }
578     
579     return DECLINED;
580 }
581
582 static int suphp_source_handler(request_rec *r)
583 {
584     suphp_conf *conf;
585     apr_status_t rv;
586     apr_pool_t *p;
587     apr_file_t *file;
588     apr_proc_t *proc;
589     apr_procattr_t *procattr;
590     char **argv;
591     char **env;
592     apr_bucket_brigade *bb;
593     apr_bucket *b;
594     char *phpexec;
595     
596     p = r->main ? r->main->pool : r->pool;
597     
598     if (strcmp(r->method, "GET"))
599     {
600         return DECLINED;
601     }
602     
603     conf = ap_get_module_config(r->server->module_config, &suphp_module);
604     phpexec = apr_pstrdup(p, conf->php_path);
605     if (phpexec == NULL)
606     {
607         return DECLINED;
608     }
609     
610     // Try to open file for reading to see whether is is accessible
611     rv = apr_file_open(&file, apr_pstrdup(p, r->filename), APR_READ, APR_OS_DEFAULT, p);
612     if (rv == APR_SUCCESS)
613     {
614         apr_file_close(file);
615         file = NULL;
616     }
617     else if (rv == EACCES)
618     {
619         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "access to %s denied", r->filename);
620         return HTTP_FORBIDDEN;
621     }
622     else if (rv == ENOENT)
623     {
624         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "File does not exist: %s", r->filename);
625         return HTTP_NOT_FOUND;
626     }
627     else
628     {
629         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Could not open file: %s", r->filename);
630         return HTTP_INTERNAL_SERVER_ERROR;
631     }
632     
633         env = ap_create_environment(p, r->subprocess_env);
634         
635     /* set attributes for new process */
636     
637     if (((rv = apr_procattr_create(&procattr, p)) != APR_SUCCESS)
638         || ((rv = apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)) != APR_SUCCESS)
639         || ((rv = apr_procattr_dir_set(procattr, ap_make_dirstr_parent(r->pool, r->filename))) != APR_SUCCESS)
640         || ((apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) != APR_SUCCESS)
641         || ((apr_procattr_error_check_set(procattr, 1)) != APR_SUCCESS)
642         || ((apr_procattr_detach_set(procattr, 0)) != APR_SUCCESS))
643     {
644         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
645                       "couldn't set child process attributes: %s", r->filename);
646         return HTTP_INTERNAL_SERVER_ERROR;
647     }
648
649     
650     /* create new process */
651     
652     argv = apr_palloc(p, 4 * sizeof(char *));
653     argv[0] = phpexec;
654     argv[1] = "-s";
655     argv[2] = apr_pstrdup(p, r->filename);
656     argv[3] = NULL;
657     
658     env = ap_create_environment(p, r->subprocess_env);
659     
660     proc = apr_pcalloc(p, sizeof(*proc));
661     rv = apr_proc_create(proc, phpexec, (const char *const *)argv, (const char *const *)env, procattr, p);
662     if (rv != APR_SUCCESS)
663     {
664        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
665                      "couldn't create child process: %s for %s", phpexec, r->filename);
666        return HTTP_INTERNAL_SERVER_ERROR;
667     }
668     apr_pool_note_subprocess(p, proc, APR_KILL_AFTER_TIMEOUT);
669
670     if (!proc->out)
671         return APR_EBADF;
672     apr_file_pipe_timeout_set(proc->out, r->server->timeout);
673     
674     if (!proc->in)
675         return APR_EBADF;
676     apr_file_pipe_timeout_set(proc->in, r->server->timeout);
677     
678     if (!proc->err)
679         return APR_EBADF;
680     apr_file_pipe_timeout_set(proc->err, r->server->timeout);
681     
682     /* discard input */
683         
684     bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
685     
686     apr_file_flush(proc->in);
687     apr_file_close(proc->in);
688     
689     rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
690     if (rv != APR_SUCCESS)
691     {
692         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
693                      "couldn't get input from filters: %s", r->filename);
694         return HTTP_INTERNAL_SERVER_ERROR;
695     }
696     suphp_discard_output(bb);
697     apr_brigade_cleanup(bb);
698     
699     /* get output from script */
700     
701 #if APR_FILES_AS_SOCKETS
702     apr_file_pipe_timeout_set(proc->out, 0);
703     apr_file_pipe_timeout_set(proc->err, 0);
704     b = suphp_bucket_create(r, proc->out, proc->err, r->connection->bucket_alloc);
705 #else
706     b = apr_bucket_pipe_create(proc->out, r->connection->bucket_alloc);
707 #endif
708     APR_BRIGADE_INSERT_TAIL(bb, b);
709     
710     b = apr_bucket_eos_create(r->connection->bucket_alloc);
711     APR_BRIGADE_INSERT_TAIL(bb, b);
712
713     /* send output to browser (through filters) */
714     
715     r->content_type = "text/html";
716     rv = ap_pass_brigade(r->output_filters, bb);
717     
718     /* write errors to logfile */
719     
720     if (rv == APR_SUCCESS && !r->connection->aborted)
721     {
722         suphp_log_script_err(r, proc->err);
723         apr_file_close(proc->err);
724     }
725     
726     return OK;
727 }
728
729 static int suphp_script_handler(request_rec *r)
730 {
731     apr_pool_t *p;
732     suphp_conf *sconf;
733     suphp_conf *dconf;
734     core_dir_config *core_conf;
735     
736     apr_finfo_t finfo;
737     
738     apr_procattr_t *procattr;
739     
740     apr_proc_t *proc;
741     
742     char **argv;
743     char **env;
744     apr_status_t rv;
745     int len = 0;
746 #if MAX_STRING_LEN < 1024
747     char strbuf[1024];
748 #else
749     char strbuf[MAX_STRING_LEN];
750 #endif
751     char *tmpbuf;
752     int nph = 0;
753     int eos_reached = 0;
754     char *auth_user = NULL;
755     char *auth_pass = NULL;
756
757 #ifdef SUPHP_USE_USERGROUP
758     char *ud_user = NULL;
759     char *ud_group = NULL;
760 #endif
761     
762     apr_bucket_brigade *bb;
763     apr_bucket *b;
764     
765     /* load configuration */
766     
767     p = r->main ? r->main->pool : r->pool;
768     sconf = ap_get_module_config(r->server->module_config, &suphp_module);
769     dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
770     core_conf = (core_dir_config *) ap_get_module_config(r->per_dir_config, &core_module);
771     
772     /* check if suPHP is enabled for this request */
773     
774     if (((sconf->engine != SUPHP_ENGINE_ON)
775         && (dconf->engine != SUPHP_ENGINE_ON))
776         || ((sconf->engine == SUPHP_ENGINE_ON)
777         && (dconf->engine == SUPHP_ENGINE_OFF)))
778         return DECLINED;
779     
780     /* check if file is existing and acessible */
781     
782     rv = apr_stat(&finfo, apr_pstrdup(p, r->filename), APR_FINFO_NORM, p);
783     
784     if (rv == APR_SUCCESS)
785         ; /* do nothing */
786     else if (rv == EACCES)
787     {
788         return HTTP_FORBIDDEN;
789         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "access to %s denied", r->filename);
790     }
791     else if (rv == ENOENT)
792     {
793         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "File does not exist: %s", r->filename);
794         return HTTP_NOT_FOUND;
795     }
796     else
797     {
798         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "could not get fileinfo: %s", r->filename);
799         return HTTP_NOT_FOUND;
800     }
801     
802     if (!(r->finfo.protection & APR_UREAD))
803     {
804         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Insufficient permissions: %s", r->filename);
805         return HTTP_FORBIDDEN;
806     }
807     
808 #ifdef SUPHP_USE_USERGROUP
809     if ((sconf->target_user == NULL || sconf->target_group == NULL)
810         && (dconf->target_user == NULL || dconf->target_group == NULL))
811     {
812         /* Check for userdir request */
813         ap_unix_identity_t *userdir_id = NULL;
814         userdir_id = ap_run_get_suexec_identity(r);
815         if (userdir_id != NULL && userdir_id->userdir) {
816             ud_user = apr_psprintf(r->pool, "#%ld", (long) userdir_id->uid);
817             ud_group = apr_psprintf(r->pool, "#%ld", (long) userdir_id->gid);
818         } else {
819             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
820                           "No user or group set - set suPHP_UserGroup");
821             return HTTP_INTERNAL_SERVER_ERROR;
822         }
823     }
824 #endif
825         
826     /* prepare argv for new process */
827     
828     argv = apr_palloc(p, 2 * sizeof(char *));
829     argv[0] = SUPHP_PATH_TO_SUPHP;
830     argv[1] = NULL;
831     
832     /* prepare environment for new process */
833     
834     ap_add_common_vars(r);
835     ap_add_cgi_vars(r);
836     
837     apr_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
838     apr_table_unset(r->subprocess_env, "SUPHP_AUTH_USER");
839     apr_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
840     
841 #ifdef SUPHP_USE_USERGROUP
842     apr_table_unset(r->subprocess_env, "SUPHP_USER");
843     apr_table_unset(r->subprocess_env, "SUPHP_GROUP");
844 #endif
845     
846     if (dconf->php_config)
847     {
848         apr_table_setn(r->subprocess_env, "SUPHP_PHP_CONFIG", apr_pstrdup(p, dconf->php_config));
849     }
850     
851     apr_table_setn(r->subprocess_env, "SUPHP_HANDLER", r->handler);
852     
853     if (r->headers_in)
854     {
855         const char *auth = NULL;
856         auth = apr_table_get(r->headers_in, "Authorization");
857         if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0)
858         {
859             char *user;
860             char *pass;
861             user = ap_pbase64decode(p, auth + 6);
862             if (user)
863             {
864                 pass = strchr(user, ':');
865                 if (pass)
866                 {
867                     *pass++ = '\0';
868                     auth_user = apr_pstrdup(r->pool, user);
869                     auth_pass = apr_pstrdup(r->pool, pass);
870                 }
871             }
872         }
873     }
874     
875     if (auth_user && auth_pass)
876     {
877         apr_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
878         apr_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
879     }
880
881 #ifdef SUPHP_USE_USERGROUP
882     if (dconf->target_user)
883     {
884         apr_table_setn(r->subprocess_env, "SUPHP_USER",
885                        apr_pstrdup(r->pool, dconf->target_user));
886     }
887     else if (sconf->target_user)
888     {
889         apr_table_setn(r->subprocess_env, "SUPHP_USER",
890                        apr_pstrdup(r->pool, sconf->target_user));
891     }
892     else
893     {
894         apr_table_setn(r->subprocess_env, "SUPHP_USER",
895                        apr_pstrdup(r->pool, ud_user));
896     }
897     
898     if (dconf->target_group)
899     {
900         apr_table_setn(r->subprocess_env, "SUPHP_GROUP",
901                        apr_pstrdup(r->pool, dconf->target_group));
902     }
903     else if (sconf->target_group)
904     {
905         apr_table_setn(r->subprocess_env, "SUPHP_GROUP",
906                        apr_pstrdup(r->pool, sconf->target_group));
907     }
908     else
909     {
910         apr_table_setn(r->subprocess_env, "SUPHP_GROUP",
911                        apr_pstrdup(r->pool, ud_group));
912     }
913 #endif
914     
915     env = ap_create_environment(p, r->subprocess_env);
916         
917     /* set attributes for new process */
918     
919     if (((rv = apr_procattr_create(&procattr, p)) != APR_SUCCESS)
920         || ((rv = apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)) != APR_SUCCESS)
921         || ((rv = apr_procattr_dir_set(procattr, ap_make_dirstr_parent(r->pool, r->filename))) != APR_SUCCESS)
922     
923     /* set resource limits */
924
925 #ifdef RLIMIT_CPU
926         || ((rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, core_conf->limit_cpu)) != APR_SUCCESS)
927 #endif
928 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
929         || ((rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, core_conf->limit_mem)) != APR_SUCCESS)
930 #endif
931 #ifdef RLIMIT_NPROC
932         || ((apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, core_conf->limit_nproc)) != APR_SUCCESS)
933 #endif
934
935         || ((apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) != APR_SUCCESS)
936         || ((apr_procattr_error_check_set(procattr, 1)) != APR_SUCCESS)
937         || ((apr_procattr_detach_set(procattr, 0)) != APR_SUCCESS))
938     {
939         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
940                       "couldn't set child process attributes: %s", r->filename);
941         return HTTP_INTERNAL_SERVER_ERROR;
942     }
943     
944     /* create new process */
945     
946
947     proc = apr_pcalloc(p, sizeof(*proc));
948     rv = apr_proc_create(proc, SUPHP_PATH_TO_SUPHP, (const char *const *)argv, (const char *const *)env, procattr, p);
949     if (rv != APR_SUCCESS)
950     {
951        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
952                      "couldn't create child process: %s for %s", SUPHP_PATH_TO_SUPHP, r->filename);
953        return HTTP_INTERNAL_SERVER_ERROR;
954     }
955     apr_pool_note_subprocess(p, proc, APR_KILL_AFTER_TIMEOUT);
956
957     if (!proc->out)
958         return APR_EBADF;
959     apr_file_pipe_timeout_set(proc->out, r->server->timeout);
960     
961     if (!proc->in)
962         return APR_EBADF;
963     apr_file_pipe_timeout_set(proc->in, r->server->timeout);
964     
965     if (!proc->err)
966         return APR_EBADF;
967     apr_file_pipe_timeout_set(proc->err, r->server->timeout);
968     
969     /* send request body to script */
970     
971     bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
972     do
973     {
974         apr_bucket *bucket;
975         rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
976         
977         if (rv != APR_SUCCESS)
978         {
979             return rv;
980         }
981         
982         for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); bucket = APR_BUCKET_NEXT(bucket))
983         {
984             const char *data;
985             apr_size_t len;
986             int child_stopped_reading = 0;
987             
988             if (APR_BUCKET_IS_EOS(bucket))
989             {
990                 eos_reached = 1;
991                 break;
992             }
993             
994             if (APR_BUCKET_IS_FLUSH(bucket) || child_stopped_reading)
995             {
996                 continue;
997             }
998             
999             apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
1000             
1001             rv = apr_file_write_full(proc->in, data, len, NULL);
1002             if (rv != APR_SUCCESS)
1003             {
1004                 child_stopped_reading = 1;
1005             }
1006         }
1007         apr_brigade_cleanup(bb);
1008     }
1009     while (!eos_reached);
1010     
1011     apr_file_flush(proc->in);
1012     apr_file_close(proc->in);
1013     
1014     /* get output from script and check if non-parsed headers are used */
1015     
1016 #if APR_FILES_AS_SOCKETS
1017     apr_file_pipe_timeout_set(proc->out, 0);
1018     apr_file_pipe_timeout_set(proc->err, 0);
1019     b = suphp_bucket_create(r, proc->out, proc->err, r->connection->bucket_alloc);
1020 #else
1021     b = apr_bucket_pipe_create(proc->out, r->connection->bucket_alloc);
1022 #endif
1023
1024     APR_BRIGADE_INSERT_TAIL(bb, b);
1025     
1026     b = apr_bucket_eos_create(r->connection->bucket_alloc);
1027     APR_BRIGADE_INSERT_TAIL(bb, b);
1028
1029     tmpbuf = suphp_brigade_read(p, bb, 8);
1030     if (strlen(tmpbuf) == 8 && !(strncmp(tmpbuf, "HTTP/1.0", 8) && strncmp(tmpbuf, "HTTP/1.1", 8)))
1031     {
1032         nph = 1;
1033     }
1034     
1035     if (!nph)
1036     {
1037         /* normal cgi headers, so we have to create the real headers ourselves */
1038         
1039         int ret;
1040         const char *location;
1041     
1042         ret = ap_scan_script_header_err_brigade(r, bb, strbuf);
1043         if (ret == HTTP_NOT_MODIFIED)
1044         {
1045             return ret;
1046         }
1047         else if (ret != APR_SUCCESS)
1048         {
1049             suphp_discard_output(bb);
1050             apr_brigade_destroy(bb);
1051             suphp_log_script_err(r, proc->err);
1052             
1053             /* ap_scan_script_header_err_brigade does logging itself,
1054                so simply return                                       */
1055                
1056             return HTTP_INTERNAL_SERVER_ERROR;
1057         }
1058         
1059         location = apr_table_get(r->headers_out, "Location");
1060         if (location && location[0] == '/' && r->status == 200)
1061         {
1062             /* empty brigade (script output) and modify headers */
1063             
1064             suphp_discard_output(bb);
1065             apr_brigade_destroy(bb);
1066             suphp_log_script_err(r, proc->err);
1067             r->method = apr_pstrdup(r->pool, "GET");
1068             r->method_number = M_GET;
1069             apr_table_unset(r->headers_in, "Content-Length");
1070             
1071             ap_internal_redirect_handler(location, r);
1072             return OK;
1073         }
1074         else if (location && r->status == 200)
1075         {
1076             /* empty brigade (script output) */
1077             suphp_discard_output(bb);
1078             apr_brigade_destroy(bb);
1079             suphp_log_script_err(r, proc->err);
1080             return HTTP_MOVED_TEMPORARILY;
1081         }
1082         
1083         /* send output to browser (through filters) */
1084         
1085         rv = ap_pass_brigade(r->output_filters, bb);
1086         
1087         /* write errors to logfile */
1088         
1089         if (rv == APR_SUCCESS && !r->connection->aborted)
1090             suphp_log_script_err(r, proc->err);
1091         
1092         apr_file_close(proc->err);
1093     }
1094     
1095     if (proc->out && nph)
1096     {
1097         /* use non-parsed headers (direct output) */
1098         
1099         struct ap_filter_t *cur;
1100         
1101         /* get rid of output filters */
1102         
1103         cur = r->proto_output_filters;
1104         while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION)
1105         {
1106             cur = cur->next;
1107         }
1108         r->output_filters = r->proto_output_filters = cur;
1109         
1110         /* send output to browser (directly) */
1111         
1112         rv = ap_pass_brigade(r->output_filters, bb);
1113         
1114         /* log errors */
1115         if (rv == APR_SUCCESS && !r->connection->aborted)
1116             suphp_log_script_err(r, proc->err);
1117             
1118         apr_file_close(proc->err);
1119     }
1120
1121     return OK;
1122 }
1123
1124 static void suphp_register_hooks(apr_pool_t *p)
1125 {
1126     ap_hook_handler(suphp_handler, NULL, NULL, APR_HOOK_MIDDLE);
1127 }
1128
1129
1130 /********************
1131   Module declaration
1132  ********************/
1133  
1134 module AP_MODULE_DECLARE_DATA suphp_module =
1135 {
1136     STANDARD20_MODULE_STUFF,
1137     suphp_create_dir_config,
1138     suphp_merge_dir_config,
1139     suphp_create_server_config,
1140     suphp_merge_server_config,
1141     suphp_cmds,
1142     suphp_register_hooks
1143 };