/* Inomads Apache module
 *
 * (C) copyright 2009-2014 PVX Plus Technologies Ltd., Ontario, Canada
 * All rights reserved worldwide.
 * Unauthorized use of this code is strictly prohibited.
 *
 * This is the Apache load module used by iNomads
 */

#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>
#include <http_core.h>
#include <apr_strings.h>
#include <apr_sha1.h>
#include <string.h>
#include <http_log.h>

#if MODULE_MAGIC_NUMBER_MAJOR > 20111129
#define remote_ip	client_ip
#endif

#if defined(_WINDOWS)
#define NL				"\r\n"
#define PXPLUS_EXE		"pxplus.exe"
#define PTR_FMT			"%8.8x"
#else
#define NL		"\n"
#define PXPLUS_EXE		"pxplus"
#define PTR_FMT			"%p"
#include <unistd.h>

#if defined(BOOL)
#else
#define BOOL int
#endif

#define stricmp(a,b) apr_strnatcasecmp(a,b)

#define min(a,b) (((a)<(b))?(a):(b))
#define max(a,b) (((a)>(b))?(a):(b))

int MyMemicmp(const void *s1, const void *s2, size_t n)
{	int	nOfst;
    int nDiff;

    unsigned char *p1 = (unsigned char *)s1;
    unsigned char *p2 = (unsigned char *)s2;
    unsigned char c1, c2;

    for (nOfst = 0; nOfst < n; nOfst++ )
	{	c1 = p1[nOfst];
		c2 = p2[nOfst];
		if (c1 == c2) continue;
		
		if ((c1 >= 'a') && (c1 <= 'z')) c1 = c1 - 'a' + 'A';
		if ((c2 >= 'a') && (c2 <= 'z')) c2 = c2 - 'a' + 'A';
		
		nDiff = c1 - c2;
		if (nDiff != 0)return(nDiff);
    }
	return 0;
}

#define memicmp MyMemicmp

#endif

#define MAX_PATH_NAME		256

static int UnEscapeURL(char *pVal);

struct SESSION
{	char				szSession[20];		/* Session ID */
	apr_pool_t			*pPool;				/* Memory sub-Pool */
	apr_socket_t		*pSocket;			/* Socket for session */
	int					nPort;				/* Session Port number */
	int					nReq;				/* Active requests */
	int					nIdleHours;			/* Number of Idle Hours (aprx) */
};

#define SESSION_CNT		100				/* Number of active sessions in a block */

struct SESSIONS
{	struct SESSIONS		*pNext;					/* Next session block */
	struct SESSION		session[SESSION_CNT];	/* Individual session information */
};


struct CONFIG
{	apr_thread_mutex_t	*mutexSessions;	/* Sessions table mutex */
	struct SESSIONS		*pSessions;		/* Linked list of sessions */
	char				*pDir;			/* Application directory */
	char				*pExe;			/* PxPlus exec */
	char				*pLib;			/* PxPlus library */
	char				*pHtml;			/* PxPlus syshtml directory */
	char				*pLaunchIP;		/* Launcher IP address */
	int					nLaunchPort;	/* Launcher Port number */
	int					nTimeout;		/* Timeout in seconds */
	char				*pLaunchPswd;	/* Launcher Password */
	char				*pRmtRoot;		/* LaunchPort Remote webroot */
	char				*pCharSet;		/* Defined Web content type/character set */
	char				*pCrypto;		/* SHA1 Seed for file IO logic */
	int					bNoBKG;			/* Launch without BKG */
	int					bSinglePort;	/* Only use the Launch port */
	int					nSesCleanHr;	/* Hour when last session cleanup performed */
};

struct INOMADS
{	request_rec			*r;
	apr_table_t			*tblForm;
	apr_table_t			*tblArgs;
	apr_pool_t			*sysPool;		/* Server process pool */
	struct CONFIG		*pConf;			/* Config info (in SysPool) */
	struct SESSION		*pSession;		/* Pointer to session */
	char				*pContent;		/* Submitted contents */
	char				*pType;			/* Content type */
	char				*pDocRoot;		/* Document root -- as we see it */
	char				*pRmtRoot;		/* Document root -- as a launch port would get it */
	char				*szSession;		/* Session ID */
	char				*pSesDiry;		/* Session temp directory */
	char				*pInid;			/* Workstation identifier */		
	char				*pAjax;			/* Ajax wrapup command */
	char				*pMeta;			/* Meta data (not used) */
	char				*pMsg;			/* Message text */
	char				*pReLaunch;		/* Anchor tag to relaunch process */
	char				*pFile;			/* Pointer to uploaded file (only one allowed) */
	char				*pFileName;		/* Users workstation file name */
	long				lContent;		/* Length of contents */
	int					bUpload;		/* Content is an Upload */
	int					rcSts;			/* Error code return value */
	char				szSHA1[44];		/* SHA1 result -- should be 40 */
};

struct TBLINFO
{	int					nKeys;		/* Number of keys */
	int					lenKeys;	/* Total length of all the keys */
	int					lenData;	/* Total length of all the data */
	char				*pKeys;		/* Key name list */
	char				*pData;		/* Data list */
};

#define Request		pNomads->r
#define TmpPool		pNomads->r->pool
#define SysPool		pNomads->sysPool
#define RetCode		pNomads->rcSts

static int Load_Config(struct INOMADS *pNomads);
static int Parse_Args(struct INOMADS *pNomads); 
static struct SESSION *Session_Find(struct INOMADS *pNomads, BOOL bCreate);
static void Session_Free(struct INOMADS *pNomads);
static void Session_Cleanup(struct INOMADS *pNomads, apr_time_t tmNow);

static int FileIO_Process(struct INOMADS *pNomads);
static void FileIO_Get(struct INOMADS *pNomads, char *szPath);
static apr_size_t FileIO_GetDir(struct INOMADS *pNomads, char *pPath, unsigned char **ppBfr);
static apr_size_t FileIO_GetFile(struct INOMADS *pNomads, char *pPath, unsigned char **ppBfr, apr_size_t fileSize);
static int Get_PortNo(struct INOMADS *pNomads);
static void FileIO_Put(struct INOMADS *pNomads, char *szPath);
static void FileIO_Del(struct INOMADS *pNomads, char *szPath);
static void FileIO_DelDir(struct INOMADS *pNomads, char *pPath);
static void FileIO_GetRange(struct INOMADS *pNomads, apr_off_t *nOffset, apr_size_t *nLength);

#define FileIO_Error(a)	ap_rprintf(Request, "Fail\r\n%s\r\n", a)
#define FileIO_Okay(a)	ap_rprintf(Request, "Okay\r\n%s\r\n", a)


static int Read_Contents(struct INOMADS *pNomads);
static int Parse_Contents(struct INOMADS *pNomads);
static int Parse_Upload(struct INOMADS *pNomads);

static int Load_session(struct INOMADS *pNomads, int bForceNew);
static int Spawn_Direct(struct INOMADS *pNomads);
static int Spawn_ViaTcp(struct INOMADS *pNomads, int nPort);
static int Load_Inid(struct INOMADS *pNomads);
static void Exchg_data(struct INOMADS *pNomads);

static int Send_Error(struct INOMADS *pNomads, char *pMsg);
static void Send_SysHtml(struct INOMADS *pNomads, char *sHtmlFile, char *pSysHTML);

static apr_table_t *TblParse(struct INOMADS *pNomads, const char *data);
static int CountArg(void* pCnt, const char* pKey, const char* pValue);
static int PrintArg(void* pFile, const char* pKey, const char* pValue);

static char *Compute_SHA1(struct INOMADS *pNomads, unsigned char *szStr, int lenStr);
static char *Convert_2HEX(unsigned char *pInput, int lenInput, char *pHexString);

static int inomads_handler(request_rec* r)
{	struct INOMADS		iNomads;
	char				*pDlm;

	
	if (!r->handler) return DECLINED;
	
	pDlm = strrchr(r->uri, '/');
	if (pDlm == NULL) 
		pDlm = (char *)r->uri;
	else
		pDlm++;

	if (apr_strnatcasecmp(pDlm, "inomads"))
	{	if (apr_strnatcasecmp(pDlm, "bmobile"))
			return DECLINED;
	}

	if (!apr_table_get(r->headers_in, "User-Agent")) return DECLINED;
	if (!apr_table_get(r->headers_in, "Host")) return DECLINED;
	
	if ( r->method_number != M_GET )
	{	if ( r->method_number != M_POST )return HTTP_METHOD_NOT_ALLOWED ;  /* Reject other methods */
	}
	
	r->no_cache = TRUE;
	
	memset(&iNomads, 0, sizeof(iNomads));

	iNomads.r = r;
	iNomads.sysPool = r->server->process->pconf;		/* Config pool -- cleared on restart */

	iNomads.pDocRoot = apr_pstrdup(r->pool, r->filename);
	pDlm = strrchr(iNomads.pDocRoot, '/');
	if (pDlm) *pDlm = 0;

	if (!Load_Config(&iNomads)) return OK;

	if (iNomads.pConf->pRmtRoot[0] != 0)
		iNomads.pRmtRoot = iNomads.pConf->pRmtRoot;
	else
		iNomads.pRmtRoot = iNomads.pDocRoot;
	
	Parse_Args(&iNomads);
	
	if (FileIO_Process(&iNomads) == TRUE) return OK;

	ap_set_content_type(r, iNomads.pConf->pCharSet);

	if (Read_Contents(&iNomads) &&
		Parse_Contents(&iNomads) &&
		Load_session(&iNomads, FALSE))
			Exchg_data(&iNomads);
	
	Session_Free(&iNomads);

	return OK;
}

/* Load_Config(pNomads)
 *
 *		Recall the config information from the system pool or create it first time if
 *		not present.
 *		This routine saves the config into the system memory so that subsequent requests
 *		will not require and file IO.
 */
