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