2 suPHP - (c)2002-2005 Sebastian Marsching <sebastian@marsching.com>
4 This file is part of suPHP.
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.
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.
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
22 #include "apr_strings.h"
23 #include "apr_thread_proc.h"
24 #include "apr_buckets.h"
29 #include "http_config.h"
30 #include "http_core.h"
33 #include "util_script.h"
34 #include "util_filter.h"
37 module AP_MODULE_DECLARE_DATA suphp_module;
40 /*********************
42 *********************/
44 static int suphp_bucket_read(apr_bucket *b, char *buf, int len)
46 const char *dst_end = buf + len;
49 const char *bucket_data;
50 apr_size_t bucket_data_len;
55 if (APR_BUCKET_IS_EOS(b))
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))
64 src_end = bucket_data + bucket_data_len;
65 while ((src < src_end) && (dst < dst_end))
77 static void suphp_log_script_err(request_rec *r, apr_file_t *script_err)
79 char argsbuffer[HUGE_STRING_LEN];
82 while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
83 script_err) == APR_SUCCESS) {
84 newline = strchr(argsbuffer, '\n');
88 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
94 /**************************
95 Configuration processing
96 **************************/
98 #define SUPHP_CONFIG_MODE_SERVER 1
99 #define SUPHP_CONFIG_MODE_DIRECTORY 2
101 #define SUPHP_ENGINE_OFF 0
102 #define SUPHP_ENGINE_ON 1
103 #define SUPHP_ENGINE_UNDEFINED 2
105 #ifndef SUPHP_PATH_TO_SUPHP
106 #define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
110 int engine; // Status of suPHP_Engine
112 int cmode; // Server of directory configuration?
113 #ifdef SUPHP_USE_USERGROUP
117 apr_table_t *handlers;
121 static void *suphp_create_dir_config(apr_pool_t *p, char *dir)
123 suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
125 cfg->php_config = NULL;
126 cfg->engine = SUPHP_ENGINE_UNDEFINED;
127 cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
129 #ifdef SUPHP_USE_USERGROUP
130 cfg->target_user = NULL;
131 cfg->target_group = NULL;
134 /* Create table with 0 initial elements */
135 /* This size may be increased for performance reasons */
136 cfg->handlers = apr_table_make(p, 0);
142 static void *suphp_merge_dir_config(apr_pool_t *p, void *base,
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));
149 merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
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);
156 merged->php_config = NULL;
158 if (child->engine != SUPHP_ENGINE_UNDEFINED)
159 merged->engine = child->engine;
161 merged->engine = parent->engine;
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);
169 merged->target_user = NULL;
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);
176 merged->target_group = NULL;
179 merged->handlers = apr_table_overlay(p, child->handlers, parent->handlers);
181 return (void *) merged;
185 static void *suphp_create_server_config(apr_pool_t *p, server_rec *s)
187 suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
189 cfg->engine = SUPHP_ENGINE_UNDEFINED;
190 cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
196 static void *suphp_merge_server_config(apr_pool_t *p, void *base,
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));
203 if (child->engine != SUPHP_ENGINE_UNDEFINED)
204 merged->engine = child->engine;
206 merged->engine = parent->engine;
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);
214 merged->target_user = NULL;
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);
221 merged->target_group = NULL;
224 return (void*) merged;
232 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
235 server_rec *s = cmd->server;
239 cfg = (suphp_conf *) mconfig;
241 cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
244 cfg->engine = SUPHP_ENGINE_ON;
246 cfg->engine = SUPHP_ENGINE_OFF;
252 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
255 server_rec *s = cmd->server;
259 cfg = (suphp_conf *) mconfig;
261 cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
263 cfg->php_config = apr_pstrdup(cmd->pool, arg);
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)
273 suphp_conf *cfg = (suphp_conf *) mconfig;
275 cfg->target_user = apr_pstrdup(cmd->pool, arg1);
276 cfg->target_group = apr_pstrdup(cmd->pool, arg2);
283 static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
286 suphp_conf *cfg = (suphp_conf *) mconfig;
287 // Mark active handler with '1'
288 apr_table_set(cfg->handlers, arg, "1");
294 static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd,
298 suphp_conf *cfg = (suphp_conf *) mconfig;
299 // Mark deactivated handler with '0'
300 apr_table_set(cfg->handlers, arg, "0");
306 static const command_rec suphp_cmds[] =
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"),
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"),
326 static int suphp_handler(request_rec *r)
331 core_dir_config *core_conf;
335 apr_procattr_t *procattr;
343 #if MAX_STRING_LEN < 1024
346 char strbuf[MAX_STRING_LEN];
350 char *auth_user = NULL;
351 char *auth_pass = NULL;
353 apr_bucket_brigade *bb;
356 /* load configuration */
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);
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'))
369 /* check if suPHP is enabled for this request */
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)))
377 /* check if file is existing and acessible */
379 rv = apr_stat(&finfo, apr_pstrdup(p, r->filename), APR_FINFO_NORM, p);
381 if (rv == APR_SUCCESS)
383 else if (rv == EACCES)
385 return HTTP_FORBIDDEN;
386 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "access to %s denied", r->filename);
388 else if (rv == ENOENT)
390 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "File does not exist: %s", r->filename);
391 return HTTP_NOT_FOUND;
395 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "could not get fileinfo: %s", r->filename);
396 return HTTP_NOT_FOUND;
399 if (!(r->finfo.protection & APR_UREAD))
401 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Insufficient permissions: %s", r->filename);
402 return HTTP_FORBIDDEN;
405 #ifdef SUPHP_USE_USERGROUP
406 if ((sconf->target_user == NULL || sconf->target_group == NULL)
407 && (dconf->target_user == NULL || dconf->target_group == NULL))
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;
415 /* prepare argv for new process */
417 argv = apr_palloc(p, 2 * sizeof(char *));
418 argv[0] = SUPHP_PATH_TO_SUPHP;
421 /* prepare environment for new process */
423 ap_add_common_vars(r);
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");
430 #ifdef SUPHP_USE_USERGROUP
431 apr_table_unset(r->subprocess_env, "SUPHP_USER");
432 apr_table_unset(r->subprocess_env, "SUPHP_GROUP");
435 if (dconf->php_config)
437 apr_table_setn(r->subprocess_env, "SUPHP_PHP_CONFIG", apr_pstrdup(p, dconf->php_config));
440 apr_table_setn(r->subprocess_env, "SUPHP_HANDLER", r->handler);
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)
450 user = ap_pbase64decode(p, auth + 6);
453 pass = strchr(user, ':');
457 auth_user = apr_pstrdup(r->pool, user);
458 auth_pass = apr_pstrdup(r->pool, pass);
464 if (auth_user && auth_pass)
466 apr_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
467 apr_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
470 #ifdef SUPHP_USE_USERGROUP
471 if (dconf->target_user)
473 apr_table_setn(r->subprocess_env, "SUPHP_USER",
474 apr_pstrdup(r->pool, dconf->target_user));
478 apr_table_setn(r->subprocess_env, "SUPHP_USER",
479 apr_pstrdup(r->pool, sconf->target_user));
482 if (dconf->target_group)
484 apr_table_setn(r->subprocess_env, "SUPHP_GROUP",
485 apr_pstrdup(r->pool, dconf->target_group));
489 apr_table_setn(r->subprocess_env, "SUPHP_GROUP",
490 apr_pstrdup(r->pool, sconf->target_group));
494 env = ap_create_environment(p, r->subprocess_env);
496 /* set attributes for new process */
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)
502 /* set resource limits */
505 || ((rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, core_conf->limit_cpu)) != APR_SUCCESS)
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)
511 || ((apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, core_conf->limit_nproc)) != APR_SUCCESS)
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))
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;
523 /* create new process */
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)
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;
534 apr_pool_note_subprocess(p, proc, APR_KILL_AFTER_TIMEOUT);
538 apr_file_pipe_timeout_set(proc->out, r->server->timeout);
542 apr_file_pipe_timeout_set(proc->in, r->server->timeout);
546 apr_file_pipe_timeout_set(proc->err, r->server->timeout);
548 /* send request body to script */
550 bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
554 rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
556 if (rv != APR_SUCCESS)
561 APR_BRIGADE_FOREACH(bucket, bb)
565 int child_stopped_reading = 0;
567 if (APR_BUCKET_IS_EOS(bucket))
573 if (APR_BUCKET_IS_FLUSH(bucket) || child_stopped_reading)
578 apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
580 rv = apr_file_write_full(proc->in, data, len, NULL);
581 if (rv != APR_SUCCESS)
583 child_stopped_reading = 1;
586 apr_brigade_cleanup(bb);
588 while (!eos_reached);
590 apr_file_flush(proc->in);
591 apr_file_close(proc->in);
593 /* get output from script and check if non-parsed headers are used */
595 b = apr_bucket_pipe_create(proc->out, r->connection->bucket_alloc);
596 APR_BRIGADE_INSERT_TAIL(bb, b);
599 if ((suphp_bucket_read(b, strbuf, len) == len)
600 && !(strcmp(strbuf, "HTTP/1.0") && strcmp(strbuf, "HTTP/1.1")))
605 b = apr_bucket_eos_create(r->connection->bucket_alloc);
606 APR_BRIGADE_INSERT_TAIL(bb, b);
608 if (proc->out && !nph)
610 /* normal cgi headers, so we have to create the real headers by hand */
613 const char *location;
615 if ((ret = ap_scan_script_header_err_brigade(r, bb, strbuf)) != APR_SUCCESS)
617 suphp_log_script_err(r, proc->err);
619 /* ap_scan_script_header_err_brigade does logging itself,
622 return HTTP_INTERNAL_SERVER_ERROR;
625 location = apr_table_get(r->headers_out, "Location");
626 if (location && location[0] == '/' && r->status == 200)
628 /* empty brigade (script output) and modify headers */
632 APR_BRIGADE_FOREACH(b, bb)
634 if (APR_BUCKET_IS_EOS(b))
636 if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS)
639 apr_brigade_destroy(bb);
640 suphp_log_script_err(r, proc->err);
641 r->method = apr_pstrdup(r->pool, "GET");
642 r->method_number = M_GET;
643 apr_table_unset(r->headers_in, "Content-Length");
645 ap_internal_redirect_handler(location, r);
648 else if (location && r->status == 200)
650 /* empty brigade (script output) */
653 APR_BRIGADE_FOREACH(b, bb)
655 if (APR_BUCKET_IS_EOS(b))
657 if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS)
660 apr_brigade_destroy(bb);
661 return HTTP_MOVED_TEMPORARILY;
664 /* send output to browser (through filters) */
666 rv = ap_pass_brigade(r->output_filters, bb);
668 /* write errors to logfile */
670 if (rv == APR_SUCCESS && !r->connection->aborted)
671 suphp_log_script_err(r, proc->err);
673 apr_file_close(proc->err);
676 if (proc->out && nph)
678 /* use non-parsed headers (direct output) */
680 struct ap_filter_t *cur;
682 /* get rid of output filters */
684 cur = r->proto_output_filters;
685 while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION)
689 r->output_filters = r->proto_output_filters = cur;
691 /* send output to browser (directly) */
693 rv = ap_pass_brigade(r->output_filters, bb);
696 if (rv == APR_SUCCESS && !r->connection->aborted)
697 suphp_log_script_err(r, proc->err);
699 apr_file_close(proc->err);
705 static void suphp_register_hooks(apr_pool_t *p)
707 ap_hook_handler(suphp_handler, NULL, NULL, APR_HOOK_MIDDLE);
711 /********************
713 ********************/
715 module AP_MODULE_DECLARE_DATA suphp_module =
717 STANDARD20_MODULE_STUFF,
718 suphp_create_dir_config,
719 suphp_merge_dir_config,
720 suphp_create_server_config,
721 suphp_merge_server_config,