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
25 #include "http_config.h"
26 #include "http_request.h"
27 #include "http_core.h"
28 #include "http_protocol.h"
29 #include "http_main.h"
31 #include "util_script.h"
34 #define SUPHP_CONFIG_MODE_SERVER 1
35 #define SUPHP_CONFIG_MODE_DIRECTORY 2
37 #define SUPHP_ENGINE_OFF 0
38 #define SUPHP_ENGINE_ON 1
39 #define SUPHP_ENGINE_UNDEFINED 2
41 #ifndef SUPHP_PATH_TO_SUPHP
42 #define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
46 /* Module declaration */
47 module MODULE_VAR_EXPORT suphp_module;
50 /* Configuration structure */
52 int engine; // Status of suPHP_Engine
54 int cmode; // Server of directory configuration?
55 #ifdef SUPHP_USE_USERGROUP
63 /* Configuration mergers/creators */
65 static void *suphp_create_dir_config(pool *p, char *dir) {
66 suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
67 cfg->php_config = NULL;
68 cfg->engine = SUPHP_ENGINE_UNDEFINED;
69 cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
71 #ifdef SUPHP_USE_USERGRPUP
72 cfg->target_user = NULL;
73 cfg->target_group = NULL;
76 /* Create table with 0 initial elements */
77 /* This size may be increased for performance reasons */
78 cfg->handlers = ap_make_table(p, 0);
83 static void *suphp_merge_dir_config(pool *p, void *base, void *overrides) {
84 suphp_conf *parent = (suphp_conf *) base;
85 suphp_conf *child = (suphp_conf *) overrides;
86 suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
88 merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
90 if (child->php_config)
91 merged->php_config = ap_pstrdup(p, child->php_config);
92 else if (parent->php_config)
93 merged->php_config = ap_pstrdup(p, parent->php_config);
95 merged->php_config = NULL;
97 if (child->engine != SUPHP_ENGINE_UNDEFINED)
98 merged->engine = child->engine;
100 merged->engine = parent->engine;
102 #ifdef SUPHP_USE_USERGROUP
103 if (child->target_user)
104 merged->target_user = ap_pstrdup(p, child->target_user);
105 else if (parent->target_user)
106 merged->target_user = ap_pstrdup(p, parent->target_user);
108 merged->target_user = NULL;
110 if (child->target_group)
111 merged->target_group = ap_pstrdup(p, child->target_group);
112 else if (parent->target_group)
113 merged->target_group = ap_pstrdup(p, parent->target_group);
115 merged->target_group = NULL;
118 merged->handlers = ap_overlay_tables(p, child->handlers, parent->handlers);
120 return (void *) merged;
124 static void *suphp_create_server_config(pool *p, server_rec *s) {
125 suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
127 cfg->engine = SUPHP_ENGINE_UNDEFINED;
128 cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
130 #ifdef SUPHP_USE_USERGRPUP
131 cfg->target_user = NULL;
132 cfg->target_group = NULL;
139 static void *suphp_merge_server_config(pool *p, void *base, void *overrides) {
140 suphp_conf *parent = (suphp_conf *) base;
141 suphp_conf *child = (suphp_conf *) overrides;
142 suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
144 if (child->engine != SUPHP_ENGINE_UNDEFINED)
145 merged->engine = child->engine;
147 merged->engine = parent->engine;
149 #ifdef SUPHP_USE_USERGROUP
150 if (child->target_user)
151 merged->target_user = ap_pstrdup(p, child->target_user);
152 else if (parent->target_user)
153 merged->target_user = ap_pstrdup(p, parent->target_user);
155 merged->target_user = NULL;
157 if (child->target_group)
158 merged->target_group = ap_pstrdup(p, child->target_group);
159 else if (parent->target_group)
160 merged->target_group = ap_pstrdup(p, parent->target_group);
162 merged->target_group = NULL;
165 return (void *) merged;
169 /* Command handlers */
171 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
176 cfg = (suphp_conf *) mconfig;
178 cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config,
182 cfg->engine = SUPHP_ENGINE_ON;
184 cfg->engine = SUPHP_ENGINE_OFF;
190 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
192 suphp_conf *cfg = (suphp_conf *) mconfig;
194 cfg->php_config = ap_pstrdup(cmd->pool, arg);
200 #ifdef SUPHP_USE_USERGROUP
201 static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig,
207 cfg = (suphp_conf *) mconfig;
209 cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
211 cfg->target_user = ap_pstrdup(cmd->pool, arg1);
212 cfg->target_group = ap_pstrdup(cmd->pool, arg2);
219 static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
221 suphp_conf *cfg = (suphp_conf *) mconfig;
223 // Mark active handlers with '1'
224 ap_table_set(cfg->handlers, arg, "1");
229 static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd,
232 suphp_conf *cfg = (suphp_conf *) mconfig;
234 // Mark deactivated handlers with '0'
235 ap_table_set(cfg->handlers, arg, "0");
243 static const command_rec suphp_cmds[] = {
244 {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF|ACCESS_CONF,
245 FLAG, "Whether suPHP is on or off, default is off"},
246 {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1,
247 "Where the php.ini resides, default is the PHP default"},
248 #ifdef SUPHP_USE_USERGROUP
249 {"suPHP_UserGroup", suphp_handle_cmd_user_group, NULL,
250 RSRC_CONF|ACCESS_CONF, TAKE2, "User and group scripts shall be run as"},
252 {"suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF|ACCESS_CONF,
253 ITERATE, "Tells mod_suphp to handle these MIME-types"},
254 {"suphp_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF|ACCESS_CONF,
255 ITERATE, "Tells mod_suphp not to handle these MIME-types"},
260 /* Helper function which is called when spawning child process */
262 int suphp_child(void *rp, child_info *cinfo) {
263 request_rec *r = (request_rec *) rp;
264 core_dir_config *core_conf;
265 pool *p = r->main ? r->main->pool : r->pool;
269 core_conf = (core_dir_config *) ap_get_module_config(
270 r->per_dir_config, &core_module);
272 /* We want to log output written to stderr */
273 ap_error_log2stderr(r->server);
275 /* prepare argv for new process */
277 argv = ap_palloc(p, 2 * sizeof(char *));
278 argv[0] = SUPHP_PATH_TO_SUPHP;
281 /* prepare environment */
283 env = ap_create_environment(p, r->subprocess_env);
285 /* We cannot use ap_call_exec because of the interference with suExec */
286 /* So we do everything ourselves */
288 /* Set resource limits from core config */
291 if (core_conf->limit_cpu != NULL) {
292 if ((setrlimit(RLIMIT_CPU, core_conf->limit_cpu)) != 0) {
293 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
294 "setrlimit: failed to set CPU usage limit");
297 #endif /* RLIMIT_CPU */
299 if (core_conf->limit_nproc != NULL) {
300 if ((setrlimit(RLIMIT_NPROC, core_conf->limit_nproc)) != 0) {
301 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
302 "setrlimit: failed to set process limit");
305 #endif /* RLIMIT_NPROC */
307 if (core_conf->limit_mem != NULL) {
308 if ((setrlimit(RLIMIT_AS, core_conf->limit_mem)) != 0) {
309 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
310 "setrlimit: failed to set memory limit");
313 #endif /* RLIMIT_VMEM */
315 if (core_conf->limit_mem != NULL) {
316 if ((setrlimit(RLIMIT_DATA, core_conf->limit_mem)) != 0) {
317 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
318 "setrlimit: failed to set memory limit");
321 #endif /* RLIMIT_VMEM */
323 if (core_conf->limit_mem != NULL) {
324 if ((setrlimit(RLIMIT_VMEM, core_conf->limit_mem)) != 0) {
325 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
326 "setrlimit: failed to set memory limit");
329 #endif /* RLIMIT_VMEM */
331 /* mandatory cleanup before execution */
332 ap_cleanup_for_exec();
334 execve(SUPHP_PATH_TO_SUPHP, argv, env);
336 /* We are still here? Okay - exec failed */
337 ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
338 SUPHP_PATH_TO_SUPHP);
347 static int suphp_handler(request_rec *r) {
355 char *auth_user = NULL;
356 char *auth_pass = NULL;
360 BUFF *script_in, *script_out, *script_err;
362 sconf = ap_get_module_config(r->server->module_config, &suphp_module);
363 dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
365 p = r->main ? r->main->pool : r->pool;
367 /* only handle request if mod_suphp is active for this handler */
368 /* check only first byte of value (second has to be \0) */
369 if ((ap_table_get(dconf->handlers, r->handler) == NULL)
370 || (*(ap_table_get(dconf->handlers, r->handler)) == '0'))
373 /* check if suPHP is enabled for this request */
375 if (((sconf->engine != SUPHP_ENGINE_ON)
376 && (dconf->engine != SUPHP_ENGINE_ON))
377 || ((sconf->engine == SUPHP_ENGINE_ON)
378 && (dconf->engine == SUPHP_ENGINE_OFF)))
381 /* check if file is existing and accessible */
383 rv = stat(ap_pstrdup(p, r->filename), &finfo);
386 } else if (errno == EACCES) {
387 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
389 return HTTP_FORBIDDEN;
390 } else if (errno == ENOENT || errno == ENOTDIR) {
391 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
393 return HTTP_NOT_FOUND;
395 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could not get fileinfo: %s",
397 return HTTP_NOT_FOUND;
400 #ifdef SUPHP_USE_USERGROUP
401 if ((sconf->target_user == NULL || sconf->target_group == NULL)
402 && (dconf->target_user == NULL || dconf->target_group == NULL)) {
403 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
404 "No user or group set - set suPHP_UserGroup");
405 return HTTP_INTERNAL_SERVER_ERROR;
407 #endif /* SUPHP_USE_USERGROUP */
410 /* prepare environment for new process */
412 ap_add_common_vars(r);
415 ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
416 ap_table_unset(r->subprocess_env, "SUHP_AUTH_USER");
417 ap_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
419 #ifdef SUPHP_USE_USERGROUP
420 ap_table_unset(r->subprocess_env, "SUPHP_USER");
421 ap_table_unset(r->subprocess_env, "SUPHP_GROUP");
422 #endif /* SUPHP_USE_USERGROUP */
424 if (dconf->php_config) {
425 ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
428 ap_table_set(r->subprocess_env, "SUPHP_HANDLER", r->handler);
432 auth = ap_table_get(r->headers_in, "Authorization");
433 if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
436 user = ap_pbase64decode(p, auth + 6);
438 pass = strchr(user, ':');
441 auth_user = ap_pstrdup(p, user);
442 auth_pass = ap_pstrdup(p, pass);
448 if (auth_user && auth_pass) {
449 ap_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
450 ap_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
453 #ifdef SUPHP_USE_USERGROUP
454 if (dconf->target_user) {
455 ap_table_set(r->subprocess_env, "SUPHP_USER", dconf->target_user);
457 ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
460 if (dconf->target_group) {
461 ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
463 ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
465 #endif /* SUPHP_USE_USERGROUP */
467 /* Fork child process */
469 if (!ap_bspawn_child(p, suphp_child, (void *) r, kill_after_timeout,
470 &script_in, &script_out, &script_err)) {
471 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
472 "couldn't spawn child process for: %s", r->filename);
473 return HTTP_INTERNAL_SERVER_ERROR;
476 /* Transfer request body to script */
478 if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
479 /* Call failed, return status */
483 if (ap_should_client_block(r)) {
484 char buffer[HUGE_STRING_LEN];
487 ap_hard_timeout("reading request body", r);
489 while ((len_read = ap_get_client_block(r, buffer, HUGE_STRING_LEN))
492 if (ap_bwrite(script_in, buffer, len_read) < len_read) {
493 /* silly script stopped reading, soak up remaining message */
494 while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
501 ap_bflush(script_in);
505 ap_bclose(script_in);
507 /* Transfer output from script to client */
510 const char *location;
511 char hbuffer[MAX_STRING_LEN];
512 char buffer[HUGE_STRING_LEN];
514 if (rv = ap_scan_script_header_err_buff(r, script_out, hbuffer)) {
515 return HTTP_INTERNAL_SERVER_ERROR;
518 location = ap_table_get(r->headers_out, "Location");
519 if (location && r->status == 200) {
520 /* Soak up all the script output */
521 ap_hard_timeout("reading from script", r);
522 while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
526 ap_bclose(script_out);
527 ap_bclose(script_err);
529 if (location[0] == '/') {
530 /* Redirect has always GET method */
531 r->method = ap_pstrdup(p, "GET");
532 r->method_number = M_GET;
534 /* Remove Content-Length - redirect should not read *
536 ap_table_unset(r->headers_in, "Content-Length");
538 /* Do the redirect */
539 ap_internal_redirect_handler(location, r);
542 /* Script did not set status 302 - so it does not want *
543 * to send its own body. Simply set redirect status */
548 /* Output headers and body */
550 ap_send_http_header(r);
551 if (!r->header_only) {
552 ap_send_fb(script_out, r);
554 ap_bclose(script_out);
555 /* Errors have already been logged by child */
556 ap_bclose(script_err);
565 static handler_rec suphp_handlers[] = {
566 {"*", suphp_handler},
570 /* Module definition */
572 module MODULE_VAR_EXPORT suphp_module = {
573 STANDARD_MODULE_STUFF,
574 NULL, /* initializer */
575 suphp_create_dir_config, /* create directory config */
576 suphp_merge_dir_config, /* merge directory config */
577 suphp_create_server_config, /* create server config */
578 suphp_merge_server_config, /* merge server config */
579 suphp_cmds, /* command table */
580 suphp_handlers, /* content handlers */
581 NULL, /* URI-to-filename translation */
582 NULL, /* check/validate user_id */
583 NULL, /* check user_id is valid *here* */
584 NULL, /* check access by host address */
585 NULL, /* MIME type checker/setter */
588 NULL, /* header parser */
589 NULL, /* process initialaization */
590 NULL, /* process exit/cleanup */
591 NULL /* post read_request handling */