1
0
mirror of https://github.com/apache/httpd.git synced 2026-01-20 04:06:16 +03:00
Files
apache/modules/generators/mod_cgi.c
Cliff Woolley 768d65eeac AcceptPathInfo was totally backwards... it would reject when set to on and
by default and accept when set to off for the default handler, and would
reject only if set to accept for mod_cgi(d) and mod_isapi.

PR: 8234


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@94751 13f79535-47bb-0310-9956-ffa450edef68
2002-04-22 08:08:38 +00:00

1048 lines
35 KiB
C

/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
/*
* http_script: keeps all script-related ramblings together.
*
* Compliant to CGI/1.1 spec
*
* Adapted by rst from original NCSA code by Rob McCool
*
* Apache adds some new env vars; REDIRECT_URL and REDIRECT_QUERY_STRING for
* custom error responses, and DOCUMENT_ROOT because we found it useful.
* It also adds SERVER_ADMIN - useful for scripts to know who to mail when
* they fail.
*/
#include "apr.h"
#include "apr_strings.h"
#include "apr_thread_proc.h" /* for RLIMIT stuff */
#include "apr_optional.h"
#include "apr_buckets.h"
#include "apr_lib.h"
#define APR_WANT_STRFUNC
#include "apr_want.h"
#define CORE_PRIVATE
#include "util_filter.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_main.h"
#include "http_log.h"
#include "util_script.h"
#include "ap_mpm.h"
#include "mod_core.h"
#include "../filters/mod_include.h"
#include "mod_cgi.h"
module AP_MODULE_DECLARE_DATA cgi_module;
static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgi_pfn_reg_with_ssi;
static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgi_pfn_gtv;
static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgi_pfn_ps;
static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) *cgi_build_command;
typedef enum {RUN_AS_SSI, RUN_AS_CGI} prog_types;
typedef struct {
apr_int32_t in_pipe;
apr_int32_t out_pipe;
apr_int32_t err_pipe;
apr_cmdtype_e cmd_type;
prog_types prog_type;
apr_bucket_brigade **bb;
include_ctx_t *ctx;
ap_filter_t *next;
} exec_info;
/* Read and discard the data in the brigade produced by a CGI script */
static void discard_script_output(apr_bucket_brigade *bb);
/* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
* in ScriptAliased directories, which means we need to know if this
* request came through ScriptAlias or not... so the Alias module
* leaves a note for us.
*/
static int is_scriptaliased(request_rec *r)
{
const char *t = apr_table_get(r->notes, "alias-forced-type");
return t && (!strcasecmp(t, "cgi-script"));
}
/* Configuration stuff */
#define DEFAULT_LOGBYTES 10385760
#define DEFAULT_BUFBYTES 1024
typedef struct {
const char *logname;
long logbytes;
int bufbytes;
} cgi_server_conf;
static void *create_cgi_config(apr_pool_t *p, server_rec *s)
{
cgi_server_conf *c =
(cgi_server_conf *) apr_pcalloc(p, sizeof(cgi_server_conf));
c->logname = NULL;
c->logbytes = DEFAULT_LOGBYTES;
c->bufbytes = DEFAULT_BUFBYTES;
return c;
}
static void *merge_cgi_config(apr_pool_t *p, void *basev, void *overridesv)
{
cgi_server_conf *base = (cgi_server_conf *) basev, *overrides = (cgi_server_conf *) overridesv;
return overrides->logname ? overrides : base;
}
static const char *set_scriptlog(cmd_parms *cmd, void *dummy, const char *arg)
{
server_rec *s = cmd->server;
cgi_server_conf *conf = ap_get_module_config(s->module_config,
&cgi_module);
conf->logname = ap_server_root_relative(cmd->pool, arg);
if (!conf->logname) {
return apr_pstrcat(cmd->pool, "Invalid ScriptLog path ",
arg, NULL);
}
return NULL;
}
static const char *set_scriptlog_length(cmd_parms *cmd, void *dummy,
const char *arg)
{
server_rec *s = cmd->server;
cgi_server_conf *conf = ap_get_module_config(s->module_config,
&cgi_module);
conf->logbytes = atol(arg);
return NULL;
}
static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy,
const char *arg)
{
server_rec *s = cmd->server;
cgi_server_conf *conf = ap_get_module_config(s->module_config,
&cgi_module);
conf->bufbytes = atoi(arg);
return NULL;
}
static const command_rec cgi_cmds[] =
{
AP_INIT_TAKE1("ScriptLog", set_scriptlog, NULL, RSRC_CONF,
"the name of a log for script debugging info"),
AP_INIT_TAKE1("ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF,
"the maximum length (in bytes) of the script debug log"),
AP_INIT_TAKE1("ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF,
"the maximum size (in bytes) to record of a POST request"),
{NULL}
};
static int log_scripterror(request_rec *r, cgi_server_conf * conf, int ret,
apr_status_t rv, char *error)
{
apr_file_t *f = NULL;
apr_finfo_t finfo;
char time_str[APR_CTIME_LEN];
int log_flags = rv ? APLOG_ERR : APLOG_NOERRNO | APLOG_ERR;
ap_log_rerror(APLOG_MARK, log_flags, rv, r,
"%s: %s", error, r->filename);
/* XXX Very expensive mainline case! Open, then getfileinfo! */
if (!conf->logname ||
((apr_stat(&finfo, conf->logname,
APR_FINFO_SIZE, r->pool) == APR_SUCCESS)
&& (finfo.size > conf->logbytes)) ||
(apr_file_open(&f, conf->logname,
APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT, r->pool)
!= APR_SUCCESS)) {
return ret;
}
/* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
apr_ctime(time_str, apr_time_now());
apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
r->args ? "?" : "", r->args ? r->args : "", r->protocol);
/* "%% 500 /usr/local/apache/cgi-bin */
apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
apr_file_printf(f, "%%error\n%s\n", error);
apr_file_close(f);
return ret;
}
/* Soak up stderr from a script and redirect it to the error log.
*/
static void log_script_err(request_rec *r, apr_file_t *script_err)
{
char argsbuffer[HUGE_STRING_LEN];
char *newline;
while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
script_err) == APR_SUCCESS) {
newline = strchr(argsbuffer, '\n');
if (newline) {
*newline = '\0';
}
ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
"%s", argsbuffer);
}
}
static int log_script(request_rec *r, cgi_server_conf * conf, int ret,
char *dbuf, const char *sbuf, apr_bucket_brigade *bb,
apr_file_t *script_err)
{
const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in);
const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
char argsbuffer[HUGE_STRING_LEN];
apr_file_t *f = NULL;
apr_bucket *e;
const char *buf;
apr_size_t len;
apr_status_t rv;
int first;
int i;
apr_finfo_t finfo;
char time_str[APR_CTIME_LEN];
/* XXX Very expensive mainline case! Open, then getfileinfo! */
if (!conf->logname ||
((apr_stat(&finfo, conf->logname,
APR_FINFO_SIZE, r->pool) == APR_SUCCESS)
&& (finfo.size > conf->logbytes)) ||
(apr_file_open(&f, conf->logname,
APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT, r->pool) != APR_SUCCESS)) {
/* Soak up script output */
discard_script_output(bb);
log_script_err(r, script_err);
return ret;
}
/* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
apr_ctime(time_str, apr_time_now());
apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
r->args ? "?" : "", r->args ? r->args : "", r->protocol);
/* "%% 500 /usr/local/apache/cgi-bin" */
apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
apr_file_puts("%request\n", f);
for (i = 0; i < hdrs_arr->nelts; ++i) {
if (!hdrs[i].key)
continue;
apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
}
if ((r->method_number == M_POST || r->method_number == M_PUT)
&& *dbuf) {
apr_file_printf(f, "\n%s\n", dbuf);
}
apr_file_puts("%response\n", f);
hdrs_arr = apr_table_elts(r->err_headers_out);
hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
for (i = 0; i < hdrs_arr->nelts; ++i) {
if (!hdrs[i].key)
continue;
apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
}
if (sbuf && *sbuf)
apr_file_printf(f, "%s\n", sbuf);
first = 1;
APR_BRIGADE_FOREACH(e, bb) {
if (APR_BUCKET_IS_EOS(e)) {
break;
}
rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
if (!APR_STATUS_IS_SUCCESS(rv) || (len == 0)) {
break;
}
if (first) {
apr_file_puts("%stdout\n", f);
first = 0;
}
apr_file_write(f, buf, &len);
apr_file_puts("\n", f);
}
if (apr_file_gets(argsbuffer, HUGE_STRING_LEN, script_err) == APR_SUCCESS) {
apr_file_puts("%stderr\n", f);
apr_file_puts(argsbuffer, f);
while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
script_err) == APR_SUCCESS)
apr_file_puts(argsbuffer, f);
apr_file_puts("\n", f);
}
apr_brigade_destroy(bb);
apr_file_close(script_err);
apr_file_close(f);
return ret;
}
/* This is the special environment used for running the "exec cmd="
* variety of SSI directives.
*/
static void add_ssi_vars(request_rec *r, ap_filter_t *next)
{
apr_table_t *e = r->subprocess_env;
if (r->path_info && r->path_info[0] != '\0') {
request_rec *pa_req;
apr_table_setn(e, "PATH_INFO", ap_escape_shell_cmd(r->pool, r->path_info));
pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r, next);
if (pa_req->filename) {
apr_table_setn(e, "PATH_TRANSLATED",
apr_pstrcat(r->pool, pa_req->filename, pa_req->path_info, NULL));
}
ap_destroy_sub_req(pa_req);
}
if (r->args) {
char *arg_copy = apr_pstrdup(r->pool, r->args);
apr_table_setn(e, "QUERY_STRING", r->args);
ap_unescape_url(arg_copy);
apr_table_setn(e, "QUERY_STRING_UNESCAPED", ap_escape_shell_cmd(r->pool, arg_copy));
}
}
static apr_status_t run_cgi_child(apr_file_t **script_out,
apr_file_t **script_in,
apr_file_t **script_err,
const char *command,
const char * const argv[],
request_rec *r,
apr_pool_t *p,
exec_info *e_info)
{
const char * const *env;
apr_procattr_t *procattr;
apr_proc_t *procnew;
apr_status_t rc = APR_SUCCESS;
#if defined(RLIMIT_CPU) || defined(RLIMIT_NPROC) || \
defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
core_dir_config *conf = ap_get_module_config(r->per_dir_config,
&core_module);
#endif
#ifdef DEBUG_CGI
#ifdef OS2
/* Under OS/2 need to use device con. */
FILE *dbg = fopen("con", "w");
#else
FILE *dbg = fopen("/dev/tty", "w");
#endif
int i;
#endif
RAISE_SIGSTOP(CGI_CHILD);
#ifdef DEBUG_CGI
fprintf(dbg, "Attempting to exec %s as CGI child (argv0 = %s)\n",
r->filename, argv[0]);
#endif
if (e_info->prog_type == RUN_AS_CGI) {
ap_add_cgi_vars(r);
}
else /* SSIs want a controlled environment and a special path. */
{
add_ssi_vars(r, e_info->next);
}
env = (const char * const *)ap_create_environment(p, r->subprocess_env);
#ifdef DEBUG_CGI
fprintf(dbg, "Environment: \n");
for (i = 0; env[i]; ++i)
fprintf(dbg, "'%s'\n", env[i]);
#endif
/* Transmute ourselves into the script.
* NB only ISINDEX scripts get decoded arguments.
*/
if (((rc = apr_procattr_create(&procattr, p)) != APR_SUCCESS) ||
((rc = apr_procattr_io_set(procattr,
e_info->in_pipe,
e_info->out_pipe,
e_info->err_pipe)) != APR_SUCCESS) ||
((rc = apr_procattr_dir_set(procattr,
ap_make_dirstr_parent(r->pool, r->filename))) != APR_SUCCESS) ||
#ifdef RLIMIT_CPU
((rc = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, conf->limit_cpu)) != APR_SUCCESS) ||
#endif
#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
((rc = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, conf->limit_mem)) != APR_SUCCESS) ||
#endif
#ifdef RLIMIT_NPROC
((rc = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, conf->limit_nproc)) != APR_SUCCESS) ||
#endif
((rc = apr_procattr_cmdtype_set(procattr, e_info->cmd_type)) != APR_SUCCESS)) {
/* Something bad happened, tell the world. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
"couldn't set child process attributes: %s", r->filename);
}
else {
procnew = apr_pcalloc(p, sizeof(*procnew));
if (e_info->prog_type == RUN_AS_SSI) {
SPLIT_AND_PASS_PRETAG_BUCKETS(*(e_info->bb), e_info->ctx, e_info->next, rc);
if (rc != APR_SUCCESS) {
return rc;
}
}
rc = ap_os_create_privileged_process(r, procnew, command, argv, env, procattr, p);
if (rc != APR_SUCCESS) {
/* Bad things happened. Everyone should have cleaned up. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
"couldn't create child process: %d: %s", rc, r->filename);
}
else {
apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
*script_in = procnew->out;
if (!*script_in)
return APR_EBADF;
apr_file_pipe_timeout_set(*script_in, (int)(r->server->timeout * APR_USEC_PER_SEC));
if (e_info->prog_type == RUN_AS_CGI) {
*script_out = procnew->in;
if (!*script_out)
return APR_EBADF;
apr_file_pipe_timeout_set(*script_out, (int)(r->server->timeout * APR_USEC_PER_SEC));
*script_err = procnew->err;
if (!*script_err)
return APR_EBADF;
apr_file_pipe_timeout_set(*script_err, (int)(r->server->timeout * APR_USEC_PER_SEC));
}
}
}
#ifdef DEBUG_CGI
fclose(dbg);
#endif
return (rc);
}
static apr_status_t default_build_command(const char **cmd, const char ***argv,
request_rec *r, apr_pool_t *p,
int process_cgi, apr_cmdtype_e * type)
{
int numwords, x, idx;
char *w;
const char *args = NULL;
if (process_cgi) {
/* Allow suexec's "/" check to succeed */
const char *argv0 = strrchr(r->filename, '/');
if (argv0 != NULL)
argv0++;
else
argv0 = r->filename;
*cmd = argv0;
args = r->args;
/* Do not process r->args if they contain an '=' assignment
*/
if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) {
args = r->args;
}
}
if (!args) {
numwords = 1;
}
else {
/* count the number of keywords */
for (x = 0, numwords = 2; args[x]; x++) {
if (args[x] == '+') {
++numwords;
}
}
}
/* Everything is - 1 to account for the first parameter
* which is the program name.
*/
if (numwords > APACHE_ARG_MAX - 1) {
numwords = APACHE_ARG_MAX - 1; /* Truncate args to prevent overrun */
}
*argv = apr_palloc(p, (numwords + 2) * sizeof(char *));
(*argv)[0] = *cmd;
for (x = 1, idx = 1; x < numwords; x++) {
w = ap_getword_nulls(p, &args, '+');
ap_unescape_url(w);
(*argv)[idx++] = ap_escape_shell_cmd(p, w);
}
(*argv)[idx] = NULL;
return APR_SUCCESS;
}
static void discard_script_output(apr_bucket_brigade *bb)
{
apr_bucket *e;
const char *buf;
apr_size_t len;
apr_status_t rv;
APR_BRIGADE_FOREACH(e, bb) {
if (APR_BUCKET_IS_EOS(e)) {
break;
}
rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
if (!APR_STATUS_IS_SUCCESS(rv)) {
break;
}
}
}
static int cgi_handler(request_rec *r)
{
int retval, nph, dbpos = 0;
const char *argv0;
const char *command;
const char **argv;
char *dbuf = NULL;
apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL;
apr_bucket_brigade *bb;
apr_bucket *b;
char argsbuffer[HUGE_STRING_LEN];
int is_included;
apr_pool_t *p;
cgi_server_conf *conf;
apr_status_t rv;
exec_info e_info;
if(strcmp(r->handler,CGI_MAGIC_TYPE) && strcmp(r->handler,"cgi-script"))
return DECLINED;
is_included = !strcmp(r->protocol, "INCLUDED");
p = r->main ? r->main->pool : r->pool;
if (r->method_number == M_OPTIONS) {
/* 99 out of 100 CGI scripts, this is all they support */
r->allowed |= (AP_METHOD_BIT << M_GET);
r->allowed |= (AP_METHOD_BIT << M_POST);
return DECLINED;
}
argv0 = apr_filename_of_pathname(r->filename);
nph = !(strncmp(argv0, "nph-", 4));
conf = ap_get_module_config(r->server->module_config, &cgi_module);
if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
"Options ExecCGI is off in this directory");
if (nph && is_included)
return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
"attempt to include NPH CGI script");
if (r->finfo.filetype == 0)
return log_scripterror(r, conf, HTTP_NOT_FOUND, 0,
"script not found or unable to stat");
if (r->finfo.filetype == APR_DIR)
return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
"attempt to invoke directory as script");
if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
r->path_info && *r->path_info)
{
/* default to accept */
return log_scripterror(r, conf, HTTP_NOT_FOUND, 0,
"AcceptPathInfo off disallows user's path");
}
/*
if (!ap_suexec_enabled) {
if (!ap_can_exec(&r->finfo))
return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
"file permissions deny server execution");
}
*/
if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
return retval;
ap_add_common_vars(r);
e_info.cmd_type = APR_PROGRAM;
e_info.in_pipe = APR_CHILD_BLOCK;
e_info.out_pipe = APR_CHILD_BLOCK;
e_info.err_pipe = APR_CHILD_BLOCK;
e_info.prog_type = RUN_AS_CGI;
e_info.bb = NULL;
e_info.ctx = NULL;
e_info.next = NULL;
/* build the command line */
if ((rv = cgi_build_command(&command, &argv, r, p, 1, &e_info.cmd_type))
!= APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"don't know how to spawn child process: %s",
r->filename);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* run the script in its own process */
if ((rv = run_cgi_child(&script_out, &script_in, &script_err,
command, argv, r, p, &e_info)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"couldn't spawn child process: %s", r->filename);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* Transfer any put/post args, CERN style...
* Note that we already ignore SIGPIPE in the core server.
*/
if (ap_should_client_block(r)) {
int len_read, dbsize;
apr_size_t bytes_written, bytes_to_write;
apr_status_t rv;
if (conf->logname) {
dbuf = apr_pcalloc(r->pool, conf->bufbytes + 1);
dbpos = 0;
}
while ((len_read =
ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) {
if (conf->logname) {
if ((dbpos + len_read) > conf->bufbytes) {
dbsize = conf->bufbytes - dbpos;
}
else {
dbsize = len_read;
}
memcpy(dbuf + dbpos, argsbuffer, dbsize);
dbpos += dbsize;
}
/* Keep writing data to the child until done or too much time
* elapses with no progress or an error occurs.
*/
bytes_written = 0;
do {
bytes_to_write = len_read - bytes_written;
rv = apr_file_write(script_out, argsbuffer + bytes_written,
&bytes_to_write);
bytes_written += bytes_to_write;
} while (rv == APR_SUCCESS
&& bytes_written < (apr_size_t)len_read);
if (rv != APR_SUCCESS || bytes_written < (apr_size_t)len_read) {
/* silly script stopped reading, soak up remaining message */
while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) {
/* dump it */
}
break;
}
}
apr_file_flush(script_out);
}
apr_file_close(script_out);
/* Handle script return... */
if (script_in && !nph) {
conn_rec *c = r->connection;
const char *location;
char sbuf[MAX_STRING_LEN];
int ret;
bb = apr_brigade_create(r->pool, c->bucket_alloc);
b = apr_bucket_pipe_create(script_in, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
b = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
if ((ret = ap_scan_script_header_err_brigade(r, bb, sbuf))) {
return log_script(r, conf, ret, dbuf, sbuf, bb, script_err);
}
location = apr_table_get(r->headers_out, "Location");
if (location && location[0] == '/' && r->status == 200) {
discard_script_output(bb);
apr_brigade_destroy(bb);
log_script_err(r, script_err);
/* This redirect needs to be a GET no matter what the original
* method was.
*/
r->method = apr_pstrdup(r->pool, "GET");
r->method_number = M_GET;
/* We already read the message body (if any), so don't allow
* the redirected request to think it has one. We can ignore
* Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
*/
apr_table_unset(r->headers_in, "Content-Length");
ap_internal_redirect_handler(location, r);
return OK;
}
else if (location && r->status == 200) {
/* XX Note that if a script wants to produce its own Redirect
* body, it now has to explicitly *say* "Status: 302"
*/
discard_script_output(bb);
apr_brigade_destroy(bb);
return HTTP_MOVED_TEMPORARILY;
}
if (!r->header_only) {
ap_pass_brigade(r->output_filters, bb);
}
log_script_err(r, script_err);
apr_file_close(script_err);
}
if (script_in && nph) {
conn_rec *c = r->connection;
struct ap_filter_t *cur;
/* get rid of all filters up through protocol... since we
* haven't parsed off the headers, there is no way they can
* work
*/
cur = r->proto_output_filters;
while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) {
cur = cur->next;
}
r->output_filters = r->proto_output_filters = cur;
bb = apr_brigade_create(r->pool, c->bucket_alloc);
b = apr_bucket_pipe_create(script_in, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
b = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
ap_pass_brigade(r->output_filters, bb);
}
return OK; /* NOT r->status, even if it has changed. */
}
/*============================================================================
*============================================================================
* This is the beginning of the cgi filter code moved from mod_include. This
* is the code required to handle the "exec" SSI directive.
*============================================================================
*============================================================================*/
static int include_cgi(char *s, request_rec *r, ap_filter_t *next,
apr_bucket *head_ptr, apr_bucket **inserted_head)
{
request_rec *rr = ap_sub_req_lookup_uri(s, r, next);
int rr_status;
apr_bucket *tmp_buck, *tmp2_buck;
if (rr->status != HTTP_OK) {
ap_destroy_sub_req(rr);
return -1;
}
/* No hardwired path info or query allowed */
if ((rr->path_info && rr->path_info[0]) || rr->args) {
ap_destroy_sub_req(rr);
return -1;
}
if (rr->finfo.filetype != APR_REG) {
ap_destroy_sub_req(rr);
return -1;
}
/* Script gets parameters of the *document*, for back compatibility */
rr->path_info = r->path_info; /* hard to get right; see mod_cgi.c */
rr->args = r->args;
/* Force sub_req to be treated as a CGI request, even if ordinary
* typing rules would have called it something else.
*/
ap_set_content_type(rr, CGI_MAGIC_TYPE);
/* Run it. */
rr_status = ap_run_sub_req(rr);
if (ap_is_HTTP_REDIRECT(rr_status)) {
apr_size_t len_loc;
const char *location = apr_table_get(rr->headers_out, "Location");
conn_rec *c = r->connection;
location = ap_escape_html(rr->pool, location);
len_loc = strlen(location);
/* XXX: if most of this stuff is going to get copied anyway,
* it'd be more efficient to pstrcat it into a single pool buffer
* and a single pool bucket */
tmp_buck = apr_bucket_immortal_create("<A HREF=\"",
sizeof("<A HREF=\"") - 1,
c->bucket_alloc);
APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
tmp2_buck = apr_bucket_heap_create(location, len_loc, NULL,
c->bucket_alloc);
APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
tmp2_buck = apr_bucket_immortal_create("\">", sizeof("\">") - 1,
c->bucket_alloc);
APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
tmp2_buck = apr_bucket_heap_create(location, len_loc, NULL,
c->bucket_alloc);
APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
tmp2_buck = apr_bucket_immortal_create("</A>", sizeof("</A>") - 1,
c->bucket_alloc);
APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
if (*inserted_head == NULL) {
*inserted_head = tmp_buck;
}
}
ap_destroy_sub_req(rr);
return 0;
}
static int include_cmd(include_ctx_t *ctx, apr_bucket_brigade **bb,
const char *command, request_rec *r, ap_filter_t *f)
{
exec_info e_info;
const char **argv;
apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL;
apr_bucket_brigade *bcgi;
apr_bucket *b;
apr_status_t rv;
e_info.cmd_type = APR_SHELLCMD;
e_info.in_pipe = APR_NO_PIPE;
e_info.out_pipe = APR_FULL_BLOCK;
e_info.err_pipe = APR_NO_PIPE;
e_info.prog_type = RUN_AS_SSI;
e_info.bb = bb;
e_info.ctx = ctx;
e_info.next = f->next;
if ((rv = cgi_build_command(&command, &argv, r, r->pool, 0, &e_info.cmd_type))
!= APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"don't know how to spawn cmd child process: %s",
r->filename);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* run the script in its own process */
if ((rv = run_cgi_child(&script_out, &script_in, &script_err,
command, argv, r, r->pool, &e_info)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"couldn't spawn child process: %s", r->filename);
return HTTP_INTERNAL_SERVER_ERROR;
}
bcgi = apr_brigade_create(r->pool, f->c->bucket_alloc);
b = apr_bucket_pipe_create(script_in, f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bcgi, b);
ap_pass_brigade(f->next, bcgi);
/* We can't close the pipe here, because we may return before the
* full CGI has been sent to the network. That's okay though,
* because we can rely on the pool to close the pipe for us.
*/
return 0;
}
static int handle_exec(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
{
char *tag = NULL;
char *tag_val = NULL;
char *file = r->filename;
apr_bucket *tmp_buck;
char parsed_string[MAX_STRING_LEN];
*inserted_head = NULL;
if (ctx->flags & FLAG_PRINTING) {
if (ctx->flags & FLAG_NO_EXEC) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
"exec used but not allowed in %s", r->filename);
CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
}
else {
while (1) {
cgi_pfn_gtv(ctx, &tag, &tag_val, 1);
if (tag_val == NULL) {
if (tag == NULL) {
return (0);
}
else {
return 1;
}
}
if (!strcmp(tag, "cmd")) {
cgi_pfn_ps(r, ctx, tag_val, parsed_string, sizeof(parsed_string), 1);
if (include_cmd(ctx, bb, parsed_string, r, f) == -1) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
"execution failure for parameter \"%s\" "
"to tag exec in file %s", tag, r->filename);
CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
}
}
else if (!strcmp(tag, "cgi")) {
apr_status_t retval = APR_SUCCESS;
cgi_pfn_ps(r, ctx, tag_val, parsed_string, sizeof(parsed_string), 0);
SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, retval);
if (retval != APR_SUCCESS) {
return retval;
}
if (include_cgi(parsed_string, r, f->next, head_ptr, inserted_head) == -1) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
"invalid CGI ref \"%s\" in %s", tag_val, file);
CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
}
}
else {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
"unknown parameter \"%s\" to tag exec in %s", tag, file);
CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
}
}
}
}
return 0;
}
/*============================================================================
*============================================================================
* This is the end of the cgi filter code moved from mod_include.
*============================================================================
*============================================================================*/
static int cgi_post_config(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
cgi_pfn_reg_with_ssi = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
cgi_pfn_gtv = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_get_tag_and_value);
cgi_pfn_ps = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_parse_string);
if ((cgi_pfn_reg_with_ssi) && (cgi_pfn_gtv) && (cgi_pfn_ps)) {
/* Required by mod_include filter. This is how mod_cgi registers
* with mod_include to provide processing of the exec directive.
*/
cgi_pfn_reg_with_ssi("exec", handle_exec);
}
/* This is the means by which unusual (non-unix) os's may find alternate
* means to run a given command (e.g. shebang/registry parsing on Win32)
*/
cgi_build_command = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command);
if (!cgi_build_command) {
cgi_build_command = default_build_command;
}
return OK;
}
static void register_hooks(apr_pool_t *p)
{
static const char * const aszPre[] = { "mod_include.c", NULL };
ap_hook_handler(cgi_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_config(cgi_post_config, aszPre, NULL, APR_HOOK_REALLY_FIRST);
}
module AP_MODULE_DECLARE_DATA cgi_module =
{
STANDARD20_MODULE_STUFF,
NULL, /* dir config creater */
NULL, /* dir merger --- default is to override */
create_cgi_config, /* server config */
merge_cgi_config, /* merge server config */
cgi_cmds, /* command apr_table_t */
register_hooks /* register hooks */
};