2 suPHP - (c)2002-2004 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
120 static void *suphp_create_dir_config(apr_pool_t *p, char *dir)
122 suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
124 cfg->php_config = NULL;
125 cfg->engine = SUPHP_ENGINE_UNDEFINED;
126 cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
128 #ifdef SUPHP_USE_USERGROUP
129 cfg->target_user = NULL;
130 cfg->target_group = NULL;
137 static void *suphp_merge_dir_config(apr_pool_t *p, void *base,
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));
144 merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
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);
151 merged->php_config = NULL;
153 if (child->engine != SUPHP_ENGINE_UNDEFINED)
154 merged->engine = child->engine;
156 merged->engine = parent->engine;
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);
164 merged->target_user = NULL;
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);
171 merged->target_group = NULL;
174 return (void *) merged;
178 static void *suphp_create_server_config(apr_pool_t *p, server_rec *s)
180 suphp_conf *cfg = (suphp_conf *) apr_pcalloc(p, sizeof(suphp_conf));
182 cfg->engine = SUPHP_ENGINE_UNDEFINED;
183 cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
189 static void *suphp_merge_server_config(apr_pool_t *p, void *base,
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));
196 if (child->engine != SUPHP_ENGINE_UNDEFINED)
197 merged->engine = child->engine;
199 merged->engine = parent->engine;
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);
207 merged->target_user = NULL;
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);
214 merged->target_group = NULL;
217 return (void*) merged;
225 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
228 server_rec *s = cmd->server;
232 cfg = (suphp_conf *) mconfig;
234 cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
237 cfg->engine = SUPHP_ENGINE_ON;
239 cfg->engine = SUPHP_ENGINE_OFF;
245 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
248 server_rec *s = cmd->server;
252 cfg = (suphp_conf *) mconfig;
254 cfg = (suphp_conf *) ap_get_module_config(s->module_config, &suphp_module);
256 cfg->php_config = apr_pstrdup(cmd->pool, arg);
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)
266 suphp_conf *cfg = (suphp_conf *) mconfig;
268 cfg->target_user = apr_pstrdup(cmd->pool, arg1);
269 cfg->target_group = apr_pstrdup(cmd->pool, arg2);
276 static const command_rec suphp_cmds[] =
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"),
294 static int suphp_handler(request_rec *r)
299 core_dir_config *core_conf;
303 apr_procattr_t *procattr;
311 #if MAX_STRING_LEN < 1024
314 char strbuf[MAX_STRING_LEN];
318 char *auth_user = NULL;
319 char *auth_pass = NULL;
321 apr_bucket_brigade *bb;
324 /* load configuration */
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);
331 /* only handle request if x-httpd-php handler is asigned */
333 if (strcmp(r->handler, "x-httpd-php")
334 && strcmp(r->handler, "application/x-httpd-php"))
337 /* check if suPHP is enabled for this request */
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)))
345 /* check if file is existing and acessible */
347 rv = apr_stat(&finfo, apr_pstrdup(p, r->filename), APR_FINFO_NORM, p);
349 if (rv == APR_SUCCESS)
351 else if (rv == EACCES)
353 return HTTP_FORBIDDEN;
354 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "access to %s denied", r->filename);
356 else if (rv == ENOENT)
358 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "File does not exist: %s", r->filename);
359 return HTTP_NOT_FOUND;
363 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "could not get fileinfo: %s", r->filename);
364 return HTTP_NOT_FOUND;
367 if (!(r->finfo.protection & APR_UREAD))
369 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Insufficient permissions: %s", r->filename);
370 return HTTP_FORBIDDEN;
373 #ifdef SUPHP_USE_USERGROUP
374 if ((sconf->target_user == NULL || sconf->target_group == NULL)
375 && (dconf->target_user == NULL || dconf->target_group == NULL))
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;
383 /* prepare argv for new process */
385 argv = apr_palloc(p, 2 * sizeof(char *));
386 argv[0] = SUPHP_PATH_TO_SUPHP;
389 /* prepare environment for new process */
391 ap_add_common_vars(r);
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");
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");
403 if (dconf->php_config)
405 apr_table_setn(r->subprocess_env, "PHP_CONFIG", apr_pstrdup(p, dconf->php_config));
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)
416 user = ap_pbase64decode(p, auth + 6);
419 pass = strchr(user, ':');
423 auth_user = apr_pstrdup(r->pool, user);
424 auth_pass = apr_pstrdup(r->pool, pass);
430 if (auth_user && auth_pass)
432 apr_table_setn(r->subprocess_env, "PHP_AUTH_USER", auth_user);
433 apr_table_setn(r->subprocess_env, "PHP_AUTH_PW", auth_pass);
436 #ifdef SUPHP_USE_USERGROUP
437 if (dconf->target_user)
439 apr_table_setn(r->subprocess_env, "PHP_SU_USER",
440 apr_pstrdup(r->pool, dconf->target_user));
444 apr_table_setn(r->subprocess_env, "PHP_SU_USER",
445 apr_pstrdup(r->pool, sconf->target_user));
448 if (dconf->target_group)
450 apr_table_setn(r->subprocess_env, "PHP_SU_GROUP",
451 apr_pstrdup(r->pool, dconf->target_group));
455 apr_table_setn(r->subprocess_env, "PHP_SU_GROUP",
456 apr_pstrdup(r->pool, sconf->target_group));
460 env = ap_create_environment(p, r->subprocess_env);
462 /* set attributes for new process */
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)
468 /* set resource limits */
471 || ((rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, core_conf->limit_cpu)) != APR_SUCCESS)
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)
477 || ((apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, core_conf->limit_nproc)) != APR_SUCCESS)
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))
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;
489 /* create new process */
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)
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;
500 apr_pool_note_subprocess(p, proc, APR_KILL_AFTER_TIMEOUT);
504 apr_file_pipe_timeout_set(proc->out, r->server->timeout);
508 apr_file_pipe_timeout_set(proc->in, r->server->timeout);
512 apr_file_pipe_timeout_set(proc->err, r->server->timeout);
514 /* send request body to script */
516 bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
520 rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
522 if (rv != APR_SUCCESS)
527 APR_BRIGADE_FOREACH(bucket, bb)
531 int child_stopped_reading = 0;
533 if (APR_BUCKET_IS_EOS(bucket))
539 if (APR_BUCKET_IS_FLUSH(bucket) || child_stopped_reading)
544 apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
546 rv = apr_file_write_full(proc->in, data, len, NULL);
547 if (rv != APR_SUCCESS)
549 child_stopped_reading = 1;
552 apr_brigade_cleanup(bb);
554 while (!eos_reached);
556 apr_file_flush(proc->in);
557 apr_file_close(proc->in);
559 /* get output from script and check if non-parsed headers are used */
561 b = apr_bucket_pipe_create(proc->out, r->connection->bucket_alloc);
562 APR_BRIGADE_INSERT_TAIL(bb, b);
565 if ((suphp_bucket_read(b, strbuf, len) == len)
566 && !(strcmp(strbuf, "HTTP/1.0") && strcmp(strbuf, "HTTP/1.1")))
571 b = apr_bucket_eos_create(r->connection->bucket_alloc);
572 APR_BRIGADE_INSERT_TAIL(bb, b);
574 if (proc->out && !nph)
576 /* normal cgi headers, so we have to create the real headers by hand */
579 const char *location;
581 if ((ret = ap_scan_script_header_err_brigade(r, bb, strbuf)) != APR_SUCCESS)
583 suphp_log_script_err(r, proc->err);
585 /* ap_scan_script_header_err_brigade does logging itself,
588 return HTTP_INTERNAL_SERVER_ERROR;
591 location = apr_table_get(r->headers_out, "Location");
592 if (location && location[0] == '/' && r->status == 200)
594 /* empty brigade (script output) and modify headers */
598 APR_BRIGADE_FOREACH(b, bb)
600 if (APR_BUCKET_IS_EOS(b))
602 if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS)
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");
611 ap_internal_redirect_handler(location, r);
614 else if (location && r->status == 200)
616 /* empty brigade (script output) */
619 APR_BRIGADE_FOREACH(b, bb)
621 if (APR_BUCKET_IS_EOS(b))
623 if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS)
626 apr_brigade_destroy(bb);
627 return HTTP_MOVED_TEMPORARILY;
630 /* send output to browser (through filters) */
632 rv = ap_pass_brigade(r->output_filters, bb);
634 /* write errors to logfile */
636 if (rv == APR_SUCCESS && !r->connection->aborted)
637 suphp_log_script_err(r, proc->err);
639 apr_file_close(proc->err);
642 if (proc->out && nph)
644 /* use non-parsed headers (direct output) */
646 struct ap_filter_t *cur;
648 /* get rid of output filters */
650 cur = r->proto_output_filters;
651 while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION)
655 r->output_filters = r->proto_output_filters = cur;
657 /* send output to browser (directly) */
659 rv = ap_pass_brigade(r->output_filters, bb);
662 if (rv == APR_SUCCESS && !r->connection->aborted)
663 suphp_log_script_err(r, proc->err);
665 apr_file_close(proc->err);
671 static void suphp_register_hooks(apr_pool_t *p)
673 ap_hook_handler(suphp_handler, NULL, NULL, APR_HOOK_MIDDLE);
677 /********************
679 ********************/
681 module AP_MODULE_DECLARE_DATA suphp_module =
683 STANDARD20_MODULE_STUFF,
684 suphp_create_dir_config,
685 suphp_merge_dir_config,
686 suphp_create_server_config,
687 suphp_merge_server_config,