blob: 2565d5b511ee60058004b7832ab97ec75c1c2799 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/***************************************************************************
* Description: DSAPI plugin for Lotus Domino *
* Author: Andy Armstrong <andy@tagish.com> *
* Version: $Revision$ *
***************************************************************************/
/* Based on the IIS redirector by Gal Shachor <shachor@il.ibm.com> */
/* Standard headers */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
/* If we're building under Windows get windows.h. This must be included
* before any APR includes because APR itself does a #include <windows.h>
* after turning off some features that we need.
*/
#ifdef WIN32
#include <windows.h>
#endif
#include "config.h"
/* JK stuff */
#include "jk_global.h"
#include "jk_requtil.h"
#include "jk_map.h"
#include "jk_pool.h"
#include "jk_logger.h"
#include "jk_env.h"
#include "jk_service.h"
#include "jk_worker.h"
#include "apr_general.h"
/* Domino DSAPI filter definitions */
#if !defined(TESTING)
#include <global.h>
#include <addin.h>
#include <osmem.h>
#include <lookup.h>
#endif
#include <dsapi.h>
int JK_METHOD jk2_logger_domino_factory(jk_env_t *env, jk_pool_t *pool, jk_bean_t *result, const char *type, const char *name);
#if defined(TESTING)
#define LOGGER "logger.printf"
int JK_METHOD jk2_logger_printf_factory(jk_env_t *env, jk_pool_t *pool, jk_bean_t *result, const char *type, const char *name);
#else
#define LOGGER "logger.domino"
#endif
#define NULLSTR(s) \
(NULL == (s) || '\0' == (s)[0])
#ifdef WIN32
static char libFileName[MAX_PATH];
static char iniFileName[MAX_PATH];
#define INI_NAME iniFileName
#else
#define INI_NAME "libtomcat2" PROPERTIES_EXT
#endif
static int iniFileUsed = JK_FALSE;
static int isInited = JK_FALSE;
static const char *tomcatStart = NULL;
static const char *tomcatStop = NULL;
static const char *workersFile = NULL;
static const char *serverRoot = NULL;
static int tomcatTimeout = TOMCAT_STARTSTOP_TO;
static const char *crlf = "\r\n";
static const char *filterdesc = FILTERDESC;
#define WORKPOOL globalPool
static jk_workerEnv_t *workerEnv;
static apr_pool_t *jk_globalPool;
/* Per request private data */
typedef struct private_ws {
/* These get passed in by Domino and are used to access various
* Domino methods and data.
*/
FilterContext *context;
FilterParsedRequest *reqData;
/* True iff the response headers have been sent
*/
int responseStarted;
/* Current pointer into and remaining size
* of request body data
*/
char *reqBuffer;
unsigned int reqSize;
} private_ws_t;
#if defined(TESTING)
static void AddInLogMessageText(char *fmt, unsigned short err, ...) {
va_list ap;
va_start(ap, err);
printf("Debug: ");
vprintf(fmt, ap);
printf("\n");
va_end(ap);
}
#endif
/* Case insentive memcmp() clone
*/
#ifdef HAVE_MEMICMP
#define noCaseMemCmp(ci, cj, l) _memicmp((void *) (ci), (void *) (cj), (l))
#else
static int noCaseMemCmp(const char *ci, const char *cj, int len) {
if (0 == memcmp(ci, cj, len)) {
return 0;
}
while (len > 0) {
int cmp = tolower(*ci) - tolower(*cj);
if (cmp != 0) {
return cmp;
}
ci++;
cj++;
len--;
}
return 0;
}
#endif
/* Case insentive strcmp() clone
*/
#ifdef HAVE_STRICMP
#define noCaseStrCmp(si, sj) _stricmp((void *) (si), (void *) (sj))
#else
static int noCaseStrCmp(const char *si, const char *sj) {
if (0 == strcmp(si, sj)) {
return 0;
}
while (*si && tolower(*si) == tolower(*sj)) {
si++;
sj++;
}
return tolower(*si) - tolower(*sj);
}
#endif
/* Case insensitive substring search.
* str string to search
* slen length of string to search
* ptn pattern to search for
* plen length of pattern
* returns 1 if there's a match otherwise 0
*/
static int scanPath(const char *str, int slen, const char *ptn, int plen) {
const char *sp = str;
while (slen >= plen) {
/* We're looking for a match for the specified string bounded by
* the start of the string, \ or / at the left and the end of the
* string, \ or / at the right. We look for \ as well as / on the
* suspicion that a Windows hosted server might accept URIs
* containing \.
*/
if (noCaseMemCmp(sp, ptn, plen) == 0 &&
(sp == str || sp[-1] == '\\' || sp[-1] == '/') &&
(slen == plen || sp[plen] == '\\' || sp[plen] == '/')) {
return 1;
}
slen--;
sp++;
}
return 0;
}
#ifdef _DEBUG
static void _printf(const char *msg, ...) {
char buf[512]; /* dangerous fixed size buffer */
va_list ap;
va_start(ap, msg);
vsprintf(buf, msg, ap);
va_end(ap);
AddInLogMessageText("Debug: %s", NOERROR, buf);
}
#endif
/* Get the value of a server (CGI) variable as a string
*/
static int getVariable(struct jk_env *env, jk_ws_service_t *s, char *hdrName,
char *buf, unsigned int bufsz, char **dest, const char *dflt) {
int errID;
private_ws_t *ws = (private_ws_t *) s->ws_private;
if (ws->context->GetServerVariable(ws->context, hdrName, buf, bufsz, &errID)) {
*dest = s->pool->pstrdup(env, s->pool, buf);
} else {
*dest = s->pool->pstrdup(env, s->pool, dflt);
}
return JK_TRUE;
}
/* Get the value of a server (CGI) variable as an integer
*/
static int getVariableInt(struct jk_env *env, jk_ws_service_t *s, char *hdrName,
char *buf, unsigned int bufsz, long *dest, long dflt) {
int errID;
private_ws_t *ws = (private_ws_t *) s->ws_private;
if (ws->context->GetServerVariable(ws->context, hdrName, buf, bufsz, &errID)) {
*dest = atol(buf);
} else {
*dest = dflt;
}
return JK_TRUE;
}
/* Get the value of a server (CGI) variable as an integer
*/
static int getVariableBool(struct jk_env *env, jk_ws_service_t *s, char *hdrName,
char *buf, unsigned int bufsz, int *dest, int dflt) {
int errID;
private_ws_t *ws = (private_ws_t *) s->ws_private;
if (ws->context->GetServerVariable(ws->context, hdrName, buf, bufsz, &errID)) {
if (isdigit(buf[0])) {
*dest = atoi(buf) != 0;
} else if (noCaseStrCmp(buf, "yes") == 0 || noCaseStrCmp(buf, "on") == 0) {
*dest = 1;
} else {
*dest = 0;
}
} else {
*dest = dflt;
}
return JK_TRUE;
}
/* A couple of utility macros to supply standard arguments to getVariable() and
* getVariableInt().
*/
#define GETVARIABLE(name, dest, dflt) \
getVariable(env, s, (name), workBuf, sizeof(workBuf), (dest), (dflt))
#define GETVARIABLEINT(name, dest, dflt) \
getVariableInt(env, s, (name), workBuf, sizeof(workBuf), (dest), (dflt))
#define GETVARIABLEBOOL(name, dest, dflt) \
getVariableBool(env, s, (name), workBuf, sizeof(workBuf), (dest), (dflt))
/* Return 1 iff the supplied string contains "web-inf" (in any case
* variation. We don't allow URIs containing web-inf, although
* scanPath() actually looks for the string bounded by path punctuation
* or the ends of the string, so web-inf must appear as a single element
* of the supplied URI
*/
static int badURI(const char *uri) {
static const char *wi = "web-inf";
return scanPath(uri, strlen(uri), wi, strlen(wi));
}
/* Replacement for strcat() that updates a buffer pointer. It's
* probably marginal, but this should be more efficient that strcat()
* in cases where the string being concatenated to gets long because
* strcat() has to count from start of the string each time.
*/
static void append(char **buf, const char *str) {
int l = strlen(str);
memcpy(*buf, str, l);
(*buf)[l] = '\0';
*buf += l;
}
/* Allocate space for a string given a start pointer and an end pointer
* and return a pointer to the allocated, copied string.
*/
static char *subStr(jk_env_t *env, jk_pool_t *pool, const char *start, const char *end) {
char *out = NULL;
if (start != NULL && end != NULL && end > start) {
int len = end - start;
if (out = pool->alloc(env, pool, len + 1), NULL != out) {
memcpy(out, start, len);
out[len] = '\0';
}
}
return out;
}
/* Like subStr() but use a static buffer if possible.
*/
static char *smartSubStr(jk_env_t *env, jk_pool_t *pool, char **bufp, int *bufSz,
const char *start, const char *end) {
int len = end - start;
if (len < *bufSz) {
char *rv = *bufp;
memcpy(rv, start, len);
rv[len++] = '\0';
/* Adjust buffer pointer, length */
*bufp += len;
*bufSz -= len;
return rv;
} else {
return subStr(env, pool, start, end);
}
}
static int JK_METHOD cbInit(struct jk_env *env, jk_ws_service_t *s,
struct jk_worker *w, void *context) {
return JK_TRUE;
}
/* Post request cleanup.
*/
static void JK_METHOD cbAfterRequest( struct jk_env *env, jk_ws_service_t *_this) {
}
/* Set the response head in the server structures. This will be called
* before the first write.
*/
static int JK_METHOD cbHead(struct jk_env *env, jk_ws_service_t *s) {
if (s->status < 100 || s->status >= 1000) {
env->l->jkLog(env, env->l, JK_LOG_ERROR, "jk_ws_service_t::cbHead, invalid status %d\n", s->status);
return JK_ERR;
}
if (s && s->ws_private) {
private_ws_t *p = s->ws_private;
if (!p->responseStarted) {
char *hdrBuf;
FilterResponseHeaders frh;
int rc, errID;
int hdrCount;
const char *reason;
p->responseStarted = JK_TRUE;
reason = (NULL == s->msg) ? "" : s->msg;
hdrCount = s->headers_out->size(env, s->headers_out);
/* Build a single string containing all the headers
* because that's what Domino needs.
*/
if (hdrCount > 0) {
int i;
int hdrLen;
char *bufp;
for (i = 0, hdrLen = 3; i < hdrCount; i++) {
hdrLen += strlen(s->headers_out->nameAt(env, s->headers_out, i));
hdrLen += strlen(s->headers_out->valueAt(env, s->headers_out, i));
hdrLen += 4;
}
hdrBuf = s->pool->alloc(env, s->pool, hdrLen);
bufp = hdrBuf;
for (i = 0; i < hdrCount; i++) {
append(&bufp, s->headers_out->nameAt(env, s->headers_out, i));
append(&bufp, ": ");
append(&bufp, s->headers_out->valueAt(env, s->headers_out, i));
append(&bufp, crlf);
}
append(&bufp, crlf);
} else {
hdrBuf = (char *) crlf;
}
frh.responseCode = s->status;
frh.reasonText = (char *) reason;
frh.headerText = hdrBuf;
/* Send the headers */
rc = p->context->ServerSupport(p->context, kWriteResponseHeaders, &frh, NULL, 0, &errID);
}
return JK_OK;
}
env->l->jkLog(env, env->l, JK_LOG_ERROR, "jk_ws_service_t::cbHead, NULL parameters\n");
return JK_ERR;
}
/*
* Read a chunk of the request body into a buffer. Attempt to read len
* bytes into the buffer. Write the number of bytes actually read into
* nRead.
*/
static int JK_METHOD cbRead(struct jk_env *env, jk_ws_service_t *s,
void *bytes, unsigned len, unsigned *nRead) {
if (s && s->ws_private && bytes && nRead) {
private_ws_t *p = s->ws_private;
/* Copy data from Domino's buffer. Although it seems slightly
* improbably we're believing that Domino always buffers the
* entire request in memory. Not properly tested yet.
*/
if (len > p->reqSize) {
len = p->reqSize;
}
memcpy(bytes, p->reqBuffer, len);
p->reqBuffer += len;
p->reqSize -= len;
*nRead = len;
return JK_OK;
}
env->l->jkLog(env, env->l, JK_LOG_ERROR, "jk_ws_service_t::Read, NULL parameters\n");
return JK_ERR;
}
/*
* Write a chunk of response data back to the browser.
*/
static int JK_METHOD cbWrite(struct jk_env *env, jk_ws_service_t *s,
const void *bytes, unsigned len) {
/* env->l->jkLog(env, env->l, JK_LOG_DEBUG, "Into jk_ws_service_t::Write\n"); */
if (s && s->ws_private && bytes) {
private_ws_t *p = s->ws_private;
int errID, rc;
/* Send the data */
if (len > 0) {
rc = p->context->WriteClient(p->context, (char *) bytes, len, 0, &errID);
}
return JK_OK;
}
env->l->jkLog(env, env->l, JK_LOG_ERROR, "jk_ws_service_t::Write, NULL parameters\n");
return JK_ERR;
}
/*
* Flush the output buffers.
*/
static int JK_METHOD cbFlush(struct jk_env *env, jk_ws_service_t *s) {
return JK_OK;
}
/* Return TRUE iff the specified filename is absolute. Note that this is only
* called in cases where the definition of 'absolute' is not security sensitive. I'm
* sure there are ways of constructing absolute Win32 paths that it doesn't catch.
*/
static int isAbsolutePath(const char *fn) {
#ifdef WIN32
return fn[0] == '\\' || (isalpha(fn[0]) && fn[1] == ':');
#else
return fn[0] == '/';
#endif
}
static const char *makeAbsolutePath(jk_env_t *env, const char *base, const char *name) {
if (base == NULL || isAbsolutePath(name)) {
return name;
} else {
int bsz = strlen(base);
int nsz = strlen(name);
int ads = (base[bsz-1] != PATHSEP) ? 1 : 0;
char *buf;
if (buf = env->WORKPOOL->alloc(env, env->WORKPOOL, bsz + ads + nsz + 1), NULL == buf) {
return NULL;
}
memcpy(buf, base, bsz);
if (ads) {
buf[bsz] = PATHSEP;
}
memcpy(buf + bsz + ads, name, nsz);
buf[bsz + ads + nsz] = '\0';
return buf;
}
}
#ifdef WIN32
static const char *readRegistry(jk_env_t *env, HKEY hkey, const char *key, const char *base) {
unsigned int type = 0;
unsigned int sz = 0;
LONG rc;
char *val;
rc = RegQueryValueEx(hkey, key, (unsigned int) 0, &type, NULL, &sz);
if (rc != ERROR_SUCCESS || type != REG_SZ) {
return NULL;
}
if (val = env->WORKPOOL->alloc(env, env->WORKPOOL, sz), NULL == val) {
return NULL;
}
rc = RegQueryValueEx(hkey, key, (unsigned int) 0, &type, val, &sz);
if (rc == ERROR_SUCCESS) {
return makeAbsolutePath(env, base, val);
}
return NULL;
}
#endif
static int readFromRegistry(jk_env_t *env) {
#ifdef WIN32
HKEY hkey;
long rc;
const char *timeout;
rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_LOCATION, (unsigned int) 0, KEY_READ, &hkey);
if (ERROR_SUCCESS != rc) {
return JK_FALSE;
}
serverRoot = readRegistry(env, hkey, SERVER_ROOT_TAG, NULL);
workersFile = readRegistry(env, hkey, WORKER_FILE_TAG, serverRoot);
tomcatStart = readRegistry(env, hkey, TOMCAT_START_TAG, serverRoot);
tomcatStop = readRegistry(env, hkey, TOMCAT_STOP_TAG, serverRoot);
timeout = readRegistry(env, hkey, TOMCAT_TIMEOUT_TAG, NULL);
if (timeout != NULL) {
tomcatTimeout = atoi(timeout);
}
RegCloseKey(hkey);
iniFileUsed = JK_FALSE;
return NULL != serverRoot &&
NULL != workersFile;
#else
return JK_FALSE;
#endif
}
/* Read an entry from a map and return a newly allocated copy of it
* on success or NULL on failure.
*/
static const char *readMap(jk_env_t *env, jk_map_t *map, const char *name, const char *base)
{
const char *s = map->get(env, map, name);
if (s) {
return makeAbsolutePath(env, base, env->WORKPOOL->pstrdup(env, env->WORKPOOL, s));
} else {
return NULL;
}
}
/* Read parameters from an ini file or the registry
*/
static int readConfigData(jk_env_t *env) {
jk_map_t *map;
/* Attempt to read from an ini file */
if (JK_OK == jk2_map_default_create(env, &map, env->WORKPOOL )) {
if (JK_OK == jk2_config_file_read(env, map, INI_NAME)) {
const char *timeout;
serverRoot = readMap(env, map, SERVER_ROOT_TAG, NULL);
workersFile = readMap(env, map, WORKER_FILE_TAG, serverRoot);
tomcatStart = readMap(env, map, TOMCAT_START_TAG, serverRoot);
tomcatStop = readMap(env, map, TOMCAT_STOP_TAG, serverRoot);
timeout = readMap(env, map, TOMCAT_TIMEOUT_TAG, NULL);
if (timeout != NULL) {
tomcatTimeout = atoi(timeout);
}
iniFileUsed = JK_TRUE;
return NULL != serverRoot &&
NULL != workersFile;
}
} else {
/* can't log here */
/*env->l->jkLog(env, env->l, JK_LOG_ERROR,
"read_registry_init_data, Failed to create map \n");*/
}
return readFromRegistry(env);
}
/* Send a simple response. Used when we don't want to bother Tomcat,
* which in practice means for various error conditions that we can
* detect internally.
*/
static void simpleResponse(FilterContext *context, int status, char *reason, char *body) {
FilterResponseHeaders frh;
int rc, errID;
char hdrBuf[35];
sprintf(hdrBuf, "Content-type: text/html%s%s", crlf, crlf);
frh.responseCode = status;
frh.reasonText = reason;
frh.headerText = hdrBuf;
rc = context->ServerSupport(context, kWriteResponseHeaders, &frh, NULL, 0, &errID);
rc = context->WriteClient(context, body, strlen(body), 0, &errID);
}
/* Called to reject a URI that contains the string "web-inf". We block
* these because they may indicate an attempt to invoke arbitrary code.
*/
static unsigned int rejectBadURI(FilterContext *context) {
static char *msg = "<html><body><h1>Access is Forbidden</h1></body></html>";
simpleResponse(context, 403, "Forbidden", msg);
return kFilterHandledRequest;
}
/* Called to generate a generic error response.
*/
static unsigned int rejectWithError(FilterContext *context, const char *diagnostic) {
static char *msg = "<html><body><h1>Error in Filter</h1><p>%s</p></body></html>";
char *mbuf;
int errID;
size_t bufSz;
if (!NONBLANK(diagnostic)) {
diagnostic = "Unspecified error";
}
/* This assumes that we're just going to expand the diagnostic into
* the page body.
*/
bufSz = strlen(msg) + strlen(diagnostic) + 1;
mbuf = context->AllocMem(context, bufSz, 0, &errID);
sprintf(mbuf, msg, diagnostic);
simpleResponse(context, 500, "Error in Filter", mbuf);
return kFilterHandledRequest;
}
#if !defined(TESTING)
/* Get the info from the lookup buffer
*/
static int getLookupInfo(FilterContext *context, char *pMatch, WORD itemNumber, char **pInfo) {
unsigned int reserved = 0;
unsigned int errID;
char *pValue = NULL;
WORD lValue, type;
STATUS error;
if (NULL == pMatch || NULL == pInfo) {
return -1;
}
/* Initialize output */
*pInfo = NULL;
/* Check the type and length of the info */
pValue = (char *) NAMELocateItem(pMatch, itemNumber, &type, &lValue);
if (NULL == pValue || lValue == 0) {
return -1;
}
lValue -= sizeof(WORD); /* remove datatype word included in the list length */
lValue++; /* Include length of terminator */
/* check the value type */
if (type != TYPE_TEXT_LIST && type != TYPE_TEXT) {
return -1;
}
/* Allocate space for the info. This memory will be freed automatically when the thread terminates */
if (*pInfo = context->AllocMem(context, lValue, reserved, &errID), NULL == *pInfo) {
return -1;
}
/* Get the info */
if (error = NAMEGetTextItem(pMatch, itemNumber, 0, *pInfo, lValue), !error) {
return 0;
}
return -1;
}
#endif
/* Lookup the user and return the user's full name
*/
static int getUserName(FilterContext *context, char *userName, char **pUserName) {
#if defined(TESTING)
*pUserName = userName;
return NOERROR;
#else
STATUS error = NOERROR;
HANDLE hLookup = NULLHANDLE;
unsigned short nMatches = 0;
char *pLookup = NULL;
char *pName = NULL;
char *pMatch = NULL;
int rc = -1;
if (NULL == userName || NULL == pUserName) {
return rc;
}
/* Initializae output */
*pUserName = NULL;
/* do the name lookup */
error = NAMELookup(NULL, 0, 1, "$Users", 1, userName, 2, "FullName", &hLookup);
if (error || (NULLHANDLE == hLookup)) {
goto done;
}
pLookup = (char *) OSLockObject(hLookup);
/* Get pointer to our entry. */
pName = (char *) NAMELocateNextName(pLookup, NULL, &nMatches);
/* If we didn't find the entry, the quit */
if (NULL == pName || nMatches <= 0) {
goto done;
}
if (pMatch = (char *) NAMELocateNextMatch(pLookup, pName, NULL), NULL == pMatch) {
goto done;
}
/* Get the full name from the info we got back */
if (getLookupInfo(context, pMatch, 0, pUserName)) {
goto done;
}
rc = 0;
done:
if(NULLHANDLE != hLookup) {
if (NULL != pLookup) {
OSUnlock(hLookup);
}
OSMemFree(hLookup);
}
return rc;
#endif
}
/* Given all the HTTP headers as a single string parse them into individual
* name, value pairs.
*/
static int parseHeaders(jk_env_t *env, jk_ws_service_t *s, const char *hdrs, int hdrSz) {
int hdrCount = 0;
const char *limit = hdrs + hdrSz;
const char *name, *nameEnd;
const char *value, *valueEnd;
int gotContentLength = JK_FALSE;
char buf[256]; /* Static buffer used for headers that are short enough to fit
* in it. A dynamic buffer is used for any longer headers.
*/
while (hdrs < limit) {
/* buf is re-used for each header */
char *bufp = buf;
char *hdrName, *hdrValue;
int sz = sizeof(buf);
/* Skip line *before* doing anything, cos we want to lose the first line which
* contains the request. This code also moves to the next line after each header.
*/
while (hdrs < limit && (*hdrs != '\n' && *hdrs != '\r')) {
hdrs++;
}
while (hdrs < limit && (*hdrs == '\n' || *hdrs == '\r')) {
hdrs++;
}
if (hdrs >= limit) {
break;
}
name = nameEnd = value = valueEnd = NULL;
name = hdrs;
while (hdrs < limit && *hdrs >= ' ' && *hdrs != ':') {
hdrs++;
}
nameEnd = hdrs;
if (hdrs < limit && *hdrs == ':') {
hdrs++;
while (hdrs < limit && (*hdrs == ' ' || *hdrs == '\t')) {
hdrs++;
}
value = hdrs;
while (hdrs < limit && *hdrs >= ' ') {
hdrs++;
}
valueEnd = hdrs;
}
hdrName = smartSubStr(env, s->pool, &bufp, &sz, name, nameEnd);
/* Need to strdup the value because map->put doesn't for some reason */
hdrValue = subStr(env, s->pool, value, valueEnd);
s->headers_in->put(env, s->headers_in, hdrName, hdrValue, NULL);
gotContentLength |= (noCaseStrCmp(hdrName, CONTENT_LENGTH) == 0);
hdrCount++;
}
/* Add a zero length content-length header if none was found in the
* request.
*/
if (!gotContentLength) {
s->headers_in->put(env, s->headers_in, CONTENT_LENGTH, "0", NULL);
hdrCount++;
}
return hdrCount;
}
/* Initialize the service structure
*/
static int processRequest(struct jk_env *env, jk_ws_service_t *s,
struct jk_worker *w, FilterRequest *fr) {
/* This is the only fixed size buffer left. It won't be overflowed
* because the Domino API that reads into the buffer accepts a length
* constraint, and it's unlikely ever to be exhausted because the
* strings being will typically be short, but it's still aesthetically
* troublesome.
*/
char workBuf[16 * 1024];
private_ws_t *ws = (private_ws_t *) s->ws_private;
char *hdrs;
int hdrsz;
int errID = 0;
int hdrCount;
long port;
static char *methodName[] = { "", "HEAD", "GET", "POST", "PUT", "DELETE" };
s->jvm_route = NULL;
GETVARIABLE("AUTH_TYPE", &s->auth_type, "");
GETVARIABLE("REMOTE_USER", &s->remote_user, "");
/* If the REMOTE_USER CGI variable doesn't work try asking Domino */
if (NULLSTR(s->remote_user) && !NULLSTR(fr->userName)) {
getUserName(ws->context, fr->userName, &s->remote_user);
}
GETVARIABLE("SERVER_PROTOCOL", &s->protocol, "");
GETVARIABLE("REMOTE_HOST", &s->remote_host, "");
GETVARIABLE("REMOTE_ADDR", &s->remote_addr, "");
GETVARIABLE("SERVER_NAME", &s->server_name, "");
GETVARIABLEINT("SERVER_PORT", &port, 80);
GETVARIABLE("SERVER_SOFTWARE", &s->server_software, SERVERDFLT);
GETVARIABLEINT("CONTENT_LENGTH", &s->content_length, 0);
s->server_port = (int) port;
/* SSL Support
*/
GETVARIABLEBOOL("HTTPS", &s->is_ssl, 0);
if (ws->reqData->requestMethod < 0 ||
ws->reqData->requestMethod >= sizeof(methodName) / sizeof(methodName[0])) {
return JK_ERR;
}
s->method = methodName[ws->reqData->requestMethod];
s->ssl_cert_len = fr->clientCertLen;
s->ssl_cert = fr->clientCert;
s->ssl_cipher = NULL;
s->ssl_session = NULL;
s->ssl_key_size = -1;
if (JK_OK != jk2_map_default_create(env, &s->headers_out, s->pool)) {
env->l->jkLog(env, env->l, JK_LOG_ERROR, "jk_ws_service_t::init, Failed to create headers_out map\n");
return JK_ERR;
}
if (JK_OK != jk2_map_default_create(env, &s->attributes, s->pool)) {
env->l->jkLog(env, env->l, JK_LOG_ERROR, "jk_ws_service_t::init, Failed to create attributes map\n");
return JK_ERR;
}
if (JK_OK != jk2_map_default_create(env, &s->headers_in, s->pool)) {
env->l->jkLog(env, env->l, JK_LOG_ERROR, "jk_ws_service_t::init, Failed to create headers_in map\n");
return JK_ERR;
}
if (s->is_ssl) {
int i;
long dummy;
/* It seems that Domino doesn't actually expose many of these but we live in hope.
*/
char *sslNames[] = {
"CERT_ISSUER", "CERT_SUBJECT", "CERT_COOKIE", "CERT_FLAGS", "CERT_SERIALNUMBER",
"HTTPS_SERVER_SUBJECT", "HTTPS_SECRETKEYSIZE", "HTTPS_SERVER_ISSUER", "HTTPS_KEYSIZE"
};
/* env->l->jkLog(env, env->l, JK_LOG_DEBUG, "Request is SSL\n"); */
/* Read the variable into a dummy variable: we do this for the side effect of
* reading it into workBuf.
*/
GETVARIABLEINT("HTTPS_KEYSIZE", &dummy, 0);
if (workBuf[0] == '[') {
s->ssl_key_size = atoi(workBuf+1);
}
/* Should also try to make suitable values for s->ssl_cipher and
* s->ssl_session
*/
for (i = 0; i < sizeof(sslNames) / sizeof(sslNames[0]); i++) {
char *value = NULL;
GETVARIABLE(sslNames[i], &value, NULL);
if (value) {
s->attributes->put(env, s->attributes, sslNames[i], value, NULL);
}
}
}
/* Duplicate all the headers now
*/
hdrsz = ws->reqData->GetAllHeaders(ws->context, &hdrs, &errID);
if (0 == hdrsz) {
return JK_ERR;
}
hdrCount = parseHeaders(env, s, hdrs, hdrsz);
return JK_OK;
}
/* Handle an HTTP request. Works out whether Tomcat will be interested then either
* despatches it to Tomcat or passes it back to Domino.
*/
static unsigned int parsedRequest(FilterContext *context, FilterParsedRequest *reqData) {
unsigned int errID;
int rc;
FilterRequest fr;
int result = kFilterNotHandled;
char *h = NULL;
/* TODO: presumably this return code should be checked */
rc = context->GetRequest(context, &fr, &errID);
if (NONBLANK(fr.URL)) {
char *uri = fr.URL;
char *qp, *turi;
jk_uriEnv_t *uriEnv = NULL;
int errID;
char buf[256]; /* enough for the server's name */
char *colon;
char *serverName;
size_t serverNameSz;
int serverPort;
char *uriBuf;
size_t uriSz, uriBufSz;
jk_env_t *env = workerEnv->globalEnv->getEnv(workerEnv->globalEnv);
/* env->l->jkLog(env, env->l, JK_LOG_DEBUG, "parsedRequest() - %s\n", uri); */
/* We used to call the context->GetServerVariable() API here but doing so
* seems to clobber some of the server's CGI variables in the case where
* we don't handle the request.
*
* Note also that we're using a static buffer for the host header. Presumably
* hostnames longer than 255 characters are either rare or illegal and there's
* no buffer overrun risk because Domino errors if the supplied buffer is too
* small.
*/
if (!reqData->GetHeader(context, "host", buf, sizeof(buf), &errID)) {
return rejectWithError(context, "Failed to retrieve host");
}
serverName = buf;
/* Parse out the port number */
if (colon = strchr(serverName, ':'), NULL != colon) {
*colon++ = '\0';
serverPort = atoi(colon);
} else {
serverPort = 80;
}
serverNameSz = strlen(serverName) + 1;
uriBuf = serverName + serverNameSz;
uriBufSz = sizeof(buf) - serverNameSz;
uriSz = strlen(uri) + 1;
/* Use the stack buffer for sufficiently short URIs */
if (uriSz <= uriBufSz) {
turi = uriBuf;
} else {
turi = context->AllocMem(context, uriSz, 0, &errID);
}
memcpy(turi, uri, uriSz);
if (qp = strchr(turi, '?'), qp != NULL) {
*qp++ = '\0';
}
rc = jk_requtil_unescapeUrl(turi);
if (rc < 0) {
return rejectBadURI(context);
}
jk_requtil_getParents(turi);
if (badURI(turi)) {
return rejectBadURI(context);
}
uriEnv = workerEnv->uriMap->mapUri(env, workerEnv->uriMap, serverName, serverPort, turi);
if (NULL != uriEnv) {
// Here we go
private_ws_t ws;
jk_ws_service_t s;
jk_pool_t *rPool = NULL;
jk_worker_t *worker = uriEnv->worker;
rPool = worker->rPoolCache->get(env, worker->rPoolCache);
if (NULL == rPool) {
rPool = worker->mbean->pool->create(env, worker->mbean->pool, HUGE_POOL_SIZE);
/* env->l->jkLog(env, env->l, JK_LOG_DEBUG, "HttpExtensionProc: new rpool\n"); */
}
jk2_requtil_initRequest(env, &s);
/* reset the reco_status, will be set to INITED in LB mode */
s.reco_status = RECO_NONE;
s.pool = rPool;
s.is_recoverable_error = JK_FALSE;
s.response_started = JK_FALSE;
s.content_read = 0;
s.ws_private = &ws;
s.workerEnv = workerEnv;
s.head = cbHead;
s.read = cbRead;
s.write = cbWrite;
s.init = cbInit; /* never seems to be used */
s.afterRequest = cbAfterRequest;
if (workerEnv->options == JK_OPT_FWDURICOMPATUNPARSED) {
size_t sz = strlen(uri) + 1;
s.req_uri = context->AllocMem(context, sz, 0, &errID);
memcpy(s.req_uri, uri, sz);
/* Find the query string again in the original URI */
if (qp = strchr(s.req_uri, '?'), NULL != qp) {
*qp++ = '\0';
}
} else if (workerEnv->options == JK_OPT_FWDURIESCAPED) {
/* Nasty static buffer */
char euri[256];
if (jk_requtil_escapeUrl(turi, buf, sizeof(euri))) {
turi = euri;
}
s.req_uri = turi;
} else {
s.req_uri = turi;
}
s.query_string = qp;
/* Init our private structure
*/
ws.responseStarted = JK_FALSE;
ws.context = context;
ws.reqData = reqData;
/* Fetch info about the request
*/
ws.reqSize = context->GetRequestContents(context, &ws.reqBuffer, &errID);
rc = processRequest(env, &s, worker, &fr);
if (JK_OK == rc) {
rc = worker->service(env, uriEnv->worker, &s);
}
if (JK_OK == rc) {
result = kFilterHandledRequest;
/* env->l->jkLog(env, env->l, JK_LOG_DEBUG, "HttpExtensionProc service() returned OK\n"); */
} else {
result = kFilterError;
env->l->jkLog(env, env->l, JK_LOG_ERROR, "HttpExtensionProc error, service() failed\n");
}
rPool->reset(env, rPool);
rc = worker->rPoolCache->put(env, worker->rPoolCache, rPool);
}
workerEnv->globalEnv->releaseEnv(workerEnv->globalEnv, env);
}
return result;
}
static int runProg(const char *cmd) {
#ifdef WIN32
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si); // Start the child process.
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWMAXIMIZED;
if (!CreateProcess(NULL, (char *) cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
DWORD err = GetLastError();
AddInLogMessageText("Command \"%s\" failed (error %u)", NOERROR, cmd, err);
return JK_FALSE;
}
if (WAIT_OBJECT_0 == WaitForSingleObject(pi.hProcess, tomcatTimeout)) {
return JK_TRUE;
}
AddInLogMessageText("Command \"%s\" didn't complete in time", NOERROR, cmd);
return JK_FALSE;
#else
int err = system(cmd);
if (0 == err) {
return 1;
}
AddInLogMessageText("Command \"%s\" failed (error %d)", NOERROR, cmd, err);
return 0;
#endif
}
/* Main entry point for the filter. Called by Domino for every HTTP request.
*/
DLLEXPORT unsigned int HttpFilterProc(FilterContext *context, unsigned int eventType, void *eventData) {
switch (eventType) {
case kFilterParsedRequest:
return parsedRequest(context, (FilterParsedRequest *) eventData);
default:
break;
}
return kFilterNotHandled;
}
/* Called when the filter is unloaded. Free various resources and
* display a banner.
*/
DLLEXPORT unsigned int TerminateFilter(unsigned int reserved) {
/*jk_env_t *env = workerEnv->globalEnv->getEnv(workerEnv->globalEnv);*/
// TODO: Work out if we're doing everything we need to here
if (isInited) {
if (workerEnv) {
jk_env_t *env = workerEnv->globalEnv;
workerEnv->close(env, workerEnv);
}
isInited = JK_FALSE;
}
#if !defined(TESTING)
if (NONBLANK(tomcatStop)) {
AddInLogMessageText("Attempting to stop Tomcat: %s", NOERROR, tomcatStop);
runProg(tomcatStop);
}
#endif
AddInLogMessageText("%s unloaded", NOERROR, filterdesc);
apr_pool_destroy(jk_globalPool);
return kFilterHandledEvent;
}
/* Called when Domino loads the filter. Reads a load of config data from
* the registry and elsewhere and displays a banner.
*/
DLLEXPORT unsigned int FilterInit(FilterInitData *filterInitData) {
jk_logger_t *l;
jk_pool_t *globalPool;
jk_bean_t *jkb;
jk_env_t *env;
apr_initialize();
apr_pool_create(&jk_globalPool, NULL);
jk2_pool_apr_create(NULL, &globalPool, NULL, jk_globalPool);
/* Create the global environment. This will register the default
* factories
*/
env = jk2_env_getEnv(NULL, globalPool);
if (!readConfigData(env)) {
goto initFailed;
}
/* Create the workerEnv.
*/
jkb = env->createBean2(env, env->globalPool,"workerEnv", "");
workerEnv = jkb->object;
env->alias(env, "workerEnv:", "workerEnv");
workerEnv->childId = 0;
if (NULL == workerEnv) {
goto initFailed;
}
/* Create the logger
*/
#if defined(TESTING)
env->registerFactory(env, "logger.printf", jk2_logger_printf_factory);
#else
env->registerFactory(env, "logger.domino", jk2_logger_domino_factory );
#endif
jkb = env->createBean2(env, env->globalPool, LOGGER, "");
env->alias(env, LOGGER ":", "logger");
l = jkb->object;
env->l = l;
#ifdef WIN32
env->soName = env->globalPool->pstrdup(env, env->globalPool, libFileName);
if (NULL == env->soName) {
env->l->jkLog(env, env->l, JK_LOG_ERROR, "Error creating env->soName\n");
goto initFailed;
}
#else
env->soName = NULL;
#endif
workerEnv->initData->add(env, workerEnv->initData, "serverRoot",
workerEnv->pool->pstrdup(env, workerEnv->pool, serverRoot));
/* Note: we cast away the const qualifier on workersFile here
*/
if (JK_OK != workerEnv->config->setPropertyString(env, workerEnv->config,
"config.file", (char *) workersFile)) {
goto initFailed;
}
env->l->init(env, env->l);
workerEnv->init(env, workerEnv);
#if !defined(TESTING)
/* Attempt to launch Tomcat
*/
if (NONBLANK(tomcatStart)) {
AddInLogMessageText("Attempting to start Tomcat: %s", NOERROR, tomcatStart);
runProg(tomcatStart);
}
#endif
filterInitData->appFilterVersion = kInterfaceVersion;
filterInitData->eventFlags = kFilterParsedRequest;
strncpy(filterInitData->filterDesc, filterdesc, sizeof(filterInitData->filterDesc));
isInited = JK_TRUE;
/* Display banner
*/
AddInLogMessageText("%s loaded", NOERROR, filterInitData->filterDesc);
return kFilterHandledEvent;
initFailed:
AddInLogMessageText("Error loading %s", NOERROR, filterdesc);
return kFilterError;
}
#ifdef WIN32
/* Handle DLL initialisation (on WIN32) by working out what the INI file
* should be called.
*/
BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason, LPVOID lpReserved) {
BOOL fReturn = TRUE;
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
switch (ulReason) {
case DLL_PROCESS_ATTACH:
if (GetModuleFileName(hInst, libFileName, sizeof(libFileName))) {
_splitpath(libFileName, drive, dir, fname, NULL);
_makepath(iniFileName, drive, dir, fname, PROPERTIES_EXT);
} else {
fReturn = FALSE;
}
break;
default:
break;
}
return fReturn;
}
#endif
#if defined(TESTING)
/* Handle initialisation in the test harness environment.
*/
void TestMain(void) {
strcpy(libFileName, "test.exe");
strcpy(iniFileName, "test" PROPERTIES_EXT);
}
#endif