#include "tcl.h"
#include "tclExtend.h"
module neoscript_module;
extern char server_root[];
extern char server_confname[];
Tcl_Interp *interp = NULL;
void Tcl_InitExtensions (Tcl_Interp *interp);
char softwareStartTimeString[32];
#define NEOSCRIPT_VERSION "2.1"
#define STARTING_NWS_SEQUENCE ""
#define ENDING_NWS_SEQUENCE ""
#define STARTING_SEQUENCE ""
#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
#define DEFAULT_TIME_FORMAT "%A, %d-%b-%y %T %Z"
#define SIZEFMT_BYTES 0
#define SIZEFMT_KMG 1
/* this will be relative to the server root, hopefully, or if you
* set it to start with /, from there.
*/
#define URL_ACCESS_COUNTER_DB "neoscript-data/system/url_access_counters.db"
static void decodehtml(char *s);
static char *get_tag(pool *p, FILE *in, char *tag, int tag_len, int dodecode);
static char *get_nws_code(pool *p, FILE *in, char *code, int codebuf_len, char *sequence);
static int get_directive(FILE *in, char *d, pool *p);
static request_rec *Tcl_request_rec = NULL;
extern int Neo_UnescapeStringCmd (ClientData clientData, Tcl_Interp *interp, int argc, char **argv);
module neoscript_module;
enum xbithack { xbithack_off, xbithack_on, xbithack_full };
#ifdef XBITHACK
#define DEFAULT_XBITHACK xbithack_full
#else
#define DEFAULT_XBITHACK xbithack_off
#endif
typedef struct {
table *neowebscript_dir_vars;
table *neowebscript_user_vars;
enum xbithack xbithack;
} neoscript_dir_config;
typedef struct {
table *neowebscript_server_vars;
} neoscript_server_config;
static char *NeoWebCacheName;
static int NeoWebCacheEnabled;
int load_sub_req (Tcl_Interp *interp, request_rec *r)
{
int errstatus;
int fd;
char *buffer;
int nLines;
int whichLine;
if (r->finfo.st_mode == 0) {
log_reason("File does not exist", r->filename, r);
return NOT_FOUND;
}
if ((errstatus = set_content_length (r, r->finfo.st_size))
|| (errstatus = set_last_modified (r, r->finfo.st_mtime)))
return errstatus;
fd = open (r->filename, O_RDONLY);
if (fd == -1) {
log_reason("file permissions deny server access", r->filename, r);
return FORBIDDEN;
}
buffer = (char *)ckalloc(r->finfo.st_size + 1);
if (read(fd,buffer,r->finfo.st_size) != r->finfo.st_size) {
if (errno != EINTR) goto bye;
}
buffer[r->finfo.st_size] = '\0';
if (Tcl_GlobalEval(interp, buffer) != TCL_OK) {
log_printf(r->server, "Error loading file '%s': %s", r->filename, interp->result);
rprintf(r,"[error loading %s]", r->filename);
ckfree(buffer);
close (fd);
return SERVER_ERROR;
}
bye:
ckfree(buffer);
close (fd);
return OK;
}
/*
*-----------------------------------------------------------------------------
*
* Neo_IncludeCmd --
* Implements the Neo TCL web load and include commands:
* SAFE_include_file slave file
* SAFE_include_virtual slave file
* SAFE_load_file slave file
* SAFE_load_virtual slave file
*
* Results:
* Standard TCL results.
*
*-----------------------------------------------------------------------------
*/
int
Neo_IncludeCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
request_rec *rr=NULL;
request_rec *p;
request_rec *r = Tcl_request_rec;
char *error_fmt = NULL;
char c;
int include;
int virtual;
Tcl_Interp *slaveInterp;
if (argc != 3) {
Tcl_AppendResult (interp,
"wrong # args: should be \"",
argv[0],
" slaveInterp file",
(char *) NULL);
return TCL_ERROR;
}
if ((slaveInterp = Tcl_GetSlave (interp, argv[1])) == (Tcl_Interp *)NULL) {
Tcl_AppendResult (interp,
argv[0],
": unknown slave interpreter '",
argv[1],
"'",
(char *) NULL);
return TCL_ERROR;
}
c = argv[0][0];
if (c == 'i') {
include = 1;
c = argv[0][8];
if (c == 'f') {
virtual = 0;
} else if (c == 'v') {
virtual = 1;
} else {
Tcl_AppendResult (interp,
argv[0],
": not invoked as 'SAFE_include_file' or 'SAFE_include_virtual'",
(char *) NULL);
return TCL_ERROR;
}
} else if (c == 'l') {
include = 0;
c = argv[0][5];
if (c == 'f') {
virtual = 0;
} else if (c == 'v') {
virtual = 1;
} else {
Tcl_AppendResult (interp,
argv[0],
": not invoked as 'SAFE_load_file' or 'SAFE_load_virtual'",
(char *) NULL);
return TCL_ERROR;
}
} else {
Tcl_AppendResult (interp,
argv[0],
": not invoked as 'SAFE_include_' or 'SAFE_load_'",
(char *) NULL);
return TCL_ERROR;
}
if (virtual) {
rr = sub_req_lookup_uri (argv[2], r);
} else {
/* be safe; only files in this directory or below allowed */
/* memory leak here -- tmp never freed */
char *tmp = (char *)ckalloc(strlen(argv[2]) + 3);
sprintf(tmp, "/%s/", argv[2]);
if (*argv[2] == '/' || strstr(tmp, "/../") != NULL) {
Tcl_AppendResult (interp,
argv[0],
": unable to include '",
argv[2],
"': illegal filename from '",
r->filename,
"'",
(char *) NULL);
return TCL_ERROR;
}
rr = sub_req_lookup_file (argv[2], r);
}
if (rr->status != 200) {
Tcl_AppendResult (interp,
argv[0],
": unable to load or include '",
argv[2],
"' from '",
r->filename,
"'",
(char *) NULL);
if (rr != NULL) destroy_sub_req (rr);
return TCL_ERROR;
}
if ((allow_options (rr) & OPT_INCNOEXEC) && rr->content_type
&& (strncmp (rr->content_type, "text/", 5))) {
Tcl_AppendResult (interp,
argv[0],
": unable to load or include potential exec '",
argv[2],
"' from '",
r->filename,
"'",
(char *) NULL);
if (rr != NULL) destroy_sub_req (rr);
return TCL_ERROR;
}
for (p = r; p != NULL; p = p->main) {
if (strcmp(p->filename, rr->filename) == 0) break;
}
if (p != NULL) {
Tcl_AppendResult (interp,
argv[0],
": recursive load or include of '",
argv[2],
"' from '",
r->filename,
"'",
(char *) NULL);
if (rr != NULL) destroy_sub_req (rr);
return TCL_ERROR;
}
if (include) {
if (run_sub_req (rr)) {
Tcl_AppendResult (interp,
argv[0],
": unable to process include '",
argv[2],
"' from '",
r->filename,
"'",
(char *) NULL);
if (rr != NULL) destroy_sub_req (rr);
return TCL_ERROR;
}
} else {
if (load_sub_req (slaveInterp, rr)) {
Tcl_AppendResult (interp,
argv[0],
": unable to load '",
argv[2],
"' from '",
r->filename,
"'",
Tcl_GetVar(slaveInterp, "errorInfo", TCL_GLOBAL_ONLY),
"
",
(char *) NULL);
if (rr != NULL) destroy_sub_req (rr);
return TCL_ERROR;
}
}
if (rr != NULL) destroy_sub_req (rr);
return TCL_OK;
}
/*-----------------------------------------------------------------------------
* Neo_FlushBufferCmd --
* Implements the flush_page TCL command:
* flush_page
*
* Results:
* Standard TCL result.
*-----------------------------------------------------------------------------
*/
int
Neo_FlushBufferCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
if (argc != 1) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
"\"", (char *) NULL);
return TCL_ERROR;
}
bflush (Tcl_request_rec->connection->client);
return TCL_OK;
}
/*-----------------------------------------------------------------------------
* Neo_AbortPageCmd --
* Implements the abort_page TCL command:
* abort_page
*
* Results:
* Standard TCL result.
*-----------------------------------------------------------------------------
*/
int
Neo_AbortPageCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
if (argc != 1) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
"\"", (char *) NULL);
return TCL_ERROR;
}
bflush (Tcl_request_rec->connection->client);
/*-------------
* removed from http_main.c in 1.2b8 - Eugene
*
abort_connection(Tcl_request_rec->connection);
*-------------
*/
Tcl_request_rec->connection->aborted = 1;
return TCL_OK;
}
/*-----------------------------------------------------------------------------
* Neo_UnescapeUrlCmd --
* Implements the unescape_url TCL command:
* unescape_url pathname
*
* Results:
* Standard TCL result.
*-----------------------------------------------------------------------------
*/
int
Neo_UnescapeUrlCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
if (argc != 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" url\"", (char *) NULL);
return TCL_ERROR;
}
if (unescape_url(argv[1]) == OK) {
interp->result = argv[1];
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* Neo_RequestInfoCmd --
* Implements the Neo TCL request_info command:
* request_info args
*
* Results:
* Standard TCL results.
*
*-----------------------------------------------------------------------------
*/
int
Neo_RequestInfoCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
char intbuf[20];
char *varName;
int depth;
request_rec *r = Tcl_request_rec;
if (argc != 2 && argc != 4) {
Tcl_AppendResult (interp,
"wrong # args: should be \"",
argv[0],
" [next|prev|main depth] arrayVarName\"",
(char *) NULL);
return TCL_ERROR;
}
if (argc == 2) {
varName = argv[1];
} else {
varName = argv[3];
strcpy(interp->result,"0");
if (Tcl_GetInt (interp, argv[2], &depth) == TCL_ERROR)
return TCL_ERROR;
if (strcmp(argv[1], "prev") == 0) {
while (depth--) {
if (r->prev) {
r = r->prev;
} else {
return TCL_OK;
}
}
} else if (strcmp(argv[1], "next") == 0) {
while (depth--) {
if (r->next) {
r = r->next;
} else {
return TCL_OK;
}
}
} else if (strcmp(argv[1], "main") == 0) {
while (depth--) {
if (r->main) {
r = r->main;
} else {
return TCL_OK;
}
}
} else {
Tcl_AppendResult (interp,
"bad arg: should be \"",
argv[0],
" [next|prev|main depth] arrayVarName\"",
(char *) NULL);
return TCL_ERROR;
}
}
#define REQUEST_CHAR_VAR(X) if (Tcl_SetVar2(interp, varName, #X, r->X == (char *)NULL ? "" : r->X, TCL_LEAVE_ERR_MSG) == NULL) return TCL_ERROR
#define REQUEST_INT_VAR(X) {sprintf(intbuf, "%d", r->X); if (Tcl_SetVar2(interp, varName, #X, intbuf, TCL_LEAVE_ERR_MSG) == NULL) return TCL_ERROR;}
REQUEST_CHAR_VAR(the_request);
REQUEST_INT_VAR(assbackwards);
REQUEST_INT_VAR(header_only);
REQUEST_CHAR_VAR(protocol);
REQUEST_CHAR_VAR(status_line);
REQUEST_INT_VAR(status);
REQUEST_CHAR_VAR(method);
REQUEST_INT_VAR(method_number);
REQUEST_INT_VAR(bytes_sent);
REQUEST_CHAR_VAR(content_type);
REQUEST_CHAR_VAR(content_encoding);
REQUEST_CHAR_VAR(content_language);
REQUEST_INT_VAR(no_cache);
REQUEST_CHAR_VAR(uri);
REQUEST_CHAR_VAR(filename);
REQUEST_CHAR_VAR(path_info);
REQUEST_CHAR_VAR(args);
if (Tcl_SetVar2 (interp, varName, "main", r->main ? "1" : "0", TCL_LEAVE_ERR_MSG) == NULL) return TCL_ERROR;
if (Tcl_SetVar2 (interp, varName, "prev", r->prev ? "1" : "0", TCL_LEAVE_ERR_MSG) == NULL) return TCL_ERROR;
if (Tcl_SetVar2 (interp, varName, "next", r->next ? "1" : "0", TCL_LEAVE_ERR_MSG) == NULL) return TCL_ERROR;
sprintf(intbuf, "%d", r->finfo.st_uid);
if (Tcl_SetVar2 (interp, varName, "file_uid", intbuf, TCL_LEAVE_ERR_MSG) == NULL) return TCL_ERROR;
interp->result = "1";
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* Neo_SetHeaderCmd --
* Implements the NeoWebScript set_header command:
* set_header header-name header-value ...
*
* Results:
* Standard TCL results.
*
*-----------------------------------------------------------------------------
*/
int
Tcl_SetHeaderCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
int i;
table *h = Tcl_request_rec->headers_out;
if (argc % 2 != 1) {
sprintf(interp->result, "usage: %s header value ...", argv[0]);
return TCL_ERROR;
}
for (i = 1; i < argc; i += 2)
table_set(h, argv[i], argv[i+1]);
return TCL_OK;
}
int
NWS_MD5Cmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
char *digest;
if (argc != 2) {
sprintf(interp->result, "usage: md5 string");
return TCL_ERROR;
}
digest = md5(Tcl_request_rec->pool, argv[1]);
Tcl_SetResult(interp, digest, TCL_STATIC);
return TCL_OK;
}
int
Tcl_gm_timestr_822Cmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
char *ascii_time;
int clock;
if (argc != 2) {
sprintf(interp->result, "usage: gm_timestr_822 time");
return TCL_ERROR;
}
if (Tcl_GetInt(interp, argv[1], &clock) == TCL_ERROR)
return TCL_ERROR;
ascii_time = gm_timestr_822(Tcl_request_rec->pool, clock);
Tcl_SetResult(interp, ascii_time, TCL_STATIC);
return TCL_OK;
}
int
Tcl_ExtendSafeSlaveCmd(dummy, interp, argc, argv)
ClientData dummy; /* Not used. */
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
Tcl_Interp *slaveInterp;
if (argc != 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" slaveInterpName\"", (char *)NULL);
return TCL_ERROR;
}
slaveInterp = Tcl_GetSlave(interp, argv[1]);
if (slaveInterp == (Tcl_Interp *)NULL) {
return TCL_ERROR;
}
/* Do this in the setup using load so that unsafe version can be used */
/*
if (Tclx_SafeInit (slaveInterp) == TCL_ERROR) {
return TCL_ERROR;
}
*/
Tcl_CreateCommand (slaveInterp, "www_request_info",
Neo_RequestInfoCmd,
(ClientData) NULL, (void (*)()) NULL);
return TCL_OK;
}
/*-----------------------------------------------------------------------------
* Neo_SimplifyPathnameCmd --
* Implements the simplify_pathname TCL command:
* simplify_pathname pathname
*
* Results:
* Standard TCL result.
*-----------------------------------------------------------------------------
*/
int
Neo_SimplifyPathnameCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
if (argc != 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" pathname\"", (char *) NULL);
return TCL_ERROR;
}
getparents(argv[1]);
interp->result = argv[1];
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Tcl_HtmlCmd --
*
* This procedure is invoked to process the "html" Tcl command.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
int
Tcl_HtmlCmd(dummy, interp, argc, argv)
ClientData dummy; /* Not used. */
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
int argIndex;
if (argc < 2 || argc > 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" text [tag]\"", (char *)NULL);
return TCL_ERROR;
}
if (argc == 2) {
rprintf (Tcl_request_rec, "%s", argv[1]);
} else {
rprintf (Tcl_request_rec, "<%s>%s%s>", argv[2], argv[1], argv[2]);
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* Neo_DigitToHex
* Helper function to convert a number 0 - 15 into the equivalent hex
* character.
*
* Results:
* The integer, or -1 if an illegal hex character is encountered.
*
*-----------------------------------------------------------------------------
*/
int
Neo_DigitToHex(int c) {
if (c < 10) {
return c + '0';
}
return c - 10 + 'a';
}
/*
*-----------------------------------------------------------------------------
*
* Neo_EscapeStringCmd --
* Implements the Neo TCL escape_string command:
* escape_string string
*
* Results:
* Standard TCL results.
*
*-----------------------------------------------------------------------------
*/
int
Neo_EscapeStringCmd (clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
char *origString, *newString, *origStringP, *newStringP, *checkP;
int origLength;
if (argc != 2) {
Tcl_AppendResult (interp,
"wrong # args: should be \"",
argv[0],
" string",
(char *) NULL);
return TCL_ERROR;
}
origString = argv[1];
/* if they sent us an empty string, we're done */
origLength = strlen (origString);
if (origLength == 0) {
return TCL_OK;
}
newString = ckalloc (strlen (origString) * 3 + 1);
/* for all the characters in the source string */
for (origStringP = origString, newStringP = newString;
*origStringP != '\0';
origStringP++) {
char c = *origStringP;
if (isalnum (c)) {
*newStringP++ = c;
} else {
*newStringP++ = '%';
*newStringP++ = Neo_DigitToHex((c >> 4) & 0x0f);
*newStringP++ = Neo_DigitToHex(c & 0x0f);
}
}
/* Don't forget to null-terminate the target string */
*newStringP = '\0';
Tcl_SetResult (interp, newString, TCL_DYNAMIC);
return TCL_OK;
}
/* ----------------------- Initialization function ------------------------- */
void init_neoscript (server_rec *s, pool *p)
{
time_t date;
table *t;
table_entry *elts;
int i;
neoscript_server_config *ns = (neoscript_server_config *)get_module_config(s->module_config ,&neoscript_module);
if (interp) {
Tcl_DeleteInterp(interp);
}
interp = Tcl_CreateInterp();
/* get the current time (startup time)
* so we can return server uptime info */
time(&date);
sprintf(softwareStartTimeString, "%ld", date);
/* Initialize core Tcl components and extensions */
/* Tcl */
if (Tcl_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript tcl component: %s\n", interp->result);
exit(1);
}
/* Extended Tcl */
if (Tclx_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript tclx component: %s\n", interp->result);
exit(1);
}
Tcl_StaticPackage (interp, "Tclx", Tclx_Init, Tclx_SafeInit);
/* Tclx does its own call to Tcl_StaticPackage */
#ifdef MIT_OTCL
/* MIT Object Tcl */
if (Otcl_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript otcl component: %s\n", interp->result);
exit(1);
}
Tcl_StaticPackage (interp, "Otcl", Otcl_Init, Otcl_Init);
#endif
#ifdef POSTGRESQL
/* PostgreSQL (v6.0+) Database Interface */
if (Pgtcl_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript postgres component: %s\n", interp->result);
exit(1);
}
Tcl_StaticPackage (interp, "Pgtcl", Pgtcl_Init, Pgtcl_SafeInit);
#endif
#ifdef POSTGRES95
/* Postgres95 Database Interface */
if (Pg_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript postgres component: %s\n", interp->result);
exit(1);
}
Tcl_StaticPackage (interp, "Pg", Pg_Init, Pg_Init);
#endif
#ifdef PQATCL
/* Another Postgres95 Database Interface */
if (Pqa_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript postgres-pqa component: %s\n", interp->result);
exit(1);
}
Tcl_StaticPackage(interp, "Pqa", Pqa_Init, Pqa_Init);
#endif
#ifdef GDTCL
/* GIF generation*/
if (Gd_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript gd component: %s\n", interp->result);
exit(1);
}
Tcl_StaticPackage(interp, "Gd", Gd_Init, Gd_Init);
#endif
/* NeoSoft Extensions */
if (Neo_Init (interp) == TCL_ERROR) {
fprintf(stderr, "failed to init NeoWebScript neo component: %s\n", interp->result);
exit(1);
}
Tcl_StaticPackage (interp, "Neo", Neo_Init, NULL);
/*
* copy any variables defined with neoscript server config commands
* into a Tcl array
*/
t = ns->neowebscript_server_vars;
elts = (table_entry *)t->elts;
for (i = 0; i < t->nelts; ++i)
Tcl_SetVar2 (interp, "NeoWebServerConf", elts[i].key, elts[i].val, TCL_GLOBAL_ONLY);
/* Tcl_InitMath (interp); */
Tcl_InitExtensions (interp);
Tcl_SetVar2 (interp, "server", "SERVER_ROOT", server_root, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "server", "SERVER_CONF", server_confname, TCL_GLOBAL_ONLY);
if (Tcl_VarEval(interp, "source ", server_root, "/conf/init.tcl", (char *)NULL) == TCL_ERROR) {
char *errorInfo;
errorInfo = Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY);
fprintf(stderr,"NeoWebScript startup failed: %s\n", errorInfo);
exit(1);
}
}
void
Tcl_InitExtensions (Tcl_Interp *interp) {
Tcl_CreateCommand(interp, "SAFE_include_file", Neo_IncludeCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand(interp, "SAFE_include_virtual", Neo_IncludeCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand(interp, "SAFE_load_file", Neo_IncludeCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand(interp, "SAFE_load_virtual", Neo_IncludeCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand(interp, "flush_page", Neo_FlushBufferCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand(interp, "abort_page", Neo_AbortPageCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand(interp, "extend_slave", Tcl_ExtendSafeSlaveCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand(interp, "html", Tcl_HtmlCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateCommand (interp, "www_unescape_string", Neo_UnescapeStringCmd,
(ClientData) NULL, (void (*)()) NULL);
Tcl_CreateCommand (interp, "www_escape_string",
Neo_EscapeStringCmd,
(ClientData) NULL, (void (*)()) NULL);
Tcl_CreateCommand (interp, "www_simplify_pathname",
Neo_SimplifyPathnameCmd,
(ClientData) NULL, (void (*)()) NULL);
Tcl_CreateCommand (interp, "www_unescape_url",
Neo_UnescapeUrlCmd,
(ClientData) NULL, (void (*)()) NULL);
Tcl_CreateCommand (interp, "www_request_info",
Neo_RequestInfoCmd,
(ClientData) NULL, (void (*)()) NULL);
Tcl_CreateCommand (interp, "set_header",
Tcl_SetHeaderCmd,
(ClientData) NULL, (void (*)()) NULL);
Tcl_CreateCommand (interp, "md5",
NWS_MD5Cmd,
(ClientData) NULL, (void (*)()) NULL);
Tcl_CreateCommand (interp, "gm_timestr_822",
Tcl_gm_timestr_822Cmd,
(ClientData) NULL, (void (*)()) NULL);
}
/* ------------------------ Environment function -------------------------- */
void add_include_vars(request_rec *r, char *timefmt)
{
struct passwd *pw;
table *e = r->subprocess_env;
char *t;
time_t date = time(NULL);
table_set(e, "DATE_LOCAL", ht_time(r->pool, date, timefmt, 0));
table_set(e, "DATE_GMT", ht_time(r->pool, date, timefmt, 1));
table_set(e, "LAST_MODIFIED",ht_time(r->pool,r->finfo.st_mtime,timefmt,0));
table_set(e, "DOCUMENT_URI", r->uri);
table_set(e, "DOCUMENT_PATH_INFO", r->path_info);
pw = getpwuid(r->finfo.st_uid);
if (pw) {
table_set(e, "USER_NAME", pw->pw_name);
} else {
char uid[16];
sprintf(uid, "user#%ld", (unsigned long)r->finfo.st_uid);
table_set(e, "USER_NAME", uid);
}
if((t = strrchr(r->filename, '/')))
table_set (e, "DOCUMENT_NAME", ++t);
else
table_set (e, "DOCUMENT_NAME", r->uri);
if (r->args) {
char *tmp = pstrdup(r->pool, r->args);
unescape_url(tmp);
table_set (e, "QUERY_STRING_UNESCAPED",
escape_shell_cmd (r->pool, tmp));
}
}
/* --------------------------- Parser functions --------------------------- */
static char *http2env(pool *a, char *w)
{
char *res = pstrcat (a, "HTTP_", w, NULL);
char *cp = res;
while (*++cp)
if (*cp == '-') *cp = '_';
else *cp = toupper(*cp);
return res;
}
#define OUTBUFSIZE 4096
/* PUT_CHAR and FLUSH_BUF currently only work within the scope of
* find_string(); they are hacks to avoid calling rputc for each and
* every character output. A common set of buffering calls for this
* type of output SHOULD be implemented.
*/
#define PUT_CHAR(c,r) \
{ \
outbuf[outind++] = c; \
if (outind == OUTBUFSIZE) { FLUSH_BUF(r) }; \
}
/* there SHOULD be some error checking on the return value of
* rwrite, however it is unclear what the API for rwrite returning
* errors is and little can really be done to help the error in
* any case.
*/
#define FLUSH_BUF(r) \
{ \
rwrite(outbuf, outind, r); \
outind = 0; \
}
/*
* f: file handle being read from
* c: character to read into
* ret: return value to use if input fails
* r: current request_rec
*
* This macro is redefined after find_string() for historical reasons
* to avoid too many code changes. This is one of the many things
* that should be fixed.
*/
#define GET_CHAR(f,c,ret,r) \
{ \
int i = getc(f); \
if(i == EOF) { /* either EOF or error -- needs error handling if latter */ \
if (ferror(f)) \
fprintf(stderr, "encountered error in GET_CHAR macro, mod_include.\n"); \
FLUSH_BUF(r); \
pfclose(r->pool,f); \
return ret; \
} \
c = (char)i; \
}
int find_string(FILE *in,char *str, request_rec *r, int printing) {
int x,l=strlen(str),p;
char outbuf[OUTBUFSIZE];
int outind = 0;
char c;
p=0;
while(1) {
GET_CHAR(in,c,1,r);
if(c == str[p]) {
if((++p) == l) {
FLUSH_BUF(r);
return 0;
}
}
else {
if (printing) {
for(x=0;x
p1) ? p2 : p1;
str = (p2 > p1) ? str2 : str1;
if (printing) {
for(x=0;x
pool);
m1 = m2 = 0;
if(c == str1[p1]) {
++m1;
if((++p1) == l1) {
*result = 1;
return 0;
}
}
if(c == str2[p2]) {
++m2;
if((++p2) == l2) {
*result = 2;
return 0;
}
}
if(!m1 && !m2) {
p = (p2 > p1) ? p2 : p1;
str = (p2 > p1) ? str2 : str1;
if(r) {
if(p) {
for(x=0;x
= 11 && val <= 31) ||
(val >= 127 && val <= 160) || val >= 256)
p--; /* no data to output */
else
*p = val;
} else{
j = i-1;
if (i-1 > MAXENTLEN || entlist[i-1] == NULL) { /* wrong length */
*p = '&';
continue; /* skip it */
}
for (ents=entlist[i-1]; *ents != '\0'; ents += i)
if (strncmp(s+1, ents, i-1) == 0) break;
if (*ents == '\0')
*p = '&'; /* unknown */
else {
*p = ((const unsigned char *)ents)[i-1];
s += i;
}
}
}
*p = '\0';
}
/*
* extract the next tag name and value.
* if there are no more tags, set the tag name to 'done'
* the tag value is html decoded if dodecode is non-zero
*/
static char *
get_tag(pool *p, FILE *in, char *tag, int tagbuf_len, int dodecode) {
char *t = tag, *tag_val, c, term;
int n;
n = 0;
do { /* skip whitespace */
GET_CHAR(in,c,NULL,p);
} while (isspace(c));
/* tags can't start with - */
if(c == '-') {
GET_CHAR(in,c,NULL,p);
if(c == '-') {
do {
GET_CHAR(in,c,NULL,p);
} while (isspace(c));
if(c == '>') {
strncpy(tag,"done", tagbuf_len-1);
tag[tagbuf_len-1] = '\0';
return tag;
}
}
return NULL; /* failed */
}
/* find end of tag name */
while(1) {
if(++n == tagbuf_len) {
t[tagbuf_len - 1] = '\0';
return NULL;
}
if(c == '=' || isspace(c)) break;
*(t++) = tolower(c);
GET_CHAR(in,c,NULL,p);
}
*t++ = '\0';
tag_val = t;
while (isspace(c)) GET_CHAR(in, c, NULL,p); /* space before = */
if (c != '=') {
ungetc(c, in);
return NULL;
}
do {
GET_CHAR(in,c,NULL,p); /* space after = */
} while (isspace(c));
/* we should allow a 'name' as a value */
if (c != '"' && c != '\'') return NULL;
term = c;
while(1) {
GET_CHAR(in,c,NULL,p);
if(++n == tagbuf_len) {
t[tagbuf_len - 1] = '\0';
return NULL;
}
/* Want to accept \" as a valid character within a string. */
if (c == '\\') {
*(t++) = c; /* Add backslash */
GET_CHAR(in,c,NULL,p);
if (c == term) /* Only if */
*(--t) = c; /* Replace backslash ONLY for terminator */
} else if (c == term) break;
*(t++) = c;
}
*t = '\0';
if (dodecode) decodehtml(tag_val);
return pstrdup (p, tag_val);
}
static char *
get_nws_code(pool *p, FILE *in, char *dummy, int codebuf_len, char *sequence) {
char *t = dummy, *code_val, c;
int n, l=(strlen(sequence)-1), cp;
n = 0;
do { /* skip whitespace */
GET_CHAR(in,c,NULL,p);
} while (isspace(c));
cp=0;
code_val = t;
while(1) {
if(++n == codebuf_len) {
t[codebuf_len - 1] = '\0';
return NULL;
}
if(c == sequence[cp]) {
*(t++) = c;
if(cp == l)
break;
else
cp++;
GET_CHAR(in,c,NULL,p);
continue;
}
else if(cp > 0)
cp=0;
*(t++) = c;
GET_CHAR(in,c,NULL,p);
}
for(n=0;n<=l;n++) {
t--;
*t = '\0';
}
return pstrdup (p, code_val);
}
static int
get_directive(FILE *in, char *d, pool *p) {
char c;
/* skip initial whitespace */
while(1) {
GET_CHAR(in,c,1,p);
if(!isspace(c))
break;
}
/* now get directive */
while(1) {
*d++ = tolower(c);
GET_CHAR(in,c,1,p);
if(isspace(c))
break;
}
*d = '\0';
return 0;
}
/*
* Do variable substitution on strings
*/
void parse_string(request_rec *r, char *in, char *out, int length,
int leave_name)
{
char ch;
char *next = out;
int numchars = 0;
while ((ch = *in++) != '\0') {
switch(ch) {
case '\\':
if(*in == '$')
*next++=*in++;
else
*next++=ch;
break;
case '$':
{
char var[MAX_STRING_LEN];
char vtext[MAX_STRING_LEN];
char *val;
int braces=0;
int vlen, vtlen;
/*
* Keep the $ and { around because we do no substitution
* if the variable isn't found
*/
vlen = vtlen = 0;
vtext[vtlen++] = ch;
if (*in == '{') { braces = 1; vtext[vtlen++] = *in++; }
while (*in != '\0') {
if (vlen == (MAX_STRING_LEN - 1)) continue;
if (braces == 1) {
if (*in == '}') break;
}
else if (! (isalpha((int)*in) || (*in == '_') || isdigit((int)*in)) ) break;
if (vtlen < (MAX_STRING_LEN - 1)) vtext[vtlen++] = *in;
var[vlen++] = *in++;
}
var[vlen] = vtext[vtlen] = '\0';
if (braces == 1) {
if (*in != '}') {
log_printf(r->server, "Invalid variable %s%s", vtext,in);
*next = '\0';
return;
} else
in++;
}
val = (char *)NULL;
if (var[0] == '\0') {
val = &vtext[0];
} else {
val = table_get (r->subprocess_env, &var[0]);
if (!val && leave_name)
val = &vtext[0];
}
while ((val != (char *)NULL) && (*val != '\0')) {
*next++ = *val++;
if (++numchars == (length -1)) break;
}
break;
}
default:
*next++ = ch;
break;
}
if (++numchars == (length -1)) break;
}
*next = '\0';
return;
}
/* ------------------------ Environment function -------------------------- */
void propagate_vars_to_neoscript(Tcl_Interp *interp, request_rec *r)
{
table *e = r->subprocess_env;
server_rec *s = r->server;
conn_rec *c = r->connection;
char *t;
char timeTextBuf[16];
char port[40];
array_header *hdrs_arr = table_elts (r->headers_in);
table_entry *hdrs = (table_entry *)hdrs_arr->elts;
int i;
/* First, add environment vars from headers... this is as per
* CGI specs, though other sorts of scripting interfaces see
* the same vars...
*/
Tcl_UnsetVar (interp, "webenv", TCL_GLOBAL_ONLY);
for (i = 0; i < hdrs_arr->nelts; ++i) {
if (!hdrs[i].key) continue;
/* A few headers are special cased --- Authorization to prevent
* rogue scripts from capturing passwords; content-type and -length
* for no particular reason.
*/
if (!strcasecmp (hdrs[i].key, "Content-type"))
Tcl_SetVar2 (interp, "webenv", "CONTENT_TYPE", hdrs[i].val, TCL_GLOBAL_ONLY);
else if (!strcasecmp (hdrs[i].key, "Content-length"))
Tcl_SetVar2 (interp, "webenv", "CONTENT_LENGTH", hdrs[i].val, TCL_GLOBAL_ONLY);
else if (!strcasecmp (hdrs[i].key, "Authorization"))
continue;
else
Tcl_SetVar2 (interp, "webenv", http2env (r->pool, hdrs[i].key), hdrs[i].val, TCL_GLOBAL_ONLY);
}
Tcl_SetVar2 (interp, "webenv", "SERVER_SOFTWARE", SERVER_VERSION, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "SERVER_ADMIN", s->server_admin, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "SERVER_NAME", s->server_hostname, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "NEOSCRIPT_VERSION", NEOSCRIPT_VERSION, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "NEO_SOFTWARE_START", softwareStartTimeString, TCL_GLOBAL_ONLY);
sprintf(port, "%d", s->port);
Tcl_SetVar2 (interp, "webenv", "SERVER_PORT", port, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "SERVER_ROOT", server_root, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "REMOTE_HOST",
(char *)get_remote_host(r->connection, r->per_dir_config,
REMOTE_NAME),
TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "REMOTE_ADDR", c->remote_ip, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "DOCUMENT_ROOT", document_root(r), TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "SCRIPT_FILENAME", r->filename, TCL_GLOBAL_ONLY);
if (c->user) Tcl_SetVar2(interp, "webenv", "REMOTE_USER", c->user, TCL_GLOBAL_ONLY);
if (c->auth_type) Tcl_SetVar2(interp, "webenv", "AUTH_TYPE", c->auth_type, TCL_GLOBAL_ONLY);
if (c->remote_logname) Tcl_SetVar2(interp, "webenv", "REMOTE_IDENT", c->remote_logname, TCL_GLOBAL_ONLY);
/* Apache custom error responses. If we have redirected set two new vars */
if (r->prev) {
if (r->prev->args) Tcl_SetVar2(interp, "webenv","REDIRECT_QUERY_STRING", r->prev->args, TCL_GLOBAL_ONLY);
if (r->prev->uri) Tcl_SetVar2 (interp, "webenv", "REDIRECT_URL", r->prev->uri, TCL_GLOBAL_ONLY);
}
/* these four are normally for CGI's */
Tcl_SetVar2 (interp, "webenv", "GATEWAY_INTERFACE", "CGI/1.1", TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "SERVER_PROTOCOL", r->protocol, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "REQUEST_METHOD", r->method, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "DOCUMENT_URI", r->uri, TCL_GLOBAL_ONLY);
if((t = strrchr(r->filename, '/')))
Tcl_SetVar2 (interp, "webenv", "DOCUMENT_NAME", ++t, TCL_GLOBAL_ONLY);
else
Tcl_SetVar2 (interp, "webenv", "DOCUMENT_NAME", r->uri, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "DOCUMENT_PATH_INFO", r->path_info ? r->path_info : "", TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "QUERY_STRING", r->args ? r->args : "", TCL_GLOBAL_ONLY);
sprintf (timeTextBuf, "%ld", r->finfo.st_mtime);
Tcl_SetVar2 (interp, "webenv", "NEO_LAST_MODIFIED", timeTextBuf, TCL_GLOBAL_ONLY);
sprintf (timeTextBuf, "%ld", r->finfo.st_uid);
Tcl_SetVar2 (interp, "webenv", "NEO_DOCUMENT_UID", timeTextBuf, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "NEO_TIME_FORMAT", DEFAULT_TIME_FORMAT, TCL_GLOBAL_ONLY);
}
/* --------------------------- Action handlers ---------------------------- */
int run_pickfile_req (request_rec *r)
{
int errstatus;
FILE *f;
char buf[IOBUFSIZE];
int nLines;
int whichLine;
if (r->method_number != M_GET) return DECLINED;
if (r->finfo.st_mode == 0 || (r->path_info && *r->path_info)) {
log_reason("File does not exist", r->filename, r);
return NOT_FOUND;
}
if ((errstatus = set_content_length (r, r->finfo.st_size))
|| (errstatus = set_last_modified (r, r->finfo.st_mtime)))
return errstatus;
f = fopen (r->filename, "r");
if (f == NULL) {
log_reason("file permissions deny server access", r->filename, r);
return FORBIDDEN;
}
if ((fgets(buf,IOBUFSIZE,f)) == NULL) {
if (errno != EINTR) goto bye;
}
nLines = atoi(buf);
if (nLines <= 0) {fclose (f); return OK;}
soft_timeout ("send-pick", r);
srand((int)(getpid() * 17 + time((long *) 0)));
for (whichLine = rand() % nLines; whichLine-- >= 0; ) {
if ((fgets(buf,IOBUFSIZE,f)) == NULL) {
if (errno != EINTR) break;
}
}
rprintf (r, "%s", buf);
bye:
fclose (f);
return OK;
}
int include_cgi(char *s, request_rec *r)
{
request_rec *rr = sub_req_lookup_uri (s, r);
if (rr->status != 200) return -1;
/* No hardwired path info or query allowed */
if ((rr->path_info && rr->path_info[0]) || rr->args) return -1;
if (rr->finfo.st_mode == 0) return -1;
/* Script gets parameters of the *document*, for back compatibility */
rr->path_info = r->path_info; /* painful 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.
*/
rr->content_type = CGI_MAGIC_TYPE;
/* Run it. */
if (run_sub_req (rr) == REDIRECT) {
char *location = table_get (rr->headers_out, "Location");
location = escape_html(rr->pool, location);
rvputs(r,"", location, "", NULL);
}
destroy_sub_req (rr);
return 0;
}
int handle_include(FILE *in, request_rec *r, char *error, int noexec) {
char tag[MAX_STRING_LEN];
char parsed_string[MAX_STRING_LEN];
char *tag_val;
while(1) {
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
return 1;
if(!strcmp(tag,"file") || !strcmp (tag, "virtual")) {
request_rec *rr=NULL;
char *error_fmt = NULL;
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
if (tag[0] == 'f')
{ /* be safe; only files in this directory or below allowed */
char tmp[MAX_STRING_LEN+2];
ap_snprintf(tmp, sizeof(tmp), "/%s/", parsed_string);
if (parsed_string[0] == '/' || strstr(tmp, "/../") != NULL)
error_fmt = "unable to include file %s in parsed file %s";
else
rr = sub_req_lookup_file (parsed_string, r);
} else
rr = sub_req_lookup_uri (parsed_string, r);
if (!error_fmt && rr->status != 200)
error_fmt = "unable to include %s in parsed file %s";
if (!error_fmt && noexec && rr->content_type
&& (strncmp (rr->content_type, "text/", 5)))
error_fmt =
"unable to include potential exec %s in parsed file %s";
if (error_fmt == NULL)
{
request_rec *p;
for (p=r; p != NULL; p=p->main)
if (strcmp(p->filename, rr->filename) == 0) break;
if (p != NULL)
error_fmt = "Recursive include of %s in parsed file %s";
}
if (!error_fmt && run_sub_req (rr))
error_fmt = "unable to include %s in parsed file %s";
if (error_fmt) {
log_printf(r->server, error_fmt, tag_val, r->filename);
rprintf(r,"%s",error);
}
if (rr != NULL) destroy_sub_req (rr);
}
else if(!strcmp(tag,"pickfile") || !strcmp (tag, "pickvirtual")) {
request_rec *rr=NULL;
char *error_fmt = NULL;
if (tag[4] == 'f')
{ /* be safe; only files in this directory or below allowed */
char tmp[MAX_STRING_LEN+2];
sprintf(tmp, "/%s/", tag_val);
if (tag_val[0] == '/' || strstr(tmp, "/../") != NULL)
error_fmt = "unable to include file %s in parsed file %s";
else
rr = sub_req_lookup_file (tag_val, r);
} else
rr = sub_req_lookup_uri (tag_val, r);
if (!error_fmt && rr->status != 200)
error_fmt = "unable to include %s in parsed file %s";
if (!error_fmt && noexec && rr->content_type
&& (strncmp (rr->content_type, "text/", 5)))
error_fmt =
"unable to include potential exec %s in parsed file %s";
if (!error_fmt && run_pickfile_req (rr))
error_fmt = "unable to pick from %s in parsed file %s";
if (error_fmt) {
log_printf(r->server, error_fmt, tag_val, r->filename);
rputs(error, r);
}
if (rr != NULL) destroy_sub_req (rr);
}
else if(!strcmp(tag,"counter")) {
extern int errno;
DBT key;
DBT data;
char newNumberText[16];
int count;
char *error_fmt = NULL;
/* char *dbfname = server_root_relative(r->pool, r->server->url_counter_db); */
char *dbfname = server_root_relative(r->pool, URL_ACCESS_COUNTER_DB);
DB *db = dbopen(dbfname, (O_CREAT|O_RDWR|DB_LOCK), 0644, DB_HASH, (void *)NULL);
if (db == (DB *)NULL) {
error_fmt = "[%s: %s]";
log_printf (r->server, error_fmt, dbfname, strerror(errno));
/* rprintf(r, "%s", error); */
rprintf(r, error_fmt, dbfname, strerror(errno));
goto countfail;
}
key.data = r->filename;
key.size = strlen(r->filename) + 1;
if (db->get(db, &key, &data, 0) != 0) {
count = 0;
} else {
count = atoi(data.data);
}
if (isdigit(tag_val[0])) {
int defcount = atoi(tag_val);
if (count < defcount)
count = defcount;
}
sprintf(newNumberText, "%d", ++count);
data.data = newNumberText;
data.size = strlen(newNumberText) + 1;
if (db->put(db, &key, &data, 0) != 0) {
db->close(db);
log_printf(r->server, "dbput failed! %s in %s, errno=%d",key,data,errno);
rprintf(r,"%s",error);
}
if (db->sync(db, 0) < 0) {
log_printf (r->server, "dbsync failed! errno=%d", errno);
rprintf(r,"%s",error);
}
if (db->close(db) < 0) {
log_printf (r->server, "dbclose failed! errno=%d", errno);
rprintf(r,"%s",error);
}
rprintf (r, "%s", newNumberText);
countfail: ;
}
else if(!strcmp(tag,"done"))
return 0;
else {
log_printf(r->server, "unknown parameter '%s' to tag 'include' in %s",
tag, r->filename);
rputs(error, r);
}
}
}
typedef struct {
request_rec *r;
char *s;
} include_cmd_arg;
void include_cmd_child (void *arg)
{
request_rec *r = ((include_cmd_arg *)arg)->r;
char *s = ((include_cmd_arg *)arg)->s;
table *env = r->subprocess_env;
#ifdef DEBUG_NEOINCLUDE_CMD
#ifdef __EMX__
/* under OS/2 /dev/tty is referenced as con */
FILE *dbg = fopen ("con", "w");
#else
FILE *dbg = fopen ("/dev/tty", "w");
#endif
#endif
char err_string [MAX_STRING_LEN];
#ifdef DEBUG_NEOINCLUDE_CMD
fprintf (dbg, "Attempting to neoinclude command '%s'\n", s);
#endif
if (r->path_info && r->path_info[0] != '\0')
{
request_rec *pa_req;
table_set (env, "PATH_INFO", escape_shell_cmd (r->pool, r->path_info));
pa_req = sub_req_lookup_uri(escape_uri(r->pool, r->path_info), r);
if (pa_req->filename)
table_set(env, "PATH_TRANSLATED",
pstrcat(r->pool, pa_req->filename, pa_req->path_info,
NULL));
}
if (r->args) {
table_set (env, "QUERY_STRING", r->args);
unescape_url(r->args);
table_set (env, "QUERY_STRING_UNESCAPED",
escape_shell_cmd (r->pool, r->args));
}
error_log2stderr (r->server);
#ifdef DEBUG_NEOINCLUDE_CMD
fprintf (dbg, "Attempting to exec '%s'\n", s);
#endif
cleanup_for_exec();
/* set shellcmd flag to pass arg to SHELL_PATH */
call_exec(r, s, create_environment (r->pool, env), 1);
/* Oh, drat. We're still here. The log file descriptors are closed,
* so we have to whimper a complaint onto stderr...
*/
#ifdef DEBUG_NEOINCLUDE_CMD
fprintf (dbg, "Exec failed\n");
#endif
ap_snprintf(err_string, sizeof(err_string),
"httpd: exec of %s failed, errno is %d\n",
SHELL_PATH,errno);
write (2, err_string, strlen(err_string));
exit(0);
}
int include_cmd(char *s, request_rec *r) {
include_cmd_arg arg;
FILE *f;
arg.r = r; arg.s = s;
if (!spawn_child (r->connection->pool, include_cmd_child, &arg,
kill_after_timeout, NULL, &f))
return -1;
send_fd(f,r);
pfclose(r->pool, f); /* will wait for zombie when
* r->pool is cleared
*/
return 0;
}
int handle_exec(FILE *in, request_rec *r, char *error)
{
char tag[MAX_STRING_LEN];
char *tag_val;
char *file = r->filename;
char parsed_string[MAX_STRING_LEN];
while(1) {
if(!(tag_val = get_tag (r->pool, in, tag, MAX_STRING_LEN, 1)))
return 1;
if(!strcmp(tag,"cmd")) {
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 1);
if(include_cmd(parsed_string, r) == -1) {
log_printf(r->server, "failed command exec %s in %s",
tag_val, file);
rputs(error, r);
}
/* just in case some stooge changed directories */
chdir_file(r->filename);
}
else if(!strcmp(tag,"cgi")) {
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
if(include_cgi(parsed_string, r) == -1) {
log_printf(r->server, "invalid CGI ref %s in %s",tag_val,file);
rputs(error, r);
}
/* grumble groan */
chdir_file(r->filename);
}
else if(!strcmp(tag,"done"))
return 0;
else {
log_printf(r->server, "unknown parameter '%s' to tag 'exec' in %s",
tag, file);
rputs(error, r);
}
}
}
int handle_echo (FILE *in, request_rec *r, char *error) {
char tag[MAX_STRING_LEN];
char *tag_val;
while(1) {
if(!(tag_val = get_tag (r->pool, in, tag, MAX_STRING_LEN, 1)))
return 1;
if(!strcmp(tag,"var")) {
char *val = table_get (r->subprocess_env, tag_val);
if (val) rputs(val, r);
else rputs("(none)", r);
} else if(!strcmp(tag,"done"))
return 0;
else {
log_printf(r->server, "unknown parameter '%s' to tag 'echo' in %s",
tag, r->filename);
rputs(error, r);
}
}
}
#ifdef USE_PERL_SSI
int handle_perl (FILE *in, request_rec *r, char *error) {
char tag[MAX_STRING_LEN];
char *tag_val;
SV *sub = Nullsv;
AV *av = newAV();
if (!(allow_options (r) & OPT_INCLUDES)) {
log_printf(r->server,
"httpd: #perl SSI disallowed by IncludesNoExec in %s", r->filename);
return DECLINED;
}
while(1) {
if(!(tag_val = get_tag (r->pool, in, tag, MAX_STRING_LEN, 1)))
break;
if(strnEQ(tag, "sub", 3))
sub = newSVpv(tag_val,0);
else if(strnEQ(tag, "arg", 3))
av_push(av, newSVpv(tag_val,0));
else if(strnEQ(tag,"done", 4))
break;
}
perl_call_handler(sub, r, av);
return OK;
}
#endif
int handle_config(FILE *in, request_rec *r, char *error, char *tf,
int *sizefmt) {
char tag[MAX_STRING_LEN];
char *tag_val;
char parsed_string[MAX_STRING_LEN];
table *env = r->subprocess_env;
while(1) {
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 0)))
return 1;
if(!strcmp(tag,"errmsg")) {
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
strncpy(error,parsed_string,MAX_STRING_LEN-1);
error[MAX_STRING_LEN-1] = '\0';
} else if(!strcmp(tag,"timefmt")) {
time_t date = r->request_time;
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
strncpy(tf,parsed_string,MAX_STRING_LEN-1);
tf[MAX_STRING_LEN-1] = '\0';
table_set (env, "DATE_LOCAL", ht_time(r->pool,date,tf,0));
table_set (env, "DATE_GMT", ht_time(r->pool,date,tf,1));
table_set (env, "LAST_MODIFIED", ht_time(r->pool,r->finfo.st_mtime,tf,0));
}
else if(!strcmp(tag,"sizefmt")) {
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
decodehtml(parsed_string);
if(!strcmp(parsed_string,"bytes"))
*sizefmt = SIZEFMT_BYTES;
else if(!strcmp(parsed_string,"abbrev"))
*sizefmt = SIZEFMT_KMG;
}
else if(!strcmp(tag,"done"))
return 0;
else {
log_printf(r->server, "unknown parameter %s to tag config in %s",
tag, r->filename);
rputs(error, r);
}
}
}
int find_file(request_rec *r, char *directive, char *tag,
char *tag_val, struct stat *finfo, char *error)
{
char dir[MAX_STRING_LEN];
char *to_send;
if(!strcmp(tag,"file")) {
getparents(tag_val); /* get rid of any nasties */
getwd(dir);
to_send = make_full_path (r->pool, dir, tag_val);
if(stat(to_send,finfo) == -1) {
log_printf(r->server,
"unable to get information about %s in parsed file %s",
to_send, r->filename);
rputs(error, r);
return -1;
}
return 0;
}
else if(!strcmp(tag,"virtual")) {
request_rec *rr = sub_req_lookup_uri (tag_val, r);
if (rr->status == 200 && rr->finfo.st_mode != 0) {
memcpy ((char*)finfo, (const char *)&rr->finfo, sizeof (struct stat));
destroy_sub_req (rr);
return 0;
} else {
log_printf(r->server,
"unable to get information about %s in parsed file %s",
tag_val, r->filename);
rputs(error, r);
destroy_sub_req (rr);
return -1;
}
}
else {
log_printf(r->server, "unknown parameter '%s' to tag '%s' in %s",
tag, directive, r->filename);
rputs(error, r);
return -1;
}
}
int handle_fsize(FILE *in, request_rec *r, char *error, int sizefmt)
{
char tag[MAX_STRING_LEN];
char *tag_val;
struct stat finfo;
char parsed_string[MAX_STRING_LEN];
while(1) {
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
return 1;
else if(!strcmp(tag,"done"))
return 0;
else {
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
if(!find_file(r,"fsize",tag,parsed_string,&finfo,error)) {
if(sizefmt == SIZEFMT_KMG) {
send_size(finfo.st_size, r);
}
else {
int l,x;
#if defined(BSD) && BSD > 199305
/* ap_snprintf can't handle %qd */
sprintf(tag,"%qd", finfo.st_size);
#else
ap_snprintf(tag, sizeof(tag), "%ld",finfo.st_size);
#endif
l = strlen(tag); /* grrr */
for(x=0;xpool, in, tag, MAX_STRING_LEN, 1)))
return 1;
else if(!strcmp(tag,"done"))
return 0;
else {
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
if(!find_file(r,"flastmod",tag,parsed_string,&finfo,error))
rputs(ht_time(r->pool, finfo.st_mtime, tf, 0), r);
}
}
}
int re_check(request_rec *r, char *string, char *rexp)
{
regex_t compiled;
char err_string[MAX_STRING_LEN];
int regex_error;
regex_error = regcomp(&compiled, rexp, REG_EXTENDED|REG_NOSUB);
if (regex_error) {
regerror(regex_error, &compiled, err_string, (size_t)MAX_STRING_LEN);
log_printf(r->server,
"unable to compile pattern %s [%s]", rexp, err_string);
return -1;
}
regex_error = regexec(&compiled, string, 0, (regmatch_t *)NULL, 0);
regfree(&compiled);
return(!regex_error);
}
enum token_type { token_string,
token_and, token_or, token_not, token_eq, token_ne,
token_rbrace, token_lbrace, token_group
};
struct token {
enum token_type type;
char value[MAX_STRING_LEN];
};
char *get_ptoken(request_rec *r, char *string, struct token *token) {
char ch;
int next=0;
int qs=0;
/* Skip leading white space */
if (string == (char *)NULL) return (char *)NULL;
while ((ch = *string++))
if (!isspace(ch)) break;
if (ch == '\0') return (char *)NULL;
switch(ch) {
case '(':
token->type = token_lbrace;
return(string);
case ')':
token->type = token_rbrace;
return(string);
case '=':
token->type = token_eq;
return(string);
case '!':
if (*string == '=') {
token->type = token_ne;
return(string+1);
} else {
token->type = token_not;
return(string);
}
case '\'':
token->type = token_string;
qs = 1;
break;
case '|':
if (*string == '|') {
token->type = token_or;
return(string+1);
}
case '&':
if (*string == '&') {
token->type = token_and;
return(string+1);
}
default:
token->type = token_string;
break;
}
/* We should only be here if we are in a string */
if (!qs) token->value[next++] = ch;
/*
* Yes I know that goto's are BAD. But, c doesn't allow me to
* exit a loop from a switch statement. Yes, I could use a flag,
* but that is (IMHO) even less readable/maintainable than the goto.
*/
/*
* I used the ++string throughout this section so that string
* ends up pointing to the next token and I can just return it
*/
for (ch = *string; ch != '\0'; ch = *++string) {
if (ch == '\\') {
if ((ch = *++string) == '\0') goto TOKEN_DONE;
token->value[next++] = ch;
continue;
}
if (!qs) {
if (isspace(ch)) goto TOKEN_DONE;
switch(ch) {
case '(': goto TOKEN_DONE;
case ')': goto TOKEN_DONE;
case '=': goto TOKEN_DONE;
case '!': goto TOKEN_DONE;
case '|': if (*(string+1) == '|') goto TOKEN_DONE;
case '&': if (*(string+1) == '&') goto TOKEN_DONE;
}
token->value[next++] = ch;
} else {
if (ch == '\'') { qs=0; ++string; goto TOKEN_DONE; }
token->value[next++] = ch;
}
}
TOKEN_DONE:
/* If qs is still set, I have an unmatched ' */
if (qs) { rputs("\nUnmatched '\n", r); next=0; }
token->value[next] = '\0';
return(string);
}
/*
* Hey I still know that goto's are BAD. I don't think that I've ever
* used two in the same project, let alone the same file before. But,
* I absolutely want to make sure that I clean up the memory in all
* cases. And, without rewriting this completely, the easiest way
* is to just branch to the return code which cleans it up.
*/
int parse_expr(request_rec *r, char *expr, char *error)
{
struct parse_node {
struct parse_node *left, *right, *parent;
struct token token;
int value, done;
} *root, *current, *new;
char *parse;
char buffer[MAX_STRING_LEN];
struct pool *expr_pool;
int retval = 0;
if ((parse = expr) == (char *)NULL) return(0);
root = current = (struct parse_node*)NULL;
if ((expr_pool = make_sub_pool(r->pool)) == (struct pool *)NULL) {
log_printf(r->server, "out of memory", r->filename);
rputs(error, r);
return(0);
}
/* Create Parse Tree */
while (1) {
new = (struct parse_node*)palloc(expr_pool, sizeof (struct parse_node));
if (new == (struct parse_node*)NULL) {
log_printf(r->server,"out of memory", r->filename);
rputs(error, r);
goto RETURN;
}
new->parent = new->left = new->right = (struct parse_node*)NULL;
new->done = 0;
if ((parse = get_ptoken(r, parse, &new->token)) == (char *)NULL)
break;
switch(new->token.type) {
case token_string:
#ifdef DEBUG_INCLUDE
rvputs(r," Token: string (", new->token.value, ")\n", NULL);
#endif
if (current == (struct parse_node*)NULL) {
root = current = new;
break;
}
switch(current->token.type) {
case token_string:
if (current->token.value[0] != '\0')
strncat(current->token.value, " ",
MAX_STRING_LEN-strlen(current->token.value)-1);
strncat(current->token.value, new->token.value,
MAX_STRING_LEN-strlen(current->token.value)-1);
break;
case token_eq:
case token_ne:
case token_and:
case token_or:
case token_lbrace:
case token_not:
new->parent = current;
current = current->right = new;
break;
default:
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
break;
case token_and:
case token_or:
#ifdef DEBUG_INCLUDE
rputs (" Token: and/or\n", r);
#endif
if (current == (struct parse_node*)NULL) {
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
/* Percolate upwards */
while (current != (struct parse_node *)NULL) {
switch(current->token.type) {
case token_string:
case token_group:
case token_not:
case token_eq:
case token_ne:
case token_and:
case token_or:
current = current->parent;
continue;
case token_lbrace:
break;
default:
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
break;
}
if (current == (struct parse_node*)NULL) {
new->left = root;
new->left->parent = new;
new->parent = (struct parse_node*)NULL;
root = new;
} else {
new->left = current->right;
current->right = new;
new->parent = current;
}
current = new;
break;
case token_not:
#ifdef DEBUG_INCLUDE
rputs(" Token: not\n", r);
#endif
if (current == (struct parse_node*)NULL) {
root = current = new;
break;
}
/* Percolate upwards */
while (current != (struct parse_node *)NULL) {
switch(current->token.type) {
case token_not:
case token_eq:
case token_ne:
case token_and:
case token_or:
case token_lbrace:
break;
default:
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
break;
}
if (current == (struct parse_node*)NULL) {
new->left = root;
new->left->parent = new;
new->parent = (struct parse_node*)NULL;
root = new;
} else {
new->left = current->right;
current->right = new;
new->parent = current;
}
current = new;
break;
case token_eq:
case token_ne:
#ifdef DEBUG_INCLUDE
rputs(" Token: eq/ne\n", r);
#endif
if (current == (struct parse_node*)NULL) {
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
/* Percolate upwards */
while (current != (struct parse_node *)NULL) {
switch(current->token.type) {
case token_string:
case token_group:
current = current->parent;
continue;
case token_lbrace:
case token_and:
case token_or:
break;
case token_not:
case token_eq:
case token_ne:
default:
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
break;
}
if (current == (struct parse_node*)NULL) {
new->left = root;
new->left->parent = new;
new->parent = (struct parse_node*)NULL;
root = new;
} else {
new->left = current->right;
current->right = new;
new->parent = current;
}
current = new;
break;
case token_rbrace:
#ifdef DEBUG_INCLUDE
rputs(" Token: rbrace\n", r);
#endif
while (current != (struct parse_node*)NULL) {
if (current->token.type == token_lbrace) {
current->token.type = token_group;
break;
}
current = current->parent;
}
if (current == (struct parse_node*)NULL) {
log_printf(r->server,"Unmatched ')'in %s\n", expr, r->filename);
rputs(error, r);
goto RETURN;
}
break;
case token_lbrace:
#ifdef DEBUG_INCLUDE
rputs(" Token: lbrace\n", r);
#endif
if (current == (struct parse_node*)NULL) {
root = current = new;
break;
}
/* Percolate upwards */
while (current != (struct parse_node *)NULL) {
switch(current->token.type) {
case token_not:
case token_eq:
case token_ne:
case token_and:
case token_or:
case token_lbrace:
break;
case token_string:
case token_group:
default:
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
break;
}
if (current == (struct parse_node*)NULL) {
new->left = root;
new->left->parent = new;
new->parent = (struct parse_node*)NULL;
root = new;
} else {
new->left = current->right;
current->right = new;
new->parent = current;
}
current = new;
break;
default:
break;
}
}
/* Evaluate Parse Tree */
current = root;
while (current != (struct parse_node *)NULL) {
switch(current->token.type) {
case token_string:
#ifdef DEBUG_INCLUDE
rputs(" Evaluate string\n", r);
#endif
parse_string(r, current->token.value, buffer, MAX_STRING_LEN, 0);
strncpy(current->token.value, buffer, MAX_STRING_LEN-1);
current->token.value[MAX_STRING_LEN-1] = '\0';
current->value = (current->token.value[0] != '\0');
current->done = 1;
current = current->parent;
break;
case token_and:
case token_or:
#ifdef DEBUG_INCLUDE
rputs(" Evaluate and/or\n", r);
#endif
if (current->left == (struct parse_node*)NULL ||
current->right == (struct parse_node*)NULL) {
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
if (!current->left->done) {
switch(current->left->token.type) {
case token_string:
parse_string(r, current->left->token.value,
buffer, MAX_STRING_LEN, 0);
strncpy(current->left->token.value, buffer,
MAX_STRING_LEN-1);
current->left->token.value[MAX_STRING_LEN-1] = '\0';
current->left->done = 1;
break;
default:
current = current->left;
continue;
}
}
if (!current->right->done) {
switch(current->right->token.type) {
case token_string:
parse_string(r, current->right->token.value,
buffer, MAX_STRING_LEN, 0);
strncpy(current->right->token.value, buffer,
MAX_STRING_LEN-1);
current->right->token.value[MAX_STRING_LEN-1] = '\0';
current->right->done = 1;
break;
default:
current = current->right;
continue;
}
}
#ifdef DEBUG_INCLUDE
rvputs(r," Left: ", current->left->value ? "1" : "0", "\n", NULL);
rvputs(r," Right: ", current->right->value ? "1" : "0", "\n", NULL);
#endif
if (current->token.type == token_and)
current->value =
current->left->value && current->right->value;
else
current->value =
current->left->value || current->right->value;
#ifdef DEBUG_INCLUDE
rvputs(r," Returning ", current->value ? "1" : "0", "\n", NULL);
#endif
current->done = 1;
current = current->parent;
break;
case token_eq:
case token_ne:
#ifdef DEBUG_INCLUDE
rputs(" Evaluate eq/ne\n", r);
#endif
if ((current->left == (struct parse_node*)NULL) ||
(current->right == (struct parse_node*)NULL) ||
(current->left->token.type != token_string) ||
(current->right->token.type != token_string)) {
log_printf(r->server,
"Invalid expression %s", expr, r->filename);
rputs(error, r);
goto RETURN;
}
parse_string(r, current->left->token.value,
buffer, MAX_STRING_LEN, 0);
strncpy(current->left->token.value, buffer, MAX_STRING_LEN-1);
current->left->token.value[MAX_STRING_LEN-1] = '\0';
parse_string(r, current->right->token.value,
buffer, MAX_STRING_LEN, 0);
strncpy(current->right->token.value, buffer, MAX_STRING_LEN-1);
current->right->token.value[MAX_STRING_LEN-1] = '\0';
if (current->right->token.value[0] == '/') {
int len;
len = strlen(current->right->token.value);
if (current->right->token.value[len-1] == '/') {
current->right->token.value[len-1] = '\0';
} else {
log_printf(r->server,"Invalid rexp %s",
current->right->token.value, r->filename);
rputs(error, r);
goto RETURN;
}
#ifdef DEBUG_INCLUDE
rvputs(r," Re Compare (", current->left->token.value,
") with /", ¤t->right->token.value[1], "/\n", NULL);
#endif
current->value =
re_check(r, current->left->token.value,
¤t->right->token.value[1]);
} else {
#ifdef DEBUG_INCLUDE
rvputs(r," Compare (", current->left->token.value,
") with (", current->right->token.value, ")\n", NULL);
#endif
current->value =
(strcmp(current->left->token.value,
current->right->token.value) == 0);
}
if (current->token.type == token_ne)
current->value = !current->value;
#ifdef DEBUG_INCLUDE
rvputs(r," Returning ", current->value ? "1" : "0", "\n", NULL);
#endif
current->done = 1;
current = current->parent;
break;
case token_not:
if (current->right != (struct parse_node *)NULL) {
if (!current->right->done) {
current = current->right;
continue;
}
current->value = !current->right->value;
} else {
current->value = 0;
}
#ifdef DEBUG_INCLUDE
rvputs(r," Evaluate !: ", current->value ? "1" : "0", "\n", NULL);
#endif
current->done = 1;
current = current->parent;
break;
case token_group:
if (current->right != (struct parse_node *)NULL) {
if (!current->right->done) {
current = current->right;
continue;
}
current->value = current->right->value;
} else {
current->value = 1;
}
#ifdef DEBUG_INCLUDE
rvputs(r," Evaluate (): ", current->value ? "1" : "0", "\n", NULL);
#endif
current->done = 1;
current = current->parent;
break;
case token_lbrace:
log_printf(r->server,"Unmatched '(' in %s\n", expr, r->filename);
rputs(error, r);
goto RETURN;
case token_rbrace:
log_printf(r->server,"Unmatched ')' in %s\n", expr, r->filename);
rputs(error, r);
goto RETURN;
default:
log_printf(r->server,"bad token type");
rputs(error, r);
goto RETURN;
}
}
retval = (root == (struct parse_node *)NULL) ? 0 : root->value;
RETURN:
destroy_pool(expr_pool);
return (retval);
}
int handle_if(FILE *in, request_rec *r, char *error,
int *conditional_status, int *printing)
{
char tag[MAX_STRING_LEN];
char *tag_val = '\0';
char *expr = '\0';
while(1) {
tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 0);
if(*tag == '\0')
return 1;
else if(!strcmp(tag,"done")) {
*printing = *conditional_status = parse_expr(r, expr, error);
#ifdef DEBUG_INCLUDE
rvputs(r,"**** if conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
#endif
return 0;
} else if(!strcmp(tag,"expr")) {
expr = tag_val;
#ifdef DEBUG_INCLUDE
rvputs(r,"**** if expr=\"", expr, "\"\n", NULL);
#endif
} else {
log_printf(r->server,"unknown parameter %s to tag if in %s",
tag, r->filename);
rputs(error, r);
}
}
}
int handle_elif(FILE *in, request_rec *r, char *error,
int *conditional_status, int *printing)
{
char tag[MAX_STRING_LEN];
char *tag_val = '\0';
char *expr = '\0';
while(1) {
tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 0);
if(*tag == '\0')
return 1;
else if(!strcmp(tag,"done")) {
#ifdef DEBUG_INCLUDE
rvputs(r,"**** elif conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
#endif
if (*conditional_status) {
*printing = 0;
return(0);
}
*printing = *conditional_status = parse_expr(r, expr, error);
#ifdef DEBUG_INCLUDE
rvputs(r,"**** elif conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
#endif
return 0;
} else if(!strcmp(tag,"expr")) {
expr = tag_val;
#ifdef DEBUG_INCLUDE
rvputs(r,"**** if expr=\"", expr, "\"\n", NULL);
#endif
} else {
log_printf(r->server,"unknown parameter %s to tag if in %s",
tag, r->filename);
rputs(error, r);
}
}
}
int handle_else(FILE *in, request_rec *r, char *error,
int *conditional_status, int *printing)
{
char tag[MAX_STRING_LEN];
char *tag_val;
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
return 1;
else if(!strcmp(tag,"done")) {
#ifdef DEBUG_INCLUDE
rvputs(r,"**** else conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
#endif
*printing = !(*conditional_status);
*conditional_status = 1;
return 0;
} else {
log_printf(r->server, "else directive does not take tags");
if (*printing) rputs(error, r);
return -1;
}
}
int handle_endif(FILE *in, request_rec *r, char *error,
int *conditional_status, int *printing)
{
char tag[MAX_STRING_LEN];
char *tag_val;
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1))) {
return 1;
} else if(!strcmp(tag,"done")) {
#ifdef DEBUG_INCLUDE
rvputs(r,"**** endif conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
#endif
*conditional_status = 1;
return 0;
} else {
log_printf(r->server, "endif directive does not take tags");
rputs(error, r);
return -1;
}
}
int handle_set(FILE *in, request_rec *r, char *error)
{
char tag[MAX_STRING_LEN];
char parsed_string[MAX_STRING_LEN];
char *tag_val;
char *var;
var = (char *)NULL;
while (1) {
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
return 1;
else if(!strcmp(tag,"done"))
return 0;
else if (!strcmp(tag,"var")) {
var = tag_val;
} else if (!strcmp(tag,"value")) {
if (var == (char *)NULL) {
log_printf(r->server,
"variable must precede value in set directive");
rputs(error, r);
return -1;
}
parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
table_set (r->subprocess_env, var, parsed_string);
}
}
}
int handle_printenv(FILE *in, request_rec *r, char *error)
{
char tag[MAX_STRING_LEN];
char *tag_val;
table_entry *elts = (table_entry *) r->subprocess_env->elts;
int i;
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
return 1;
else if(!strcmp(tag,"done")) {
for (i = 0; i < r->subprocess_env->nelts; ++i)
rvputs(r, elts[i].key, "=", elts[i].val, "\n", NULL);
return 0;
} else {
log_printf(r->server, "printenv directive does not take tags");
rputs(error, r);
return -1;
}
}
Tcl_Interp *get_slave_interp (request_rec *r, char *handler_name, char *name) {
extern Tcl_Interp *interp;
Tcl_Interp *safeInterp;
struct request_rec *q;
char *safeInterpName;
table *t;
table_entry *elts;
int i;
/* look into the request record and find out if we already have a safe
* interpreter running with the right user ID.
*
* if not, create one and register it. if so, hook up to it.
*
*/
for (q = r, safeInterpName = (char *)NULL; q != (struct request_rec *)NULL; q = q->main) {
if (r->finfo.st_uid != q->finfo.st_uid) break;
if ((safeInterpName = get_module_config(q->request_config, &neoscript_module)) != NULL) break;
}
if (safeInterpName) {
safeInterp = Tcl_GetSlave(interp, safeInterpName);
assert (safeInterp != NULL);
} else {
/* Set array for the config information here, once, when the slave
* interp is created. Saves repeating work when several script blocks
* exist within a page.
*/
neoscript_dir_config *ns = (neoscript_dir_config *)get_module_config(r->per_dir_config,&neoscript_module);
t = ns->neowebscript_dir_vars;
elts = (table_entry *)t->elts;
/* Make sure it doesn't exist from a previous page served by this process */
Tcl_UnsetVar (interp, "NeoWebDirConf", TCL_GLOBAL_ONLY);
for (i = 0; i < t->nelts; ++i)
Tcl_SetVar2 (interp, "NeoWebDirConf", elts[i].key, elts[i].val, TCL_GLOBAL_ONLY);
if (Tcl_GlobalEval(interp, "setup_safe_interpreter") != TCL_OK) {
char *errorInfo;
errorInfo = Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY);
log_error(errorInfo, r->server);
fprintf(stderr, "setup_safe_interpreter: %s\n", errorInfo);
exit(1);
} else {
safeInterpName = (char *)palloc (r->pool, strlen(interp->result) + 1);
strcpy (safeInterpName, interp->result);
safeInterp = Tcl_GetSlave(interp, safeInterpName);
/*assert (safeInterp != NULL);*/
if (safeInterp == NULL) {
exit(1);
}
t = ns->neowebscript_user_vars;
elts = (table_entry *)t->elts;
for (i = 0; i < t->nelts; ++i)
Tcl_SetVar2 (safeInterp, "NeoWebUserConf", elts[i].key, elts[i].val, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (safeInterp, "webenv", "NEO_HANDLER", handler_name, TCL_GLOBAL_ONLY);
Tcl_SetVar2 (interp, "webenv", "NEO_HANDLER", handler_name, TCL_GLOBAL_ONLY);
}
register_cleanup (r->pool, (void *)safeInterp, (void (*)())Tcl_DeleteInterp, (void (*)())Tcl_DeleteInterp);
set_module_config(r->request_config, &neoscript_module, safeInterpName);
propagate_vars_to_neoscript (safeInterp, r);
}
if (name != NULL) {
strcpy(name, safeInterpName);
}
return safeInterp;
}
int handle_neoscript (FILE *in, request_rec *r, char *error,
char *safeInterpName)
{
char tag[MAX_STRING_LEN];
char *tag_val;
extern Tcl_Interp *interp;
/* parse the tag=value pairs. build up a neoscript command
* including name of the safe interp we're running against.
*/
while(1) {
Tcl_DString userCommand;
char *commandString;
if(!(tag_val = get_tag (r->pool, in, tag, MAX_STRING_LEN, 1))) {
return 1;
}
if(!strcmp(tag,"done"))
goto done_ok;
Tcl_DStringInit(&userCommand);
Tcl_DStringAppendElement(&userCommand, "handle_neoscript_request");
Tcl_DStringAppendElement(&userCommand, safeInterpName);
Tcl_DStringAppendElement(&userCommand, tag);
Tcl_DStringAppendElement(&userCommand, tag_val);
commandString = Tcl_DStringValue(&userCommand);
if (Tcl_GlobalEval (interp, commandString) == TCL_ERROR) {
rprintf (r, "[%s error %s]", tag, interp->result);
}
Tcl_DStringFree(&userCommand);
}
done_ok:
return 0;
}
int handle_neowebscript (FILE *in, request_rec *r, char *error,
char *safeInterpName)
{
char code[MAX_STRING_LEN];
char *nws_code;
extern Tcl_Interp *interp;
request_rec *Tcl_saved_request_rec;
Tcl_DString userCommand;
char *commandString;
Tcl_saved_request_rec = Tcl_request_rec;
Tcl_request_rec = r;
if(!(nws_code = get_nws_code (r->pool, in, code, MAX_STRING_LEN, ENDING_NWS_SEQUENCE))) {
Tcl_request_rec = Tcl_saved_request_rec;
return 1;
}
Tcl_DStringInit(&userCommand);
Tcl_DStringAppendElement(&userCommand, "handle_neoscript_request");
Tcl_DStringAppendElement(&userCommand, safeInterpName);
Tcl_DStringAppendElement(&userCommand, "code");
Tcl_DStringAppendElement(&userCommand, nws_code);
commandString = Tcl_DStringValue(&userCommand);
if (Tcl_GlobalEval (interp, commandString) == TCL_ERROR) {
rprintf (r, "[%s error %s]", "code", interp->result);
}
Tcl_DStringFree(&userCommand);
Tcl_request_rec = Tcl_saved_request_rec;
return 0;
}
int handle_neoscript_config(FILE *in, request_rec *r, char *error, char *tf,
int *sizefmt) {
char tag[MAX_STRING_LEN];
char *tag_val;
table *env = r->subprocess_env;
while(1) {
if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 0)))
return 1;
if(!strcmp(tag,"errmsg"))
strcpy(error,tag_val);
else if(!strcmp(tag,"timefmt")) {
time_t date = time(NULL);
strcpy(tf,tag_val);
table_set (env, "DATE_LOCAL", ht_time(r->pool,date,tf,0));
table_set (env, "DATE_GMT", ht_time(r->pool,date,tf,1));
table_set (env, "LAST_MODIFIED", ht_time(r->pool,r->finfo.st_mtime,tf,0));
}
else if(!strcmp(tag,"sizefmt")) {
decodehtml(tag_val);
if(!strcmp(tag_val,"bytes"))
*sizefmt = SIZEFMT_BYTES;
else if(!strcmp(tag_val,"abbrev"))
*sizefmt = SIZEFMT_KMG;
}
else if(!strcmp(tag,"done"))
return 0;
else {
char errstr[MAX_STRING_LEN];
sprintf(errstr,"unknown parameter '%s' to tag 'config' in %s",
tag, r->filename);
log_error(errstr, r->server);
rprintf (r,"%s",error);
}
}
}
void add_include_environment(request_rec *r)
{
add_common_vars (r);
add_cgi_vars(r);
add_include_vars (r, DEFAULT_TIME_FORMAT);
}
/* -------------------------- The main function --------------------------- */
/* This is a stub which parses a file descriptor. */
int send_parsed_content(FILE *f, request_rec *r)
{
char directive[MAX_STRING_LEN], error[MAX_STRING_LEN];
char timefmt[MAX_STRING_LEN];
int noexec = allow_options (r) & OPT_INCNOEXEC;
int ret, sizefmt, seqtype=0;
int init_environment, if_nesting, printing, conditional_status;
Tcl_Interp *safeInterp = 0;
int has_includes = 0;
char safeInterpName[20];
request_rec *Tcl_saved_request_rec;
Tcl_saved_request_rec = Tcl_request_rec;
Tcl_request_rec = r;
strncpy(error,DEFAULT_ERROR_MSG, sizeof(error)-1);
error[sizeof(error)-1] = '\0';
strncpy(timefmt,DEFAULT_TIME_FORMAT, sizeof(timefmt)-1);
timefmt[sizeof(timefmt)-1] = '\0';
sizefmt = SIZEFMT_KMG;
/* Turn printing on */
printing = conditional_status = 1;
if_nesting = 0;
init_environment = 0;
chdir_file (r->filename);
if (r->args) { /* add QUERY stuff to env cause it ain't yet */
table_set (r->subprocess_env, "QUERY_STRING", r->args);
unescape_url (r->args);
table_set (r->subprocess_env, "QUERY_STRING_UNESCAPED",
escape_shell_cmd (r->pool, r->args));
}
while(1) {
if(!find_string2(f,STARTING_SEQUENCE,STARTING_NWS_SEQUENCE,r,&seqtype,printing))
{
has_includes = 1;
if(seqtype == 2)
{
if (!safeInterp) {
safeInterp = get_slave_interp(r, "server-parsed", safeInterpName);
}
ret=handle_neowebscript(f, r, error, safeInterpName);
if(ret) {
log_printf(r->server, "httpd: premature EOF in parsed file %s",
r->filename);
break;
}
}
else if(seqtype == 1) {
if(get_directive(f,directive,r->pool))
break;
if(!strcmp(directive,"if")) {
if (!printing) {
if_nesting++;
} else {
ret=handle_if(f, r, error, &conditional_status, &printing);
if_nesting = 0;
}
continue;
} else if(!strcmp(directive,"else")) {
if (!if_nesting)
ret=handle_else(f, r, error, &conditional_status, &printing);
continue;
} else if(!strcmp(directive,"elif")) {
if (!if_nesting)
ret = handle_elif(f, r, error, &conditional_status, &printing);
continue;
} else if(!strcmp(directive,"endif")) {
if (!if_nesting) {
ret=handle_else(f, r, error, &conditional_status, &printing);
printing = 1;
} else {
if_nesting--;
}
continue;
}
if (!printing) continue;
if(!strcmp(directive,"exec")) {
if(noexec) {
log_printf(r->server,
"httpd: exec used but not allowed in %s",
r->filename);
if (printing) rputs(error, r);
ret = find_string(f,ENDING_SEQUENCE,r,0);
} else {
/* exec needs environment */
if (!init_environment++)
add_include_environment(r);
ret=handle_exec(f, r, error);
}
}
else if (!strcmp(directive,"neowebscript") || !strcmp(directive,"neoscript"))
{
if (!safeInterp) {
safeInterp = get_slave_interp(r, "server-parsed", safeInterpName);
}
ret=handle_neoscript(f, r, error, safeInterpName);
}
else if(!strcmp(directive,"neoconfig"))
ret=handle_neoscript_config(f, r, error, timefmt, &sizefmt);
else if(!strcmp(directive,"config"))
ret=handle_config(f, r, error, timefmt, &sizefmt);
else if(!strcmp(directive,"set"))
ret=handle_set(f, r, error);
else if(!strcmp(directive,"include"))
ret=handle_include(f, r, error, noexec);
else if(!strcmp(directive,"echo")) {
/* exec needs environment */
if (!init_environment++)
add_include_environment(r);
ret=handle_echo(f, r, error);
}
else if(!strcmp(directive,"fsize"))
ret=handle_fsize(f, r, error, sizefmt);
else if(!strcmp(directive,"flastmod"))
ret=handle_flastmod(f, r, error, timefmt);
else if(!strcmp(directive,"printenv"))
ret=handle_printenv(f, r, error);
#ifdef USE_PERL_SSI
else if(!strcmp(directive,"perl"))
ret=handle_perl(f, r, error);
#endif
else {
log_printf(r->server,
"httpd: unknown directive '%s' in parsed doc %s",
directive, r->filename);
rputs(error, r);
ret=find_string(f,ENDING_SEQUENCE,r,0);
}
if(ret) {
log_printf(r->server, "httpd: premature EOF in parsed file %s",
r->filename);
break;
}
}
} else break;
}
getout:
Tcl_request_rec = Tcl_saved_request_rec;
return has_includes;
}
/*
* send_subst_content
*
* The contents of the file are passed to the Tcl slave's subst command.
* The result is written to the browser. We just do the setup, and the
* final output. Most of the work (and policy) is implemented in Tcl
* code using the master interpreter.
*/
void send_subst_content(FILE *f, request_rec *r)
{
Tcl_Interp *safeInterp = 0;
char safeInterpName[20];
extern Tcl_Interp *interp;
request_rec *Tcl_saved_request_rec;
Tcl_Channel fchan;
char *fchan_name;
Tcl_DString userCommand;
char *commandString;
Tcl_saved_request_rec = Tcl_request_rec;
Tcl_request_rec = r;
chdir_file (r->filename);
safeInterp = get_slave_interp(r, "server-subst", safeInterpName);
fchan = Tcl_MakeFileChannel((ClientData) fileno(f), (ClientData)NULL, TCL_READABLE);
Tcl_RegisterChannel(interp, fchan);
fchan_name = Tcl_GetChannelName(fchan);
Tcl_DStringInit(&userCommand);
Tcl_DStringAppendElement(&userCommand, "handle_subst_request");
Tcl_DStringAppendElement(&userCommand, safeInterpName);
Tcl_DStringAppendElement(&userCommand, fchan_name);
commandString = Tcl_DStringValue(&userCommand);
if (Tcl_GlobalEval (interp, commandString) == TCL_ERROR) {
rprintf (r, "[%s error %s]", commandString, interp->result);
}
Tcl_DStringFree(&userCommand);
Tcl_request_rec = Tcl_saved_request_rec;
}
const char *set_xbithack (cmd_parms *cmd, neoscript_dir_config *m, char *arg)
{
if (!strcasecmp (arg, "off")) m->xbithack = xbithack_off;
else if (!strcasecmp (arg, "on")) m->xbithack = xbithack_on;
else if (!strcasecmp (arg, "full")) m->xbithack = xbithack_full;
else return "XBitHack must be set to Off, On, or Full";
return NULL;
}
int check_cache_status(char *filename, long mtime)
{
int status;
DBT key;
DBT data;
DB *db = dbopen(NeoWebCacheName, (O_CREAT|O_RDWR|DB_LOCK), 0644, DB_HASH, (void *)NULL);
if (db == (DB *)NULL)
{
perror(NeoWebCacheName);
return NOT_FOUND;
}
key.data = filename;
key.size = strlen(filename) + 1;
status = db->get(db, &key, &data, 0);
db->close(db);
if (status != 0)
return NOT_FOUND;
/* fprintf(stderr, "db get %s = %d, data = %ld\n", filename, status, *(long*)data.data); */
if (*(long*)data.data == mtime)
return USE_LOCAL_COPY;
return 0;
};
void set_cache_status(char *filename, int cache_status, int has_includes, long mtime)
{
int status;
DBT key;
DBT data;
DB *db;
/* No entry in cache and there are includes -- no change */
if (cache_status == 0 && has_includes)
return;
db = dbopen(NeoWebCacheName, (O_CREAT|O_RDWR|DB_LOCK), 0644, DB_HASH, (void *)NULL);
if (db == (DB *)NULL)
return;
key.data = filename;
key.size = strlen(filename) + 1;
/* delete the previous entry if no longer needed */
if (!has_includes && cache_status == 0) {
/* fprintf(stderr, "Delete key %s\n", filename); */
status = db->del(db, &key, 0);
goto getout;
}
data.data = (void*)&mtime;
data.size = sizeof(long);
/* fprintf(stderr, "Put %s: %ld\n", filename, mtime); */
status = db->put(db, &key, &data, 0);
/* if (status == -1)
perror("db->put"); */
getout:
db->close(db);
return;
};
int send_parsed_file(request_rec *r, int mode)
{
FILE *f;
neoscript_dir_config *ns = (neoscript_dir_config *)get_module_config(r->per_dir_config,&neoscript_module);
enum xbithack xbit = ns->xbithack;
int errstatus;
int has_mime = 0;
char *pragma;
int cache_status;
extern Tcl_Interp *interp;
int is_included = !strcmp (r->protocol, "INCLUDED");
char *argv0;
int nph;
char *lenp = table_get (r->headers_in, "Content-length");
char argsbuffer[HUGE_STRING_LEN];
if (!(allow_options (r) & OPT_INCLUDES)) return DECLINED;
if (r->finfo.st_mode == 0) {
log_reason("File does not exist",
r->path_info ? pstrcat(r->pool, r->filename, r->path_info, NULL)
: r->filename, r);
return NOT_FOUND;
}
if(!(f=pfopen(r->pool, r->filename, "r"))) {
log_reason("file permissions deny server access", r->filename, r);
return FORBIDDEN;
}
if (xbit == xbithack_full
#ifndef __EMX__
/* OS/2 dosen't support Groups. */
&& (r->finfo.st_mode & S_IXGRP)
#endif
&& (errstatus = set_last_modified (r, r->finfo.st_mtime)))
return errstatus;
if (NeoWebCacheEnabled && mode == 0 && !is_included) {
pragma = table_get (r->headers_in, "Pragma");
if (!pragma || strcasecmp(pragma, "no-cache")) {
cache_status = check_cache_status(r->filename, r->finfo.st_mtime);
if (cache_status == USE_LOCAL_COPY
&& (errstatus = set_last_modified (r, r->finfo.st_mtime)))
return errstatus;
}
}
/**** Handle PUT and POST for NeoScript ****/
/* Point argv0 to the tail part of the filename */
if((argv0 = strrchr(r->filename,'/')) != NULL)
argv0++;
else argv0 = r->filename;
/* It's NPH if we want to create our own response headers on output.
*
* Note that the whole "No Parse Headers" concept shows all the
* kludgey ways people have invented to try to work around the
* lack of a better hints mechanism for the page to tell the server
* things about itself without altering the text of the URL. Sigh.
*/
nph = !(strncmp(argv0,"nph-",4));
if (nph && is_included) {
log_reason("attempt to include NPH NeoScript CGI script", r->filename, r);
return FORBIDDEN;
}
if (errstatus = setup_client_block(r, REQUEST_CHUNKED_ERROR))
return errstatus;
hard_timeout ("send", r);
/* If it's not "no parse headers", send the header.
*
* If it's header only (The received HTTP request was HEAD
* instead of GET), we're done.
*/
if (!nph) {
send_http_header(r);
if (r->header_only) {
kill_timeout (r);
pfclose (r->pool, f);
return OK;
}
}
/***** End of PUT and POST handling for NeoScript ****/
if (r->main) {
/* Kludge --- for nested includes, we want to keep the
* subprocess environment of the base document (for compatibility);
* that means torquing our own last_modified date as well so that
* the LAST_MODIFIED variable gets reset to the proper value if
* the nested document resets
*/
r->subprocess_env = r->main->subprocess_env;
r->finfo.st_mtime= r->main->finfo.st_mtime;
} else {
/* Delay variable exporting until last moment (hopefully never). */
#if 0
add_common_vars (r);
add_cgi_vars(r);
add_include_vars (r, DEFAULT_TIME_FORMAT);
#endif
/* ??? Not needed most of the time; need to figure this out */
propagate_vars_to_neoscript (interp, r);
}
/* Last part of NeoScript PUT and POST handling */
/* If it's a POST or a PUT,
*
* If it's not a MIME upload, we read the data from stdin and build
* it up in a Tcl dynamic string (DString), then store it into an
* element of our global "webenv" array.
*
* If it is a MIME upload, we call the Tcl interpreter to set
* things up for us, then suck in any form fields that aren't
* files, and squirrel away things that are files, for the
* NeoScript code will make a decision about what to do with
* it from there.
*/
if (!is_included && should_client_block(r)) {
Tcl_DString tclStdinString;
void (*handler)();
char *content_type = table_get (r->headers_in, "Content-type");
int remaining = atoi (lenp);
hard_timeout ("Uplinking PUT/POST", r);
handler = signal (SIGPIPE, SIG_IGN);
if (content_type &&
strncmp (content_type, "multipart/form-data", 19) == 0) {
/* It is a MIME upload. Call Tcl's handle_neoscript_mime_upload
* procedure. It is expected to return a filename (which
* from C we'll see in interp->result) that we'll copy the
* uploaded data to.
*
* When done, we'll call a Tcl proc
* named "finish_neoscript_mime_upload"
*/
has_mime = 1; /* note this so we can clean up later */
if (Tcl_GlobalEval (interp, "handle_neoscript_mime_upload") == TCL_ERROR) {
char *errorInfo;
/* An error occured setting up for the upload. We have
* failed. Report it and move on.
*/
log_printf(r->server, "Error handling MIME file upload '%s': %s", r->filename, interp->result);
errorInfo = Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY);
log_printf(r->server, "NeoWebScript MIME upload failed: %s\n", errorInfo);
} else {
FILE *fp;
long len_written;
/* No Tcl error while setting up to handle the MIME upload,
* let's reel it in.
*
* Note: File must already exist because we are counting
* on the Tcl code to have set up a basic mime multipart
* header there for us */
if ((fp = fopen (interp->result, "a")) == (FILE *)NULL) {
log_printf (r->server, "Error opening save file during MIME upload: %s: %s", interp->result, strerror(errno));
} else {
while ((remaining > 0))
{
int len_read, len_to_read = remaining;
if (len_to_read > HUGE_STRING_LEN) len_to_read = HUGE_STRING_LEN;
len_read = get_client_block (r, argsbuffer, len_to_read);
len_written = fwrite(argsbuffer, sizeof(char), len_read, fp);
if (len_read == 0) break;
if (len_written != len_read) {
log_printf (r->server, "Error writing to MIME save file during upload: %s: %s", interp->result, strerror(errno));
break;
}
remaining -= len_read;
}
fclose(fp);
if (Tcl_GlobalEval (interp, "finish_neoscript_mime_upload") == TCL_ERROR) {
char *errorInfo;
/* An error occured finishing handling the MIME
* upload.
*/
log_printf(r->server, "Error finishing MIME file upload: %s", interp->result);
}
}
}
} else {
/* It's not a MIME upload; it's a regular POST.
* Read in all the data and store it in the Tcl
* global array element webenv(NEO_POST_DATA).
* Tcl's string handling will make quick work of parsing
* out the response, etc.
*/
Tcl_DStringInit(&tclStdinString);
while ((remaining > 0))
{
int len_read, len_to_read = remaining;
if (len_to_read > HUGE_STRING_LEN) len_to_read = HUGE_STRING_LEN;
len_read = get_client_block (r, argsbuffer, len_to_read);
Tcl_DStringAppend(&tclStdinString, argsbuffer, len_read);
if (len_read == 0) break;
remaining -= len_read;
}
Tcl_SetVar2 (interp, "webenv", "NEO_POST_DATA", Tcl_DStringValue(&tclStdinString), TCL_GLOBAL_ONLY);
Tcl_DStringFree(&tclStdinString);
}
signal (SIGPIPE, handler);
kill_timeout (r);
}
/* End of last part of put and post handling for NeoScript */
hard_timeout("SSI Parsing", r);
if (mode == 1)
send_subst_content (f, r);
else
{
int has_includes = send_parsed_content (f, r);
if (NeoWebCacheEnabled && !is_included && !(cache_status == NOT_FOUND && has_includes))
set_cache_status(r->filename, cache_status, has_includes, r->finfo.st_mtime);
}
if (has_mime) {
if (Tcl_GlobalEval (interp, "cleanup_neoscript_mime_upload") == TCL_ERROR) {
char *errorInfo;
/* An error occured finishing handling the MIME
* upload.
*/
log_printf(r->server, "Error cleaning up after MIME file upload: %s", interp->result);
}
}
kill_timeout (r);
return OK;
}
int send_sparsed_file (request_rec *r)
{
r->content_type = "text/html";
return send_parsed_file(r, 0);
}
int send_subst_file (request_rec *r)
{
r->content_type = "text/html";
return send_parsed_file(r, 1);
}
int xbithack_handler (request_rec *r)
{
neoscript_dir_config *ns;
enum xbithack xbit;
#ifdef __EMX__
/* OS/2 dosen't currently support the xbithack. This is being worked on. */
return DECLINED;
#else
if (!(r->finfo.st_mode & S_IXUSR)) return DECLINED;
ns = (neoscript_dir_config *)
get_module_config(r->per_dir_config, &neoscript_module);
xbit = ns->xbithack;
if (xbit == xbithack_off) return DECLINED;
return send_parsed_file (r, 0);
#endif
}
/*
* send_generated_image:
*
* Sets up safe interpreter and then calls Tcl proc handle_image_request.
* Tcl should return a file handle containing the data to be used for the
* image. All caching, expiration and other policies are handled in Tcl
* code by handle_image_request.
*/
int send_generated_image(request_rec *r)
{
FILE *f = NULL;
neoscript_dir_config *ns = (neoscript_dir_config *)get_module_config(r->per_dir_config,&neoscript_module);
int errstatus;
Tcl_Interp *safeInterp = 0;
Tcl_Channel channel;
char *channel_name;
char *command;
char safeInterpName[20];
extern Tcl_Interp *interp;
/* if POST or PUT, it is an error. will try to allow QUERY_STRING
* to work with images to allow passing of options
*/
if (r->method_number != M_GET) {
log_reason("Method != GET for generated image:", r->filename, r);
return BAD_REQUEST;
}
hard_timeout ("send", r);
Tcl_request_rec = r;
propagate_vars_to_neoscript (interp, r);
safeInterp = get_slave_interp(r, "generate-image", safeInterpName);
/* if not exists, make automatic reference to cache */
if (r->finfo.st_mode == 0) {
errstatus = Tcl_GlobalEval(interp, "handle_cached_location");
} else {
errstatus = Tcl_VarEval(interp, "handle_image_request ",
safeInterpName, (char*)NULL);
}
if (errstatus == TCL_ERROR) {
log_error(Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY), r->server);
return NOT_FOUND;
}
if (interp->result == '\0') {
log_reason("no image returned", r->filename, r);
return NOT_FOUND;
}
channel_name = pstrdup(r->pool, interp->result);
if (Tcl_GetOpenFile(interp, channel_name, 0, 0, (ClientData*)&f) == TCL_ERROR) {
log_reason("unable to access tcl_file", interp->result, r);
return NOT_FOUND;
}
if (f == NULL) {
log_reason("file permissions deny server access", interp->result, r);
return FORBIDDEN;
}
/* Save a copy of the file descriptor - must close the one in the
* trusted interp or it would stick around too long.
*/
f = pfdopen(r->pool, dup(fileno(f)), "r");
Tcl_VarEval(interp, "close ", channel_name, (char*)NULL);
fstat(fileno(f), &r->finfo);
if (errstatus = set_content_length (r, r->finfo.st_size))
return errstatus;
if (table_get(r->headers_out, "Expires")) {
if (errstatus = set_last_modified (r, r->finfo.st_mtime))
return errstatus;
}
soft_timeout ("send", r);
send_http_header (r);
if (!r->header_only) send_fd (f, r);
return OK;
}
const char *neowebscript_dir_command(cmd_parms *cmd, neoscript_dir_config *m, char *var, char *val)
{
/*fprintf(stderr, "Dir-Config: %s = %s\n", var, val);*/
table_set (m->neowebscript_dir_vars, var, val);
return NULL;
}
const char *neowebscript_user_command(cmd_parms *cmd, neoscript_dir_config *m, char *var, char *val)
{
/*fprintf(stderr, "User-Config: %s = %s\n", var, val);*/
table_set (m->neowebscript_user_vars, var, val);
return NULL;
}
const char *neowebscript_server_command(cmd_parms *cmd, void *dummy, char *var, char *val)
{
server_rec *s = cmd->server;
neoscript_server_config *ns;
ns = (neoscript_server_config *)get_module_config(s->module_config, &neoscript_module);
table_set (ns->neowebscript_server_vars, var, val);
return NULL;
}
/* -------------------------- The main function --------------------------- */
/* This is a stub which parses a file descriptor. */
void *create_neoscript_dir_config (pool *p, char *dummy)
{
neoscript_dir_config *new =
(neoscript_dir_config *) palloc (p, sizeof(neoscript_dir_config));
new->neowebscript_dir_vars = make_table (p, 4);
new->neowebscript_user_vars = make_table (p, 4);
new->xbithack = DEFAULT_XBITHACK;
return new;
}
void *merge_neoscript_dir_configs (pool *p, void *basev, void *addv)
{
neoscript_dir_config *base = (neoscript_dir_config *)basev;
neoscript_dir_config *add = (neoscript_dir_config *)addv;
neoscript_dir_config *new =
(neoscript_dir_config *)palloc (p, sizeof(neoscript_dir_config));
/*fprintf(stderr, "Merge-Dir-Config\n");*/
new->neowebscript_dir_vars = overlay_tables (p, base->neowebscript_dir_vars,
add->neowebscript_dir_vars);
new->neowebscript_user_vars = overlay_tables (p, base->neowebscript_user_vars,
add->neowebscript_user_vars);
new->xbithack = add->xbithack;
return new;
}
void *create_neoscript_server_config (pool *p, server_rec *s)
{
neoscript_server_config *new =
(neoscript_server_config *) palloc (p, sizeof(neoscript_server_config));
new->neowebscript_server_vars = make_table (p, 4);
return new;
}
void *merge_neoscript_server_configs (pool *p, void *basev, void *addv)
{
neoscript_server_config *base = (neoscript_server_config *)basev;
neoscript_server_config *add = (neoscript_server_config *)addv;
neoscript_server_config *new =
(neoscript_server_config *)palloc (p, sizeof(neoscript_server_config));
new->neowebscript_server_vars
= overlay_tables (p, base->neowebscript_server_vars,
add->neowebscript_server_vars);
return new;
}
const char *neowebscript_cache_path_command (cmd_parms *cmd, neoscript_dir_config *m, char *arg)
{
DB *db;
NeoWebCacheName = server_root_relative(cmd->pool, arg);
NeoWebCacheEnabled = 1;
fprintf(stderr, "Lookaside enabled: %s\n", NeoWebCacheName);
/*
db = dbopen(NeoWebCacheName, (O_CREAT|O_RDWR|DB_LOCK), 0644, DB_HASH, (void *)NULL);
if (db == (DB *)NULL) {
perror(NeoWebCacheName);
} else {
db->close(db);
NeoWebCacheEnabled = 1;
}
*/
return NULL;
}
command_rec neoscript_cmds[] = {
{ "XBitHack", set_xbithack, NULL, OR_OPTIONS, TAKE1, "Off, On, or Full" },
{ "NeoWebServerConf", neowebscript_server_command, NULL, RSRC_CONF, TAKE2, NULL },
{ "NeoWebCache", neowebscript_cache_path_command, NULL, RSRC_CONF, TAKE1, NULL },
{ "NeoWebDirConf", neowebscript_dir_command, NULL, ACCESS_CONF, TAKE2, NULL },
{ "NeoWebUserConf", neowebscript_user_command, NULL, ACCESS_CONF|OR_FILEINFO, TAKE2, "NeoWebUserConf key value: sets NeoWebUserConf(key)=value in safe interpreter" },
{ NULL }
};
handler_rec neoscript_handlers[] = {
{ INCLUDES_MAGIC_TYPE, send_sparsed_file },
{ INCLUDES_MAGIC_TYPE3, send_sparsed_file },
{ "server-parsed", send_sparsed_file },
{ "server-subst", send_subst_file },
{ "text/html", xbithack_handler },
{ "generate-image", send_generated_image },
{ NULL }
};
module neoscript_module = {
STANDARD_MODULE_STUFF,
init_neoscript, /* initializer */
create_neoscript_dir_config, /* dir config creater */
merge_neoscript_dir_configs, /* dir merger --- default is to override */
create_neoscript_server_config, /* server config */
merge_neoscript_server_configs, /* merge server config */
neoscript_cmds, /* command table */
neoscript_handlers, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
NULL, /* fixups */
NULL /* logger */
};