static int Load_Config(struct INOMADS *pNomads)
{	apr_file_t		*pfConf;
	char			szLine[MAX_PATH_NAME+100];
	char			szPath[MAX_PATH_NAME];
	char			*pDlm;
	char			*pTemp;
	
	strcpy(szLine, "[Inomads]");
	strcat(szLine, pNomads->pDocRoot);
	
	apr_pool_userdata_get((void **)&(pNomads->pConf), szLine, SysPool);
	if (pNomads->pConf != NULL) return TRUE;
	
	pNomads->pConf = (struct CONFIG *)apr_pcalloc(SysPool, sizeof(struct CONFIG));
	apr_pool_userdata_set(pNomads->pConf, szLine, NULL, SysPool);

	apr_thread_mutex_create(&pNomads->pConf->mutexSessions, APR_THREAD_MUTEX_DEFAULT, SysPool);

	strcpy(szPath, pNomads->pDocRoot);
	strcat(szPath, "/inomads.conf");
	
	pNomads->pConf->pLaunchPswd = "";
	pNomads->pConf->nLaunchPort = 4093;
	pNomads->pConf->nTimeout = 60 * 5;			/* Five minute default */
	pNomads->pConf->pRmtRoot = "";
	pNomads->pConf->bNoBKG = FALSE;
	pNomads->pConf->pCharSet = "text/html;";
	pNomads->pConf->pCrypto = "";

	if (apr_file_open(&pfConf, szPath, APR_READ, APR_OS_DEFAULT, TmpPool) == APR_SUCCESS)
	{	for(;;)
		{	if (apr_file_gets(szLine, sizeof(szLine), pfConf) == APR_EOF) break;
			pDlm = strchr(szLine, '\r');
			if (pDlm) *pDlm = 0;
			
			pDlm = strchr(szLine, '\n');
			if (pDlm) *pDlm = 0;
			
			pDlm = strchr(szLine, '=');
			if (pDlm == NULL) continue;

			*pDlm = 0;
			pDlm++;

			if (pDlm[0] == 0) continue;			/* No value */

			if (stricmp(szLine, "DIRECTORY") == 0)
				pNomads->pConf->pDir = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "PXPLUS_EXEC") == 0)
				pNomads->pConf->pExe = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "PXPLUS_LIB") == 0)
				pNomads->pConf->pLib = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "SYSHTML_DIR") == 0)
				pNomads->pConf->pHtml = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "LAUNCH_IP") == 0)
				pNomads->pConf->pLaunchIP = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "LAUNCH_PORT") == 0)
				pNomads->pConf->nLaunchPort = atoi(pDlm);
			else
			if (stricmp(szLine, "LAUNCH_PSWD") == 0)
				pNomads->pConf->pLaunchPswd = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "LAUNCH_ROOT") == 0)
				pNomads->pConf->pRmtRoot = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "TCP_TIMEOUT") == 0)
				pNomads->pConf->nTimeout = atoi(pDlm);
			else
			if (stricmp(szLine, "CHARACTER_SET") == 0)
				pNomads->pConf->pCharSet = apr_psprintf(SysPool, "text/html;charset=%s", pDlm);
			else
			if (stricmp(szLine, "SHOW_TASKS") == 0)
				pNomads->pConf->bNoBKG = atoi(pDlm);
			else
			if (stricmp(szLine, "CRYPTO_SEED") == 0)
				pNomads->pConf->pCrypto = apr_pstrdup(SysPool, pDlm);
			else
			if (stricmp(szLine, "SINGLE_PORT") == 0)
				pNomads->pConf->bSinglePort = atoi(pDlm);
		}

		apr_file_close(pfConf);
	}

	
	if (pNomads->pConf->pDir == NULL)
		pNomads->pConf->pDir = apr_pstrdup(SysPool, pNomads->pDocRoot);
	
	if (pNomads->pConf->pExe == NULL) 
	{	strcpy(szLine, pNomads->pConf->pDir);
		pTemp = szLine;
		for(;;)
		{	pDlm = strchr(pTemp, '/');
			if (pDlm == NULL) pDlm = strchr(pTemp, '\\');
			if (pDlm == NULL) break;
			
			pTemp = pDlm + 1;			
			if (memcmp(pTemp, "lib", 3) == 0)
			{	strcpy(pTemp, PXPLUS_EXE);
				pNomads->pConf->pExe = apr_pstrdup(SysPool, szLine);
				break;
			}
		}
	}
	
	if (pNomads->pConf->pLib == NULL)
	{	strcpy(szPath, pNomads->pConf->pExe);
		
		pDlm = strrchr(szPath, '/');
		if (pDlm == NULL) pDlm = strrchr(szPath, '\\');
		
		if (pDlm) *pDlm = 0;
		pNomads->pConf->pLib = apr_psprintf(SysPool, "%s/lib", szPath);
	}
		
	if (pNomads->pConf->pHtml == NULL)
		pNomads->pConf->pHtml = "/syshtml";

	if (pNomads->pConf->pLaunchIP == NULL)
		pNomads->pConf->bSinglePort = 0;

	return TRUE;
}

/* Parse_Args(pNomads)
 *
 *		Parse the request argument list into the tblArgs array
 */
static int Parse_Args(struct INOMADS *pNomads)
{	char szArgs[HUGE_STRING_LEN];
	
	if (Request->args)
		strcpy(szArgs, Request->args);
	else
		strcpy(szArgs, "");
	
	pNomads->tblArgs = TblParse(pNomads, szArgs);
	return OK;
}

/* Read_Contents(pNomads)
 *
 *		If the submission is a form or file upload read the contents of the submission
 *		into the pContents (len in lContents) for subsequent processing.
 *		If no data then set contents to "" and length to zero.
 */
static int Read_Contents(struct INOMADS *pNomads)
{	char				bfrTemp[HUGE_STRING_LEN];
    int					nRead;
    int					nOfs;
    
    pNomads->pContent = "";
    pNomads->lContent = 0;
    
	if (Request->method_number != M_POST) return TRUE;
	
	pNomads->pType = (char *)apr_table_get(Request->headers_in, "Content-Type");

	if (memicmp(pNomads->pType, "application/x-www-form-urlencoded", 33) != 0) 
	{	if (strlen(pNomads->pType) <= 19) return FALSE;
		if (memicmp(pNomads->pType, "multipart/form-data", 19) != 0) return TRUE;
		pNomads->bUpload = TRUE;
	}

	if ((RetCode = ap_setup_client_block(Request, REQUEST_CHUNKED_ERROR)) != OK)
		return Send_Error(pNomads, "Cannot access POSTed data");
	
	if (!ap_should_client_block(Request)) return TRUE;

	if (Request->remaining > 10*1024*1024) 
		return Send_Error(pNomads, "Upload failure: File size exceeds allowable"); 

	pNomads->lContent = (long)Request->remaining;
	pNomads->pContent = apr_pcalloc(TmpPool, pNomads->lContent + 1);
	
	nOfs = 0;
		
	while ((nRead = ap_get_client_block(Request, bfrTemp, sizeof(bfrTemp))) > 0)
	{	if ((nOfs + nRead) > pNomads->lContent)
		{	nRead = pNomads->lContent - nOfs;
		}

		if (nRead) memcpy(pNomads->pContent + nOfs, bfrTemp, nRead);
		nOfs += nRead;
	}

	return TRUE;
}

/* Parse_Contents(pNomads)
 *
 *		If the content was a file upload then parse the uploaded data otherwise simply
 *		parse the contents to construct the table of form fields.  
 *
 *		Note that the contents may be "" however we still parse the data to that the 
 *		form array gets created
 */
static int Parse_Contents(struct INOMADS *pNomads)
{	if (pNomads->bUpload) return Parse_Upload(pNomads);

	pNomads->tblForm = TblParse(pNomads, pNomads->pContent);
	return TRUE;
}

/* Parse_Upload(pNomads)
 *
 *		This function is called if the request was a file upload.
 *		It will parse the received contents in order to extract the file and write
 *		it to the session directory.
 */
