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
64 /* Configuration mergers/creators */
66 static void *suphp_create_dir_config(pool *p, char *dir) {
67 suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
68 cfg->php_config = NULL;
69 cfg->engine = SUPHP_ENGINE_UNDEFINED;
70 cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
72 #ifdef SUPHP_USE_USERGROUP
73 cfg->target_user = NULL;
74 cfg->target_group = NULL;
77 /* Create table with 0 initial elements */
78 /* This size may be increased for performance reasons */
79 cfg->handlers = ap_make_table(p, 0);
84 static void *suphp_merge_dir_config(pool *p, void *base, void *overrides) {
85 suphp_conf *parent = (suphp_conf *) base;
86 suphp_conf *child = (suphp_conf *) overrides;
87 suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
89 merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
91 if (child->php_config)
92 merged->php_config = ap_pstrdup(p, child->php_config);
93 else if (parent->php_config)
94 merged->php_config = ap_pstrdup(p, parent->php_config);
96 merged->php_config = NULL;
98 if (child->engine != SUPHP_ENGINE_UNDEFINED)
99 merged->engine = child->engine;
101 merged->engine = parent->engine;
103 #ifdef SUPHP_USE_USERGROUP
104 if (child->target_user)
105 merged->target_user = ap_pstrdup(p, child->target_user);
106 else if (parent->target_user)
107 merged->target_user = ap_pstrdup(p, parent->target_user);
109 merged->target_user = NULL;
111 if (child->target_group)
112 merged->target_group = ap_pstrdup(p, child->target_group);
113 else if (parent->target_group)
114 merged->target_group = ap_pstrdup(p, parent->target_group);
116 merged->target_group = NULL;
119 merged->handlers = ap_overlay_tables(p, child->handlers, parent->handlers);
121 return (void *) merged;
125 static void *suphp_create_server_config(pool *p, server_rec *s) {
126 suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
128 cfg->engine = SUPHP_ENGINE_UNDEFINED;
129 cfg->php_path = NULL;
130 cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
132 #ifdef SUPHP_USE_USERGROUP
133 cfg->target_user = NULL;
134 cfg->target_group = NULL;
137 /* Create table with 0 initial elements */
138 /* This size may be increased for performance reasons */
139 cfg->handlers = ap_make_table(p, 0);
145 static void *suphp_merge_server_config(pool *p, void *base, void *overrides) {
146 suphp_conf *parent = (suphp_conf *) base;
147 suphp_conf *child = (suphp_conf *) overrides;
148 suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
150 if (child->engine != SUPHP_ENGINE_UNDEFINED)
151 merged->engine = child->engine;
153 merged->engine = parent->engine;
155 if (child->php_path != NULL)
156 merged->php_path = ap_pstrdup(p, child->php_path);
158 merged->php_path = ap_pstrdup(p, parent->php_path);
160 #ifdef SUPHP_USE_USERGROUP
161 if (child->target_user)
162 merged->target_user = ap_pstrdup(p, child->target_user);
163 else if (parent->target_user)
164 merged->target_user = ap_pstrdup(p, parent->target_user);
166 merged->target_user = NULL;
168 if (child->target_group)
169 merged->target_group = ap_pstrdup(p, child->target_group);
170 else if (parent->target_group)
171 merged->target_group = ap_pstrdup(p, parent->target_group);
173 merged->target_group = NULL;
176 merged->handlers = ap_overlay_tables(p, child->handlers, parent->handlers);
178 return (void *) merged;
182 /* Command handlers */
184 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
189 cfg = (suphp_conf *) mconfig;
191 cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config,
195 cfg->engine = SUPHP_ENGINE_ON;
197 cfg->engine = SUPHP_ENGINE_OFF;
203 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
205 suphp_conf *cfg = (suphp_conf *) mconfig;
207 cfg->php_config = ap_pstrdup(cmd->pool, arg);
213 #ifdef SUPHP_USE_USERGROUP
214 static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig,
220 cfg = (suphp_conf *) mconfig;
222 cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
224 cfg->target_user = ap_pstrdup(cmd->pool, arg1);
225 cfg->target_group = ap_pstrdup(cmd->pool, arg2);
232 static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
236 cfg = (suphp_conf *) mconfig;
238 cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
240 // Mark active handlers with '1'
241 ap_table_set(cfg->handlers, arg, "1");
246 static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd,
251 cfg = (suphp_conf *) mconfig;
253 cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
255 // Mark deactivated handlers with '0'
256 ap_table_set(cfg->handlers, arg, "0");
262 static const char *suphp_handle_cmd_phppath(cmd_parms *cmd, void* mconfig, const char *arg)
266 cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);
268 cfg->php_path = ap_pstrdup(cmd->pool, arg);
276 static const command_rec suphp_cmds[] = {
277 {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF|ACCESS_CONF,
278 FLAG, "Whether suPHP is on or off, default is off"},
279 {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1,
280 "Where the php.ini resides, default is the PHP default"},
281 #ifdef SUPHP_USE_USERGROUP
282 {"suPHP_UserGroup", suphp_handle_cmd_user_group, NULL,
283 RSRC_CONF|ACCESS_CONF, TAKE2, "User and group scripts shall be run as"},
285 {"suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF | ACCESS_CONF,
286 ITERATE, "Tells mod_suphp to handle these MIME-types"},
287 {"suphp_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF | ACCESS_CONF,
288 ITERATE, "Tells mod_suphp not to handle these MIME-types"},
289 {"suPHP_PHPPath", suphp_handle_cmd_phppath, NULL, RSRC_CONF, TAKE1, "Path to the PHP binary used to render source view"},
294 /* Helper function which is called when spawning child process */
296 int suphp_source_child(void *rp, child_info *cinfo) {
297 request_rec *r = (request_rec *) rp;
299 pool *p = r->main ? r->main->pool : r->pool;
302 conf = ap_get_module_config(r->server->module_config, &suphp_module);
304 /* We want to log output written to stderr */
305 ap_error_log2stderr(r->server);
307 /* prepare argv for new process */
309 argv = ap_palloc(p, 4 * sizeof(char *));
310 argv[0] = ap_pstrdup(p, conf->php_path);
312 argv[2] = ap_pstrdup(p, r->filename);
315 /* prepare environment */
317 env = ap_create_environment(p, r->subprocess_env);
319 /* We cannot use ap_call_exec because of the interference with suExec */
320 /* So we do everything ourselves */
322 /* mandatory cleanup before execution */
323 ap_cleanup_for_exec();
325 execve(ap_pstrdup(p, conf->php_path), argv, env);
327 /* We are still here? Okay - exec failed */
328 ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
336 int suphp_child(void *rp, child_info *cinfo) {
337 request_rec *r = (request_rec *) rp;
338 core_dir_config *core_conf;
339 pool *p = r->main ? r->main->pool : r->pool;
343 core_conf = (core_dir_config *) ap_get_module_config(
344 r->per_dir_config, &core_module);
346 /* We want to log output written to stderr */
347 ap_error_log2stderr(r->server);
349 /* prepare argv for new process */
351 argv = ap_palloc(p, 2 * sizeof(char *));
352 argv[0] = SUPHP_PATH_TO_SUPHP;
355 /* prepare environment */
357 env = ap_create_environment(p, r->subprocess_env);
359 /* We cannot use ap_call_exec because of the interference with suExec */
360 /* So we do everything ourselves */
362 /* Set resource limits from core config */
365 if (core_conf->limit_cpu != NULL) {
366 if ((setrlimit(RLIMIT_CPU, core_conf->limit_cpu)) != 0) {
367 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
368 "setrlimit: failed to set CPU usage limit");
371 #endif /* RLIMIT_CPU */
373 if (core_conf->limit_nproc != NULL) {
374 if ((setrlimit(RLIMIT_NPROC, core_conf->limit_nproc)) != 0) {
375 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
376 "setrlimit: failed to set process limit");
379 #endif /* RLIMIT_NPROC */
381 if (core_conf->limit_mem != NULL) {
382 if ((setrlimit(RLIMIT_AS, core_conf->limit_mem)) != 0) {
383 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
384 "setrlimit: failed to set memory limit");
387 #endif /* RLIMIT_VMEM */
389 if (core_conf->limit_mem != NULL) {
390 if ((setrlimit(RLIMIT_DATA, core_conf->limit_mem)) != 0) {
391 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
392 "setrlimit: failed to set memory limit");
395 #endif /* RLIMIT_VMEM */
397 if (core_conf->limit_mem != NULL) {
398 if ((setrlimit(RLIMIT_VMEM, core_conf->limit_mem)) != 0) {
399 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
400 "setrlimit: failed to set memory limit");
403 #endif /* RLIMIT_VMEM */
405 /* mandatory cleanup before execution */
406 ap_cleanup_for_exec();
408 execve(SUPHP_PATH_TO_SUPHP, argv, env);
410 /* We are still here? Okay - exec failed */
411 ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
412 SUPHP_PATH_TO_SUPHP);
421 static int suphp_source_handler(request_rec *r) {
426 BUFF *script_in, *script_out, *script_err;
427 char buffer[HUGE_STRING_LEN];
429 if (strcmp(r->method, "GET")) {
433 conf = ap_get_module_config(r->server->module_config, &suphp_module);
434 if (conf->php_path == NULL) {
438 p = r->main ? r->main->pool : r->pool;
440 fd = open(r->filename, O_NOCTTY, O_RDONLY);
443 } else if (errno == EACCES) {
444 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
446 return HTTP_FORBIDDEN;
447 } else if (errno == ENOENT || errno == ENOTDIR) {
448 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
450 return HTTP_NOT_FOUND;
452 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could open file: %s",
454 return HTTP_NOT_FOUND;
457 /* Fork child process */
459 if (!ap_bspawn_child(p, suphp_source_child, (void *) r, kill_after_timeout,
460 &script_in, &script_out, &script_err)) {
461 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
462 "couldn't spawn child process for: %s", r->filename);
463 return HTTP_INTERNAL_SERVER_ERROR;
466 /* Read request body */
468 if (ap_should_client_block(r)) {
469 char buffer[HUGE_STRING_LEN];
472 ap_hard_timeout("reading request body", r);
474 while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
479 ap_bflush(script_in);
483 ap_bclose(script_in);
485 /* Transfer output from PHP to client */
488 /* Output headers and body */
490 r->content_type = "text/html";
491 ap_send_http_header(r);
492 if (!r->header_only) {
493 ap_send_fb(script_out, r);
495 ap_bclose(script_out);
496 /* Errors have already been logged by child */
497 ap_bclose(script_err);
505 static int suphp_handler(request_rec *r) {
509 #ifdef SUPHP_USE_USERGROUP
510 char *ud_user = NULL;
511 char *ud_group = NULL;
518 char *auth_user = NULL;
519 char *auth_pass = NULL;
523 BUFF *script_in, *script_out, *script_err;
527 sconf = ap_get_module_config(r->server->module_config, &suphp_module);
528 dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
530 p = r->main ? r->main->pool : r->pool;
532 /* only handle request if mod_suphp is active for this handler */
533 /* check only first byte of value (second has to be \0) */
534 if (r->handler != NULL) {
535 handler = r->handler;
537 handler = r->content_type;
539 if ((ap_table_get(dconf->handlers, handler) == NULL)) {
540 if ((ap_table_get(sconf->handlers, handler) == NULL)
541 || (*(ap_table_get(sconf->handlers, handler)) == '0')) {
544 } else if (*(ap_table_get(dconf->handlers, handler)) == '0') {
548 /* check if suPHP is enabled for this request */
550 if (((sconf->engine != SUPHP_ENGINE_ON)
551 && (dconf->engine != SUPHP_ENGINE_ON))
552 || ((sconf->engine == SUPHP_ENGINE_ON)
553 && (dconf->engine == SUPHP_ENGINE_OFF)))
556 /* check if file is existing and accessible */
558 rv = stat(ap_pstrdup(p, r->filename), &finfo);
561 } else if (errno == EACCES) {
562 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
564 return HTTP_FORBIDDEN;
565 } else if (errno == ENOENT || errno == ENOTDIR) {
566 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
568 return HTTP_NOT_FOUND;
570 ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could not get fileinfo: %s",
572 return HTTP_NOT_FOUND;
575 #ifdef SUPHP_USE_USERGROUP
576 if ((sconf->target_user == NULL || sconf->target_group == NULL)
577 && (dconf->target_user == NULL || dconf->target_group == NULL)) {
579 /* Identify mod_userdir request
580 As Apache 1.3 does not yet provide a clean way to see
581 whether a request was handled by mod_userdir, we assume
582 this is true for any request beginning with ~ */
584 int ud_success = 0; /* set to 1 on success */
586 if (!strncmp("/~", r->uri, 2)) {
587 char *username = ap_pstrdup(r->pool, r->uri + 2);
588 char *pos = strchr(username, '/');
591 if (strlen(username)) {
596 if ((pw = getpwnam(username)) != NULL) {
599 if ((gr = getgrgid(gid)) != NULL) {
600 grpname = gr->gr_name;
602 if ((grpname = ap_palloc(r->pool, 16)) == NULL) {
603 return HTTP_INTERNAL_SERVER_ERROR;
605 ap_snprintf(grpname, 16, "#%ld", (long) gid);
617 /* This is not a userdir request and user/group are not
618 set, so log the error and return */
619 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
620 "No user or group set - set suPHP_UserGroup");
621 return HTTP_INTERNAL_SERVER_ERROR;
624 #endif /* SUPHP_USE_USERGROUP */
627 /* prepare environment for new process */
629 ap_add_common_vars(r);
632 ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
633 ap_table_unset(r->subprocess_env, "SUPHP_AUTH_USER");
634 ap_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
636 #ifdef SUPHP_USE_USERGROUP
637 ap_table_unset(r->subprocess_env, "SUPHP_USER");
638 ap_table_unset(r->subprocess_env, "SUPHP_GROUP");
639 #endif /* SUPHP_USE_USERGROUP */
641 if (dconf->php_config) {
642 ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
645 ap_table_set(r->subprocess_env, "SUPHP_HANDLER", handler);
649 auth = ap_table_get(r->headers_in, "Authorization");
650 if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
653 user = ap_pbase64decode(p, auth + 6);
655 pass = strchr(user, ':');
658 auth_user = ap_pstrdup(p, user);
659 auth_pass = ap_pstrdup(p, pass);
665 if (auth_user && auth_pass) {
666 ap_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
667 ap_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
670 #ifdef SUPHP_USE_USERGROUP
671 if (dconf->target_user) {
672 ap_table_set(r->subprocess_env, "SUPHP_USER", dconf->target_user);
673 } else if (sconf->target_user) {
674 ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
676 ap_table_set(r->subprocess_env, "SUPHP_USER", ud_user);
679 if (dconf->target_group) {
680 ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
681 } else if (sconf->target_group) {
682 ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
684 ap_table_set(r->subprocess_env, "SUPHP_GROUP", ud_group);
686 #endif /* SUPHP_USE_USERGROUP */
688 /* Fork child process */
690 if (!ap_bspawn_child(p, suphp_child, (void *) r, kill_after_timeout,
691 &script_in, &script_out, &script_err)) {
692 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
693 "couldn't spawn child process for: %s", r->filename);
694 return HTTP_INTERNAL_SERVER_ERROR;
697 /* Transfer request body to script */
699 if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
700 /* Call failed, return status */
704 if (ap_should_client_block(r)) {
705 char buffer[HUGE_STRING_LEN];
708 ap_hard_timeout("reading request body", r);
710 while ((len_read = ap_get_client_block(r, buffer, HUGE_STRING_LEN))
713 if (ap_bwrite(script_in, buffer, len_read) < len_read) {
714 /* silly script stopped reading, soak up remaining message */
715 while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
722 ap_bflush(script_in);
726 ap_bclose(script_in);
728 /* Transfer output from script to client */
731 const char *location;
732 char hbuffer[MAX_STRING_LEN];
733 char buffer[HUGE_STRING_LEN];
735 rv = ap_scan_script_header_err_buff(r, script_out, hbuffer);
736 if (rv == HTTP_NOT_MODIFIED) {
739 return HTTP_INTERNAL_SERVER_ERROR;
742 location = ap_table_get(r->headers_out, "Location");
743 if (location && r->status == 200) {
744 /* Soak up all the script output */
745 ap_hard_timeout("reading from script", r);
746 while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
750 ap_bclose(script_out);
751 ap_bclose(script_err);
753 if (location[0] == '/') {
754 /* Redirect has always GET method */
755 r->method = ap_pstrdup(p, "GET");
756 r->method_number = M_GET;
758 /* Remove Content-Length - redirect should not read *
760 ap_table_unset(r->headers_in, "Content-Length");
762 /* Do the redirect */
763 ap_internal_redirect_handler(location, r);
766 /* Script did not set status 302 - so it does not want *
767 * to send its own body. Simply set redirect status */
772 /* Output headers and body */
774 ap_send_http_header(r);
775 if (!r->header_only) {
776 ap_send_fb(script_out, r);
778 ap_bclose(script_out);
779 /* Errors have already been logged by child */
780 ap_bclose(script_err);
789 static handler_rec suphp_handlers[] = {
790 {"*", suphp_handler},
791 {"x-httpd-php-source", suphp_source_handler},
792 {"application/x-httpd-php-source", suphp_source_handler},
796 /* Module definition */
798 module MODULE_VAR_EXPORT suphp_module = {
799 STANDARD_MODULE_STUFF,
800 NULL, /* initializer */
801 suphp_create_dir_config, /* create directory config */
802 suphp_merge_dir_config, /* merge directory config */
803 suphp_create_server_config, /* create server config */
804 suphp_merge_server_config, /* merge server config */
805 suphp_cmds, /* command table */
806 suphp_handlers, /* content handlers */
807 NULL, /* URI-to-filename translation */
808 NULL, /* check/validate user_id */
809 NULL, /* check user_id is valid *here* */
810 NULL, /* check access by host address */
811 NULL, /* MIME type checker/setter */
814 NULL, /* header parser */
815 NULL, /* process initialaization */
816 NULL, /* process exit/cleanup */
817 NULL /* post read_request handling */