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_USERGROUP
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_USERGROUP
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, ACCESS_CONF,
253 ITERATE, "Tells mod_suphp to handle these MIME-types"},
254 {"suphp_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, 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) {
351 #ifdef SUPHP_USE_USERGROUP
352 char *ud_user = NULL;
353 char *ud_group = NULL;
360 char *auth_user = NULL;
361 char *auth_pass = NULL;
365 BUFF *script_in, *script_out, *script_err;
367 sconf = ap_get_module_config(r->server->module_config, &suphp_module);
368 dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
370 p = r->main ? r->main->pool : r->pool;
372 /* only handle request if mod_suphp is active for this handler */
373 /* check only first byte of value (second has to be \0) */
374 if ((ap_table_get(dconf->handlers, r->handler) == NULL)
375 || (*(ap_table_get(dconf->handlers, r->handler)) == '0'))
378 /* check if suPHP is enabled for this request */
380 if (((sconf->engine != SUPHP_ENGINE_ON)
381 && (dconf->engine != SUPHP_ENGINE_ON))
382 || ((sconf->engine == SUPHP_ENGINE_ON)
383 && (dconf->engine == SUPHP_ENGINE_OFF)))
386 /* check if file is existing and accessible */
388 rv = stat(ap_pstrdup(p, r->filename), &finfo);
391 } else if (errno == EACCES) {
392 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
394 return HTTP_FORBIDDEN;
395 } else if (errno == ENOENT || errno == ENOTDIR) {
396 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
398 return HTTP_NOT_FOUND;
400 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could not get fileinfo: %s",
402 return HTTP_NOT_FOUND;
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 /* Identify mod_userdir request
410 As Apache 1.3 does not yet provide a clean way to see
411 whether a request was handled by mod_userdir, we assume
412 this is true for any request beginning with ~ */
414 int ud_success = 0; /* set to 1 on success */
416 if (!strncmp("/~", r->uri, 2)) {
417 char *username = ap_pstrdup(r->pool, r->uri + 2);
418 char *pos = strchr(username, '/');
421 if (strlen(username)) {
426 if ((pw = getpwnam(username)) != NULL) {
429 if ((gr = getgrgid(gid)) != NULL) {
430 grpname = gr->gr_name;
432 if ((grpname = ap_palloc(r->pool, 16)) == NULL) {
433 return HTTP_INTERNAL_SERVER_ERROR;
435 ap_snprintf(grpname, 16, "#%ld", (long) gid);
447 /* This is not a userdir request and user/group are not
448 set, so log the error and return */
449 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
450 "No user or group set - set suPHP_UserGroup");
451 return HTTP_INTERNAL_SERVER_ERROR;
454 #endif /* SUPHP_USE_USERGROUP */
457 /* prepare environment for new process */
459 ap_add_common_vars(r);
462 ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
463 ap_table_unset(r->subprocess_env, "SUPHP_AUTH_USER");
464 ap_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
466 #ifdef SUPHP_USE_USERGROUP
467 ap_table_unset(r->subprocess_env, "SUPHP_USER");
468 ap_table_unset(r->subprocess_env, "SUPHP_GROUP");
469 #endif /* SUPHP_USE_USERGROUP */
471 if (dconf->php_config) {
472 ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
475 ap_table_set(r->subprocess_env, "SUPHP_HANDLER", r->handler);
479 auth = ap_table_get(r->headers_in, "Authorization");
480 if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
483 user = ap_pbase64decode(p, auth + 6);
485 pass = strchr(user, ':');
488 auth_user = ap_pstrdup(p, user);
489 auth_pass = ap_pstrdup(p, pass);
495 if (auth_user && auth_pass) {
496 ap_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
497 ap_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
500 #ifdef SUPHP_USE_USERGROUP
501 if (dconf->target_user) {
502 ap_table_set(r->subprocess_env, "SUPHP_USER", dconf->target_user);
503 } else if (sconf->target_user) {
504 ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
506 ap_table_set(r->subprocess_env, "SUPHP_USER", ud_user);
509 if (dconf->target_group) {
510 ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
511 } else if (sconf->target_group) {
512 ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
514 ap_table_set(r->subprocess_env, "SUPHP_GROUP", ud_group);
516 #endif /* SUPHP_USE_USERGROUP */
518 /* Fork child process */
520 if (!ap_bspawn_child(p, suphp_child, (void *) r, kill_after_timeout,
521 &script_in, &script_out, &script_err)) {
522 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
523 "couldn't spawn child process for: %s", r->filename);
524 return HTTP_INTERNAL_SERVER_ERROR;
527 /* Transfer request body to script */
529 if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
530 /* Call failed, return status */
534 if (ap_should_client_block(r)) {
535 char buffer[HUGE_STRING_LEN];
538 ap_hard_timeout("reading request body", r);
540 while ((len_read = ap_get_client_block(r, buffer, HUGE_STRING_LEN))
543 if (ap_bwrite(script_in, buffer, len_read) < len_read) {
544 /* silly script stopped reading, soak up remaining message */
545 while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
552 ap_bflush(script_in);
556 ap_bclose(script_in);
558 /* Transfer output from script to client */
561 const char *location;
562 char hbuffer[MAX_STRING_LEN];
563 char buffer[HUGE_STRING_LEN];
565 rv = ap_scan_script_header_err_buff(r, script_out, hbuffer);
566 if (rv == HTTP_NOT_MODIFIED) {
569 return HTTP_INTERNAL_SERVER_ERROR;
572 location = ap_table_get(r->headers_out, "Location");
573 if (location && r->status == 200) {
574 /* Soak up all the script output */
575 ap_hard_timeout("reading from script", r);
576 while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
580 ap_bclose(script_out);
581 ap_bclose(script_err);
583 if (location[0] == '/') {
584 /* Redirect has always GET method */
585 r->method = ap_pstrdup(p, "GET");
586 r->method_number = M_GET;
588 /* Remove Content-Length - redirect should not read *
590 ap_table_unset(r->headers_in, "Content-Length");
592 /* Do the redirect */
593 ap_internal_redirect_handler(location, r);
596 /* Script did not set status 302 - so it does not want *
597 * to send its own body. Simply set redirect status */
602 /* Output headers and body */
604 ap_send_http_header(r);
605 if (!r->header_only) {
606 ap_send_fb(script_out, r);
608 ap_bclose(script_out);
609 /* Errors have already been logged by child */
610 ap_bclose(script_err);
619 static handler_rec suphp_handlers[] = {
620 {"*", suphp_handler},
624 /* Module definition */
626 module MODULE_VAR_EXPORT suphp_module = {
627 STANDARD_MODULE_STUFF,
628 NULL, /* initializer */
629 suphp_create_dir_config, /* create directory config */
630 suphp_merge_dir_config, /* merge directory config */
631 suphp_create_server_config, /* create server config */
632 suphp_merge_server_config, /* merge server config */
633 suphp_cmds, /* command table */
634 suphp_handlers, /* content handlers */
635 NULL, /* URI-to-filename translation */
636 NULL, /* check/validate user_id */
637 NULL, /* check user_id is valid *here* */
638 NULL, /* check access by host address */
639 NULL, /* MIME type checker/setter */
642 NULL, /* header parser */
643 NULL, /* process initialaization */
644 NULL, /* process exit/cleanup */
645 NULL /* post read_request handling */