static int Parse_Upload(struct INOMADS *pNomads)
{	char			*pTemp;
	char			*pToken;
    char			*pFile;
    char			*pStart;
    char			*pName;
    char			*pBound;
	char			*pLine;
    apr_size_t		lBound = 0;
	char			szPath[MAX_PATH_NAME];

	char			*pData;
	apr_size_t		lData;
	apr_file_t		*pfFile;
	
    pFile = (char *)apr_table_get(pNomads->tblArgs,"_upload");
    if (pFile == NULL) return Send_Error(pNomads, "Upload failure: No upload name"); 
    
    if (strlen(pFile) == 0) return TRUE;
    
   	pNomads->szSession = (char *)apr_table_get(pNomads->tblArgs,"_session");
    if (pNomads->szSession == NULL) return Send_Error(pNomads, "Upload failure: No session identifed"); 

    pTemp = pNomads->pType;
    
    for (;pTemp != NULL;)
    {	pTemp = strchr(pTemp,'b');
		if (pTemp == NULL) break;
		if (memicmp(pTemp, "boundary=", 9) == 0) break;
		pTemp++;
		break;
	}
	
	if (!pTemp) return Send_Error(pNomads, "Upload failure: Invalid boundary in Content-type"); 

	pBound = pTemp + 9;
	pTemp = strchr(pBound, ';');
	if (pTemp)
		lBound = (int)(pTemp - pBound);
	else
		lBound = (int)strlen(pBound);

	/* Split input based on Boundaries
	** Set pointers/length to Content sent with the request
	*/
	
	lData = pNomads->lContent;
	pData = pNomads->pContent;
	
	pStart = NULL;			/* File not found */
	pName = NULL;			/* File name not found */

	for(;;)
	{	/* Find next Boundary */
	
		for(;lData > 0;)
		{	pTemp = memchr(pData, '-', lData);
			if (pTemp == NULL) 
			{	lData = 0;
				break;
			}
			
			lData -= (int)(pTemp - pData);
			pData = pTemp;
					
			if (lData < lBound+2)
			{	lData = 0;
				break;
			}
			
			if ((pData[1] == '-') && (memcmp(pData+2, pBound, lBound) == 0)) break;

			pData++;
			lData--;
		}
		
		if (!lData) break;
		if (pStart) break;			/* New boundary after start */
		
		/* Break out the records */
		for(;lData > 0;)
		{	pLine = pData;
		
			pTemp = (char *)memchr(pData, '\n', lData);
			if (pTemp == NULL) 
			{	lData = 0;
				break;
			}

			lData -= (int)(pTemp - pData) + 1;
			pData = pTemp + 1;

			*pTemp = 0;
			pTemp--;
			if (*pTemp == '\r') *pTemp = 0;

			if (pLine[0] == 0)
			{	pStart = pData;
				break;
			}

			pTemp = strchr(pLine, ':');
			if (pTemp != NULL)
			{	*(pTemp++) = 0;
				if (apr_strnatcasecmp("Content-Disposition", pLine) == 0)
				{	strcat(pTemp, ";");
				
					for (pTemp = apr_strtok(pTemp, ";", &pToken); pTemp != NULL; pTemp = apr_strtok(NULL, ";", &pToken))
					{	size_t				nLen;
					
						/* Strip leading and trailing spaces from token */
					
						for (;*pTemp == ' ';pTemp++);
						for (nLen = strlen(pTemp);nLen > 0;)
						{	if (pTemp[--nLen] != ' ') break;
							pTemp[nLen] = 0;
						}
					
						if (memicmp("filename=", pTemp, 9) == 0)
						{	pName = pTemp + 9;
							if (pName[0] == '"')
							{	pName++;
								pTemp = strchr(pName, '"');
								if (pTemp) *pTemp = 0;
							}
							UnEscapeURL(pName);
							break;
						}
					}
				}
				continue;
			}
		}
	}	

	if (!pName) return Send_Error(pNomads, "Upload failure: Cannot locate file name"); 
	if (!pStart) return Send_Error(pNomads, "Upload failure: No data for file"); 
	
	pNomads->tblForm = apr_table_make(TmpPool, 10);
	apr_table_merge(pNomads->tblForm, "upload..filename", pName);							

	if (*(pData-1) == '\n')
	{	pData--;			/* Toss the trailing Line feed */
		if (*(pData-1) == '\r') pData--;
	}

	lData = (int)(pData - pStart);

	strcpy(szPath, pNomads->pDocRoot);
	strcat(szPath, "/tmp/");
	strcat(szPath, pNomads->szSession);
    strcat(szPath, "/");
    strcat(szPath, pFile);

	if (apr_file_open(&pfFile, szPath, APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
		return Send_Error(pNomads, apr_psprintf(TmpPool, "Upload failure: Cannot open file on server.<br>(%s)", szPath)); 

	apr_file_write(pfFile, pStart, &lData);
	apr_file_close(pfFile);

	return TRUE;
}

static int Load_session(struct INOMADS *pNomads, int bForceNew)
{	int				nIdx;
	int				bNew;
	char			szPath[MAX_PATH_NAME];
	char			*pTmp;
	apr_file_t		*pfFile;
	struct TBLINFO	tblInfo;
	BOOL			bUsedCookie;	/* Indicates if cookie was used to get session ID */
	
	/* See if we have a session ID */

	bUsedCookie = FALSE;			/* Did not use cookie to recover session ID */

	if (bForceNew)
		pNomads->szSession = NULL;
	else
	{	pNomads->szSession = (char *)apr_table_get(pNomads->tblArgs,"_session");
		if ((pNomads->szSession == NULL) &&  (pNomads->tblForm))
			pNomads->szSession = (char *)apr_table_get(pNomads->tblForm, "_session");
		if (pNomads->szSession == NULL)
		{	char	*pCookies;
			char	*pTemp, *pDlm, *pToken;
		
			pCookies = (char *)apr_table_get(Request->headers_in, "Cookie");
			if (pCookies != NULL)
			{	pTemp = apr_pstrdup(TmpPool, pCookies);

				for (pTemp = apr_strtok(pTemp, " ;", &pToken); pTemp != NULL; pTemp = apr_strtok(NULL, " ;", &pToken))
				{	pDlm = strchr(pTemp, '=');
					if (pDlm)
					{	*(pDlm++) = 0;
						if (apr_strnatcasecmp(pTemp, "_session") == 0) 
						{	UnEscapeURL(pDlm);
							pNomads->szSession = pDlm;
							apr_table_merge(pNomads->tblArgs, "_session", pDlm);
							bUsedCookie = TRUE;
							break;
						}
					}
				}
			}
		}
	}
	
	if (pNomads->szSession != NULL)
	{	bNew = FALSE;

		strcpy(szPath, pNomads->pDocRoot);
		strcat(szPath, "/tmp/");
		strcat(szPath, pNomads->szSession);

		pNomads->pSesDiry = apr_pstrdup(TmpPool, szPath);
				
		/* See if we know about this session (Find with no Create)
		 * If not and last sequence was Exit and session dir does not exist
		 * start a new session by just ignoring the session id
		 */

		if (Session_Find(pNomads, FALSE) == NULL)
		{	ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Session %s not found in table", pNomads->szSession);
			if (access(szPath, 0) != -1)			/* If the directory exists... */
			{	int		nPort;

				/* Session directory exists -- get the port number of the application process */
				ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Session %s directory exists", pNomads->szSession);
				nPort = Get_PortNo(pNomads);
				ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Session %s has port %d", pNomads->szSession, nPort);

				if (nPort >= 0)
				{	/* Session info exists and we know the port number on the host
					** If .using single port we need to reconnect if possible,
					** If non-single port we will assume session is still alive
					** and create the session info and process request
					*/

					if (pNomads->pConf->bSinglePort)
						return Spawn_ViaTcp(pNomads, nPort);
					else
						return TRUE;
				}
				else
				{	/* There was no port number file (ors its invalid) then
					** the session never really started so just ignore the session
					** provied and consider this a new session
					*/

					pNomads->szSession = NULL;
				}
			}
			else
			{	/* Session directory does not exist -- thus session likely gone
				** See if URL has _seq of Exit and if so then user likely
				** just did a resubmit so we can ignore session id
				** (If came from cookie then just ignore the session passed in -- its gone)
				*/
				ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Session %s Cannot find directory ", pNomads->szSession);
				if (bUsedCookie) 
				{	pNomads->szSession = NULL;
					apr_table_unset(pNomads->tblArgs, "_session");
				}
				else
				{	pTmp = (char *)apr_table_get(pNomads->tblArgs,"_seq");
					if (pTmp == NULL) pTmp = (char *)apr_table_get(pNomads->tblForm,"_seq");

					if ((pTmp != NULL) && (strcmp(pTmp, "Exit") == 0)) pNomads->szSession = NULL;
				}
			}
		}
	}
	
	if (pNomads->szSession == NULL)
	{	apr_time_t		tmNow;
		apr_time_t		tmWrk;
		apr_time_t		tmSecs;
		
		bNew =  TRUE;
		pNomads->szSession = (char *)apr_pcalloc(TmpPool, 13);
		
		tmNow = apr_time_now();
		
		Session_Cleanup(pNomads, tmNow);
		
		for(;;)
		{	tmSecs = apr_time_sec(tmNow);
			tmWrk = tmNow;
			
			for (;tmWrk != 0;)
			{	if (tmWrk%26) break;
				tmWrk /= 26;
			}
	
			apr_generate_random_bytes((unsigned char *)pNomads->szSession, 12);

			for (nIdx=0; nIdx<12; nIdx++)
			{	pNomads->szSession[nIdx] += (unsigned char)tmWrk%26;
				tmWrk /= 26;
				
				pNomads->szSession[nIdx] = 97 + (((unsigned char *)pNomads->szSession)[nIdx] % 26);
			}
			
			/* Fix in case random number generator ain't very random
			** Replace first four characters with value based on time of day
			** this will cycle every 5.29 days and one must assume that a session 
			** will not start/stop in less than one second in the off (and highly unlikely)
			** chance that the random numbers are replicated
			*/
			
			for (nIdx = 0; nIdx < 4; nIdx++)
			{	pNomads->szSession[nIdx] = 97 + (unsigned char)(tmSecs % 26);
				tmSecs /= 26;
			}
			
			strcpy(szPath, pNomads->pDocRoot);
			strcat(szPath, "/tmp/");

			strcat(szPath, pNomads->szSession);
			if (access(szPath, 0) == -1) break;
		}

		pNomads->pSesDiry = apr_pstrdup(TmpPool, szPath);
	}
	
	if (!bNew) return TRUE;
	
	if (!Load_Inid(pNomads)) return FALSE;

	/* Create the directory */
	
	if (apr_dir_make(szPath, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
	{	pNomads->pMsg = apr_psprintf(TmpPool, "Unable to create working directory: %s", szPath);
		Send_SysHtml(pNomads, "sys_error.htm", NULL);
		return FALSE;
	}
	
	strcat(szPath, "/.info");

	pfFile = NULL;
	if (apr_file_open(&pfFile, szPath, APR_WRITE | APR_CREATE | APR_TRUNCATE, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
	{	pNomads->pMsg = apr_psprintf(TmpPool, "Unable to open: %s", szPath);
		Send_SysHtml(pNomads, "sys_error.htm", NULL);
		return FALSE;
	}
	
	apr_file_printf(pfFile,"HTTP_USER_AGENT=%s" NL,	apr_table_get(Request->headers_in,"User-Agent"));

	apr_file_printf(pfFile,"HTTP_HOST=%s" NL,		apr_table_get(Request->headers_in,"Host"));
	apr_file_printf(pfFile,"REMOTE_ADDR=%s" NL,		Request->connection->remote_ip);
	apr_file_printf(pfFile,"SERVER_ADDR=%s" NL,		Request->connection->local_ip);
	apr_file_printf(pfFile,"SERVER_PORT=%d" NL,		ap_get_server_port(Request));

	if (apr_table_get(Request->subprocess_env, "HTTPS"))
		nIdx = 1;
	else
		nIdx = 0;
			
	apr_file_printf(pfFile,"SERVER_SECURE=%d" NL,	nIdx);
	
	apr_file_printf(pfFile,"DOCUMENT_ROOT=%s" NL,	pNomads->pRmtRoot);

	pTmp = strchr(Request->the_request, '/') + 1;

	for (nIdx = 0;;)
	{	if ((*pTmp == ' ') || (*pTmp == '?') || (*pTmp == 0)) break;
		szPath[nIdx++] = *(pTmp++);
	}
	
	if (szPath[nIdx-1] == '/') nIdx--;
	szPath[nIdx] = 0;
	if (nIdx == 0) strcpy(szPath, "inomads");
	
	apr_file_printf(pfFile,"WEB_URL=%s" NL,			szPath);
	apr_file_printf(pfFile,"INID=%s" NL NL,			pNomads->pInid);

	memset(&tblInfo, 0, sizeof(tblInfo));
		
	apr_table_do(CountArg, &tblInfo, pNomads->tblArgs, NULL);
	apr_file_printf(pfFile,"<Args count=%d>" NL,		tblInfo.nKeys);
	
	apr_table_do(PrintArg, pfFile, pNomads->tblArgs, NULL);
	apr_file_printf(pfFile,"</Args>" NL);

	apr_file_close(pfFile);
	
	if (pNomads->pConf->pLaunchIP)
	{	if (!Spawn_ViaTcp(pNomads, 0)) return FALSE;
	}
	else
	{	if (!Spawn_Direct(pNomads)) return FALSE;
	}
   
    strcpy(szPath, pNomads->pSesDiry);
    strcat(szPath, "/.link");
    
	for (nIdx = 200; nIdx > 0; nIdx--)
	{	apr_sleep(apr_time_from_sec(5) / 100);
		if (access(szPath, 0) == 0) break;
	}
	
	if (nIdx <= 0)
	{	return Send_Error(pNomads, "Unable to connect to newly spawned task");
	}

	return TRUE;
}

/* Spawn_Direct(pNomads)
 *
 *		This function will directly spawn the process on the save server/service
 */
static int Spawn_Direct(struct INOMADS *pNomads)
{	const char			*szArgs[10];
	char				*szArg;
	int					n;
	
	apr_procattr_t	*procAttr;
	apr_proc_t		procInfo;
	apr_exit_why_e	procWhy;

	/* Spawn the process */

//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Starting Spawn_Direct");

	if ((pNomads->pConf->pExe == NULL) || (pNomads->pConf->pExe[0] == 0))
	{	return Send_Error(pNomads, "Path to PxPlus EXEC undefined");
	}

	if (apr_procattr_create(&procAttr, TmpPool) != APR_SUCCESS)
	{	return Send_Error(pNomads, "Unable to create process attribute structure");
	}
//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Created Procattr");	
	if (pNomads->pConf->pDir)
	{	
//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "About to change directory to:<%s>", pNomads->pConf->pDir);	
		if (apr_procattr_dir_set(procAttr, pNomads->pConf->pDir) != APR_SUCCESS)
		{	return Send_Error(pNomads, apr_psprintf(TmpPool, "Unable to change directory to: %s", pNomads->pConf->pDir));
		}
//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Changed directory to:<%s>", pNomads->pConf->pDir);	
	}

//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "About to detach set");	
	apr_procattr_detach_set(procAttr, TRUE);
//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "About to cmdtype_set");
	apr_procattr_cmdtype_set(procAttr, APR_PROGRAM_ENV);
	
	szArgs[0] = pNomads->pConf->pExe;			/* PxPlus EXE */
	if (pNomads->pConf->bNoBKG)
		szArgs[1] = "-mn";
	else
		szArgs[1] = "-bkg";

#if defined(_WINDOWS)
	szArgs[2] = "'*plus/inomads/inomads'";
	szArgs[3] = "-arg";
#else
	szArgs[2] = "*plus/inomads/inomads";	
	szArgs[3] = "-arg";
#endif

	szArg = apr_psprintf(TmpPool, "%s=%s", pNomads->szSession, pNomads->pRmtRoot);
	for (n = 0; szArg[n] != 0; n++)
	{	if (szArg[n] == ' ') szArg[n] = '+';
	}
	
	szArgs[4] = szArg;
	szArgs[5] = NULL;
//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Set arg list");	
	memset(&procInfo, 0, sizeof(procInfo));

//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Cleared procinfo");
//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "About to launch <%s>", pNomads->pConf->pExe);

	if (apr_proc_create(&procInfo, pNomads->pConf->pExe, szArgs, NULL, procAttr, TmpPool) != APR_SUCCESS)
		return Send_Error(pNomads, "Unable to create/launch process");

//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Created task");			
	apr_proc_wait(&procInfo, NULL, &procWhy, APR_NOWAIT);
//ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Issued nowait");	

	Session_Find(pNomads, TRUE);			/* Create session entry */
	return TRUE;
}


/* Spawn_ViaTcp(pNomads, int nPort)
 *
 *		This function will call the specified launch port and request the task be spawned
 */
static int Spawn_ViaTcp(struct INOMADS *pNomads, int nPort)
{	char			szSpawn[512];
	char			*szPort;
	char			*pConnectID;
	apr_size_t		lTemp;
	apr_size_t		lSpawn;
	
	apr_sockaddr_t	*pSockAddr;
	apr_socket_t	*pSocket;
	apr_pool_t		*pPool;
	struct SESSION	*pSession;

	pSession = Session_Find(pNomads, TRUE);

	if (pNomads->pConf->bSinglePort)
	{	if (pSession->pPool == NULL) apr_pool_create(&pSession->pPool, SysPool);
		pPool = pSession->pPool;
		pConnectID = "\xFA\xBB\xA5\x1C";
	}
	else
	{	pPool = TmpPool;
		pConnectID = "\xDE\xAD\xBE\xEF";
	}

	memset(szSpawn, 0, sizeof(szSpawn));
	memcpy(szSpawn, pConnectID, 4);
	lSpawn = 4;
	
	lTemp = strlen(pNomads->pConf->pLaunchPswd) + 1;
	memcpy(szSpawn+lSpawn, pNomads->pConf->pLaunchPswd, lTemp);
	lSpawn += lTemp;
	
	lTemp = strlen(pNomads->szSession) + 1;
	memcpy(szSpawn+lSpawn, pNomads->szSession, lTemp);
	lSpawn += lTemp;
	
	lTemp = strlen(pNomads->pRmtRoot) + 1;
	memcpy(szSpawn+lSpawn, pNomads->pRmtRoot, lTemp);
	lSpawn += lTemp;

	if (nPort != 0)
	{	szPort = apr_psprintf(TmpPool, "%d", nPort);
		lTemp = strlen(szPort) + 1;
		memcpy(szSpawn+lSpawn, szPort, lTemp);
		lSpawn += lTemp;
	}

	pSocket = NULL;

	if ((apr_sockaddr_info_get(&pSockAddr, pNomads->pConf->pLaunchIP, APR_INET, pNomads->pConf->nLaunchPort, 0, pPool) != APR_SUCCESS) ||
		(apr_socket_create(&pSocket, pSockAddr->family, SOCK_STREAM, APR_PROTO_TCP, pPool) != APR_SUCCESS)||
		(apr_socket_opt_set(pSocket, APR_SO_NONBLOCK, 1) != APR_SUCCESS) ||
		(apr_socket_timeout_set(pSocket, apr_time_from_sec(5)) != APR_SUCCESS) ||
		(apr_socket_connect(pSocket, pSockAddr) != APR_SUCCESS) ||
		(apr_socket_opt_set(pSocket, APR_SO_NONBLOCK, 0) != APR_SUCCESS) )
		{	if (pSocket) apr_socket_close(pSocket);
			return Send_Error(pNomads, "Unable to connect process launch task");
		}
		
	if ((apr_socket_timeout_set(pSocket, apr_time_from_sec(5)) != APR_SUCCESS) ||
		(apr_socket_send(pSocket, szSpawn, &lSpawn) != APR_SUCCESS))
		{	apr_socket_close(pSocket);
			return Send_Error(pNomads, "Unable to send request to launcher");
		}

	lSpawn = 2;

	if ((apr_socket_recv(pSocket, szSpawn, &lSpawn) != APR_SUCCESS) || 
		(lSpawn != 2) ||
		(memcmp(szSpawn, "OK", 2) != 0))
		{	apr_socket_close(pSocket);
			return Send_Error(pNomads, "Invalid launcher response");
		}
	
	if (pNomads->pConf->bSinglePort)
	{	pNomads->pSession->pSocket = pSocket; 
		pNomads->pSession->nPort = pNomads->pConf->nLaunchPort;

		ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Open perm socket to SESSION %s port %d", pNomads->szSession, pNomads->pSession->nPort);

	}
	else
	{	apr_socket_close(pSocket);
	}

	return TRUE;
}	

static int CountArg(void* pInfo, const char* pKey, const char* pValue)
{	struct TBLINFO	*pTbl;
	int				nLen, nOfs;
	unsigned char	c;
	
	pTbl = (struct TBLINFO *)pInfo;

	nLen = (int)strlen(pKey);
	if ((nLen > 127) || (nLen == 0)) return 1;
	
	for (nOfs = 0; nOfs < nLen; nOfs++)
	{	c = (unsigned char)pKey[nOfs];

		if ((c >= 'a') && (c <=	'z')) continue;
		if ((c >= 'A') && (c <=	'Z')) continue;
		if (c == '_') continue;

		if (nOfs == 0) break;

		if ((c >= '0') && (c <=	'9')) continue;
		if (c == '.') continue;
		break;
	}
	
	if (nOfs < nLen) return 1;

	pTbl->nKeys++;
	pTbl->lenKeys += nLen + 2;

	if (pTbl->pKeys)
	{	*(pTbl->pKeys++) = ',';
		
		memcpy(pTbl->pKeys, pKey, nLen);
		pTbl->pKeys += nLen;
		
		*(pTbl->pKeys++) = '$';
	}

	nLen = (int)strlen(pValue);
	pTbl->lenData += nLen + 1;
	if (pTbl->pData)
	{	*(pTbl->pData++) = 0;
	
		memcpy(pTbl->pData, pValue, nLen);
		pTbl->pData += nLen;
	}

	return 1;	
}

static int PrintArg(void* pFile, const char* pKey, const char* pValue)
{	apr_file_printf((apr_file_t *)pFile, "%s=%s" NL, pKey, pValue);
	return 1;	
}

static void Exchg_data(struct INOMADS *pNomads)
{	char			szExitInfo[1024];
	char			szIPaddr[80];
	char			*pWrk;
	char			*pExit;
	char			*pSeq;
	char			*pRemoteIP;
	char			*pSend;
	char			*pData;
	char			*pSysHTML;
	char			*pReLaunch;
	apr_size_t		lenData;

	char			bfrRecv[8192];
	apr_size_t		lenRecv;
	int				bMore;
	int				bExit;
	
	int				nPort;
	struct TBLINFO	tblInfo;
	
	apr_sockaddr_t	*pSockAddr;
	apr_socket_t	*pSocket;
	apr_interval_time_t	nTimeout;
	apr_status_t	nErr;
	
	Session_Find(pNomads, TRUE);

	nPort = pNomads->pSession->nPort;
	pSocket = pNomads->pSession->pSocket;

	/* If not a always open socket and port not yet known then read
	** the .link file to get the port number
	*/

	if ((nPort == 0) && (pSocket == NULL))
	{	nPort = Get_PortNo(pNomads);
		if (nPort < 0) return;
	}

	/* Define the host for the application */

	if (pNomads->pConf->pLaunchIP) 
		strcpy(szIPaddr, pNomads->pConf->pLaunchIP);
	else
		strcpy(szIPaddr, "127.0.0.1");

	/* Generate data to pass to system */
	
	pRemoteIP = Request->connection->remote_ip;
	if (pRemoteIP == NULL) pRemoteIP = "0.0.0.0";

	pSeq = (char *)apr_table_get(pNomads->tblArgs,"_seq");
	
	if ((pSeq == NULL) && (pNomads->tblForm))
		pSeq = (char *)apr_table_get(pNomads->tblForm, "_seq");
	
	if (pSeq == NULL)
		pSeq = "0000";
	else
	if (strlen(pSeq) != 4)
	{	pNomads->pMsg = apr_pstrcat(TmpPool, "Invalid sequence number:", pSeq, NULL);	
		
		/* This may be a transmission or user error so no need to close any always open socket
		** which is done by default when sending SysHTML files.  Simply clear the open socket
		** then restore it after.
		*/

		pNomads->pSession->pSocket = NULL;
		Send_SysHtml(pNomads, "sys_error.htm", NULL);
		pNomads->pSession->pSocket = pSocket;

		return;
	}

	if ((strcmp(pSeq, "Rscu") == 0) || (pNomads->pInid))
	{	if (!pNomads->pInid) Load_Inid(pNomads);
		apr_table_merge(pNomads->tblArgs, "_inid", pNomads->pInid);
	}
	else
	{	char	*pCookies;
		char	*pTemp, *pDlm, *pToken;
		
		pCookies = (char *)apr_table_get(Request->headers_in, "Cookie");
		if (pCookies != NULL)
		{	pTemp = apr_pstrdup(TmpPool, pCookies);
			
			for (pTemp = apr_strtok(pTemp, " ;", &pToken); pTemp != NULL; pTemp = apr_strtok(NULL, " ;", &pToken))
			{	pDlm = strchr(pTemp, '=');
				if (pDlm)
				{	*(pDlm++) = 0;
					if (apr_strnatcasecmp(pTemp, "inid") == 0) 
					{	UnEscapeURL(pDlm);
						apr_table_merge(pNomads->tblArgs, "_inid", pDlm);
						break;
					}
				}
			}
		}
	}
		
	memset(&tblInfo, 0, sizeof(tblInfo));
	apr_table_do(CountArg, &tblInfo, pNomads->tblArgs, NULL);
	apr_table_do(CountArg, &tblInfo, pNomads->tblForm, NULL);

	/* Allocate memory required for message */	

	lenData = 4 + 4 + 1 + (int)strlen(pRemoteIP) + 1 + tblInfo.lenKeys + tblInfo.lenData + 1;
	
	/* If length exceeds 4 bytes use an 8 byte length with '+' preamble */
	
	if (lenData > 9900) lenData += 1 + 8 - 4;
	 
	pSend = pData = (char *)apr_pcalloc(TmpPool, lenData);	

	/* Insert length & sequence number */
	
	if (lenData > 9900)
	{	sprintf(pData, "+%08d%s", (int)(lenData - (1+8+4)), pSeq);
		pData += 1 + 8 + 4 + 1;
	}
	else
	{	sprintf(pData, "%04d%s", (int)(lenData - (4+4)), pSeq);
		pData += 4 + 4 + 1;
	}
	
	/* Insert IP address */
	
	strcpy(pData, pRemoteIP);
	pData += (int)strlen(pRemoteIP) + 1;
	
	/* Now insert keys and data values */

	tblInfo.pKeys = pData;
	tblInfo.pData = pData + tblInfo.lenKeys;
	
	tblInfo.lenData = tblInfo.lenKeys = tblInfo.nKeys = 0;
	
	apr_table_do(CountArg, &tblInfo, pNomads->tblArgs, NULL);
	apr_table_do(CountArg, &tblInfo, pNomads->tblForm, NULL);

	/* Okay now lets open the socket */
	
	if (pNomads->pConf->nTimeout == 0)
		nTimeout = -1;
	else
		nTimeout = apr_time_from_sec(pNomads->pConf->nTimeout);

	if (pSocket == NULL)
	{	if ((apr_sockaddr_info_get(&pSockAddr, szIPaddr, APR_INET, nPort, 0, TmpPool) != APR_SUCCESS ) ||
			(apr_socket_create(&pSocket, pSockAddr->family, SOCK_STREAM, APR_PROTO_TCP, TmpPool) != APR_SUCCESS ) ||
			(apr_socket_opt_set(pSocket, APR_SO_NONBLOCK, 1) != APR_SUCCESS ) ||
			(apr_socket_timeout_set(pSocket, apr_time_from_sec(60)) != APR_SUCCESS ) ||
			(apr_socket_connect(pSocket, pSockAddr) != APR_SUCCESS ) ||
			(apr_socket_opt_set(pSocket, APR_SO_NONBLOCK, 0) != APR_SUCCESS ))
			{	Send_SysHtml(pNomads, "sys_no_ses.htm", NULL);
				return;
			}
	}
			
	if ((apr_socket_timeout_set(pSocket, nTimeout) != APR_SUCCESS )||
		(apr_socket_send(pSocket, pSend, &lenData) != APR_SUCCESS) )
		{	Send_SysHtml(pNomads, "sys_no_ses.htm", NULL);
			return;
		}	
	
	bExit = TRUE;
	memset(szExitInfo, 0, sizeof(szExitInfo));
		
	lenData = 0;
	for(;;)
	{	lenRecv = 2 - lenData;

		nErr = apr_socket_recv(pSocket, bfrRecv + lenData, &lenRecv);
		if (nErr != APR_SUCCESS)
		{	char szErr[512];
			pNomads->pMsg = apr_psprintf(TmpPool, "Reception error on header<br /><h5>%s</h5>", apr_strerror(nErr, szErr, sizeof(szErr)));
			Send_SysHtml(pNomads, "sys_error.htm", NULL);
			return;
		}	
	
		if(lenRecv > 2)
		{	pNomads->pMsg = "Response length too long ";
			Send_SysHtml(pNomads, "sys_error.htm", NULL);
			return;
		}	
	
		lenData += lenRecv;
		if (lenData < 2) continue;
		
		if (bfrRecv[0] & 0x80)
			bMore = TRUE;
		else
			bMore = FALSE;
			
		lenData = ((bfrRecv[0] & 0x7F) << 8) | (bfrRecv[1] & 0x00FF);
		
		if ((lenData < 0) || (lenData > 10240))
		{	lenRecv = 40;
			apr_socket_recv(pSocket, bfrRecv, &lenRecv);
			
			pNomads->pMsg = apr_psprintf(TmpPool, "Header length indicated %d bytes -- {%40.40s}", (int)lenData, bfrRecv);
			Send_SysHtml(pNomads, "sys_error.htm", NULL);
			bExit = FALSE;
			break;
		}
					
		for (;lenData > 0;)
		{	apr_status_t		stsRecv;
		
			lenRecv = lenData;
			if (lenRecv > sizeof(bfrRecv)) lenRecv = sizeof(bfrRecv);
			
			stsRecv = apr_socket_recv(pSocket, bfrRecv, &lenRecv);

			if (stsRecv != APR_SUCCESS) 
			{	pNomads->pMsg = apr_psprintf(TmpPool, "Unable to receive full block - Want %u got %u - sts=%d", 
					(unsigned int)lenData, (unsigned int)lenRecv, stsRecv);
				Send_SysHtml(pNomads, "sys_error.htm", NULL);
				return;
			}	
			
			if (lenRecv)
			{	if ((bExit) && (lenRecv >= 5) && (memcmp(bfrRecv, "exit:", 5) == 0))
				{	memcpy(szExitInfo, bfrRecv + 5, lenRecv - 5);
				}
				else
				{	bExit = FALSE;
					ap_rwrite(bfrRecv, (int)lenRecv, Request);
				}
				
				lenData -= lenRecv;
			}
		}
	
		if (!bMore) break;
	}
	
	if (pNomads->pSession->pSocket == NULL)	apr_socket_close(pSocket);
	
	if (bExit != TRUE) return;
	
	pSysHTML = "";
	pReLaunch = "";

	pWrk = strchr(szExitInfo, 0x10);
	if (pWrk)
	{	pNomads->pReLaunch = pWrk + 1;
		*pWrk = 0;
	}

	pExit = strchr(szExitInfo, 0x09);
	if (pExit == NULL)
		pExit = "";
	else
	{	*pExit = 0;	
		pExit++;
	}
		
	pWrk = strchr(pExit, 0x09);
	if (pWrk)
	{	*pWrk = 0;

		pSysHTML = pWrk + 1;

		pWrk = strchr(pSysHTML, 0x09);
		if (pWrk)
		{	*pWrk = 0;
			pReLaunch = pWrk + 1;

			pWrk = strchr(pReLaunch, 0x09);
			if (pWrk) *pWrk = 0;
		}
	}

	if (pSysHTML[0] == 0) pSysHTML = NULL;
	if (szExitInfo[0] != 0) pNomads->pAjax = szExitInfo;
	if (pReLaunch[0] != 0) pNomads->pReLaunch = pReLaunch;

	if (pExit[0] != 0)
	{	if ((memicmp(pExit, "http", 4) != 0) && (strchr(pExit, '.') == NULL))
		{	Send_SysHtml(pNomads, apr_psprintf(TmpPool, "sys_%s.htm", pExit), pSysHTML);
			return;
		}

		pNomads->pMsg = pExit;
	
		if (!pNomads->pAjax) pNomads->pAjax = "";
		pNomads->pAjax = apr_psprintf(TmpPool, "%s\nsUrl = '%s';", pNomads->pAjax, pExit);
		Send_SysHtml(pNomads, "sys_xfer.htm", pSysHTML);
		return;
	}
	
	Send_SysHtml(pNomads,"sys_endapp.htm", pSysHTML);
	return;
}

static int Get_PortNo(struct INOMADS *pNomads)
{	char			szPath[MAX_PATH_NAME];
	char			szLine[80];
	char			*pWrk;
	int				nPort;

	apr_file_t		*pfFile;

	strcpy(szPath, pNomads->pSesDiry);
	strcat(szPath, "/.link");

	if (apr_file_open(&pfFile, szPath, APR_READ, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
	{	Send_SysHtml(pNomads, "sys_no_ses.htm", NULL);
		return -1;
	}	

	if (apr_file_gets(szLine, sizeof(szLine), pfFile) != APR_SUCCESS)
	{	Send_SysHtml(pNomads, "sys_no_ses.htm", NULL);
		return -1;
	}	
		
	apr_file_close(pfFile);
	
	pWrk = strchr(szLine, '\r');
	if (pWrk) *pWrk = 0;
	pWrk = strchr(szLine, '\n');
	if (pWrk) *pWrk = 0;
	
	pWrk = strchr(szLine, ';');
	if (!pWrk)
	{	pNomads->pMsg = apr_psprintf(TmpPool, "Invalid Connection string: '%s'", szLine);
		Send_SysHtml(pNomads, "sys_error.htm", NULL);
		return -1;
	}

	*pWrk = 0;
	nPort = atoi(pWrk+1);
	if (pNomads->pSession != NULL) pNomads->pSession->nPort = nPort;

	ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Setting port for SESSION %s to %d", pNomads->szSession, nPort);

	return nPort;
}

static int Send_Error(struct INOMADS *pNomads, char *pMsg)
{	pNomads->pMsg = pMsg;
	Send_SysHtml(pNomads, "sys_error.htm", NULL);
	return FALSE;
}
	
static void Send_SysHtml(struct INOMADS *pNomads, char *sHtmlFile, char *pSysHTML)
{	char			szPath[MAX_PATH_NAME];
	char			szLine[1000];
	apr_file_t		*pfHtml;
	
	/* If known session and there is a permanent socket attached drop it and reset port number
	** Port number can be recovered if needed
	*/

	if (pNomads->pSession)
	{	if (pNomads->pSession->pSocket != NULL) 
		{	apr_socket_close(pNomads->pSession->pSocket);
			pNomads->pSession->pSocket = NULL;
			ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Closing perm socket for SESSION %s", pNomads->szSession);
		}
		pNomads->pSession->nPort = 0;
		ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Freeing SESSION %s", pNomads->szSession);
	}

	if (pSysHTML == NULL) pSysHTML = pNomads->pConf->pHtml;
	sprintf(szPath, "%s/%s/%s",  pNomads->pDocRoot, pSysHTML, sHtmlFile);

	if (apr_file_open (&pfHtml, szPath, APR_READ, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
	{	sprintf(szPath, "%s/syshtml/%s",  pNomads->pDocRoot, sHtmlFile);
		if (apr_file_open (&pfHtml, szPath, APR_READ, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
		{	ap_rprintf(Request, "<html><body>Cannot find %s</body></html>", szPath);
			return;
		}
	}
	
	for(;;)
	{	if (apr_file_gets(szLine, sizeof(szLine), pfHtml) == APR_EOF) break;
		if (memcmp(szLine, "//Ajax", 6) == 0)
		{	if (pNomads->pAjax) ap_rputs(pNomads->pAjax, Request);
			continue;
		}
		if (memcmp(szLine, "//Meta", 6) == 0)
		{	if (pNomads->pMeta) ap_rputs(pNomads->pMeta, Request);
			continue;
		}
		if (memcmp(szLine, "//Msg", 5) == 0)
		{	if (pNomads->pMsg) ap_rputs(pNomads->pMsg, Request);
			continue;
		}
		if (memcmp(szLine, "//Launch", 8) == 0)
		{	if (pNomads->pReLaunch) ap_rputs(pNomads->pReLaunch, Request);
			continue;
		}
		
		ap_rputs(szLine, Request);
	}
	apr_file_close(pfHtml);
	return;
}

static int Load_Inid(struct INOMADS *pNomads)
{	char			*pCookies;
	char			*pTemp;
	char			*pToken;
	char			*pDlm;
	
	char			*pInidCookie;
	char			szLastInid[100], szCurInid[20];
	char			szPath[MAX_PATH_NAME];
		
	int				n;
		
	apr_file_t		*pfInid;
	apr_time_exp_t	tmLocal;
 
	pCookies = (char *)apr_table_get(Request->headers_in, "Cookie");
	if (pCookies != NULL)
	{	pTemp = apr_pstrdup(TmpPool, pCookies);
		
		for (pTemp = apr_strtok(pTemp, " ;", &pToken); pTemp != NULL; pTemp = apr_strtok(NULL, " ;", &pToken))
		{	pDlm = strchr(pTemp, '=');
			if (pDlm)
			{	*(pDlm++) = 0;
				if (apr_strnatcasecmp(pTemp, "inid") == 0) 
				{	UnEscapeURL(pDlm);
					pNomads->pInid = apr_pstrdup(TmpPool, pDlm);
					break;
				}
			}
		}
	}
	
	if (pNomads->pInid == NULL) 
	{	Send_SysHtml(pNomads, "sys_getnid.htm", NULL);
		ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Sending getnid");	
		return FALSE;
	}

	if (strcmp(pNomads->pInid, "undefined") != 0) return TRUE;

	strcpy(szPath, pNomads->pDocRoot);
	strcat(szPath, "/lastinid.conf");
	
	strcpy(szLastInid, "0000000000");
	
	pfInid = NULL;
	
	if (apr_file_open (&pfInid, szPath, APR_READ, APR_OS_DEFAULT, TmpPool) == APR_SUCCESS)
	{	apr_file_gets(szLastInid, sizeof(szLastInid), pfInid);
		szLastInid[10] = 0;
		apr_file_close(pfInid);
	}

	apr_time_exp_lt(&tmLocal, apr_time_now());

	strcpy(szCurInid, "0000000000");
	
	n = tmLocal.tm_year % 100;
	n = (n * 100) + tmLocal.tm_mon + 1;
	n = (n * 100) + tmLocal.tm_mday;
	sprintf(szCurInid, "%06d", n);

	n = tmLocal.tm_hour;
	n = (n * 100) + tmLocal.tm_min;
	sprintf(szCurInid + 6, "%04d", n);
			
	if (strcmp(szCurInid, szLastInid) <= 0)
	{	strcpy(szCurInid, szLastInid);
		sprintf(szCurInid+4, "%06d", atoi(szCurInid+4) + 1);
	}
	
	pNomads->pInid = (char *)apr_pcalloc(TmpPool, 13);
	strcpy(pNomads->pInid, szCurInid);
	
	if (apr_file_open (&pfInid, szPath, APR_WRITE | APR_CREATE | APR_TRUNCATE, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
	{	pNomads->pMsg = apr_psprintf(TmpPool, "Unable update file %s with browser ID", szPath);
		Send_SysHtml(pNomads, "sys_error.htm", NULL);
		return FALSE;
	}

	apr_file_printf(pfInid, "%s" NL, pNomads->pInid);
	apr_file_close(pfInid);

	tmLocal.tm_year += 5;
/*	
	pInidCookie = apr_psprintf(TmpPool,	"inid=%s; path=/; expires=%s, %.2d-%s-%.2d %.2d:%.2d:%.2d GMT; SameSite=Lax",
						pNomads->pInid,
						apr_day_snames[tmLocal.tm_wday],
						tmLocal.tm_mday,
						apr_month_snames[tmLocal.tm_mon],
						tmLocal.tm_year % 100,
						tmLocal.tm_hour, 
						tmLocal.tm_min,
						tmLocal.tm_sec);
*/

	pInidCookie = apr_psprintf(TmpPool,	"inid=%s; path=/; SameSite=Lax", pNomads->pInid);
	
	apr_table_setn(Request->headers_out, "Set-Cookie", pInidCookie);
	return TRUE;
}

static apr_table_t *TblParse(struct INOMADS *pNomads, const char *data)
{	const char *key, *val;
	apr_table_t *tab;
	
	tab = apr_table_make(TmpPool, 20);

    while(*data && (val = ap_getword(TmpPool, &data, '&')))
    {	key = ap_getword(TmpPool, &val, '=');
		UnEscapeURL((char*)key);
		UnEscapeURL((char*)val);
		apr_table_merge(tab, key, val);
    }

    return tab;
}

static int UnEscapeURL(char *pVal)
{	int			oInp;
	int			oOut;
	char		cChar, cChar2;

	oInp = oOut = 0;
	for (;;)
	{	cChar = pVal[oInp++];
		if (cChar == '+') cChar = ' ';
		if (cChar != '%')
		{	pVal[oOut++] = cChar;
			if (cChar == 0) return TRUE;
			continue;
		}
			
		cChar = pVal[oInp++];
		if ((cChar >= '0') && (cChar <= '9'))
			cChar2 = cChar - '0';
		else
		if ((cChar >= 'A') && (cChar <= 'F'))
			cChar2 = cChar - 'A' + 10;
		else
		if ((cChar >= 'a') && (cChar <= 'f'))
			cChar2 = cChar - 'a' + 10;
		else
			break;
			
		cChar = pVal[oInp++];
		if ((cChar >= '0') && (cChar <= '9'))
			cChar = cChar - '0';
		else
		if ((cChar >= 'A') && (cChar <= 'F'))
			cChar = cChar - 'A' + 10;
		else
		if ((cChar >= 'a') && (cChar <= 'f'))
			cChar = cChar - 'a' + 10;
		else
			break;
		
		pVal[oOut++] = cChar2 << 4 | cChar;
	}
	
	return FALSE;
}			

/* Session_Find(struct INOMADS *pNomads, BOOL bCreate)
 *
 *		Return a SESSION structure pointer 
 *		If not found allocate one
 */

static struct SESSION * Session_Find(struct INOMADS *pNomads, BOOL bCreate)
{	struct SESSIONS		*pSessions;
	struct SESSION		*pSession;
	int					nIdx;

	ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Find session %s", pNomads->szSession);	

	if ((pNomads->pSession) && bCreate) return pNomads->pSession;

	pSession = NULL;
	apr_thread_mutex_lock(pNomads->pConf->mutexSessions);

	for(pSessions = pNomads->pConf->pSessions; pSessions != NULL; pSessions = pSessions->pNext)
	{	for (nIdx = 0; nIdx < SESSION_CNT; nIdx++)
		{	if (stricmp(pNomads->szSession, pSessions->session[nIdx].szSession) == 0)
			{	pSession = &(pSessions->session[nIdx]);
				break;
			}
			
			if (pSession != NULL) continue;

			if (pSessions->session[nIdx].szSession[0] == 0)
				pSession = &(pSessions->session[nIdx]);
		}
	}

	if (!bCreate)
	{	apr_thread_mutex_unlock(pNomads->pConf->mutexSessions);

		if ((pSession == NULL) || (pSession->szSession[0] == 0)) return NULL;
		pSession->nIdleHours = 0;
		return pSession;
	}

	if (pSession == NULL)
	{	pSessions = (struct SESSIONS *)apr_pcalloc(SysPool, sizeof(struct SESSIONS));

		ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "AlLocating SESSIONS table at " PTR_FMT , pSessions);	

		pSessions->pNext = pNomads->pConf->pSessions;
		pNomads->pConf->pSessions = pSessions;

		pSession = &(pSessions->session[0]);
	}
	
	if (pSession->szSession[0] == 0)
	{	memset(pSession, 0, sizeof(struct SESSION));
		strcpy(pSession->szSession, pNomads->szSession);

		ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Allocating SESSION %s entry at " PTR_FMT, pNomads->szSession, pSession);
	}

	pSession->nReq++;
	pSession->nIdleHours = 0;

	apr_thread_mutex_unlock(pNomads->pConf->mutexSessions);

	ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Returning SESSION %s entry at " PTR_FMT " (socket=" PTR_FMT ")", pNomads->szSession, pSession, pSession->pSocket);
	return pNomads->pSession = pSession;
}

/* Session_Free(struct INOMADS *pNomads)
 *
 *		Free a session structure
 */

static void Session_Free(struct INOMADS *pNomads)
{	struct SESSION	*pSession;
	
	if (pNomads->pSession == NULL) return;
	pSession = pNomads->pSession;

	apr_thread_mutex_lock(pNomads->pConf->mutexSessions);

	pSession->nReq--;
	if ((pSession->nReq <= 0) && (pSession->nPort == 0) && (pSession->pSocket == NULL))
	{	if (pSession->pPool) apr_pool_destroy(pSession->pPool);
		memset(pSession, 0, sizeof(struct SESSION));
		ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, Request->server, "Releasing SESSION %s entry at " PTR_FMT, pNomads->szSession, pSession);
	}

	pNomads->pSession = NULL;

	apr_thread_mutex_unlock(pNomads->pConf->mutexSessions);

	return;
}

/* Session_Cleanup(struct INOMADS *pNomads, apr_time_t tmNow)
 *
 *		Every hour scan and purge sessions with no activity for past 6 hours
 */

static void Session_Cleanup(struct INOMADS *pNomads, apr_time_t tmNow)
{	int					nSesCleanHr;
	struct SESSIONS		*pSessions;
	struct SESSION		*pSession;
	int					nIdx;

	nSesCleanHr = (int)(apr_time_sec(tmNow) / 3600);
	if (nSesCleanHr == pNomads->pConf->nSesCleanHr) return;

	apr_thread_mutex_lock(pNomads->pConf->mutexSessions);
	
	/* Re-check after the Mutex to avoid any potential race condition */

	if (nSesCleanHr != pNomads->pConf->nSesCleanHr)
	{	pNomads->pConf->nSesCleanHr = nSesCleanHr;

		for(pSessions = pNomads->pConf->pSessions; pSessions != NULL; pSessions = pSessions->pNext)
		{	for (nIdx = 0; nIdx < SESSION_CNT; nIdx++)
			{	pSession = &pSessions->session[nIdx];
				if (pSession->szSession[0] == 0) continue;
			
				pSession->nIdleHours++;
				if (pSession->nIdleHours <= 6) continue;
			
				if (pSession->pSocket != NULL) apr_socket_close(pSession->pSocket);
				if (pSession->pPool != NULL) apr_pool_destroy(pSession->pPool);

				memset(pSession, 0, sizeof(struct SESSION));
			}
		}
	}

	apr_thread_mutex_unlock(pNomads->pConf->mutexSessions);
	return;
}

/* FileIO_Process(pNomads)
 *
 *		Check to see if txid indicates file io request and if so processes the request
 *
 *		Valid functions (in fnc= from URL/Query string) are
 *			GET		- Return file
 *			PUT		- Write posted data to file -- create if needed (including sub directories)
 *			DEL		- Delete file (if directory deletes subordinates)
 *
 *		Routine returns TRUE if file IO request overwise FALSE
 */

#define FILEIO_BLOCK_SIZE		100000

static int FileIO_Process(struct INOMADS *pNomads)
{	char		*pTxid;
	char		*pFnc;
	char		*pPath;
	char		*pScan, c;
	char		szHash[512];
	char		szPath[MAX_PATH_NAME];

	pTxid = (char *)apr_table_get(pNomads->tblArgs,"txid");
	if (!pTxid) return FALSE;
	if (strlen(pTxid) != 48) return FALSE;

	ap_set_content_type(Request, "application/octet-stream");
	
	pFnc = (char *)apr_table_get(pNomads->tblArgs,"fnc");
	if (!pFnc) 
	{	FileIO_Error("Missing function specification");
		return TRUE;
	}

	pPath = (char *)apr_table_get(pNomads->tblArgs,"path");
	if (!pPath)
	{	FileIO_Error("Missing pathname");
		return TRUE;
	}

	strcpy(szPath, pNomads->pDocRoot);
	if ((pPath[0] != '/') && (pPath[0] != '\\')) strcat(szPath, "/");
	strcat(szPath, pPath);

	for (pScan = szPath + strlen(pNomads->pDocRoot);;)
	{	c = *(pScan++);
		if (c == 0) break;

		if (c == '\\') *(pScan-1) = c = '/';

		if (c == '/')
		{	if ((pScan[0] == '.') && (pScan[1] == '.'))
			{	FileIO_Error("Invalid pathname (double dot)");
				return TRUE;
			}
		}
	}

	memcpy(szHash, pTxid, 8);
	strcpy(szHash+8, pNomads->pConf->pCrypto);
	strcat(szHash+8, pPath);
	strcat(szHash+8, pFnc);
	strcat(szHash, Compute_SHA1(pNomads, (unsigned char *)szHash, strlen(szHash)));

	if (strcmp(Compute_SHA1(pNomads, (unsigned char *)szHash, strlen(szHash)), pTxid+8) != 0)
	{	FileIO_Error(apr_psprintf(TmpPool, "Wrong hash. S/B=%s", pNomads->szSHA1));
		return TRUE;
	}

	if (apr_strnatcasecmp(pFnc,"GET") == 0)
		FileIO_Get(pNomads, szPath);
	else
	if (apr_strnatcasecmp(pFnc,"PUT") == 0)
		FileIO_Put(pNomads, szPath);
	else
	if (apr_strnatcasecmp(pFnc,"DEL") == 0)
		FileIO_Del(pNomads, szPath);
	else
		FileIO_Error("Un-supported function");
	
	return TRUE;
}

/* FileIO_Get(pNomads, szPath)
 *
 *		Send contents of file plus include the SHA1 in the header as an ETag
 *		Content-Range will also be sent
 */

static void FileIO_Get(struct INOMADS *pNomads, char *pPath)
{	unsigned char	*pBfr;
	apr_size_t		nBytes;
	apr_finfo_t		infoFile;

	if (apr_stat(&infoFile, pPath, APR_FINFO_TYPE | APR_FINFO_SIZE, TmpPool) != APR_SUCCESS)
	{	FileIO_Error("Cannot find file on server");
		return;
	}

	pBfr = NULL;
	if (infoFile.filetype == APR_DIR)
	{	nBytes = FileIO_GetDir(pNomads, pPath, &pBfr);
	}	
	else
	if (infoFile.filetype == APR_REG)
	{	nBytes = FileIO_GetFile(pNomads, pPath, &pBfr, (apr_size_t)infoFile.size); 
	}
	else
	{	FileIO_Error("Invalid file type");
	}
	if (pBfr == NULL) return;


	if (nBytes > 0)
	{	apr_table_setn(Request->headers_out, "ETag", apr_psprintf(TmpPool, "\"%s\"", Compute_SHA1(pNomads, pBfr, nBytes) ) );
		ap_rwrite(pBfr, nBytes, Request);
	}
}

/* FileIO_GetDir(pNomads, szPath, ppBfr)
 *
 *		Generate directory listing of all files as a text file with each line having
 *			filename <tab> type <tab> size <tab> last_mtime_in_sec 
 *		Type will be D or F
 *
 *		Pipes and other odd ball file types are not sent
 *		Content-Range will also be sent
 */

static apr_size_t FileIO_GetDir(struct INOMADS *pNomads, char *pPath, unsigned char **ppBfr)
{	unsigned char		*pBfr;
	int					nBytes, nBfrSize;
	apr_dir_t			*pDir;
	apr_finfo_t			infoFile;
	char				szLine[200], fType;
	int					lenLine;
	int					tmUTC;
	pBfr = NULL;
	nBytes = 0;

	apr_dir_open (&pDir,  pPath,  TmpPool);
	for(;;)
	{	if (apr_dir_read(&infoFile,	APR_FINFO_NAME | APR_FINFO_TYPE | APR_FINFO_SIZE | APR_FINFO_MTIME, pDir) != APR_SUCCESS)
		{	if (pBfr != NULL) break;
			apr_dir_rewind(pDir);
			
			nBfrSize = nBytes + 100;	/* In case files created during rewind */
			*ppBfr = pBfr = (unsigned char *)apr_palloc(TmpPool, nBfrSize + 1);
			nBytes = 0;
			continue;
		}

		if (strcmp(infoFile.name,".") == 0) continue;
		if (strcmp(infoFile.name,"..") == 0) continue;

		if (infoFile.filetype == APR_DIR)
			fType = 'D';
		else
		if (infoFile.filetype == APR_REG)
			fType = 'F';
		else
			continue;

		tmUTC = (int)apr_time_sec(infoFile.mtime);
		sprintf(szLine, "%s\t%c\t%d\t%d\r\n", infoFile.name, fType, (int)infoFile.size, tmUTC);
		lenLine = strlen(szLine);

		if ((pBfr != NULL) && ((nBytes + lenLine) < nBfrSize))  memcpy(pBfr + nBytes, szLine, lenLine);
		nBytes += lenLine;
	}

	apr_dir_close(pDir);
	apr_table_setn(Request->headers_out, "Content-Range", apr_psprintf(TmpPool, "bytes 0-%d/%d", max(0,nBytes-1), nBytes));

	return nBytes;
}

/* FileIO_GetFile(pNomads, szPath, ppBfr, fileSize)
 *
 *		Return all or portion of file contents.  Will look for Content-Range tag to send partial
 *		section of file otherwise will send full contents
 *
 *		Pipes and other odd ball file types are not sent
 *		Content-Range will also be sent
 */

static apr_size_t FileIO_GetFile(struct INOMADS *pNomads, char *pPath, unsigned char **ppBfr, apr_size_t nFileSize)
{	apr_file_t		*pFile;
	apr_off_t		nOffset;
	apr_size_t		nLength;
	unsigned char	*pBfr;

	nOffset = 0;
	nLength = nFileSize;

	FileIO_GetRange(pNomads, &nOffset, &nLength);

	/* If file empty and we didn't specify what we want in terms of length return 0 bytes */

	if (nLength == 0)
	{	*ppBfr = (unsigned char *)"";
	}
	else
	{	if (apr_file_open(&pFile, pPath, APR_READ | APR_BINARY, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
		{	FileIO_Error("Cannot open file on server");
			return 0;
		}

		if (nOffset > 0) apr_file_seek(pFile, APR_SET, &nOffset);

		pBfr = (unsigned char *)apr_palloc(TmpPool, nLength);

		if (apr_file_read(pFile, pBfr, &nLength) != APR_SUCCESS)
		{	apr_file_close(pFile);
			FileIO_Error("Cannot read file");
			return 0;
		}

		apr_file_close(pFile);
	
		*ppBfr = pBfr;
	}
	
	apr_table_setn(Request->headers_out, "Content-Range", apr_psprintf(TmpPool, "bytes %d-%d/%d", (int)nOffset, (int)(nOffset+max(0,nLength-1)), (int)nFileSize));
	return nLength;
}


/* FileIO_Put(pNomads, szPath)
 *
 *		Send contents of file plus include the SHA1 in the header as an ETag
 *		Content-Range will also be sent
 */

static void FileIO_Put(struct INOMADS *pNomads, char *pPath)
{	apr_file_t		*pFile;
	unsigned char	*pBfr;
	char			*pETag;
	apr_size_t		nBytes;
	apr_finfo_t		infoFile;
	apr_off_t		nOffset, nOfsIn;
	apr_int32_t		flgOpen;

	char			*pDlm;
	char			bfrTemp[HUGE_STRING_LEN];
	long			nRead;
	int				lenPath;

	if ((RetCode = ap_setup_client_block(Request, REQUEST_CHUNKED_ERROR)) != OK)
	{	FileIO_Error("Cannot accept PUT data");
		return;
	}
	
	if (!ap_should_client_block(Request)) return;

	if (Request->remaining > 10*1024*1024) 
	{	FileIO_Error("PUT file exceeds 10MB"); 
		return;
	}

	nBytes = (long)Request->remaining;
	pBfr = (unsigned char *)apr_palloc(TmpPool, nBytes + 1);
	
	nOfsIn = 0;
	while (1)
	{	nRead = (long)min(sizeof(bfrTemp), nBytes-nOfsIn);
		if (nRead <= 0) break;

		nRead = ap_get_client_block(Request, bfrTemp, nRead);
		if (nRead <= 0) break;
		
		memcpy(pBfr + nOfsIn, bfrTemp, nRead);
		nOfsIn += nRead;
	}

	nOffset = 0;
	FileIO_GetRange(pNomads, &nOffset, &nBytes);

	if (nBytes != nOfsIn)
	{	FileIO_Error(apr_psprintf(TmpPool, "PUT length mismatch: Read=%d Expected=%d", (int)nOfsIn, (int)nBytes));
		return;
	}
	
	if (nBytes > 0)
	{	pETag = (char *)apr_table_get(Request->headers_in, "ETag");
		if (pETag == NULL) pETag = "";

		if (strcmp(Compute_SHA1(pNomads, pBfr, nBytes), pETag) != 0)
		{	FileIO_Error("SHA compare error"); 
			return;
		}
	}

	/* If offset is zero might be a file create request */

	flgOpen = APR_WRITE | APR_BINARY;
	if (nOffset == 0)
	{	flgOpen |= APR_CREATE | APR_TRUNCATE;
	
		if (apr_stat(&infoFile, pPath, APR_FINFO_TYPE | APR_FINFO_SIZE, TmpPool) != APR_SUCCESS)
		{	/* File does not exist -- Create intermediary directories */
				
			pDlm = pPath + strlen(pNomads->pRmtRoot);
			for(;;)
			{	pDlm = strchr(pDlm + 1, '/');
				if (pDlm == NULL) break;

				*pDlm = 0;
				apr_dir_make(pPath, APR_OS_DEFAULT, TmpPool);
				*pDlm = '/';
			}
		}
	}

	/* Special logic to make sure .link doesn't actually exist with no data in it */

	lenPath = strlen(pPath);
	if ((lenPath >= 6) && (memcmp(pPath + lenPath - 6, "/.link", 6) == 0))
		pPath[lenPath-1] = 'x';
	else
		lenPath = 0;

	if (apr_file_open(&pFile, pPath, flgOpen, APR_OS_DEFAULT, TmpPool) != APR_SUCCESS)
	{	FileIO_Error("Cannot open/create file for output");
		return;
	}

	if (nBytes > 0) 
	{	if (apr_file_seek(pFile, APR_SET, &nOffset) != APR_SUCCESS)
		{	FileIO_Error("Seek failed");
			return;
		}
		if (apr_file_write(pFile, pBfr, &nBytes) != APR_SUCCESS)
		{	FileIO_Error("Write failed");
			return;
		}

	}
	apr_file_close(pFile);

	if (lenPath != 0)
	{	strcpy(bfrTemp, pPath);
		pPath[lenPath-1] = 'k';
		apr_file_remove(pPath, TmpPool);
		apr_file_rename(bfrTemp, pPath, TmpPool);
	}

	FileIO_Okay(apr_psprintf(TmpPool, "File updated: wrote %d of %d", (int)nBytes, (int)nOfsIn));
	return;
}

/* FileIO_Del(pNomads, szPath)
 *
 *		Delete the specified file or directory
 */

static void FileIO_Del(struct INOMADS *pNomads, char *pPath)
{	apr_finfo_t		infoFile;
	char			szPath[MAX_PATH_NAME];

	strcpy(szPath, pNomads->pDocRoot);
	strcat(szPath, "/tmp/");

	if (memcmp(szPath, pPath, strlen(szPath)) != 0)
	{	FileIO_Error("Can only delete in tmp directory");
		return;
	}

	if (apr_stat(&infoFile, pPath, APR_FINFO_TYPE | APR_FINFO_SIZE, TmpPool) != APR_SUCCESS)
	{	FileIO_Okay("File not found");
		return;
	}

	if (infoFile.filetype == APR_DIR)
	{	FileIO_DelDir(pNomads, pPath);
		FileIO_Okay("Directory removed");
	}
	else
	{	apr_file_remove(pPath, TmpPool);
		FileIO_Okay("File removed");
	}
	return;
}

/* FileIO_DelDir(pNomads, szPath)
 *
 *		Delete the specified directory with recursion
 */

static void FileIO_DelDir(struct INOMADS *pNomads, char *pPath)
{	apr_finfo_t			infoFile;
	apr_dir_t			*pDir;

	apr_dir_open(&pDir,  pPath,  TmpPool);

	for(;;)
	{	if (apr_dir_read(&infoFile,	APR_FINFO_NAME | APR_FINFO_TYPE | APR_FINFO_SIZE | APR_FINFO_MTIME, pDir) != APR_SUCCESS) break;
		
		if (strcmp(infoFile.name, ".") == 0) continue;
		if (strcmp(infoFile.name, "..") == 0) continue;

		if (infoFile.filetype == APR_DIR)
		{	FileIO_DelDir(pNomads, apr_psprintf(TmpPool, "%s/%s", pPath, infoFile.name));
		}
		else
		{	apr_file_remove(apr_psprintf(TmpPool, "%s/%s", pPath, infoFile.name), TmpPool);
		}
	}
	
	apr_dir_close(pDir);
	apr_dir_remove(pPath, TmpPool);
}	


static void FileIO_GetRange(struct INOMADS *pNomads, apr_off_t *nOffset, apr_size_t *nLength)
{	char		*pRange, c;
	int			nEnd, nStrt;

	nEnd = nStrt = 0;

	pRange = (char *)apr_table_get(Request->headers_in, "Content-Range");
	if (!pRange) return;

	for(;;)
	{	c = *(pRange++);
		if ((c == 0) || (c == '/')) break;

		if (c == '-') 
		{	nStrt = nEnd;
			nEnd = 0;
		}
		else
		if ((c >= '0') && (c <= '9'))
		{	nEnd = (nEnd * 10) + (c - '0');
		}
	}

	*nOffset = nStrt;
	*nLength = nEnd - nStrt + 1;
}

/* Compute_SHA1(pNomads, char *szStr, int lenStr)
 *
 *		Converts string to SHA1 hash and stores it in szSHA1 field in the pNomads structure
 */

static char *Compute_SHA1(struct INOMADS *pNomads, unsigned char *szStr, int lenStr)
{	unsigned char		bfrSHA1[APR_SHA1_DIGESTSIZE];
	apr_sha1_ctx_t		sha1CTX;

	if (lenStr < 0) lenStr = strlen((char *)szStr);

	apr_sha1_init(&sha1CTX);	
	apr_sha1_update_binary(&sha1CTX, szStr, lenStr);
	apr_sha1_final(bfrSHA1, &sha1CTX);

	return Convert_2HEX(bfrSHA1, APR_SHA1_DIGESTSIZE, pNomads->szSHA1); 
}

/* Convert_2HEX(unsigned char *pInput, int lenInput, char *pHexString)
 *
 *		Converts binary data into a hexstring and returns ptr to it
 */

static char *Convert_2HEX(unsigned char *pInput, int lenInput, char *pHexString)
{	int					i;
	unsigned char		c;
	static char			szHex[] = "0123456789ABCDEF";	

	for (i=0; i < lenInput; i++)
	{	c = pInput[i];
		pHexString[i*2] = szHex[(c >> 4) & 0x0F];
		pHexString[i*2 + 1] = szHex[c & 0x0F];
	}
			
	return pHexString;
}

/* Hook our handler into Apache at startup */

static void inomads_hooks(apr_pool_t* pool)
{	ap_hook_handler(inomads_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA inomads_module = {
        STANDARD20_MODULE_STUFF,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        inomads_hooks
};
