1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
60 * mod_suphp is based on mod_cgi from the original Apache sources
61 * Code for authorization environment variables is partly taken from mod_php
63 * mod_suphp was written by Sebastian Marsching <sebastian@marsching.com>
64 * Feel free to contact me if you have bug-reports or suggestions
68 #include "http_config.h"
69 #include "http_request.h"
70 #include "http_core.h"
71 #include "http_protocol.h"
72 #include "http_main.h"
74 #include "util_script.h"
75 #include "http_conf_globals.h"
78 #define PATH_TO_SUPHP "/usr/sbin/suphp"
81 module MODULE_VAR_EXPORT suphp_module;
83 /* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
84 * in ScriptAliased directories, which means we need to know if this
85 * request came through ScriptAlias or not... so the Alias module
86 * leaves a note for us.
89 static int is_scriptaliased(request_rec *r)
91 const char *t = ap_table_get(r->notes, "alias-forced-type");
92 return t && (!strcasecmp(t, "cgi-script"));
97 * We have to use an own version of ap_call_exec() because suExec
98 * does not allow to execute setuid-root programs
101 static char **suphp_create_argv(pool *p, char *path, char *user, char *group,
102 char *av0, const char *args)
109 /* count the number of keywords */
111 for (x = 0, numwords = 1; args[x]; x++) {
112 if (args[x] == '+') {
117 if (numwords > APACHE_ARG_MAX - 5) {
118 numwords = APACHE_ARG_MAX - 5; /* Truncate args to prevent overrun */
120 av = (char **) ap_palloc(p, (numwords + 5) * sizeof(char *));
134 for (x = 1; x <= numwords; x++) {
135 w = ap_getword_nulls(p, &args, '+');
137 av[idx++] = ap_escape_shell_cmd(p, w);
143 int suphp_call_exec(request_rec *r, child_info *pinfo, char *argv0,
144 char **env, int shellcmd)
150 #if defined(RLIMIT_CPU) || defined(RLIMIT_NPROC) || \
151 defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
153 core_dir_config *conf;
154 conf = (core_dir_config *) ap_get_module_config(r->per_dir_config,
160 if (conf->limit_cpu != NULL) {
161 if ((setrlimit(RLIMIT_CPU, conf->limit_cpu)) != 0) {
162 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
163 "setrlimit: failed to set CPU usage limit");
168 if (conf->limit_nproc != NULL) {
169 if ((setrlimit(RLIMIT_NPROC, conf->limit_nproc)) != 0) {
170 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
171 "setrlimit: failed to set process limit");
175 #if defined(RLIMIT_AS)
176 if (conf->limit_mem != NULL) {
177 if ((setrlimit(RLIMIT_AS, conf->limit_mem)) != 0) {
178 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
179 "setrlimit(RLIMIT_AS): failed to set memory "
183 #elif defined(RLIMIT_DATA)
184 if (conf->limit_mem != NULL) {
185 if ((setrlimit(RLIMIT_DATA, conf->limit_mem)) != 0) {
186 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
187 "setrlimit(RLIMIT_DATA): failed to set memory "
191 #elif defined(RLIMIT_VMEM)
192 if (conf->limit_mem != NULL) {
193 if ((setrlimit(RLIMIT_VMEM, conf->limit_mem)) != 0) {
194 ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
195 "setrlimit(RLIMIT_VMEM): failed to set memory "
203 // execve("/usr/sbin/suphp",
204 // suphp_create_argv(r->pool, NULL, NULL, NULL, "suphp", r->args),
206 execle(PATH_TO_SUPHP, "suphp", NULL, env);
212 /* Configuration stuff */
214 #define DEFAULT_LOGBYTES 10385760
215 #define DEFAULT_BUFBYTES 1024
217 #define CONFIG_MODE_SERVER 1
218 #define CONFIG_MODE_DIRECTORY 2
219 #define CONFIG_MODE_COMBO 3 /* Shouldn't ever happen. */
221 #define SUPHP_ENGINE_OFF 0
222 #define SUPHP_ENGINE_ON 1
223 #define SUPHP_ENGINE_UNDEFINED 2
234 static void *suphp_create_dir_config(pool *p, char *dirspec)
237 cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
238 cfg->php_config = NULL;
239 cfg->cmode = CONFIG_MODE_DIRECTORY;
243 static void *suphp_merge_dir_config(pool *p, void *base_conf, void *new_conf)
245 suphp_conf *parent = (suphp_conf *) base_conf;
246 suphp_conf *child = (suphp_conf *) new_conf;
247 suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
249 merged->php_config = child->php_config ? child->php_config : parent->php_config;
254 static void *suphp_create_server_config(pool *p, server_rec *s)
256 suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
259 cfg->logbytes = DEFAULT_LOGBYTES;
260 cfg->bufbytes = DEFAULT_BUFBYTES;
261 cfg->engine = SUPHP_ENGINE_UNDEFINED;
266 static void *suphp_merge_server_config(pool *p, void *base_conf, void *new_conf)
268 suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
269 suphp_conf *parent = (suphp_conf *) base_conf;
270 suphp_conf *child = (suphp_conf *) new_conf;
274 merged->logname = child->logname;
275 merged->logbytes = child->logbytes;
276 merged->bufbytes = child->bufbytes;
280 merged->logname = parent->logname;
281 merged->logbytes = parent->logbytes;
282 merged->bufbytes = parent->bufbytes;
285 if (child->engine != SUPHP_ENGINE_UNDEFINED)
286 merged->engine = child->engine;
288 merged->engine = parent->engine;
290 return (void *) merged;
293 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig, int flag)
295 suphp_conf *cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config, &suphp_module);
298 cfg->engine = SUPHP_ENGINE_ON;
300 cfg->engine = SUPHP_ENGINE_OFF;
305 static const char *suphp_handle_cmd_config(cmd_parms *parms, void *mconfig, const char *arg)
307 suphp_conf *cfg = (suphp_conf *) mconfig;
308 cfg->php_config = (char*)ap_pstrdup(parms->pool, arg);
312 static const command_rec suphp_cmds[] =
314 {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF, FLAG, "Whether PHP is on or off, default is off"},
315 {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1, "Where the php.ini resists, default is to use PHP's default configuration"},
319 static int log_scripterror(request_rec *r, suphp_conf * conf, int ret,
320 int show_errno, char *error)
325 ap_log_rerror(APLOG_MARK, show_errno|APLOG_ERR, r,
326 "%s: %s", error, r->filename);
328 if (!conf->logname ||
329 ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
330 && (finfo.st_size > conf->logbytes)) ||
331 ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
336 /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
337 fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
338 r->args ? "?" : "", r->args ? r->args : "", r->protocol);
339 /* "%% 500 /usr/local/apache/cgi-bin */
340 fprintf(f, "%%%% %d %s\n", ret, r->filename);
342 fprintf(f, "%%error\n%s\n", error);
344 ap_pfclose(r->pool, f);
348 static int log_script(request_rec *r, suphp_conf * conf, int ret,
349 char *dbuf, const char *sbuf, BUFF *script_in, BUFF *script_err)
351 array_header *hdrs_arr = ap_table_elts(r->headers_in);
352 table_entry *hdrs = (table_entry *) hdrs_arr->elts;
353 char argsbuffer[HUGE_STRING_LEN];
358 if (!conf->logname ||
359 ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
360 && (finfo.st_size > conf->logbytes)) ||
361 ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
363 /* Soak up script output */
364 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
366 #if defined(WIN32) || defined(NETWARE)
367 /* Soak up stderr and redirect it to the error log.
368 * Script output to stderr is already directed to the error log
369 * on Unix, thanks to the magic of fork().
371 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
372 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
376 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
382 /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
383 fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
384 r->args ? "?" : "", r->args ? r->args : "", r->protocol);
385 /* "%% 500 /usr/local/apache/cgi-bin" */
386 fprintf(f, "%%%% %d %s\n", ret, r->filename);
388 fputs("%request\n", f);
389 for (i = 0; i < hdrs_arr->nelts; ++i) {
392 fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
394 if ((r->method_number == M_POST || r->method_number == M_PUT)
396 fprintf(f, "\n%s\n", dbuf);
399 fputs("%response\n", f);
400 hdrs_arr = ap_table_elts(r->err_headers_out);
401 hdrs = (table_entry *) hdrs_arr->elts;
403 for (i = 0; i < hdrs_arr->nelts; ++i) {
406 fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
410 fprintf(f, "%s\n", sbuf);
412 if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
413 fputs("%stdout\n", f);
414 fputs(argsbuffer, f);
415 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
416 fputs(argsbuffer, f);
420 if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
421 fputs("%stderr\n", f);
422 fputs(argsbuffer, f);
423 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
424 fputs(argsbuffer, f);
428 ap_bclose(script_in);
429 ap_bclose(script_err);
431 ap_pfclose(r->pool, f);
435 /****************************************************************
437 * Actual suPHP handling...
441 struct suphp_child_stuff {
451 static int suphp_child(void *child_stuff, child_info *pinfo)
453 struct suphp_child_stuff *cld = (struct suphp_child_stuff *) child_stuff;
454 request_rec *r = cld->r;
455 char *argv0 = cld->argv0;
457 const char *authorization=NULL;
459 char *auth_user=NULL;
460 char *auth_password=NULL;
461 suphp_conf *cfg = (suphp_conf *) ap_get_module_config(r->per_dir_config, &suphp_module);
465 /* Under OS/2 need to use device con. */
466 FILE *dbg = fopen("con", "w");
468 FILE *dbg = fopen("/dev/tty", "w");
475 RAISE_SIGSTOP(SUPHP_CHILD);
477 fprintf(dbg, "Attempting to exec %s as %ssuPHP child (argv0 = %s)\n",
478 r->filename, cld->nph ? "NPH " : "", argv0);
482 * Set environment variables with authorization information like mod_php
487 authorization = ap_table_get(r->headers_in, "Authorization");
490 && !strcasecmp(ap_getword(r->pool, &authorization, ' '), "Basic"))
492 tmp = ap_uudecode(r->pool, authorization);
493 auth_user = ap_getword_nulls_nc(r->pool, &tmp, ':');
497 auth_user = estrdup(auth_user);
504 auth_password = estrdup(auth_password);
509 ap_table_setn(r->subprocess_env, "PHP_AUTH_USER", auth_user);
513 ap_table_setn(r->subprocess_env, "PHP_AUTH_PW", auth_password);
519 ap_table_setn(r->subprocess_env, "PHP_CONFIG", cfg->php_config);
523 env = ap_create_environment(r->pool, r->subprocess_env);
526 fprintf(dbg, "Environment: \n");
527 for (i = 0; env[i]; ++i)
528 fprintf(dbg, "'%s'\n", env[i]);
532 ap_chdir_file(r->filename);
534 ap_error_log2stderr(r->server);
536 /* Transumute outselves into the script.
537 * NB only ISINDEX scripts get decoded arguments.
543 ap_cleanup_for_exec();
545 child_pid = suphp_call_exec(r, pinfo, argv0, env, 0);
548 /* Uh oh. Still here. Where's the kaboom? There was supposed to be an
549 * EARTH-shattering kaboom!
551 * Oh, well. Muddle through as best we can...
553 * Note that only stderr is available at this point, so don't pass in
554 * a server to aplog_error.
557 ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed", r->filename);
564 static int suphp_handler(request_rec *r)
566 int retval, nph, dbpos = 0;
567 char *argv0, *dbuf = NULL;
568 BUFF *script_out, *script_in, *script_err;
569 char argsbuffer[HUGE_STRING_LEN];
570 int is_included = !strcmp(r->protocol, "INCLUDED");
571 void *sconf = r->server->module_config;
573 (suphp_conf *) ap_get_module_config(sconf, &suphp_module);
574 suphp_conf *mconf = (suphp_conf *) ap_get_module_config(r->server->module_config, &suphp_module);
576 struct suphp_child_stuff cld;
578 if ((mconf->engine == SUPHP_ENGINE_OFF) || (mconf->engine == SUPHP_ENGINE_UNDEFINED))
581 if (r->method_number == M_OPTIONS) {
582 /* 99 out of 100 CGI scripts, this is all they support */
583 r->allowed |= (1 << M_GET);
584 r->allowed |= (1 << M_POST);
588 if ((argv0 = strrchr(r->filename, '/')) != NULL)
593 // nph = !(strncmp(argv0, "nph-", 4));
596 // if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
597 // return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
598 // "Options ExecCGI is off in this directory");
599 // if (nph && is_included)
600 // return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
601 // "attempt to include NPH CGI script");
603 if (r->finfo.st_mode == 0)
604 return log_scripterror(r, conf, NOT_FOUND, APLOG_NOERRNO,
605 "script not found or unable to stat");
606 if (S_ISDIR(r->finfo.st_mode))
607 return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
608 "attempt to invoke directory as script");
610 if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
613 ap_add_common_vars(r);
617 cld.debug = conf->logname ? 1 : 0;
619 cld.t.filename = r->filename;
620 cld.t.subprocess_env = r->subprocess_env;
621 cld.t.prog_type = FORK_FILE;
624 #ifdef CHARSET_EBCDIC
625 /* The included MIME headers must ALWAYS be in text/ebcdic format.
626 * Only after reading the MIME headers, we check the Content-Type
627 * and switch to the necessary conversion mode.
628 * Until then (and in case an nph- script was called), use the
629 * configured default conversion:
631 ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out);
632 #endif /*CHARSET_EBCDIC*/
635 * we spawn out of r->main if it's there so that we can avoid
636 * waiting for free_proc_chain to cleanup in the middle of an
639 if (!ap_bspawn_child(r->main ? r->main->pool : r->pool, suphp_child,
640 (void *) &cld, kill_after_timeout,
641 &script_out, &script_in, &script_err)) {
642 ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
643 "couldn't spawn child process: %s", r->filename);
644 return HTTP_INTERNAL_SERVER_ERROR;
647 /* Transfer any put/post args, CERN style...
648 * Note that we already ignore SIGPIPE in the core server.
651 if (ap_should_client_block(r)) {
652 int dbsize, len_read;
655 dbuf = ap_pcalloc(r->pool, conf->bufbytes + 1);
659 ap_hard_timeout("copy script args", r);
662 ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) {
664 if ((dbpos + len_read) > conf->bufbytes) {
665 dbsize = conf->bufbytes - dbpos;
670 memcpy(dbuf + dbpos, argsbuffer, dbsize);
674 if (ap_bwrite(script_out, argsbuffer, len_read) < len_read) {
675 /* silly script stopped reading, soak up remaining message */
676 while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) {
683 ap_bflush(script_out);
688 ap_bclose(script_out);
690 /* Handle script return... */
691 if (script_in && !nph) {
692 const char *location;
693 char sbuf[MAX_STRING_LEN];
696 if ((ret = ap_scan_script_header_err_buff(r, script_in, sbuf))) {
697 return log_script(r, conf, ret, dbuf, sbuf, script_in, script_err);
700 location = ap_table_get(r->headers_out, "Location");
702 if (location && location[0] == '/' && r->status == 200) {
704 /* Soak up all the script output */
705 ap_hard_timeout("read from script", r);
706 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
709 #if defined(WIN32) || defined(NETWARE)
710 /* Soak up stderr and redirect it to the error log.
711 * Script output to stderr is already directed to the error log
712 * on Unix, thanks to the magic of fork().
714 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
715 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
719 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
726 /* This redirect needs to be a GET no matter what the original
729 r->method = ap_pstrdup(r->pool, "GET");
730 r->method_number = M_GET;
732 /* We already read the message body (if any), so don't allow
733 * the redirected request to think it has one. We can ignore
734 * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
736 ap_table_unset(r->headers_in, "Content-Length");
738 ap_internal_redirect_handler(location, r);
741 else if (location && r->status == 200) {
742 /* XX Note that if a script wants to produce its own Redirect
743 * body, it now has to explicitly *say* "Status: 302"
748 ap_send_http_header(r);
749 if (!r->header_only) {
750 ap_send_fb(script_in, r);
752 ap_bclose(script_in);
754 ap_soft_timeout("soaking script stderr", r);
755 #if defined(WIN32) || defined(NETWARE)
756 /* Script output to stderr is already directed to the error log
757 * on Unix, thanks to the magic of fork().
759 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
760 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
764 while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
769 ap_bclose(script_err);
772 if (script_in && nph) {
773 ap_send_fb(script_in, r);
776 return OK; /* NOT r->status, even if it has changed. */
779 static const handler_rec suphp_handlers[] =
781 {"x-httpd-php", suphp_handler},
785 module MODULE_VAR_EXPORT suphp_module =
787 STANDARD_MODULE_STUFF,
788 NULL, /* initializer */
789 suphp_create_dir_config, /* dir config creater */
790 suphp_merge_dir_config, /* dir merger --- default is to override */
791 suphp_create_server_config, /* server config */
792 suphp_merge_server_config, /* merge server config */
793 suphp_cmds, /* command table */
794 suphp_handlers, /* handlers */
795 NULL, /* filename translation */
796 NULL, /* check_user_id */
797 NULL, /* check auth */
798 NULL, /* check access */
799 NULL, /* type_checker */
802 NULL, /* header parser */
803 NULL, /* child_init */
804 NULL, /* child_exit */
805 NULL /* post read-request */