#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 "http_conf_globals.h"
module MODULE_VAR_EXPORT cgi_module;
static
int
is_scriptaliased(request_rec *r)
{
const
char
*t = ap_table_get(r->notes,
"alias-forced-type"
);
return
t && (!strcasecmp(t,
"cgi-script"
));
}
#define DEFAULT_LOGBYTES 10385760
#define DEFAULT_BUFBYTES 1024
typedef
struct
{
char
*logname;
long
logbytes;
int
bufbytes;
} cgi_server_conf;
static
void
*create_cgi_config(pool *p, server_rec *s)
{
cgi_server_conf *c =
(cgi_server_conf *) ap_pcalloc(p,
sizeof
(cgi_server_conf));
c->logname = NULL;
c->logbytes = DEFAULT_LOGBYTES;
c->bufbytes = DEFAULT_BUFBYTES;
return
c;
}
static
void
*merge_cgi_config(pool *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,
char
*arg)
{
server_rec *s = cmd->server;
cgi_server_conf *conf =
(cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module);
conf->logname = arg;
return
NULL;
}
static
const
char
*set_scriptlog_length(cmd_parms *cmd,
void
*dummy,
char
*arg)
{
server_rec *s = cmd->server;
cgi_server_conf *conf =
(cgi_server_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,
char
*arg)
{
server_rec *s = cmd->server;
cgi_server_conf *conf =
(cgi_server_conf *) ap_get_module_config(s->module_config, &cgi_module);
conf->bufbytes =
atoi
(arg);
return
NULL;
}
static
const
command_rec cgi_cmds[] =
{
{
"ScriptLog"
, set_scriptlog, NULL, RSRC_CONF, TAKE1,
"the name of a log for script debugging info"
},
{
"ScriptLogLength"
, set_scriptlog_length, NULL, RSRC_CONF, TAKE1,
"the maximum length (in bytes) of the script debug log"
},
{
"ScriptLogBuffer"
, set_scriptlog_buffer, NULL, RSRC_CONF, TAKE1,
"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,
int
show_errno,
char
*error)
{
FILE
*f;
struct
stat finfo;
ap_log_rerror(APLOG_MARK, show_errno|APLOG_ERR, r,
"%s: %s"
, error, r->filename);
if
(!conf->logname ||
((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
&& (finfo.st_size > conf->logbytes)) ||
((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
"a"
)) == NULL)) {
return
ret;
}
fprintf
(f,
"%%%% [%s] %s %s%s%s %s\n"
, ap_get_time(), r->method, r->uri,
r->args ?
"?"
:
""
, r->args ? r->args :
""
, r->protocol);
fprintf
(f,
"%%%% %d %s\n"
, ret, r->filename);
fprintf
(f,
"%%error\n%s\n"
, error);
ap_pfclose(r->pool, f);
return
ret;
}
static
int
log_script(request_rec *r, cgi_server_conf * conf,
int
ret,
char
*dbuf,
const
char
*sbuf, BUFF *script_in, BUFF *script_err)
{
array_header *hdrs_arr = ap_table_elts(r->headers_in);
table_entry *hdrs = (table_entry *) hdrs_arr->elts;
char
argsbuffer[HUGE_STRING_LEN];
FILE
*f;
int
i;
struct
stat finfo;
if
(!conf->logname ||
((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
&& (finfo.st_size > conf->logbytes)) ||
((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
"a"
)) == NULL)) {
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
continue
;
#if defined(WIN32) || defined(NETWARE)
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
"%s"
, argsbuffer);
}
#else
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
continue
;
#endif
return
ret;
}
fprintf
(f,
"%%%% [%s] %s %s%s%s %s\n"
, ap_get_time(), r->method, r->uri,
r->args ?
"?"
:
""
, r->args ? r->args :
""
, r->protocol);
fprintf
(f,
"%%%% %d %s\n"
, ret, r->filename);
fputs
(
"%request\n"
, f);
for
(i = 0; i < hdrs_arr->nelts; ++i) {
if
(!hdrs[i].key)
continue
;
fprintf
(f,
"%s: %s\n"
, hdrs[i].key, hdrs[i].val);
}
if
((r->method_number == M_POST || r->method_number == M_PUT)
&& *dbuf) {
fprintf
(f,
"\n%s\n"
, dbuf);
}
fputs
(
"%response\n"
, f);
hdrs_arr = ap_table_elts(r->err_headers_out);
hdrs = (table_entry *) hdrs_arr->elts;
for
(i = 0; i < hdrs_arr->nelts; ++i) {
if
(!hdrs[i].key)
continue
;
fprintf
(f,
"%s: %s\n"
, hdrs[i].key, hdrs[i].val);
}
if
(sbuf && *sbuf)
fprintf
(f,
"%s\n"
, sbuf);
if
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
fputs
(
"%stdout\n"
, f);
fputs
(argsbuffer, f);
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0)
fputs
(argsbuffer, f);
fputs
(
"\n"
, f);
}
if
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
fputs
(
"%stderr\n"
, f);
fputs
(argsbuffer, f);
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0)
fputs
(argsbuffer, f);
fputs
(
"\n"
, f);
}
ap_bclose(script_in);
ap_bclose(script_err);
ap_pfclose(r->pool, f);
return
ret;
}
struct
cgi_child_stuff {
#ifdef TPF
TPF_FORK_CHILD t;
#endif
request_rec *r;
int
nph;
int
debug;
char
*argv0;
};
static
int
cgi_child(
void
*child_stuff, child_info *pinfo)
{
struct
cgi_child_stuff *cld = (
struct
cgi_child_stuff *) child_stuff;
request_rec *r = cld->r;
char
*argv0 = cld->argv0;
int
child_pid;
#ifdef DEBUG_CGI
#ifdef OS2
FILE
*dbg =
fopen
(
"con"
,
"w"
);
#else
FILE
*dbg =
fopen
(
"/dev/tty"
,
"w"
);
#endif
int
i;
#endif
char
**env;
RAISE_SIGSTOP(CGI_CHILD);
#ifdef DEBUG_CGI
fprintf
(dbg,
"Attempting to exec %s as %sCGI child (argv0 = %s)\n"
,
r->filename, cld->nph ?
"NPH "
:
""
, argv0);
#endif
ap_add_cgi_vars(r);
env = ap_create_environment(r->pool, r->subprocess_env);
#ifdef DEBUG_CGI
fprintf
(dbg,
"Environment: \n"
);
for
(i = 0; env[i]; ++i)
fprintf
(dbg,
"'%s'\n"
, env[i]);
#endif
#ifndef WIN32
ap_chdir_file(r->filename);
#endif
if
(!cld->debug)
ap_error_log2stderr(r->server);
#ifdef TPF
return
(0);
#else
ap_cleanup_for_exec();
child_pid = ap_call_exec(r, pinfo, argv0, env, 0);
#if defined(WIN32) || defined(OS2)
return
(child_pid);
#else
ap_log_error(APLOG_MARK, APLOG_ERR, NULL,
"exec of %s failed"
, r->filename);
exit
(0);
return
(0);
#endif
#endif /* TPF */
}
static
int
cgi_handler(request_rec *r)
{
int
retval, nph, dbpos = 0;
char
*argv0, *dbuf = NULL;
BUFF *script_out, *script_in, *script_err;
char
argsbuffer[HUGE_STRING_LEN];
int
is_included = !
strcmp
(r->protocol,
"INCLUDED"
);
void
*sconf = r->server->module_config;
cgi_server_conf *conf =
(cgi_server_conf *) ap_get_module_config(sconf, &cgi_module);
struct
cgi_child_stuff cld;
if
(r->method_number == M_OPTIONS) {
r->allowed |= (1 << M_GET);
r->allowed |= (1 << M_POST);
return
DECLINED;
}
if
((argv0 =
strrchr
(r->filename,
'/'
)) != NULL)
argv0++;
else
argv0 = r->filename;
nph = !(
strncmp
(argv0,
"nph-"
, 4));
if
(!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
return
log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
"Options ExecCGI is off in this directory"
);
if
(nph && is_included)
return
log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
"attempt to include NPH CGI script"
);
#if defined(OS2) || defined(WIN32)
if
(r->finfo.st_mode == 0) {
struct
stat statbuf;
char
*newfile;
newfile = ap_pstrcat(r->pool, r->filename,
".EXE"
, NULL);
if
((stat(newfile, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode))) {
return
log_scripterror(r, conf, NOT_FOUND, 0,
"script not found or unable to stat"
);
}
else
{
r->filename = newfile;
}
}
#else
if
(r->finfo.st_mode == 0)
return
log_scripterror(r, conf, NOT_FOUND, APLOG_NOERRNO,
"script not found or unable to stat"
);
#endif
if
(S_ISDIR(r->finfo.st_mode))
return
log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
"attempt to invoke directory as script"
);
if
(!ap_suexec_enabled) {
if
(!ap_can_exec(&r->finfo))
return
log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO,
"file permissions deny server execution"
);
}
if
((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
return
retval;
ap_add_common_vars(r);
cld.argv0 = argv0;
cld.r = r;
cld.nph = nph;
cld.debug = conf->logname ? 1 : 0;
#ifdef TPF
cld.t.filename = r->filename;
cld.t.subprocess_env = r->subprocess_env;
cld.t.prog_type = FORK_FILE;
#endif /* TPF */
#ifdef CHARSET_EBCDIC
ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out);
#endif /*CHARSET_EBCDIC*/
if
(!ap_bspawn_child(r->main ? r->main->pool : r->pool, cgi_child,
(
void
*) &cld, kill_after_timeout,
&script_out, &script_in, &script_err)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"couldn't spawn child process: %s"
, r->filename);
return
HTTP_INTERNAL_SERVER_ERROR;
}
if
(ap_should_client_block(r)) {
int
dbsize, len_read;
if
(conf->logname) {
dbuf = ap_pcalloc(r->pool, conf->bufbytes + 1);
dbpos = 0;
}
ap_hard_timeout(
"copy script args"
, r);
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;
}
ap_reset_timeout(r);
if
(ap_bwrite(script_out, argsbuffer, len_read) < len_read) {
while
(ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) {
}
break
;
}
}
ap_bflush(script_out);
ap_kill_timeout(r);
}
ap_bclose(script_out);
if
(script_in && !nph) {
const
char
*location;
char
sbuf[MAX_STRING_LEN];
int
ret;
if
((ret = ap_scan_script_header_err_buff(r, script_in, sbuf))) {
return
log_script(r, conf, ret, dbuf, sbuf, script_in, script_err);
}
location = ap_table_get(r->headers_out,
"Location"
);
if
(location && location[0] ==
'/'
&& r->status == 200) {
ap_hard_timeout(
"read from script"
, r);
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) {
continue
;
}
#if defined(WIN32) || defined(NETWARE)
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
"%s"
, argsbuffer);
}
#else
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
continue
;
}
#endif
ap_kill_timeout(r);
r->method = ap_pstrdup(r->pool,
"GET"
);
r->method_number = M_GET;
ap_table_unset(r->headers_in,
"Content-Length"
);
ap_internal_redirect_handler(location, r);
return
OK;
}
else
if
(location && r->status == 200) {
return
REDIRECT;
}
ap_send_http_header(r);
if
(!r->header_only) {
ap_send_fb(script_in, r);
}
ap_bclose(script_in);
ap_soft_timeout(
"soaking script stderr"
, r);
#if defined(WIN32) || defined(NETWARE)
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
"%s"
, argsbuffer);
}
#else
while
(ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
continue
;
}
#endif
ap_kill_timeout(r);
ap_bclose(script_err);
}
if
(script_in && nph) {
ap_send_fb(script_in, r);
}
return
OK;
}
static
const
handler_rec cgi_handlers[] =
{
{CGI_MAGIC_TYPE, cgi_handler},
{
"cgi-script"
, cgi_handler},
{NULL}
};
module MODULE_VAR_EXPORT cgi_module =
{
STANDARD_MODULE_STUFF,
NULL,
NULL,
NULL,
create_cgi_config,
merge_cgi_config,
cgi_cmds,
cgi_handlers,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};