Click here to Skip to main content
15,896,557 members
Articles / Desktop Programming / MFC

FiveLoaves v1.0

Rate me:
Please Sign up or sign in to vote.
3.84/5 (10 votes)
2 Jul 20028 min read 84.8K   4.4K   49  
FiveLoaves is an Internet utility designed to meet the most common needs of internet users - primarily secure connectivity
// --------------------------------------------------------------------------
//					www.UnitedBusinessTechnologies.com
//			     Copyright (c) 2002  All Rights Reserved.
//
// Source in this file is released to the public under the following license:
// --------------------------------------------------------------------------
// This product may be used free of charge for any purpose including corporate
// and academic use.  For profit, and Non-Profit uses are permitted.
//
// This source code and any work derived from this source code must retain 
// this copyright at the top of each source file.
// 
// UBT welcomes any suggestions, improvements or new platform ports.
// email to: 5Loaves@UnitedBusinessTechnologies.com
// --------------------------------------------------------------------------



// Some resources that might be useful to future engineers:

// bZIP						=  http://sources.redhat.com/bzip2/index.html
// TwoFish					=  http://www.counterpane.com/twofish.html
// POSIX Threads			=  http://www.math.arizona.edu/swig/pthreads/threads.html
// Sockets Win&BSD			= (http://www.sockets.com/winsock.htm) && (http://www.developerweb.net/sock-faq/)
// UBT C++ Framework		=  http://www.ub2b.com/XMLFoundation.html
// Socket Error Descriptions=  http://www.sockets.com/err_lst1.htm

//  Special compiler patch or security notes: 
//  VC++:  http://www.cigital.com/news/mscompiler-tech.html
//  GNU:   http://www.trl.ibm.com/projects/security/ssp/




// one include for all portable utility classes
// (lists, sorts, xmlparser, cipher, compression etc...)
#include "XMLFoundation.h"



// OS specific includes:
#ifdef _WIN32
	#define LINK_VNC_REMOTE_WS // currently only in windows
	#include <io.h>			// for _chmod()
#else // linux & sun
	#include <arpa/inet.h>	// for inet_addr()
	#include <netdb.h>		// for gethostbyname()
	#include <unistd.h>		// for gethostname()
#endif


#ifdef LINK_VNC_REMOTE_WS
 #define _UBT_BUILD_    // define the correct flags for vncServer.h
 #ifdef _WIN32
  #define __WIN32__
 #endif
 #include "vncServer.h" // and include the RemoteWorkstation definitions
#endif //LINK_VNC_REMOTE_WS


#include <ctype.h> //  for isprint()
#include <time.h>		
#include <sys/types.h>
#include <sys/stat.h> 
#include <stdlib.h> // for atol(), strtol(), srand(), rand(), atoi()

#ifdef _AIX
#include <sys/select.h>
#endif


#ifdef _WIN32			// define a portable (Win32/Unix) error retrieval macro
	#define SOCK_ERR WSAGetLastError()
	#include <direct.h>
	#define socklen_t int
#else
	#define SOCK_ERR errno
#endif


// UBT Server extension
#ifdef INTERNAL_UBT_BUILD
 #include "ServerGlobal.cpp"
#else
 #define OBJECT_PRECACHE
 #define SERVER_MEMORY_EXTENSION
 #define SERVER_MEMORY_CLEANUP
 #define SERVER_MEMORY_RESET
 #define PROPIGATE_SERVER_EXCEPTION
#endif


#define MAX_RAW_CHUNK		64000					// max size of raw packet data
//#define MAX_RAW_CHUNK		2000					// tested, works fine. uses less memory.  runs a little slower.
#define MAX_DATA_CHUNK		MAX_RAW_CHUNK + 1024	// max size of pressed/crypted data packet


// Data conversion attributes. 
#define ATTRIB_SYNCRONIZED				0x0001
#define ATTRIB_COMPRESSED				0x0002
#define ATTRIB_CRYPTED_2FISH128			0x0004
#define ATTRIB_CRYPTED_2FISH256			0x0008
#define ATTRIB_UNUSED_BIT5				0x0010
#define ATTRIB_UNUSED_BIT6				0x0020
#define ATTRIB_UNUSED_BIT7				0x0040
#define ATTRIB_UNUSED_BIT8				0x0080
#define ATTRIB_UNUSED_BIT9				0x0100
#define ATTRIB_UNUSED_BIT10				0x0200
#define ATTRIB_UNUSED_BIT11				0x0400
#define ATTRIB_UNUSED_BIT12				0x0800
#define ATTRIB_UNUSED_BIT13				0x1000
#define ATTRIB_UNUSED_BIT14				0x2000
#define ATTRIB_UNUSED_BIT15				0x4000
#define ATTRIB_UNUSED_BIT16				0x8000

//function prototypes and global variables 
void *clientThread(void *);
int g_ServerIsShuttingDown = 0;
int g_ThreadPoolSize = 0;
int g_ProxyPoolSize = 0;
int g_ThreadIDCount = 0;
int g_TraceIn = 0;
int g_TraceOut = 0;
int g_nRrandSeed = 777;
int g_DataTrace = 0;
int g_ThreadPing = 0;
int g_listenerIsRebooting = 0;
int g_PingListenThread = 0;
int g_ConnectionDataTrace = 0;
int g_HTTPTrace = 0;
int g_TraceThread = 0;
int g_LogStdOut = 0;


#ifndef _WIN32
char g_chSlash = '/';
#else
char g_chSlash = '\\';
#endif

static const char *g_pzCoreErrorSection = "ServerCore";

unsigned int g_RandomNumbers[8];
GString g_DataTraceFile;
GString g_strThisIPAddress;
GString g_strThisHostName;
long g_ServerStartTime;

__int64 g_TotalHits = 0;
__int64 g_Total404s = 0;
__int64 g_TotalTooBusy = 0;
__int64 g_PreZipBytes = 0;
__int64 g_PostZipBytes = 0;



// given either an ip address or a host name, this function 
// returns true if the address is 'this' machine
int IsMe(const char *pzIP)
{
	if (g_strThisIPAddress.Compare(pzIP) == 0)
		return 1;
	if (g_strThisHostName.CompareNoCase(pzIP) == 0)
		return 1;
	if(strcmp(pzIP,"127.0.0.1") == 0)
		return 1;

	struct hostent *pHELocal = (struct hostent *)gethostbyname(pzIP);
	if (pHELocal)
	{
		struct sockaddr_in my_addr; 
		memcpy((char *)&(my_addr.sin_addr), pHELocal->h_addr,pHELocal->h_length); 
		memset(&(my_addr.sin_zero),0, 8);// zero the rest of the (unused) struct
		
		const char *pzThatIPResolved = inet_ntoa(my_addr.sin_addr);
		if (pzThatIPResolved && pzThatIPResolved[0])
		{
			if (g_strThisIPAddress.Compare(pzThatIPResolved) == 0)
				return 1;
		}
	}

	return 0;
}


// A single point in this application for all Log/Trace output.
void InfoLog(GString &str, int nNewLine = 1)
{
	if (nNewLine)
		str += "\n";

	const char *pzLogFile = GetProfile().GetString("System","FileLog",false);
	if (pzLogFile)
		str.ToFileAppend(pzLogFile);
	else
		str.ToFileAppend("Log.txt");

	if (g_LogStdOut)
		printf((const char *)str);
}
void InfoLog(const char *pDataToLog, int nNewLine = 1)
{
	GString str(pDataToLog);
	InfoLog(str,nNewLine);
}



// Init application instance random numbers used to securely filter
// local host connections. 
void InitRandomNumbers()
{
	InfoLog("Random Key Init:");

	// capture the server start time, used for admin statistic info.
	time(&g_ServerStartTime);

	// seed the system random number generator
	srand( g_ServerStartTime );

	// increment each by time() seeded rand() to give them a starting value
	for(int nIndex=0; nIndex < 8; nIndex++)
		g_RandomNumbers[nIndex] += rand();


	// each number is incremented for a 'random()' amount of time. 
	// some machines are faster than others, and current system load also affects the values.
	for(int n_RandomNumberWorkerIndex=0; n_RandomNumberWorkerIndex < 8; n_RandomNumberWorkerIndex++)
	{
		InfoLog("*",0);
		int nYeildTime = 1 + (rand() % 7);
		unsigned long lStartTime = time(0);

		// round and round she goes, where she stops nobody knows.
		while (time(0) - lStartTime < nYeildTime)
		{
			g_RandomNumbers[n_RandomNumberWorkerIndex]++;
			
			// context switching randomizes the numbers better and magnifies the 
			// effects of other applications currently using system resource.
			sched_yield(); 
		}
	}

	// now re-seed the random generator(with 8 different CPU-Cycle based seed numbers), 
	// then increment each value by rand() for each seed.
	for(int nSeedCount=0; nSeedCount<8; nSeedCount++)
	{
		srand( g_RandomNumbers[nSeedCount] );
		InfoLog(".",0);
		for(int nIndex2=0; nIndex2 < 8; nIndex2++)
			g_RandomNumbers[nIndex2] += rand();	
	}
	InfoLog("Done");

	// finally make sure none of the numbers are == 0, it breaks other algoriths
	// that depend on them as valid seed numbers
	for(int nIndex3=0; nIndex3<8; nIndex3++)
	{
		if (g_RandomNumbers[nIndex3] == 0)
		{
			srand( time(0) );
			g_RandomNumbers[nIndex3] = rand();
			if (g_RandomNumbers[nIndex3] == 0)		
				// luck, superstition, & horoscopes are false religions.
				g_RandomNumbers[nIndex3] = 777 | 13; // any non-zero number.
		}
	}
}


// Verify that the connection we just recieved originated from 'this' 
// application.  In version 1, this is used to authenticate VNC session connections.
int JudgeConnection(unsigned char *pzIn, const char *pzConnectingFromIP) 
{
	// check the TCP headers to see where this guy came from.
	// TCP headers can lie, so this check is pointless against a professional hacker.
	if (!IsMe(pzConnectingFromIP))
	{
		//1=JudgeConnection: TCP header[%s] IP not from localhost
		GString strLog;
		strLog.LoadResource(g_pzCoreErrorSection, 1, pzConnectingFromIP);
		InfoLog(strLog);
		return 0;
	}

	// encode the number of seconds that have elapsed since this(127.0.0.1) was started to right now.
	// This makes a small window of time, that the verification attempt is valid.
	// Guarding against 'record and playback' attacks, where a valid logon sequence could be
	// recorded in it's encrypted state.
	long lNow;
	time(&lNow);
	long lElapsed = lNow - g_ServerStartTime;
	long inElapsed;
	memcpy(&inElapsed,pzIn,4); // no regard to byte order since it must come from 'this' host

	if (lElapsed - inElapsed > 2 || lElapsed - inElapsed < 0)
	{
		// 2=JudgeConnection: Not within the 2 second time window[%d][%d]
		GString strLog;
		strLog.LoadResource(g_pzCoreErrorSection, 2, inElapsed,lElapsed);
		InfoLog(strLog);

		return 0;
	}

	// see if he knows the password, he sent it MD5 digested, so digest ours then compare them
	unsigned char pzDoctorUp[16];
	const char *pzPass = GetProfile().GetString("RemoteWorkstation","Password",false);
	Hash((void *)pzPass, strlen(pzPass), pzDoctorUp, 128);
	if (memcmp(pzDoctorUp,&pzIn[4],16) != 0)
	{
		//3=JudgeConnection: Cannot verify password
		GString strLog;
		strLog.LoadResource(g_pzCoreErrorSection, 3);
		InfoLog(strLog);
		return 0;
	}
	
	
	// ok, finally see if this he came from this machine  and knows the random 256 bits that only we know.
	if (memcmp(&pzIn[20],g_RandomNumbers,32) != 0)
	{
		// he knows the password.  he has excellent timing.
		// his TCP headers report a local host client......
		// but we don't know where he came from because he 
		// doesn't know our unique memory.  kill him.

		//4=JudgeConnection: Failed random memory verification
		GString strLog;
		strLog.LoadResource(g_pzCoreErrorSection, 4);
		InfoLog(strLog);
		return 0;
	}

	// he's legit. let him in - and give him the keys to the kingdom.
	return 1;
}


// so that the GUI does not need to concern it's self with what direction the slashes go.
// Allows paths like this "/usr/path\subdir" or "c:\my/folder/file.ext"
const char *ReSlash(const char *pzInParam)
{
	char *pzIn = (char *)pzInParam; 
	int nLen = strlen(pzInParam);
	for(int i=0; i<nLen; i++ )
	{
		if (pzIn[i] == '\\' || pzIn[i] == '/')
		{
			pzIn[i] = g_chSlash;
		}
	}
	return pzIn;
}


// contains the attributes of two connections that will 'meet' at 'this' server
class SwitchEntry
{
public:
	int fdClient;
	int fdServer;
	pthread_mutex_t lock;
	pthread_cond_t	cond;
	SwitchEntry()
	{
		fdServer = -1;
		fdClient = -1;
		pthread_mutex_init(&lock,0);
		pthread_cond_init(&cond,0);
		pthread_mutex_lock(&lock);
	}
	~SwitchEntry()
	{
		pthread_mutex_destroy(&lock);
		pthread_cond_destroy(&cond);
	}
};


// implementation of the [SwitchBoradServer]
class SwitchBoard
{
	GBTree m_hshState;
	pthread_mutex_t m_cs;
public:
	SwitchBoard()
	{
		pthread_mutex_init(&m_cs, NULL);
	}
	~SwitchBoard()
	{
		pthread_mutex_destroy(&m_cs);
	}
	
	// an inbound fd that was accepted() from a port 80 listen()
	// on this machine, and a connection name.
	int HandOffConnection (int fd, const char *key)
	{
		pthread_mutex_lock(&m_cs);
		SwitchEntry *pSE = (SwitchEntry *)m_hshState.search(key);
		if (pSE)
		{
			m_hshState.remove(key);
			if (g_ConnectionDataTrace)
			{
				//5=Picking up Connection [%s] on SwitchBoardServer
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 5, key);
				InfoLog(strLog);
			}
			pSE->fdServer = fd;

			
			// FYI: SwitchBoard process flow.  See comment in ConnectToServer()
			// send(fd, "OK", 2, 0 );


			pthread_cond_signal(  &pSE->cond );
			pthread_mutex_unlock(&m_cs);
			return 1;
		}
		
		if (g_ConnectionDataTrace)
		{
			//6=No Picking up for [%s] on SwitchBoardServer
			GString strLog;
			strLog.LoadResource(g_pzCoreErrorSection, 6, key);
			InfoLog(strLog);
		}

		send(fd, "--", 2, 0 );
		pthread_mutex_unlock(&m_cs);
		return 0;
	}
	
	int ConnectToServer (const char *key)
	{
		SwitchEntry se;
	
		timespec ts;
		ts.tv_sec=time(NULL) + 60; // 1 minute wait
		ts.tv_nsec=0;


		// insert the pending connection into the hash
		pthread_mutex_lock(&m_cs);
		m_hshState.insert(key, (void *)&se);
		pthread_mutex_unlock(&m_cs);

		if (g_ConnectionDataTrace)
		{
			//6=Waiting for a [%s] server to pick up this connection
			GString strLog;
			strLog.LoadResource(g_pzCoreErrorSection, 6, key);
			InfoLog(strLog);
		}

		// wait for a condition signal(connection picked up by another thread) or timeout
		pthread_cond_timedwait(&se.cond, &se.lock, &ts); 

		// if we timed out 
		if (time(0) >= ts.tv_sec)
		{
			// nobody picked up this connection
			if (g_ConnectionDataTrace)	
			{
				//7=Done Waiting ..... No connection 
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 7);
				InfoLog(strLog);
			}
			
			pthread_mutex_lock(&m_cs);
			m_hshState.remove(key);
			pthread_mutex_unlock(&m_cs);
			return -1; // there is no "posted listener" for key
		}
		
		// otherwise the connection is now being serviced by another thread

		if (g_ConnectionDataTrace)
		{
			//8=Done Waiting ..... Connecting
			GString strLog;
			strLog.LoadResource(g_pzCoreErrorSection, 8);
			InfoLog(strLog);
		}
		
		// FYI: To understand the SwitchBoard process flow.
		// Send the protocol control pickup notification to whomever initiated this connection.
		// This line of code may exist here or in HandOffConnection() that runs on the 'pickup' thread.
		send(se.fdServer, "OK", 2, 0 );

		return se.fdServer;
	}

};
SwitchBoard g_SwitchBoard;





// For a very fast implementation of connection restrictions:
// Incomming connections are hashed(based on IP address) to index 
// an integer in this array, that is incremented when a connection
// is being processed, and decremented when that thread completes.

// This may need to be mutexed on multi-CPU RISC machines:
//   http://www3.sympatico.ca/n.rieck/docs/riscy-c-code.html
// It should remain non-mutexed on other platforms for performance gain.

// Collisions will case 2 ip's to 'double the cumulative active connection total'
// A large hash causes the collision factor to be almost non existant
// considering proxies/routers will cause the same (cumulative active connection total) 
// to all ip's on the subnet behind the proxy/router.  So connections are restricted
// to a limited number per subnet, for security that's even better than per IP.

// the hash is divided into two equal regions (increment & decrement). So :
//			hash[i] - hash[ACTIVE_CONN_HASH_SIZE / 2) + i]
//	equals the current connection count for the ip(s) that hash to i. 
//                              AND
//  hash[i] is the total number of connections from the IP that have been
//  counted since 'this' server has started.

#define ACTIVE_CONN_HASH_SIZE 200000
static int HashOfActiveConnections[ACTIVE_CONN_HASH_SIZE];
struct ConnHashInit
{
	ConnHashInit(){ memset(HashOfActiveConnections,0,ACTIVE_CONN_HASH_SIZE ); }
} g_ConnHashInit ;

// nChange of 1 increments, -1 decrements
static unsigned int ConnectionCounter ( const char *pzAddress, int nChange )
{
	const unsigned char *ipAddress = (const unsigned char *)pzAddress;
	unsigned long   h = 0, g;
    while ( *ipAddress )
    {
        h = ( h << 4 ) + *ipAddress++;
        if ( g = h & 0xF0000000 )
            h ^= g >> 24;
        h &= ~g;
    }
    int idx = h % ( ACTIVE_CONN_HASH_SIZE / 2);

	if (nChange == 1)
		HashOfActiveConnections[idx]++;
	else
		HashOfActiveConnections[(ACTIVE_CONN_HASH_SIZE / 2) + idx]++;
	
	return HashOfActiveConnections[idx] - 
			HashOfActiveConnections[(ACTIVE_CONN_HASH_SIZE / 2) + idx];
}



// There are 1 million microseconds in a second.
void PortableSleep(int nSeconds, int nMicroseconds)
{
#ifdef _AIX
		struct timespec ts;
		getclock(TIMEOFDAY, &abstime);
		ts.tv_sec += nSeconds;
#else
		timespec ts;
		ts.tv_sec=time(NULL) + nSeconds;
#endif
		ts.tv_nsec=nMicroseconds;
		pthread_cond_t _TimeoutCond;
		pthread_mutex_t _TimeoutLock;
		pthread_mutex_init(&_TimeoutLock,0);
		pthread_cond_init(&_TimeoutCond,0);

		pthread_mutex_lock(&_TimeoutLock);
		pthread_cond_timedwait(&_TimeoutCond, &_TimeoutLock, &ts); 
}





// Rather than adding every known mime type, (many found here:)
//   http://www.isi.edu/in-notes/iana/assignments/media-types/media-types
//   http://home.att.net/~skchen/pwp-mime-type.html
// All types currently in-use will be added in minor version increments
// in a effort to eliminate old/unused types and provide a static bound 
// default of the most common settings.
const char *mimeTypeTable[] = {
   "xml",  "text/xml",
   "css",  "text/css",
   "exe",  "application/octet-stream",
   "gif",  "image/gif",
   "jpeg", "image/jpeg",
   "jpg",  "image/jpeg",
   "htm",  "text/html",
   "html", "text/html",
   "txt",  "text/plain",
   "png",  "image/png",
   "pdf",  "application/pdf",
   "js",   "application/x-javascript",
   "zip",  "application/zip",
   "jar",  "application/java-archive",
   0
};



// This (structure/class/object) contains information "about a listener"
// That is everything needed to listen on a port and apply a protocol.
// This information is passed into the thread that "does the work" but
// no thread state should ever be written into this structure because
// multiple threads servicing this port will all point to the same 
// ThreadStartupData (structure/class/object) it should be treated
// as "read-only" to the servicing thread, they are public only for
// unwanted accessors.
class ThreadStartupData
{
	ThreadStartupData *pNext;
public:
	int nProtocol; // 1 HTTP, 2 Proxy/Tunnel, 3 File Xfer, 4 TXML, 5 TXML&HTTP, 6 Remote-WS, 7 FileXFerProxy
	int nPort;
	int sockfd;	// the handle to the port listener
	
	// if nProtocol == 2 this will also be set
	int nProxyToPort;
	char szProxyToServer[1024];
	char szConfigSectionName[32];
	int nProxyTimeout;
	int bIsTunnel;

	// ThreadStartupData describes the static aspects of a type of connection,
	// If the connection was initiated through the (~route) mechanism, the nPort
	// will be set to -777 and pzPostConnectPathAndName will contain a value like:
	// /MySwitchPath/MyVNCSwitchName.html
	char pzPostConnectPathAndName[1024];

	CipherData *pcd;

	ThreadStartupData * Next(int bCreateIfNone = 0)
	{
		if (!pNext && bCreateIfNone)
			pNext = new ThreadStartupData;
		return pNext;
	}
	ThreadStartupData()
	{
		bIsTunnel = 0;
		nProtocol = 0;
		pNext = 0;
		nProxyToPort = 0;
		szProxyToServer[0] = 0;
		szConfigSectionName[0] = 0;
		pcd = 0;
	}
};



class ThreadConnectionData
{
public:
	int *pnProxyClosed;
	ThreadStartupData *pTSD;
	int sockfd;	// the handle for 'this' accept()'ed connection on pTSD->nPort
	int *pnProxyfd;	
	unsigned long *plLastActivityTime;
	int *pbHelperThreadIsRunning;
	int nParentThreadID;
	pthread_cond_t	*pcondHelper;

	// only set when pTSD->nProtocol == 6 (remote workstation)
	int nAction; // 1=Proxy, 2=Tunnel, 3=Destination
	char pzIP[1024]; // could be a host name, make room for a big one
	char pzConnectRoute[1024];
	char pzRemoteKey[1024];
	int nConnectRouteSize;

	// when this structure is used to poll for a connection
	// this variable contains the section name like "PollExternalConnect1" ... "PollExternalConnectN"
	char pzPollSectionName[32]; 
	int  nPollInterval; 
	long lLastPoll;


	
	ThreadConnectionData()
	{
		// only set when pTSD->nProtocol == 6 (remote workstation)
		nAction = 0; // 1=Proxy, 2=Tunnel, 3=Destination
		pzIP[0] = 0;
		pzConnectRoute[0] = 0;
		nConnectRouteSize = 0;
		pzRemoteKey[0] = 0;
		
		pzPollSectionName[0] = 0;
		nPollInterval = 5;
		lLastPoll = 0;

		nParentThreadID = 0;
		pcondHelper = 0;
		plLastActivityTime = 0;
		pbHelperThreadIsRunning = 0;
		pnProxyClosed = 0;
		sockfd = 0;
	}
};



class ThreadData
{
public:
	ThreadStartupData *pTSD;
	int *pnProxyClosed;

	pthread_t chld_thr;

	pthread_mutex_t lock;
	pthread_cond_t	cond;
	int	sockfd;
	int nThreadIsBusy;
	int nThreadID;
	int m_bUseThreads;

	int nParentThreadID;

	unsigned long *plLastActivityTime;
	int *pbHelperThreadIsRunning;

	pthread_mutex_t lockLog;
	pthread_cond_t	condLog;
	pthread_mutex_t lockHelper;
	pthread_cond_t	condHelper;
	// only set if this is a ProxyHelperThread 'attached' to the ThreadData of it's 
	// clientthread.  The following variables will actually point to the variables 
	// preceeding this comment in the ThreadData of the clientthread that is proxying.
	pthread_cond_t	*pcondHelper;
	int	*pnProxyfd;

	// only set when pTSD->nProtocol == 6 (remote workstation)
	int nAction; // 1=Proxy, 2=Tunnel, 3=Destination
	char pzIP[1024];
	char pzConnectRoute[1024];
	char pzRemoteKey[1024];
	int nConnectRouteSize;



	ThreadData( bool bUseThreads = 1 )
	{
		nAction = 0; // 1=Proxy, 2=Tunnel, 3=Destination
		pzIP[0] = 0;
		pzRemoteKey[0] = 0;
		pzConnectRoute[0] = 0;
		nConnectRouteSize = 0;

		nParentThreadID = 0;
		pcondHelper = 0;
		pnProxyClosed = 0;
		plLastActivityTime = 0;
		pbHelperThreadIsRunning = 0;
		pTSD = 0;

		m_bUseThreads = bUseThreads;
		if (bUseThreads)
			nThreadID = ++g_ThreadIDCount;
		else
			nThreadID = 1;
		sockfd = 0;
		nThreadIsBusy = 0;
		if (m_bUseThreads)
		{
			pthread_mutex_init(&lock,0);
			pthread_cond_init(&cond,0);
			pthread_mutex_init(&lockHelper,0);
			pthread_cond_init(&condHelper,0);
			pthread_mutex_init(&lockLog,0);
			pthread_cond_init(&condLog,0);


			// lock the mutex so the thread awaits until woken.
			pthread_mutex_lock(&lockHelper);
			pthread_mutex_lock(&lock);
			
		}
	}
	~ThreadData()
	{
		if (m_bUseThreads)
		{
			pthread_mutex_destroy(&lock);
			pthread_cond_destroy(&cond);
			pthread_mutex_destroy(&lockHelper);
			pthread_cond_destroy(&condHelper);
			pthread_mutex_destroy(&lockLog);
			pthread_cond_destroy(&condLog);
		}
	}
};
ThreadData *g_ThreadPool;
ThreadData *g_ProxyPool;
int g_nProxyPoolRoundRobin = 0;
int g_nThreadPoolRoundRobin = 0;


void PingPool(ThreadData *pPool, int nPoolSize)
{
	g_ThreadPing = 1;
	if( nPoolSize > 1 )
	{
		for(int i=0; i < nPoolSize; i++)
		{
			if (!pPool[i].nThreadIsBusy)
			{
				// wake it up it'll call InfoLog() then go back to sleep.
				pthread_cond_signal(  &((pPool[i]).cond) ); 
			}
		}
	}
	
	// give 'em plenty of time to finish before we turn off the g_ThreadPing flag.
	for(int i=0 ;i < nPoolSize * 2; i++)
		sched_yield();
	g_ThreadPing = 0;
}



void DestroyThreadPool(ThreadData *pPool, int nPoolSize)
{
	g_ServerIsShuttingDown = 1;
	if( nPoolSize > 1 )
	{
		for(int i=0; i < nPoolSize; i++)
		{
			if (!pPool[i].nThreadIsBusy)
			{
				// wake it up to die a natural death
				pthread_cond_signal(  &((pPool[i]).cond) ); 

				// give it plenty of time to end normally.
				for(int i=0 ;i < nPoolSize * 2; i++)
					sched_yield();
			}
		}
	}
}


void CreateThreadPool(ThreadData **pPool, int nPoolSize, void *(*pfnThreadProc) (void *))
{
	if( nPoolSize > 1 )
	{
//#ifdef _WIN32
//		_pthread_processInitialize ();
//#endif

		*pPool = new ThreadData[nPoolSize];
		for(int i=0; i < nPoolSize; i++)
		{
			ThreadData *td = &((*pPool)[i]);
			// the mutex is locked so pfnThreadProc hangs shortly after creation
			pthread_create(&td->chld_thr,NULL, pfnThreadProc, (void *)td );
			sched_yield();
		}
	}
}


// There are 1 million microseconds in a second.
int readableTimeout(int fd, int seconds, int microseconds) 
{ 
  struct timeval tv; 
  fd_set rset; 
   
  tv.tv_sec = seconds; 
  tv.tv_usec = microseconds; 
   
  FD_ZERO(&rset); 
  FD_SET(fd, &rset); 
   
  // returns 0 if the time limit expired.
  // returns -1 on error, otherwise there is data on the port ready to read.
  return select(fd+1, &rset, NULL, NULL, &tv); 
} 

int writableTimeout(int fd, int seconds, int microseconds) 
{ 
  struct timeval tv; 
  fd_set rset; 
   
  tv.tv_sec = seconds; 
  tv.tv_usec = microseconds; 
   
  FD_ZERO(&rset); 
  FD_SET(fd, &rset); 
   
  // returns 0 if the time limit expired.
  // returns -1 on error, otherwise there is data on the port ready to read.
  return select(fd+1, NULL, &rset, NULL, &tv); 
} 


int nonblockrecv(int fd,char *pBuf,int nExpectedDataLen)
{
	int nBytesIn = 0;
	int nRecvRetry = 0;
RE_RECV:
	while (nBytesIn < nExpectedDataLen)
	{
		int rslt = readableTimeout(fd, 60, 0);
		if ( rslt > 0)
		{
			int nRslt = recv(fd, &pBuf[nBytesIn], nExpectedDataLen - nBytesIn, 0 );
			if (nRslt > 0)
			{
				nBytesIn += nRslt;
			}
		}
		else if (rslt == -1)
		{
			if (nRecvRetry++ < 3)
			{
				sched_yield();
				goto RE_RECV;
			}
			return -1;
		}
		else
		{
			break;  // timeout
		}
	}
	if (nBytesIn > nExpectedDataLen)
	{
		//9=Internal Buffering Error
		GString strLog;
		strLog.LoadResource(g_pzCoreErrorSection, 9);
		InfoLog(strLog);
	}
	if (nBytesIn == nExpectedDataLen)
	{
		return nBytesIn;
	}

	// rslt = 0, the time limit expired.
	return 0;
}


int nonblocksend(int fd,const char *pData,int nDataLen)
{
	int nSent = 0;
	while(nSent < nDataLen)
	{
		int nSendRetries = 0;
SHOULD_BLOCK:
		int nRet = writableTimeout(fd, 60, 0);
		if (nRet < 1) // 0  or -1
		{
			if (SOCK_ERR == 10038 || SOCK_ERR == 10022 || SOCK_ERR == 10054)
			{
				if (nSendRetries++ < 3)
				{
					sched_yield();
					goto SHOULD_BLOCK; 
				}
			}
			return 0; // failed to send code = SOCK_ERR;
		}
		nRet = send(fd,(const char *)&pData[nSent],nDataLen-nSent,0);
		if (nRet == -1)
		{ 
			if (SOCK_ERR == 10035 || SOCK_ERR == 10054 || SOCK_ERR == 10053)
			{
				if (nSendRetries++ < 3)
				{
					sched_yield();
					goto SHOULD_BLOCK; 
				}
				if(SOCK_ERR == 10035) // 10035 = Resource temporarily unavailable
				{
					// i've only seen this logic execute in Windows98
					nSendRetries=0;
					sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();
					goto SHOULD_BLOCK; 
				}
			}
			return 0; // failed to send code = SOCK_ERR;
		}
		else
		{
			nSent += nRet;
		}
	}
	return nSent; // bytes sent
}

int nonblocksend(int fd,unsigned char *pData,int nDataLen)
{
	return nonblocksend(fd,(const char *)pData,nDataLen);
}


void AttachToClientThread(ThreadConnectionData *pTCD, ThreadData *pPool, int nPoolSize, int *pnPoolIssuedCount, void *(*pfnThreadProc) (void *))
{
	int newsockfd = pTCD->sockfd;
	ThreadStartupData *pTSD = pTCD->pTSD;
	int nLocalTotalTooBusy = 0;

	// try round robin approach first, see if the thread that was woken up
	// the longest time ago has completed it's task, and is sleeping again.
	// Depending on load and pool size 99+% of connections attach on Round Robin.
	int n = ++(*pnPoolIssuedCount) % nPoolSize;
	if (!pPool[n].nThreadIsBusy)
	{
		pPool[n].nThreadIsBusy = 1;
		pPool[n].sockfd = newsockfd;
		pPool[n].pTSD = pTSD;
		pPool[n].pcondHelper = pTCD->pcondHelper;
		pPool[n].pnProxyClosed = pTCD->pnProxyClosed;
		pPool[n].nParentThreadID = pTCD->nParentThreadID;
		pPool[n].pnProxyfd = pTCD->pnProxyfd;
		pPool[n].plLastActivityTime = pTCD->plLastActivityTime;
		pPool[n].pbHelperThreadIsRunning = pTCD->pbHelperThreadIsRunning;

		strcpy(pPool[n].pzIP,pTCD->pzIP); // for protocol 6 && 3

		// only non-default when pTSD->nProtocol == 6 (remote workstation)
		if ( pTSD->nProtocol == 6)
		{
			pPool[n].nAction = pTCD->nAction;
			pPool[n].nConnectRouteSize = pTCD->nConnectRouteSize;
			strcpy(pPool[n].pzRemoteKey,pTCD->pzRemoteKey);
			memcpy(pPool[n].pzConnectRoute,pTCD->pzConnectRoute,pTCD->nConnectRouteSize);
		}
		pthread_cond_signal(  &((pPool[n]).cond) ); 
	}
	
	else 
	// that thread is still busy, find another one.
	// walk through the thread pool until we find an available thread
	{
		for(int i = 0;;i++)
		{
			if (!pPool[i].nThreadIsBusy)
			{
				pPool[i].nThreadIsBusy = 1;
				pPool[i].sockfd = newsockfd;
				pPool[i].pTSD = pTSD;
				pPool[i].pcondHelper = pTCD->pcondHelper;
				pPool[i].pnProxyClosed = pTCD->pnProxyClosed;
				pPool[i].nParentThreadID = pTCD->nParentThreadID;
				pPool[i].pnProxyfd = pTCD->pnProxyfd;
				pPool[i].plLastActivityTime = pTCD->plLastActivityTime;
				pPool[i].pbHelperThreadIsRunning = pTCD->pbHelperThreadIsRunning;
				strcpy(pPool[i].pzIP,pTCD->pzIP);

				// only non-default when pTSD->nProtocol == 6 (remote workstation)
				if ( pTSD->nProtocol == 6)
				{
					pPool[i].nAction = pTCD->nAction;
					pPool[i].nConnectRouteSize = pTCD->nConnectRouteSize;
					strcpy(pPool[i].pzRemoteKey,pTCD->pzRemoteKey);
					memcpy(pPool[i].pzConnectRoute,pTCD->pzConnectRoute,pTCD->nConnectRouteSize);
				}
				pthread_cond_signal(  &((pPool[i]).cond) ); 
				break;
			}
			if (i == nPoolSize - 1)
			{
				//10=-- Server Too Busy
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 10);
				InfoLog(strLog);

				// bad news.  The requests are commin faster than we can deal with them.
				// we could just hang up right now, but instead we'll yield() for a while
				// try again then hang up on them if we still cannot find a thread to service
				// this connection.  Start at the beginning, keep looping until a thread becomes 
				// available.  If this happens, you need a bigger thread pool or a faster server.
				g_TotalTooBusy++;
				if (nLocalTotalTooBusy++ > 2)
				{
					// way too busy.  Either under attack or too small thread pools.
					//11=-- Server Way Too Busy
					GString strLog;
					strLog.LoadResource(g_pzCoreErrorSection, 11);
					InfoLog(strLog);

					// reject the pending connection.
					#ifdef _WIN32    
						closesocket(newsockfd);
					#elif _HPUX
						shutdown(newsockfd,2);
					#else // linux, sun, AIX
						close(newsockfd);
					#endif
					return;
				}

				i = 0;
				
				for(int y=0;y<5;y++)
					sched_yield();
				PortableSleep(1,0);

			}
		}
	}
}


void LogRequest(ThreadData *td,const char *pzRequestingIP,const char *pzFileNoPath,const char *pzExt,const char *pzReferer,const char *pzFileName )
{
	GString strLogFile (    GetProfile().GetPath("HTTP","HitLogPath",false)   );
	if (strLogFile.Length())
	{
		// If we have been explicitly told not to log requests from this IP - leave.
		const char *pzNonLogIPs = GetProfile().GetString("HTTP","NonLoggingIPs",false);
		if (pzNonLogIPs)
		{
			GStringList row(",",pzNonLogIPs);
			GStringIterator itParams(&row);
			while (itParams())
			{
				const char *pzIP = itParams++;
				if (strcmp(pzIP,pzRequestingIP) == 0 )
					return; // don't log this one
			}
		}
		
		// If we have been explicitly told not to log requests for this file type - leave.
		const char *pzNonLogTypes = GetProfile().GetString("HTTP","NonLoggingTypes",false);
		if (pzNonLogTypes)
		{
			GStringList row(",",pzNonLogTypes);
			GStringIterator itParams(&row);
			while (itParams())
			{
				GString strNonLogType(itParams++);
				if (strNonLogType.CompareNoCase(pzExt) == 0 )
					return; // don't log this one
			}
		}
		
		
		char pzJoulDate[64];
		char pzTime[64];
		char pzJoulDay[16];
		struct tm *newtime;
		long ltime;
		time(&ltime);
		newtime = gmtime(&ltime);
		strftime(pzJoulDate, 64, "%j-%H:%M %b %d %Y", newtime);
		strftime(pzTime, 64, "%H:%M:%S", newtime);
		strftime(pzJoulDay, 16, "%j%Y", newtime);

		strLogFile += pzJoulDay;
		
		// create a new directory for each day
		#ifdef _WIN32
			mkdir(strLogFile);
		#else
			mkdir(strLogFile,777);
		#endif


		strLogFile += g_chSlash;


		// log each thread into it's own file so we don't have to block threads to log.
		// makes for a lot of log files, but we're in a hurry now(a customer is waiting), 
		// we won't be in a hurry while a log file viewer program gathers the data 
		// for presentation or into data mining applications.
		strLogFile += "Segment";
		strLogFile += td->nThreadID;
		strLogFile += ".txt";

		GString strLogEntry;
		strLogEntry.Format("%s,%s,%s,%s,%s,%s,%s\r\n",pzRequestingIP,pzFileNoPath,pzExt,pzReferer,pzJoulDate,pzTime,pzFileName );
		strLogEntry.ToFileAppend(strLogFile,0);
	}
}

// When posting data through a GET, from a browser, the 
// URL Data has certain characters hex encoded (a space becomes %20 for example)
int unEscapeData(const char *pSource,const char *pDest)
{
	// cast off the const and store the values
	char *pszQuery = (char *)pSource;
	char *t = (char *)pDest;
	
	// walk the pSource until we find a NULL or a space
	while (*pszQuery && *pszQuery != 32)
	{
		switch (*pszQuery)
		{
		case '+' :
			*t = ' ';
			break;
		case '%' :
			{
				pszQuery++;
				char chTemp = pszQuery[2];
				pszQuery[2] = 0;
				char *pStart = pszQuery;
				char *pEnd;
				*t = (char)strtol(pStart, &pEnd, 16);
				pszQuery[2] = chTemp;
				pszQuery = pEnd;
				t++;
				continue;
			}
			break;
		default :
			*t = *pszQuery;
			break;
		}
		pszQuery++;
		t++;
	}
	*t = 0;
	return t - pDest;
}

// for sending back 404's or 301's , any non-file response.
void SendHTTPNoFileResponse(int fd, GString &strError, GString &strHTML)
{

	// HTTP header server name
	const char *pWebSrvType=GetProfile().GetString("HTTP","HTTPHeaderServerName",false);
	strError += "Server: ";
	if (pWebSrvType && pWebSrvType[0])
		strError += pWebSrvType;
	else
		strError += "5Loaves";
	strError += "\r\n";

	// add the current time
	char pzTime[128];
	struct tm *newtime;
	long ltime;
	time(&ltime);
	newtime = gmtime(&ltime);
	strftime(pzTime, 128, "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", newtime);
	strError += pzTime;
	strError += "\r\n";

	strError += "Content-Type: text/html\r\n";

	strError += "Content-Length: ";
	strError += strHTML.Length();
	strError += "\r\n\r\n";
	strError += strHTML;
	
	nonblocksend(fd,strError, strError.Length());
}


int ReturnFile(ThreadData *td, const char *pzHomeDir, const char *pzHTTPGET, int nSize, const char *pzRequestingIP, int nKeepAlive, int bisHead)
{
	g_TotalHits++;
	
	char pzFilePartialPath[128];		// relative path (ex. /images/pic.gif)
	GString strFileNoPath;				// no path - file name only (ex. pic.gif)
	GString strExt(" ");				// ext only(ex.  gif)
	GString strFileName(pzHomeDir);		// complete path (ex. d:\home/images/pic.gif)

	GString strError;	// only used when sending back a 404, or 301				
	GString strHTML;    // only used when sending back a 404, or 301



	//
	// Get the file name
	//
	char *pSpace = strpbrk((char *)pzHTTPGET," \r\n"); // pzHTTPGET = "/images/pic.gif HTTP/1.1 ............."
	char chSave;
	if (pSpace)	
	{
		// get the file name previous to the space before HTTP/1.1
		chSave = *pSpace;				// temp null 1st byte after the file name
		*pSpace = 0;
		strcpy(pzFilePartialPath,pzHTTPGET);

		// When posting data through a GET, from a browser, the 
		// URL Data has certain characters hex encoded (a space becomes %20 for example)
		unEscapeData(pzFilePartialPath,pzFilePartialPath);
		
		// append the file name to the path
		strFileName += pzFilePartialPath;	

		*pSpace = chSave;				// un-null
	}
	else
	{
		// This is a GET with no HTTP headers
		GString strF(pzHTTPGET, nSize);
		strFileName += strF;
		strcpy(pzFilePartialPath,pzHTTPGET);
	}


	// prevent any file access behind the root or home directory.
	// the file path may be "/images/pic.gif", may not be "../images/pic.gif"
	if (strstr(pzFilePartialPath,".."))
	{
		// build a 404 Error
		strError = "HTTP/1.1 404 Not Found\r\n";
		strHTML.Format("<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY><H1>404 Not Found</H1>The requested URL <code>%s</code> was not found on this server.</BODY></HTML>",(const char *)pzFilePartialPath);
		SendHTTPNoFileResponse(td->sockfd, strError, strHTML);
		return 0;
	}


	
	// This HTTP server can be configured to load balance by redirecting
	// the client to another server.  Check to see if Balancing is enabled.
	static const char *pzBalance = GetProfile().GetString("HTTP","Balance",false);
	if (pzBalance)
	{
		// issue an HTTP redirect to another server.
		int nDistributionKey = rand() % 100; // creates a random #: 0-99
		GString strBalanceToServer;

		// static so the list is only parsed once - on the initial balance.
		static GStringList lst(",",pzBalance);

		GStringIterator itParams(&lst);
		int nDistributionChannel = 0;
		while (itParams())
		{
			nDistributionChannel += atoi(itParams++); // should add up to 100
			if (!itParams())
				break; // bad config file format
			const char *pzServer = itParams++;
			if (nDistributionChannel > nDistributionKey)
			{
				strBalanceToServer = pzServer;
				break;
			}
		}

		// as long as the config file is valid - strBalanceToServer will always have a value
		if (strBalanceToServer.Length())
		{
			// build the redirect location
			GString strHREF(strBalanceToServer);
			strHREF += pzFilePartialPath;

			strError =  "HTTP/1.1 301 Moved Permanently\r\n";
			strError += "Location: ";
			strError += strHREF;
			strError += "\r\n";

			strHTML.Format("<head><title>Document Moved</title></head><body><h1>Object Moved</h1>This document may be found <a HREF=\"%s\">here</a></body>",(const char *)strHREF);
			SendHTTPNoFileResponse(td->sockfd, strError, strHTML);
			return 0;
		}
	}


	// a GET for the index looks like this from IE or Netscape: pzHTTPGET= "/ HTTP/1.1 ..." 
	// also if the browser gives us a path (with no file) the FileName may end with
	//   a slash - append the default index page name as specified in the config file.
	if (strFileName.GetAt(strFileName.Length()-1) == '/' )
	{
		const char *pzIndex = GetProfile().GetString("HTTP","Index",false);
		if (pzIndex)
		{
			strcat(pzFilePartialPath,pzIndex);
			strFileName += pzIndex;
		}
		else
		{
			strcat(pzFilePartialPath,"index.html");
			strFileName += "index.html";
		}
	}
	
	
	// if strFileName contains only the home-dir path... give them the homepage
	if (strFileName.Compare(pzHomeDir) == 0)
	{
		const char *pzIndex = GetProfile().GetString("HTTP","Index",false);
		if (pzIndex)
		{
			strcat(pzFilePartialPath,pzIndex);
			strFileName += g_chSlash;
			strFileName += pzIndex;
		}
		else
		{
			strcat(pzFilePartialPath,"index.html");
			strFileName += g_chSlash;
			strFileName += "index.html";
		}
	}


	// walk the file name backwards until the first '.' to get the extention
	int nIdx = strlen(pzFilePartialPath) - 1;
	int bHasExt = 0;
	while (nIdx > -1)
	{
		if ( pzFilePartialPath[nIdx] == '.' && bHasExt == 0)
		{
			strExt = &pzFilePartialPath[nIdx] + 1;
			bHasExt = 1;
		}
		if ( pzFilePartialPath[nIdx] == '\\' ||  pzFilePartialPath[nIdx] == '/')
		{
			strFileNoPath = &pzFilePartialPath[nIdx] + 1;
			break;
		}
		nIdx--;
	}

	// determine (next transaction) connection attributes
	GString strConnectionType("close");
	int nIsKeepAlive = 0;
	int nKeepAliveReturnValue = 0;
	if ( GetProfile().GetBoolean("HTTP","UseKeepAlives",false) )
	{
		char *pCon = strstr((char *)pzHTTPGET,"Connection:");
		if (pCon)
		{
			char *pEnd = strpbrk(pCon + 12, " \r\n");
			if (pEnd)
			{
				char chSave = *pEnd;
				*pEnd = 0;
				strConnectionType = pCon + 12;
				*pEnd = chSave;
				if (strConnectionType.CompareNoCase("Keep-Alive") == 0)
				{
					nIsKeepAlive = 1;
					nKeepAliveReturnValue = --nKeepAlive;
				}
			}
		}
	}



	// extract the Referer from the HTTP headers if supplied.
	GString strReferer(" ");
	char *pReferer = strstr(pzHTTPGET,"Referer:");
	if (pReferer)
	{
		char *pRefererEnd = strpbrk(pReferer,"\r\n");
		if (pRefererEnd)
		{
			char chSave = *pRefererEnd;
			*pRefererEnd = 0;
			strReferer = pReferer + 9; // skip 9 bytes "Referer: "
			*pRefererEnd = chSave;
		}
	}


	
	//
	// Log this request if logging is enabled
	//
	LogRequest(td,pzRequestingIP,strFileNoPath,(const char *)strExt,(const char *)strReferer,(const char *)strFileName );


	// Extract the ETag from the HTTP GET command, 
	GString strETag;
	char *p = strstr((char *)pzHTTPGET,"If-None-Match: ");
	if(p)
	{
		p+=15;// move past "If-None-Match: "
		char *pEnd = strpbrk(p, " \r\n");
		if (pEnd)
		{
			char chSave = *pEnd;
			*pEnd = 0;
			strETag = p;
			*pEnd = chSave;
		}
	}
	
	// if there was an ETag in the HTTP headers
	if (strETag.Length()) 
	{
		// load the last modified time from the file on disk
		struct stat s;
		stat((const char *)strFileName,&s);
		GString strLastModTime;
		strLastModTime.Format("%ld",s.st_mtime);

		// if it matches the ETag - shoot back a 304 to have the browser use it's local cache.
		if ( strLastModTime.Compare(strETag) == 0 )
		{
			GString strBrowserCachedHeader;
			strBrowserCachedHeader += "HTTP/1.1 304 Not Modified\r\n";
			const char *pWebSrvType=GetProfile().GetString("HTTP","HTTPHeaderServerName",false);
			strBrowserCachedHeader += "Server: ";
			if (pWebSrvType && pWebSrvType[0])
				strBrowserCachedHeader += pWebSrvType;
			else
				strBrowserCachedHeader += "5Loaves";
			strBrowserCachedHeader += "\r\n";
				

			char pzTime[128];
			struct tm *newtime;
			long ltime;
			time(&ltime);
			newtime = gmtime(&ltime);
			strftime(pzTime, 128, "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", newtime);

			strBrowserCachedHeader += pzTime;
			
			strBrowserCachedHeader += "Connection: "; // "close" or "Keep-Alive"
			strBrowserCachedHeader += (const char *)strConnectionType;
			strBrowserCachedHeader += "\r\n";

			if (nIsKeepAlive)
			{
				strBrowserCachedHeader += "Keep-Alive: timeout=7, max=";
				strBrowserCachedHeader += nKeepAliveReturnValue;
				strBrowserCachedHeader +=  "\r\n";
			}

			strBrowserCachedHeader += "ETag: "; // or Keep-Alive
			strBrowserCachedHeader +=  strETag;
			strBrowserCachedHeader +=  "\r\n";
			strBrowserCachedHeader +=  "Content-Length: 0\r\n\r\n";

			nonblocksend(td->sockfd,strBrowserCachedHeader,strBrowserCachedHeader.Length());
			return nKeepAliveReturnValue;
		}
		else
		{
			// otherwise uncache any file that has been changed since it was cached.
			char *pOldHTTPData = (char *)cacheManager.UncacheData(strFileName);
			if (pOldHTTPData)
				delete pOldHTTPData;
		}
	}

	// look in the data cache for the pre-assembled HTTP headers and unciphered data.
	long length;
	unsigned char *pData = (unsigned char *)cacheManager.GetCacheData((const char *)strFileName, &length);
	if (pData)
	{
		if (bisHead) // HTTP HEAD command
		{
			char *pContent= strstr((char *)pData,"\r\n\r\n");
			length = (pContent - (const char *)pData) + 4;
		}
		// send him the file he asked for
		nonblocksend(td->sockfd,pData,length);
	}
	else
	{
		char *buf = 0;
		long lBytes = 0;

		const char *pDiskCipherPass=GetProfile().GetString("HTTP","EncryptedHomeDirPassword",false);
		if (pDiskCipherPass)
		{
			// returns 1 on success, 0 on Fail with description in  strErrorOut
			// pDest will be allocated upon success - YOU must clean up.
			// pDest will always start with these 7 bytes: 5Loaves, the nDestLen will 
			// be set to the length of the data following the first 7 bytes.
			int nDestLen;
			GString strErrorOut;
			if (!FileDecryptToMemory(pDiskCipherPass, strFileName, &buf, &nDestLen, strErrorOut))
			{
				GString strErr;
				strErr.Format("\nEncryptedHomeDir file[%s]\nReports:[%s]",(const char *)strFileName,(const char *)strErrorOut);
				InfoLog(strErr);

				// build a 404 Error
				strError = "HTTP/1.1 404 Not Found\r\n";
				strHTML.Format("<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY><H1>404 Not Found</H1>The requested URL <code>%s</code> was not found on this server.</BODY></HTML>",(const char *)pzFilePartialPath);
				SendHTTPNoFileResponse(td->sockfd, strError, strHTML);
				return 0;
			}
			lBytes = nDestLen;

		}
		else // get the file from an unencrypted disk file
		{
			FILE *fp = fopen((const char *)strFileName,"rb");
			if (fp)
			{
				
				// get the size of the file
				fseek(fp,0,SEEK_END);
				lBytes = ftell(fp);
				fseek(fp,0,SEEK_SET);

				// load the file content
				buf = new char[lBytes +1];
				if (fread(buf,sizeof(char),lBytes,fp) != lBytes)
				{
					InfoLog("Failed to read entire file\n");
					lBytes = 0;
				}
				fclose(fp);
			}
		}

		if (buf && lBytes)
		{
			//
			// Build the HTTP Headers
			//

			char header[512];
			memset(header,0,512);
			strcat(header,"HTTP/1.1 200 OK\r\n");

			// get last modified time
			struct stat ss;
			stat((const char *)strFileName,&ss);


			// HTTP header: "Date: Sunday Sun, 21 Oct 1995 19:38:46 GMT\n"
			char pzTime[128];
			struct tm *newtime;
			long ltime;
			time(&ltime);
			newtime = gmtime(&ltime);
			strftime(pzTime, 128, "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", newtime);
			strcat(header,pzTime );

			// HTTP header server name
			const char *pWebSrvType=GetProfile().GetString("HTTP","HTTPHeaderServerName",false);
			strcat(header,"Server: ");
			if (pWebSrvType && pWebSrvType[0])
				strcat(header,pWebSrvType);
			else
				strcat(header,"5Loaves");
			strcat(header,"\r\n");

			
			// HTTP header connection type
			GString strBrowserCachedHeader;
			strBrowserCachedHeader += "Connection: "; // "close" or "Keep-Alive"
			strBrowserCachedHeader += (const char *)strConnectionType;
			strBrowserCachedHeader += "\r\n";
			if (nIsKeepAlive)
			{
				strBrowserCachedHeader += "Keep-Alive: timeout=7, max=";
				strBrowserCachedHeader += nKeepAliveReturnValue;
				strBrowserCachedHeader +=  "\r\n";
			}
			strcat(header,(const char *)strBrowserCachedHeader);

			
			// HTTP header: "Last-modified: Sunday Sun, 21 Oct 1995 19:38:46 GMT\n"
			char pzFileTime[256];
			long lFileTime = atol((const char *)strETag);
			struct tm *tmFileTime = gmtime(&lFileTime);
			strftime(pzFileTime, 128, "Last-modified: %a, %d %b %Y %H:%M:%S GMT\r\n", tmFileTime);
			strcat(header,pzFileTime );

			// HTTP header: ETag
			GString strETag("ETag: ");
			strETag += (unsigned long)ss.st_mtime; 
			strETag += "\r\n";
			strcat(header,(const char *)strETag);
			
			// HTTP header: Content-type
			int bAddedContentType = 0;
			strcat(header,"Content-type: "); 
			for (int iMime = 0; ;iMime++)
			{
				if (mimeTypeTable[iMime])
				{
					if ( strExt.CompareNoCase( mimeTypeTable[iMime] ) == 0)
					{
						bAddedContentType = 1;
						strcat(header,mimeTypeTable[iMime + 1]); 
						strcat(header,"\r\n");
						break;
					}
				}
				else
					break;
			}
			if (!bAddedContentType)
			{
				strcat(header,"text/html\r\n");
			}

			// HTTP header: Content-length
			char cl[128];
			sprintf(cl,"Content-length: %d\r\n\r\n",lBytes);
			strcat (header,cl);
			

			// Add Headers + data 
			char *pzCacheData = new char[lBytes + strlen(header) + 1];
			strcpy(pzCacheData,header);
			int nHeaderLen = strlen(header);
			
			if (pDiskCipherPass)
				memcpy(&pzCacheData[nHeaderLen],&buf[7],lBytes);
			else
				memcpy(&pzCacheData[nHeaderLen],buf,lBytes);

			// send it back (to the browser usually)
			nonblocksend(td->sockfd,pzCacheData,nHeaderLen+lBytes);
			

			// if so configured, leave this complete HTTP response (headers + data) in the cache
			if ( GetProfile().GetBoolean("HTTP","ContentCache",false) )
			{
				cacheManager.EnterCacheData(nHeaderLen+lBytes,pzCacheData,(const char *)strFileName);
			}
			else
			{
				// otherwise free the memory and rebuild this message next time someone asks for it.
				delete pzCacheData;
			}

			delete buf; // the data buffer
		}
		else // we failed to find the file requested.
		{
			// A common problem in websites is that search engines index a site,
			// then that site gets modified so that although it may not contain
			// any broken links, the search engines do.  Rather than sputtering
			// out a 404, we auto-redirect them to an alternate page/site.
			// might as well send them to the home page rather than sending them away
			// or send them to a site map.
			const char *pz404Redirect = GetProfile().GetString("HTTP","PageRedirect",false);

			// this only applies to HTML pages (not images, javascript etc)
			const char *p = strExt;
			if (  
				  pz404Redirect 
				  && strlen(p) > 2 
				  && (p[0] == 'H' || p[0] == 'h') 
				  && (p[1] == 'T' || p[1] == 't') 
				  && (p[2] == 'M' || p[2] == 'm') 
			   )
			{

				g_Total404s++;	// HTTP 404 error count
				// log the "broken link" if so configured
				const char *pzLog = GetProfile().GetString("HTTP","404Log",false);
				if (pzLog)
				{
					GString strError;
					strError.Format("File [%s] not found\n",(const char *)strFileName);
					strError.ToFileAppend(pzLog,0);
				}

				strError =  "HTTP/1.1 301 Moved Permanently\r\n";
				strError += "Location: ";
				strError += pz404Redirect;
				strError += "\r\n";

				strHTML.Format("<head><title>Document Moved</title></head><body><h1>Object Moved</h1>This document may be found <a HREF=\"%s\">here</a></body>",pz404Redirect);
			}
			else
			{
				
				// see if strFileName is a directory.  If the user supplied a trailing slash
				// we already knew that this was a directory and never got here - but since you
				// cannot diferentiate between a file and a folder(unless a trailing slash is 
				// provided) we assumed it was a file - but didn't find this file.  Now, if this
				// turns out to be a folder, append the trailing slash and redirect the browser
				// back to the same location with the the trailing slash.
				if ( CDirectoryListing::IsDirectory(ReSlash(strFileName)) )
				{
					// in the config file is a setting like this:
					// ThisSiteURL=http://www.UnitedBusinessTechnologies.com
					const char *pz404Redirect = GetProfile().GetString("HTTP","ThisSiteURL",false);
					if (pz404Redirect)
					{
						GString strRedirectTo(pz404Redirect);
						strRedirectTo += pzFilePartialPath; // add something like "/AtHome"
						strRedirectTo += "/";

						strError =  "HTTP/1.1 301 Moved Permanently\r\n";
						strError += "Location: ";
						strError += strRedirectTo;
						strError += "\r\n";

						strHTML.Format("<head><title>Document Moved</title></head><body><h1>Object Moved</h1>This document may be found <a HREF=\"%s\">here</a></body>",(const char *)strRedirectTo);
					}
				}
				else
				{
					// build a 404 Error
					strError = "HTTP/1.1 404 Not Found\r\n";
					strHTML.Format("<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY><H1>404 Not Found</H1>The requested URL <code>%s</code> was not found on this server.</BODY></HTML>",(const char *)pzFilePartialPath);

					g_Total404s++;	// HTTP 404 error count
					// log the "broken link" if so configured
					const char *pzLog = GetProfile().GetString("HTTP","404Log",false);
					if (pzLog)
					{
						//12=HTTP Content File [%s] requested but not found
						GString strLog;
						strLog.LoadResource(g_pzCoreErrorSection, 12,(const char *)strFileName);
						InfoLog(strLog);
					}

				}
			}

			SendHTTPNoFileResponse(td->sockfd, strError, strHTML);
			
			nKeepAliveReturnValue = 0;
		}
	}
	return nKeepAliveReturnValue ;
}



void IOBlocking(int fd, int isNonBlocking)
{
	if (isNonBlocking)
	{
// some notes for future ports here:
//  int i = 1;
//	BEOS			setsockopt(fd, SOL_SOCKET, SO_NONBLOCK, &i, sizeof(i));  
//	OTHER-OS's		ioctl(fd, FIONBIO, &i);


		// Set non-blocking IO
		#ifdef _WIN32
			unsigned long icmd = 1;   
			ioctlsocket(fd,FIONBIO,&icmd);
		#else
			int fdflags = fdflags = fcntl(fd, F_GETFL);
			fcntl(fd, F_SETFL, fdflags | O_NONBLOCK); 
		#endif
	}
	else
	{
		// Set blocking IO
		#ifdef _WIN32
			unsigned long icmd = 0;   
			ioctlsocket(fd,FIONBIO,&icmd);
		#else
			int fdflags = fdflags = fcntl(fd, F_GETFL);
			fdflags ^= O_NONBLOCK;
			fcntl(fd, F_SETFL, fdflags); 
		#endif
	}
}




void ProxyLog(const char *szConfigSectionName, int nThreadID, char *sockBuffer, int nBytes, const char *prefix )
{
	GString strLogFile (    GetProfile().GetPath(szConfigSectionName,"LogPath",false)   );
	if (strLogFile.Length() && GetProfile().GetBoolean(szConfigSectionName,"LogEnabled",false) )
	{
		char pzJoulDate[16];
		char pzTime[64];
		struct tm *newtime;
		long ltime;
		time(&ltime);
		newtime = gmtime(&ltime);
		strftime(pzJoulDate, 16, "%j%Y", newtime);
		strftime(pzTime, 64, "%H:%M:%S", newtime);
		
		
		#ifdef _WIN32
			mkdir(strLogFile);
		#else
			mkdir(strLogFile,777);
		#endif


		strLogFile += pzJoulDate;
		#ifdef _WIN32
			mkdir(strLogFile);
		#else
			mkdir(strLogFile,777);
		#endif


		strLogFile += g_chSlash;

		strLogFile += szConfigSectionName;
		strLogFile += ".txt";

		char pzBytes[16];
		sprintf(pzBytes,"%06d",nBytes);

		FILE *fp = fopen((const char *)strLogFile,"a");
		if (fp)
		{
			
			fwrite("\n\n\n",1,3,fp);
			fwrite(prefix,1,strlen(prefix),fp);
			fwrite(pzTime,1,8,fp);
			fwrite("-",1,1,fp);
			fwrite(pzBytes,1,6,fp);
			fwrite(">",1,1,fp);

			fwrite(sockBuffer,1,nBytes,fp);
			fclose(fp);
		}
	}
}

void DataTrace(const char *pzTag, char *pData, int nBytes)
{
	GString strMessage;
	strMessage.Format("\n\n%s\n%06d:",pzTag,nBytes);
	int nByteIndex = 0;
	while(nBytes > 0)
	{
		if (isprint(pData[nByteIndex]))
		{
			strMessage += pData[nByteIndex];
		}
		else
		{
			strMessage += "[";
			strMessage += (int)(unsigned char)pData[nByteIndex];
			strMessage += "]";
		}
		nByteIndex++;
		nBytes--;
	}
	strMessage.ToFileAppend(g_DataTraceFile);
}



int InitializeCipherKey(CipherData *pcd, const char *pKey128, const char *pKey256, int nDIR)
{
	memset(pcd,0,sizeof(CipherData));

	if( (pKey128 && pKey128[0]) || (pKey256 && pKey256[0]))
	{
		if ( pKey256 )
			pcd->CipherKeyBits = 256;
		else if ( pKey128 )
			pcd->CipherKeyBits = 128;

		pcd->pzCipherPass=(pKey128) ? pKey128 : pKey256;


		if (cipherInit(&pcd->ci,MODE_ECB,NULL) != TRUE)
		{
			InfoLog("cipherInit() failed.");
		}

		// create an MD5 digest onto the memory at: (unsigned char *)pcd->ki.key32
		Hash((void *)pcd->pzCipherPass, strlen(pcd->pzCipherPass), (unsigned char *)pcd->ki.key32, pcd->CipherKeyBits);

		//	makeKey(&pcd->ki,nDIR,pcd->CipherKeyBits,0);
		//	makeKey() is from the TwoFish sample implementation code, 
		//  I intend to replicate and simplify this call here:
		pcd->ki.keySig		= 0x48534946;			// initialization signature ('FISH')
		pcd->ki.direction	= nDIR;					// DIR_ENCRYPT or DIR_DECRYPT
		pcd->ki.keyLen		= pcd->CipherKeyBits;	// 128 or 256
		pcd->ki.numRounds	= 16;


		// build the key schedule
		reKey(&pcd->ki);

		return 1;
	}
	return 0;
}




int PrepForServer(unsigned short sAttribs, int bIsTunnel,CipherData *pcd, char *sockBuffer,int nDataSize,char *cryptDest, int *newLength)
{
	int bCipher = (( sAttribs & ATTRIB_CRYPTED_2FISH128 ) || ( sAttribs & ATTRIB_CRYPTED_2FISH256 )) ? 1 : 0;
	int bZip = ( sAttribs & ATTRIB_COMPRESSED ) ? 1 : 0;

	GString strMessage;
	int nBitsCrypted;
	if (bIsTunnel)
	{
		// Compress
		if (bZip)
		{
			if (g_DataTrace) DataTrace("Before Compress", sockBuffer, nDataSize);
			
			// in the worst case (data is already compressed) the output
			// may actually expand a little (~500 bytes on a 65535 byte buffer)
			*newLength = MAX_DATA_CHUNK;
			
			if (nDataSize > 64)
			{
				// true indicates that the packet uses compression
				cryptDest[0] = 1;

				// done
				int cRet = BZ2_bzBuffToBuffCompress( &cryptDest[1], // destination
									   (unsigned int*)newLength,	
									   sockBuffer, // source
									   nDataSize,
									   1, //blockSize100k, 
									   0,// verbosity, 
									   30);//workFactor )
				if (cRet != 0)
				{
					InfoLog("Compress failed. point 1");
					*newLength = 0;
				}
				else
				{
					*newLength += 1;
				}
				
			}
			else
			{
				// false indicates that the packet does not use compression, it's too small to benefit
				cryptDest[0] = 0;
				memcpy(&cryptDest[1],sockBuffer,nDataSize);
				*newLength = nDataSize + 1;
			}
			
			// hang onto running total for runtime statistical info
			g_PreZipBytes += nDataSize;
			g_PostZipBytes += *newLength;

			if (bCipher) // put it where the cipher input will pick it up
			{
				memcpy(sockBuffer,cryptDest,*newLength);
				nDataSize = *newLength;
			}

			if (g_DataTrace) DataTrace("After Compress", cryptDest, *newLength);
		}
		// Encrypt
		if (bCipher)
		{
			if (g_DataTrace) DataTrace("Before Encrypt", sockBuffer, nDataSize);

			// "Encrypted - Sent to tunnel server:

			// bytes required to pad the last block to a 128 bit boundry
			unsigned char nPad = (16 - nDataSize % 16);
			nPad = (nPad == 16) ? 0 : nPad;

			// this is the size (in bytes) to encrypt.
			*newLength = nDataSize + nPad + 16;

			// create a trailer block of 128 bits
			// write the pad size in the first trailer byte 
			sockBuffer[nDataSize + nPad] = nPad;
			// null the rest of the last block
			memset(&sockBuffer[nDataSize + nPad+1],0,15);

			try
			{
				nBitsCrypted = blockEncrypt(&pcd->ci,&pcd->ki, (unsigned char *)sockBuffer,(*newLength)*8,(unsigned char *)cryptDest);
			}
			catch(...)
			{
				return 0;
			}
			if (nBitsCrypted != (*newLength)*8)
			{
				return 0;
			}
			if (g_DataTrace) DataTrace("After Encrypt", cryptDest, *newLength);
		}
	}
	else
	{
		if (bCipher)
		{
			if (g_DataTrace) DataTrace("Before Decrypt", sockBuffer, nDataSize);
			
			// Decrypted - Sent to Application server

			if (nDataSize*8 % BLOCK_SIZE)
			{
				InfoLog("protocol error.");
				return 0;
			}

			try
			{
				nBitsCrypted = blockDecrypt(&pcd->ci,&pcd->ki, (unsigned char *)sockBuffer,nDataSize*8,(unsigned char *)cryptDest);
			}
			catch(...) // this can happen when using a 128 key to undo 256 data
			{
				return 0;
			}
			if (nBitsCrypted != nDataSize*8)
			{
				return 0;
			}
			// get the first byte of the last 128 bit block
			unsigned char chPad = (unsigned char)cryptDest[(nBitsCrypted/8) - 16];
			// ASSERTION, the byte pad count must be less than a block size
			if (chPad > 16)
			{
				//13=Protocol Error. Invalid value[%d] cannot be greater than 16
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 13,chPad);
				InfoLog(strLog);
				return 0;
			}
			// and the new size removes the last block and padding bits added to the second to last block.
			*newLength = (nBitsCrypted/8) - 16 - chPad;
			if (g_DataTrace) DataTrace("After Decrypt", cryptDest, *newLength);
		}

		// Decompress
		if (bZip) 
		{
			if (g_DataTrace) DataTrace("Before DeCompress", sockBuffer, nDataSize);

			if (bCipher) // move cipher output to compression input
			{
				memcpy(sockBuffer,cryptDest,*newLength);
				nDataSize = *newLength;
			}

			if ( sockBuffer[0] )
			{
				*newLength = MAX_DATA_CHUNK;
				int cRet = BZ2_bzBuffToBuffDecompress 
										   ( cryptDest, 
											 (unsigned int *)newLength,
											 &sockBuffer[1], 
											 nDataSize - 1,
											 1,// or 0 small,
											 0);//verbosity )
				if (cRet != 0)
				{
					//14=Decompress failed.  Wrong password-key attempt?
					GString strLog;
					strLog.LoadResource(g_pzCoreErrorSection, 14);
					InfoLog(strLog);

					*newLength = 0;
					return 0;
				}
			}
			else
			{
				memcpy(cryptDest,&sockBuffer[1],nDataSize-1);
				*newLength = nDataSize-1;
			}

			if (g_DataTrace) DataTrace("After Decompress", cryptDest, *newLength);
		}

	}
	return 1;
}


int PrepForClient(unsigned short sAttribs, int bIsTunnel,CipherData *pcd, char *sockBuffer,int nDataSize,char *cryptDest, int *newLength)
{
	 
	int bCipher = (( sAttribs & ATTRIB_CRYPTED_2FISH128 ) || ( sAttribs & ATTRIB_CRYPTED_2FISH256 )) ? 1 : 0;
	int bZip = ( sAttribs & ATTRIB_COMPRESSED ) ? 1 : 0;

	if (bIsTunnel)
	{
		if (bCipher)
		{
			if (g_DataTrace) DataTrace("Before Decrypt", sockBuffer, nDataSize);

			// Decrypted - Sent to Client Application:
			if (nDataSize*8 % BLOCK_SIZE)
			{
				//15=protocol error - invalid block size[%d]
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 15, nDataSize);
				InfoLog(strLog);
				return 0;
			}

			int nBitsCrypted;
			try
			{
				nBitsCrypted = blockDecrypt(&pcd->ci,&pcd->ki, (unsigned char *)sockBuffer,nDataSize*8,(unsigned char *)cryptDest);
			}
			catch(...) // this can happen when using a 128 key to undo 256 data
			{
				return 0;
			}
			if (nBitsCrypted != nDataSize*8)
			{
				//16=Wrong password-key attempt?
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 16);
				InfoLog(strLog);
				return 0;
			}
			// get the first byte of the last 128 bit block
			unsigned char chPad = (unsigned char)cryptDest[(nBitsCrypted/8) - 16];
			// the byte pad count must be less than a block size if the decrypt was a success
			if (chPad > 16)
			{
				//16=Wrong password-key attempt?
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 16);
				InfoLog(strLog);
				return 0;
			}

			// and the new size removes the last block and padding bits added to the second to last block.
			*newLength = (nBitsCrypted/8) - 16 - chPad;
			if (g_DataTrace) DataTrace("After Decrypt", cryptDest, *newLength);
		}

		// Decompress
		if (bZip)
		{
			if (g_DataTrace) DataTrace("Before Decompress", sockBuffer, nDataSize);

			if (bCipher) // move cipher output to compression input
			{
				memcpy(sockBuffer,cryptDest,*newLength);
				nDataSize = *newLength;
			}

			if ( sockBuffer[0] ) // first byte is a flag indicating if this packet is compressed
			{
				*newLength = MAX_DATA_CHUNK;
				int cRet = BZ2_bzBuffToBuffDecompress 
										   ( cryptDest, 
											 (unsigned int *)newLength,
											 &sockBuffer[1], 
											 nDataSize - 1,
											 1,// or 0 small,
											 0);//verbosity )
				if (cRet != 0)
				{
					//14=Decompress failed.  Wrong password-key attempt?
					GString strLog;
					strLog.LoadResource(g_pzCoreErrorSection, 14);
					InfoLog(strLog);

					*newLength = 0;
					return 0;
				}
			}
			else
			{
				memcpy(cryptDest,&sockBuffer[1],nDataSize-1);
				*newLength = nDataSize-1;
			}
			if (g_DataTrace) DataTrace("Decompressed", cryptDest, *newLength);
		}
	}
	else
	{
		// Compress
		if (bZip)
		{
			if (g_DataTrace) DataTrace("Before Compress", sockBuffer, *newLength);
			*newLength = MAX_DATA_CHUNK;
			if (nDataSize > 64)
			{
				// true indicates that the packet uses compression
				cryptDest[0] = 1;

				// done
				int cRet = BZ2_bzBuffToBuffCompress( &cryptDest[1],
									   (unsigned int*)newLength,
									   sockBuffer, // raw data
									   nDataSize,
									   1, //blockSize100k, 
									   0,// verbosity, 
									   30);//workFactor )

				if (cRet != 0)
				{
					*newLength = 0;
				}
				else
				{
					*newLength += 1;
				}
				
			}
			else
			{
				// false indicates that the packet does not use compression
				cryptDest[0] = 0;
				memcpy(&cryptDest[1],sockBuffer,nDataSize);
				*newLength = nDataSize + 1;
			}
			
			// hang onto running total for statistical info
			g_PreZipBytes += nDataSize;
			g_PostZipBytes += *newLength;

			if (bCipher) // put it where the cipher input will pick it up
			{
				memcpy(sockBuffer,cryptDest,*newLength);
				nDataSize = *newLength;
			}
			if (g_DataTrace) DataTrace("After Compress", cryptDest, *newLength);

		}
		
		// Encrypt
		if (bCipher)
		{
			if (g_DataTrace) DataTrace("Before Crypt", sockBuffer, *newLength);
			// "Encrypted - Sent to Client Tunnel:";
			// bytes required to pad the last block to a 128 bit boundry
			unsigned char nPad = (16 - nDataSize % 16);
			nPad = (nPad == 16) ? 0 : nPad;

			// this is the size (in bytes) to encrypt.
			*newLength = nDataSize + nPad + 16;

			// create a trailer block of 128 bits
			// write the pad size in the first trailer byte 
			sockBuffer[nDataSize + nPad] = nPad;
			// null the rest of the last block
			memset(&sockBuffer[nDataSize + nPad+1],0,15);
			
			int nBitsCrypted;
			try
			{
				nBitsCrypted = blockEncrypt(&pcd->ci,&pcd->ki, (unsigned char *)sockBuffer,(*newLength)*8,(unsigned char *)cryptDest);
			}
			catch(...)
			{
				return 0;
			}
			if (nBitsCrypted != (*newLength)*8)
			{
				return 0;
			}
			if (g_DataTrace) DataTrace("After Crypt", cryptDest, *newLength);
		}
	}
	
	// for packet sync support
	if (!bCipher && !bZip)
	{
		memcpy(cryptDest,sockBuffer,nDataSize);
		*newLength = nDataSize;
	}
	return 1;
}


// Protocol =  #*DATA Packet 1 ... #*DATA Packet 2 ... #*DATA Packet 3
// where # is a 16 bit numeric byte count of the DATA Packet that follows
// and * is a 16 bit flag field of data attributes (compression and encryption attributes)
int ReadPacket(int fd, int nTimeOutSec, int nTimeOutNano, int *pnBytesInBuf,char *Buf,char **ppPkt,int *nPktLen, int *nPktIndex, unsigned short *psAttrFlags, unsigned short sAttribsExpected, int bPacketSync)
{
READ_PACKET_BEGIN:
	if (*nPktIndex > -1)// if the beginning of the next packet was in the last recv()
	{
		short theNumber = 0;						// storage on platform architecture byte boundry
		memcpy(&theNumber,&Buf[*nPktIndex],2);		// storage in network byte order
		short nNextPktSize = ntohs( theNumber );    // convert to host byte order

		memcpy(&theNumber,&Buf[(*nPktIndex) + sizeof(short)],2); // the same thing here.
		*psAttrFlags = ntohs( theNumber );
		
		if (*psAttrFlags != sAttribsExpected)
		{
			// within the second block of 16 bits recv()'d, If any of the reserved protocol 
			// bits (bits 5-16) are on or all bits(1-16) are off, this is raw(non-packeted) data.
			// This machine is expecting packeted data.  
			if ( ( *psAttrFlags & ATTRIB_UNUSED_BIT5 ) || ( *psAttrFlags & ATTRIB_UNUSED_BIT6 ) || 
				 ( *psAttrFlags & ATTRIB_UNUSED_BIT7 ) || ( *psAttrFlags & ATTRIB_UNUSED_BIT8 ) || 
				 ( *psAttrFlags & ATTRIB_UNUSED_BIT9 ) || ( *psAttrFlags & ATTRIB_UNUSED_BIT10) ||
				 ( *psAttrFlags & ATTRIB_UNUSED_BIT11) || ( *psAttrFlags & ATTRIB_UNUSED_BIT12) ||
				 ( *psAttrFlags & ATTRIB_UNUSED_BIT13) || ( *psAttrFlags & ATTRIB_UNUSED_BIT14) ||
				 ( *psAttrFlags & ATTRIB_UNUSED_BIT15) || ( *psAttrFlags & ATTRIB_UNUSED_BIT16) ||
				 *psAttrFlags == 0) //there is always > 0 attributes when synchronized
			{
				// 17=Protocol error. If you are attempting to proxy or 'bounce' TCP, enable RawPacketProxy=yes in the correct subsection of the config file.
				GString strLog;
				strLog.LoadResource(g_pzCoreErrorSection, 17);
				InfoLog(strLog);
				return -1;
			}
		}

		int nBufBytesNeeded = *nPktIndex + nNextPktSize + (2 * sizeof(short));
		// If the next packet is the complete remainder of the recv() buffer
		if (nBufBytesNeeded == *pnBytesInBuf)
		{
			*ppPkt = &Buf[*nPktIndex + (2 * sizeof(short))]; // 4 bytes of header, then data
			*nPktLen = nNextPktSize;
			*nPktIndex = -1; // last packet
			return 1;
		}
		// if the next packet is in the recv() buffer with more data after it
		else if (nBufBytesNeeded < *pnBytesInBuf)
		{
			*ppPkt = &Buf[*nPktIndex + (2 * sizeof(short))]; // 4 bytes of header, then data
			*nPktLen = nNextPktSize;
			*nPktIndex = nBufBytesNeeded; // index to the first byte of the next packet
			return 1;
		}
		// otherwise, we need to revc() the remainder of this packet
		else 
		{
			int rslt = readableTimeout(fd, nTimeOutSec, nTimeOutNano);
			if ( rslt > 0)
			{
				// slide the data to the front of the buffer to make room for this packet
				if (*nPktIndex > 0)
				{
					memmove(Buf,&Buf[*nPktIndex],*pnBytesInBuf - *nPktIndex); // ANSI <string.h>
					*pnBytesInBuf = *pnBytesInBuf - *nPktIndex;
					*nPktIndex = 0;
				}

				// test the buffer for necessary free space now
				if (MAX_DATA_CHUNK - *pnBytesInBuf < 1)
				{
					// 18=ReadPacket buffer overrun.
					GString strLog;
					strLog.LoadResource(g_pzCoreErrorSection, 18);
					InfoLog(strLog);
					return -1;
				}

				// read data from the server
				int nBytes = recv(fd, &Buf[*pnBytesInBuf], MAX_DATA_CHUNK - *pnBytesInBuf, 0 );
				if (nBytes > 0)
				{
					char *pNewDataBegin = &Buf[*pnBytesInBuf];
					*pnBytesInBuf = *pnBytesInBuf + nBytes;
					if (g_DataTrace) DataTrace("raw rx", pNewDataBegin, nBytes);
					goto READ_PACKET_BEGIN;
				}
				else // nBytes = -1 || 0
				{
					// recv() failed (connection closed by server)
					return -1;
				}
			}
			else if (rslt == -1)
			{
				// failure during wait (connection closed by server)
				return -1;
			}
			else 
			{
				// rslt = 0, the time limit expired.
			}
			
			// no data or only a partial packet available - nothing to do.
			return 0;
		}
	}
	else
	{
		int rslt = readableTimeout(fd, nTimeOutSec, nTimeOutNano);
		if ( rslt > 0)
		{
			// read data from the server (tunnel server, or app server )
			int nMaxRead = (bPacketSync) ? MAX_DATA_CHUNK : MAX_RAW_CHUNK;
			int nBytes = recv(fd, Buf, nMaxRead, 0 );
			if (nBytes > 0)
			{
				if (g_DataTrace) DataTrace("raw rx", Buf, nBytes);
				*pnBytesInBuf = nBytes;
				if (bPacketSync)
				{
					// a packet from the tunnel server / tunnel client has the protocol headers
					*nPktIndex = 0;
					goto READ_PACKET_BEGIN;
				}
				else
				{
					// a raw packet from an application server (ftp server, telnet server etc)
					*ppPkt = Buf;
					*nPktLen = nBytes;
					*psAttrFlags = 0;	// no attributes
					return 1; // process the data
				}
			}
			else// if (nBytes == -1)
			{
				// recv() failed (connection closed by server)
				return -1;
			}
		}
		else if (rslt == -1)
		{
			// failure during wait (connection closed by server)
			return -1;
		}
		
		// no data or only a partial packet available - nothing to do.
		return 0;
	}
}

unsigned short BuildAttributesHeader(const char *pzCfgSection, int nProtocol, int nProtocolAction)
{
	unsigned short sRet = 0;

	if (nProtocol == 6)
	{
		if (nProtocolAction == 1 || nProtocolAction == 3)
		{
			sRet |= ATTRIB_CRYPTED_2FISH128;
			sRet |= ATTRIB_COMPRESSED;
		}
		return sRet;
	}
	if (nProtocol == 3)
	{
		return sRet; // 0 is a raw proxy
	}


	const char *pKey128 = GetProfile().GetString(pzCfgSection,"CipherPass128",false);
	const char *pKey256 = GetProfile().GetString(pzCfgSection,"CipherPass256",false);

	if ( pKey256 )
	{
		sRet |= ATTRIB_CRYPTED_2FISH256;
	}
	else if ( pKey128 )
	{
		sRet |= ATTRIB_CRYPTED_2FISH128;
	}
	if ( GetProfile().GetBoolean(pzCfgSection,"CompressEnabled",false) )
	{
		sRet |= ATTRIB_COMPRESSED;
	}
	if ( GetProfile().GetBoolean(pzCfgSection,"PacketSyncEnabled",false) )
	{
		sRet |= ATTRIB_SYNCRONIZED;
	}

	// if no attributes were specified and a raw(non-synchronized) was not specified.
	if (!sRet && !GetProfile().GetBoolean(pzCfgSection,"RawPacketProxy",false))
	{
		sRet |= ATTRIB_SYNCRONIZED;
	}

	return sRet;
}


int VerifyAttributes( unsigned short sExpectedFlags, unsigned short sReceivedFlags )
{
	if (sExpectedFlags == sReceivedFlags)
		return 1;

	// 19=This machine is configured to expect data with the following:
	GString strError;
	strError.LoadResource(g_pzCoreErrorSection, 19);
	strError += "\n";

	GString strTemp;

	if ( sExpectedFlags & ATTRIB_CRYPTED_2FISH128 )
	{
		//128 bit TwoFish Encryption
		strTemp.LoadResource(g_pzCoreErrorSection, 20);
		strError += strTemp;
		strError += "\n";
	}
	if ( sExpectedFlags & ATTRIB_CRYPTED_2FISH256 ) 
	{
		//256 bit TwoFish Encryption
		strTemp.LoadResource(g_pzCoreErrorSection, 21);
		strError += strTemp;
		strError += "\n";
	}
	if ( sExpectedFlags & ATTRIB_COMPRESSED ) 
	{
		//Compression Enabled
		strTemp.LoadResource(g_pzCoreErrorSection, 22);
		strError += strTemp;
		strError += "\n";
	}
	if ( sExpectedFlags & ATTRIB_SYNCRONIZED ) 
	{
		//Packet Synchronization Enabled
		strTemp.LoadResource(g_pzCoreErrorSection, 23);
		strError += strTemp;
		strError += "\n";
	}


	//24=but the actual data received had the following attributes:
	strTemp.LoadResource(g_pzCoreErrorSection, 24);
	strError += strTemp;
	strError += "\n";

	if ( sReceivedFlags & ATTRIB_CRYPTED_2FISH128 )
	{
		//128 bit TwoFish Encryption
		strTemp.LoadResource(g_pzCoreErrorSection, 20);
		strError += strTemp;
		strError += "\n";
	}
	if ( sReceivedFlags & ATTRIB_CRYPTED_2FISH256 ) 
	{
		//256 bit TwoFish Encryption
		strTemp.LoadResource(g_pzCoreErrorSection, 21);
		strError += strTemp;
		strError += "\n";
	}
	if ( sReceivedFlags & ATTRIB_COMPRESSED ) 
	{
		//Compression Enabled
		strTemp.LoadResource(g_pzCoreErrorSection, 22);
		strError += strTemp;
		strError += "\n";
	}
	if ( sReceivedFlags & ATTRIB_SYNCRONIZED ) 
	{
		//Packet Synchronization Enabled
		strTemp.LoadResource(g_pzCoreErrorSection, 23);
		strError += strTemp;
		strError += "\n";
	}

	InfoLog((const char *)strError);

	return 0;
}

#ifdef LINK_VNC_REMOTE_WS
void ApplyStaticServerSettings( vncServer* pvncSrv )
{

	pvncSrv->SetQuerySetting(2);
	pvncSrv->SetQueryTimeout(10);
	pvncSrv->SetAutoIdleDisconnectTimeout(0);
	pvncSrv->EnableRemoveWallpaper(1);


	// Now change the listening port settings
	pvncSrv->SetAutoPortSelect(1);
	pvncSrv->SetPort(0);
	pvncSrv->SockConnect(0);
	
	// Set the CORBA connection status - None
	pvncSrv->CORBAConnect(0);

	// Remote access prefs
	pvncSrv->EnableRemoteInputs(1);
	pvncSrv->SetLockSettings(-1);
	pvncSrv->DisableLocalInputs(0);

	// Polling prefs
	pvncSrv->PollUnderCursor(0);
	pvncSrv->PollForeground(1);
	pvncSrv->PollFullScreen(0);
	pvncSrv->PollConsoleOnly(1);
	pvncSrv->PollOnEventOnly(0);
}

class RemoteWorkstationThreadData
{
public:
	int nPort;
	pthread_mutex_t lock;
	pthread_cond_t	cond;
	RemoteWorkstationThreadData()
	{
		pthread_mutex_init(&lock,0);
		pthread_cond_init(&cond,0);
		pthread_mutex_lock(&lock);
	}
	~RemoteWorkstationThreadData()
	{
		pthread_mutex_destroy(&lock);
		pthread_cond_destroy(&cond);
	}
};
#endif //LINK_VNC_REMOTE_WS



void *RemoteWorkstationThread(void *arg)
{
#ifdef LINK_VNC_REMOTE_WS
	RemoteWorkstationThreadData *pWSD = (RemoteWorkstationThreadData *)arg;

	
	struct sockaddr_in  serv_addr;
	int fdNew;
	struct sockaddr_in cli_addr;

	int fd;
	if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		InfoLog("socket() failed");
		return 0;
	}
	memset((char *) &serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons( pWSD->nPort );
	
	if(bind(fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
	{
		// if the bind fails, we're already running a listener on this port
		// The RemoteWorkstation server is ready.
		pthread_cond_signal(  &pWSD->cond ); 
		return 0;
	}

	vncServer *pVNCServer = new vncServer;
	ApplyStaticServerSettings(pVNCServer);		
	listen(fd, 5);
	
	int clilen = sizeof(cli_addr);
	pthread_cond_signal( &pWSD->cond ); // The RemoteWorkstation server is ready.

	
	IOBlocking(fd, 1); // Set non-blocking IO
	while(1)
	{
		int rslt = readableTimeout(fd, 10/*seconds*/, 0/*microseconds*/);
		if ( rslt == -1 )
		{
			break; 
		}
		if ( rslt > 0)
		{
			fdNew = accept(fd, (struct sockaddr *) &cli_addr, &clilen);
			if(fdNew < 0)
			{
				// error - ignore and exit thread.
			}
			else 
			{
				int rslt = readableTimeout(fdNew, 60, 0);
				if ( rslt < 1 )
				{
					GString strLog;
					strLog.Format("end it! [%d][%d][%d]",fdNew,rslt,SOCK_ERR);
					InfoLog(strLog);
					continue;
				}
				unsigned char pzPendingHostId[52];
				int bytes = recv(fdNew, (char *)pzPendingHostId, 52, 0);
				if (bytes == 52)
				{

					struct sockaddr_in	sockinfo;
					struct in_addr		address;
					int					sockinfosize = sizeof(sockinfo);

					getpeername(fdNew,(struct sockaddr *) &sockinfo, (socklen_t *)&sockinfosize);

					memcpy(&address, &sockinfo.sin_addr, sizeof(address));

					
					if (!JudgeConnection(pzPendingHostId,inet_ntoa(address)))
					{
						// 25=Connection failed to pass judgement
						GString strInfo;
						strInfo.LoadResource(g_pzCoreErrorSection, 25);
						InfoLog(strInfo);
						continue;
					}
				}
				else
				{
					// 26=Failed to read HostID packet
					GString strInfo;
					strInfo.LoadResource(g_pzCoreErrorSection, 26);
					InfoLog(strInfo);
					continue;
				}


				
				IOBlocking(fdNew, 0); // Set blocking IO


				VSocket *new_socket = new VSocket;
				new_socket->Attach(fdNew);

				int one = 1;
				setsockopt(fdNew, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));

				pVNCServer->AddClient(new_socket, FALSE, FALSE);
			}
		}
		else
		{
			// if pVNCServer is not serving any connections then break, and free up this thread.
			// a new instance will be created on demand when neceassary.
		}
	}
	#ifdef _WIN32    
		closesocket(fd);
	#elif _HPUX
		shutdown(fd,2);
	#else // linux, sun, AIX
		close(fd);
	#endif
	delete pVNCServer;
#endif // LINK_VNC_REMOTE_WS
	return 0;
}

void StartRemoteWorkstation(int nPort)
{
#ifdef LINK_VNC_REMOTE_WS
	RemoteWorkstationThreadData wsd;
	wsd.nPort = nPort;

	pthread_t remoteWS_thr;
	pthread_create(&remoteWS_thr,	NULL, RemoteWorkstationThread, (void *)&wsd );
	pthread_cond_wait(&wsd.cond, &wsd.lock);  // wait for the service to start
#endif // LINK_VNC_REMOTE_WS
}



void CreateHostID(unsigned char *pzDest) // pzDest must be able to hold 52 bytes
{
	// send the "unlock code" to validate that the user end of this connection knows the password.
	// doctor up (md5) the local password, so network monitoring tools can't see it pass through the TCP/IP stack.
	const char *pzPass = GetProfile().GetString("RemoteWorkstation","Password",false);
	Hash((void *)pzPass, strlen(pzPass), &pzDest[4], 128);

	// encode 256 bits that identify 'this' machine.  
	memcpy(&pzDest[20],g_RandomNumbers,32);

	// encode the number of seconds that have elapsed since this(127.0.0.1) was started to right now.
	// This makes a small window of time, that the verification attempt is valid.
	long lNow;
	time(&lNow);
	long lElapsed = lNow - g_ServerStartTime;
	memcpy(pzDest,&lElapsed,4);
}


int	DoConnect(const char *pzConnectTo, int nPort)
{
	if (!pzConnectTo || !pzConnectTo[0])
		return -1;
	
	// if the first byte of pzConnectTo is a '~' - make an inbound-connect.  
	// wait for 2 minutes for the named connection to be posted in (connection opened by the destination).
	if (pzConnectTo[0] == '~')
	{
		// returns -1 or the socket handle
		return g_SwitchBoard.ConnectToServer( &pzConnectTo[1] );
	}
	
	// else make an outbound-connect....
	struct sockaddr_in their_addr; 
	their_addr.sin_family = AF_INET;
	their_addr.sin_port = htons(nPort);
	their_addr.sin_addr.s_addr = inet_addr (pzConnectTo);
	if (their_addr.sin_addr.s_addr == -1)
	{
		// resolve a DNS server name if inet_addr() came up empty.
		struct hostent *pHE = (struct hostent *)gethostbyname(pzConnectTo);
		if (pHE == 0)
		{ 
			// 27=Info:Failed to resolve[%s].  Connection refused.
			GString strInfo;
			strInfo.LoadResource(g_pzCoreErrorSection, 27, pzConnectTo);
			InfoLog(strInfo);
			return -1;
		}
		memcpy((char *)&(their_addr.sin_addr), pHE->h_addr,pHE->h_length); 
	}
	memset(&(their_addr.sin_zero),0, 8);//zero the rest of the (unused) struct


	int fd;
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
	{	
		return -1;
	}

	if (connect(fd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) != 0)
	{
		return -1;
	}
	return fd;
}


void *ProxyHelperThread(void *arg)
{
	ThreadData *td = (ThreadData *)arg;

	CipherData *pcdToServer;
	CipherData *pcdToClient;

	int fd;
	char sockBuffer[MAX_DATA_CHUNK];
	int nBufBytes;
	int nPacketIndex;
	int nPacketLen;
	char *pPacket;

	GStringList lstRollOver;

	char *cryptDest = new char[MAX_DATA_CHUNK];

	struct sockaddr_in their_addr; 
	their_addr.sin_family = AF_INET;
	
	// seed the random number generator once on each thread with a unique seed.
	g_nRrandSeed += (777 * (g_nRrandSeed % 100)) ;
	srand( g_nRrandSeed );

BACK_TO_THE_POOL:
	nBufBytes = 0;
	nPacketIndex = -1;
	nPacketLen = 0;
	pPacket = 0;
	// ----------------------------------------------------------------------
	// wait here until the clientthread wakes up this thread.
	// ----------------------------------------------------------------------
	if ( td->m_bUseThreads )
	{
		td->nThreadIsBusy = 0;
		pthread_cond_wait(&td->cond, &td->lock); 
	}
	if ( g_ThreadPing )
	{
		GString strLog;
		strLog.Format("H%d   ",td->nThreadID);
		InfoLog(strLog,0);

		goto BACK_TO_THE_POOL;
	}
	if (g_ServerIsShuttingDown)
	{
		pthread_exit(0);
		return 0;
	}


	// the 16 bit flag field indicating data attributes
	unsigned short sAttribs = BuildAttributesHeader(td->pTSD->szConfigSectionName,td->pTSD->nProtocol, td->nAction);
	int bIsTunnel = td->pTSD->bIsTunnel;
	if (td->pTSD->nProtocol == 6 )
	{
		if (td->nAction == 1)
			bIsTunnel = 1;
		else
			bIsTunnel = 0;
	}


	CipherData en;
	CipherData de;
	if ( ( sAttribs & ATTRIB_CRYPTED_2FISH128 ) || ( sAttribs & ATTRIB_CRYPTED_2FISH256 ) )
	{
		const char *pKey128 = GetProfile().GetString(td->pTSD->szConfigSectionName,"CipherPass128",false);
		const char *pKey256 = GetProfile().GetString(td->pTSD->szConfigSectionName,"CipherPass256",false);
		if (td->pTSD->nProtocol == 6 ) // Remote Workstation (VNC)
		{
			if (td->nAction == 1)
				pKey128 = td->pzRemoteKey;
			else
				pKey128 = GetProfile().GetString("RemoteWorkstation","Password",false);
			
		}

		InitializeCipherKey(&en,pKey128,pKey256,DIR_ENCRYPT);
		InitializeCipherKey(&de,pKey128,pKey256,DIR_DECRYPT);
		if (bIsTunnel)
		{
			pcdToClient = &de;
			pcdToServer = &en;
		}
		else
		{
			pcdToClient = &en;
			pcdToServer = &de;
		}
	}
	
	
	// determine the destination server.
	GString strProxyToServer;
	int nProxyToPort = td->pTSD->nProxyToPort;
	if (td->pTSD->nProtocol == 3 )
	{
		strProxyToServer = td->pzIP;
		nProxyToPort = 10777;
	}
	else if (td->pTSD->nProtocol == 6 ) // in the ProxyHelperThread
	{
		if (td->nAction == 3)
		{
#ifdef LINK_VNC_REMOTE_WS
			strProxyToServer = "127.0.0.1";
			
			// The port can be incremented for multi-display support in UNIX   -- Version 2
			nProxyToPort = 10999;
			StartRemoteWorkstation(10999);
#else
			// it will fail with a nice error message
			strProxyToServer = "No-Remote-Workstation-Available";
			nProxyToPort = 0;

#endif // LINK_VNC_REMOTE_WS

		}
		else
		{
			strProxyToServer = td->pzIP;
			nProxyToPort = 10888;
		}
	}
	else
	{
		strProxyToServer = td->pTSD->szProxyToServer; // default

		// parse the comma seperated "on fail to connect - roll over list"
		const char *pzRollOver = GetProfile().GetString(td->pTSD->szConfigSectionName,"RollOver",false);
		lstRollOver.DeSerialize(",",pzRollOver);


		// See if there is a 'load balanced' connection order specified:
		// for example if the config file has an entry like this:
		//
		// [Proxy2]
		// Balance=50,vert.synchro.net,25,wolfgrotto.2y.net,25,bbs.checksix.net
		// 
		// then the "RemoteMachine" is overridden with 50% routed to vert.synchro.net
		// 25% routed to wolfgrotto.2y.net and 25% routed to bbs.checksix.net
		const char *pzBalance = GetProfile().GetString(td->pTSD->szConfigSectionName,"Balance",false);
		if (pzBalance)
		{
			int nDistributionKey = rand() % 100; // creates a random #: 0-99
			GStringList lst(",",pzBalance);
			GStringIterator itParams(&lst);
			int nDistributionChannel = 0;
			while (itParams())
			{
				nDistributionChannel += atoi(itParams++); // should add up to 100
				if (!itParams())
					break; // bad config file format
				const char *pzServer = itParams++;
				if (nDistributionChannel > nDistributionKey)
				{
					strProxyToServer = pzServer;
					break;
				}
			}
		}
	}

//							 ----- Version 2.0 ----- 
//  I would like to open the socket SOCK_RAW,IPPROTO_RAW, for outbound data and manually construct
//	the TCP packet headers.  This would then allow the proxy to (optionally per config setting) 
//	maintain it's originating ip address as the data is exchanged, as well as possibly provide a 
//	small performance gain.  Anybody out there have experience with a portable solution?

//	-- Usefull resources:
//			http://www.whitefang.com/rin/rawfaq.html
//			http://www.securiteam.com/exploits/2BUQ7QAQKO.html
//							 ----- Version 2.0 ----- 

	if(g_ConnectionDataTrace)
	{
		// 28=Outbound Connection to [%d:%s]----->
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 28, nProxyToPort,(const char *)strProxyToServer);
		InfoLog(strInfo,0);
	}
ROLL_OVER_CONNECT:	
	fd = DoConnect(strProxyToServer, nProxyToPort);
	if (fd == -1)
	{
		if(g_ConnectionDataTrace)
		{
			// 29=Failed
			GString strInfo;
			strInfo.LoadResource(g_pzCoreErrorSection, 29);
			InfoLog(strInfo);
		}
		
		// roll over to a backup server if configured
		if (lstRollOver.Size())
		{
			strProxyToServer = lstRollOver.RemoveFirst();
			goto ROLL_OVER_CONNECT;
		}

		*td->pnProxyClosed = 1;
		goto THREAD_EXIT;
	}
	if(g_ConnectionDataTrace)
	{
		// 30=Connected-OK
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 30);
		InfoLog(strInfo);
	}


	if (td->pTSD->nProtocol == 6) // Remote Workstation
	{
		int nWritable = writableTimeout(fd, 60, 0); 
		if (nWritable < 1) 
		{
			*td->pnProxyClosed = 1;
			goto THREAD_EXIT;
		}
		
		if (td->nAction == 3) // this machine will be remotely controlled
		{
			unsigned char pzHostId[52];
			CreateHostID(pzHostId);
			int bytes = send(fd, (const char *)pzHostId, 52, 0);
			if (bytes != 52)
			{
				// 31=Failed to send LocalHostID verification.
				GString strInfo;
				strInfo.LoadResource(g_pzCoreErrorSection, 31);
				InfoLog(strInfo);
			}
		}
		else // this machine is proxying to another that will be remotely controlled
		{
			int bytes = send(fd, td->pzConnectRoute, td->nConnectRouteSize, 0);
			if (bytes != td->nConnectRouteSize)
			{
				// 32=Failed to forward connection-routing data.
				GString strInfo;
				strInfo.LoadResource(g_pzCoreErrorSection, 32);
				InfoLog(strInfo);
			}
		}
	}
	
	*(td->pnProxyfd) = fd;					// Give the other thread this connection handle
	pthread_cond_signal( td->pcondHelper ); // and notify it that the connection is ready

	// ProxyHelperThread main loop, 
	while( !(*td->pnProxyClosed)  )
	{
		// if what we are about to recv() is expected to be (bCipher || bZip || bSync)
		int bPacketSync = ( bIsTunnel && sAttribs ) ? 1 : 0;

		unsigned short sPktAttributes = 0;
		int rslt = ReadPacket(fd,0,75000,&nBufBytes,sockBuffer,&pPacket,&nPacketLen, &nPacketIndex,&sPktAttributes, sAttribs, bPacketSync);
		if (rslt == 0)
		{
			// no data or only a partial packet available - nothing to do.
			if (*td->plLastActivityTime)
			{
				if ( (time(0) - *td->plLastActivityTime ) > td->pTSD->nProxyTimeout )
				{
					// the client ended the connection
					goto THREAD_EXIT;
				}
			}
			else
			{
				*td->plLastActivityTime = time(0);
			}
		}
		else if (rslt == -1) // failure during wait (connection closed by server)
		{
			goto THREAD_EXIT;
		}
		else // process this packet.
		{
			*td->plLastActivityTime = time(0);
			if (g_DataTrace) DataTrace("rx from s", pPacket, nPacketLen);

			if (bIsTunnel)// about to decrypt/decompress
			{
				if (!VerifyAttributes(sAttribs,sPktAttributes))
				{
					// 33=Bad attributes header
					GString strInfo;
					strInfo.LoadResource(g_pzCoreErrorSection, 33);
					InfoLog(strInfo);
					goto THREAD_EXIT;
				}
			}


			char *pSendData = pPacket;
			int nSendDataLen = nPacketLen;

			if ( sAttribs ) // bCipher || bZip || bSync
			{
				int newLength;
				if (!bIsTunnel) // if we are about to encrypt/compress data from the application to send into the tunnel
				{
					cryptDest += 4; // move the pointer ahead 4 bytes where we will put the header
				}

				// compress/decompress/crypt/decrypt or custom manipulation
				if ( PrepForClient(sAttribs,bIsTunnel,pcdToClient,pSendData,nSendDataLen,cryptDest,&newLength) )
				{
					if (!bIsTunnel) 
					{
						cryptDest -= 4; // move the pointer back 4 bytes to make room for the header
						unsigned short sh = htons(newLength); // packet length in network byte order
						memcpy(&cryptDest[0],&sh,sizeof(short));
						sh = htons(sAttribs); // attributes in network byte order
						memcpy(&cryptDest[2],&sh,sizeof(short));
						newLength += 4; // account for the header in the packet size
					}
					pSendData = cryptDest;
					nSendDataLen = newLength;
				}
				else
				{
					goto THREAD_EXIT;
				}
			}

			// send to the client
			if (g_DataTrace) DataTrace("tx to c", pSendData, nSendDataLen);
			
			if (nonblocksend(td->sockfd,pSendData, nSendDataLen) != nSendDataLen)
			{
				goto THREAD_EXIT;
			}
		
			ProxyLog(td->pTSD->szConfigSectionName, td->nParentThreadID, pSendData, nSendDataLen, "tx->c");
		}
	} 
THREAD_EXIT:
	if(g_ConnectionDataTrace && !g_ServerIsShuttingDown)
	{
		// 34=Closed Outbound Connection to [%d:%s]
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 34,nProxyToPort,(const char *)strProxyToServer);
		InfoLog(strInfo);
	}

	// In many cases *td->pnProxyClosed was already set to '1' a few steps earlier.
	*td->pnProxyClosed = 1; // 1 == Shut down in progress.

#ifdef _WIN32    
	closesocket(fd);
	closesocket(td->sockfd);
#elif _HPUX
	shutdown(fd,2);
	shutdown(td->sockfd,2);
#else // linux, sun, AIX
	close(fd);
	close(td->sockfd);
#endif
	*td->pbHelperThreadIsRunning = 0; // helper shutdown complete.

	if (g_ServerIsShuttingDown)
	{
		pthread_exit(0);
	}
	else
	{
		goto BACK_TO_THE_POOL;
	}
	return 0;
}



int DoFileTransferPacketSend(CipherData *en,int fd,char *buf1,int nBytesToSend,char *buf2,int nTimeOutSeconds,unsigned short sAttrib)
{
	// encrypt(using the local password) and compress
	int nBuf2Len = 0;
	if ( PrepForServer(sAttrib,1,en,buf1,nBytesToSend,&buf2[4],&nBuf2Len))
	{
		unsigned long sh = htonl(nBuf2Len); // packet length in network byte order
		memcpy(buf2,&sh,sizeof(long));
		nBuf2Len += 4;
		
		// sending binary packet len[nBuf2Len]
	}
	else
	{
		// 35=DoFileTransferPacketSend - Prep for server failed.
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 35);
		InfoLog(strInfo);
		return -1;
	}

	int nSendRetries = 0;
	int nBytesSent = 0;
SHOULD_BLOCK:
	int nWritable = writableTimeout(fd, nTimeOutSeconds, 0); 
	if (nWritable < 1) 
	{
		// error, or timeout or connection closed by client
		// 10038 = invalid socket (socket has been closed by peer)
		// 10022 = invalid argument (fixed by retry)
		// 10054 = Connection reset by peer
		if (SOCK_ERR == 10038 || SOCK_ERR == 10022 || SOCK_ERR == 10054)
		{
			if (nSendRetries++ < 3)
			{
				sched_yield();
				goto SHOULD_BLOCK; 
			}
		}

		// 36=Timeout, Error or aborted by client[%d].
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 36,SOCK_ERR);
		InfoLog(strInfo);

		return 0; // failed to send
	}
	
	int nBytes = send(fd, &buf2[nBytesSent], nBuf2Len - nBytesSent, 0);
	if (nBytes == -1)
	{ 
		if (SOCK_ERR == 10035 || SOCK_ERR == 10054 || SOCK_ERR == 10053)
		{
			if (nSendRetries++ < 3)
			{
				sched_yield();
				goto SHOULD_BLOCK; 
			}
			if(SOCK_ERR == 10035) // 10035 = Resource temporarily unavailable
			{
				// i've only seen this logic execute in Windows98
				nSendRetries=0;
				sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();
				goto SHOULD_BLOCK; 
			}
		}
		// SOCK_ERR generally is 10053 WSAECONNABORTED at this point
		// In Linux i've seen SOCK_ERR = 104, what's that?

		// 37=DoFileTransferPacketSend() --> Send() aborted[%d].
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 37,SOCK_ERR);
		InfoLog(strInfo);

		return 0; // failed
	}
	else
	{
		// nBytes were just sent
		nBytesSent += nBytes;
		if ( nBytesSent < nBuf2Len )
		{
			// send() only sent part of data in buf2, this is common in Linux, never happens in Windows.
			goto SHOULD_BLOCK;
		}
	}

	// ---Version 2---
	// it may be best to wait for an "OK" here.  It will slow things down a bit,
	// but it relieves the sockets library of heavy buffering and eliminates a wide
	// variety of possible socket errors.

	return 1; // success
}





int DoFileTransferPacketRead(int fd, char *buf1, char *buf2, int *nBuf2Bytes, CipherData *de, int *nPktIndex, char **ppPkt, int *nPktLen, int *pnBytesInBuf)
{
	// An ALL command on the root of a full 24 GB IDE drive took 540 seconds.
	// all other commands generally return data within sub-second(LAN) or
	// 10 seconds (WAN).  Even a GET/PUT of a 100Mb file returns an "OK" or
	// the first data packet immediately.
	// The big timeout covers the ALL case, and over-does the other cases.  
	// To prevent any form holding connections hostage and creating a denial 
	// of service attack, this value can be overridden, with the understanding 
	// that huge ALL commands may fail using a more secure timeout value like ~15 seconds.
	int nTimeout = GetProfile().GetInt("FileTransfer","Timeout",false);
	if (nTimeout < 1)
		nTimeout = 500;

	

	unsigned long lBeginReadTime = time(0); 
	// if this is the initial read, otherwise the beginning of the next packet was in the last recv()
	if (*nPktIndex == -1)
	{
		int rslt = readableTimeout(fd, nTimeout, 0);
		if ( rslt > 0)
		{
			if (time(0) - lBeginReadTime > nTimeout )
			{
				// 38=Modify the [FileTransfer] Timeout= value in the config file.
				GString strInfo;
				strInfo.LoadResource(g_pzCoreErrorSection, 38);
				InfoLog(strInfo);
				return -1;
			}
			int nRecvRetry = 0;
RE_RECV:
			// read data from the server
			int nBytes = recv(fd, buf1, MAX_DATA_CHUNK, 0 );
			if (nBytes > 0)
			{
				// just got [nBytes] more bytes of data
				*pnBytesInBuf = nBytes;
				*nPktIndex = 0;
				// check to see if this was the completion of this packet.
				goto READ_FILE_PACKET_BEGIN; 
			}
			else if (nBytes == -1)
			{
				if (nRecvRetry++ < 3)
				{
					sched_yield();
					goto RE_RECV;
				}
				
				// 10038 = invalid socket , or this socket has been closed
				// 10054 = Connection reset(closed) by peer

				// 39=recv() aborted [%d] (connection closed by remote)
				GString strInfo;
				strInfo.LoadResource(g_pzCoreErrorSection, 39,SOCK_ERR);
				InfoLog(strInfo);


				return -1;
			}
			else // (nBytes == 0)
			{
				// This happens when the server closes the connection before 
				// sending back any data.
				return 0;	
			}
		}
		else if (rslt == -1)
		{
			// failure during wait (connection closed by server)
			return -1;
		}
	}


READ_FILE_PACKET_BEGIN:
	// the first 4 bytes of the packet is the packet length in network byte order
	unsigned long theNumber = 0;					// storage on platform architecture byte boundry
	memcpy(&theNumber,&buf1[*nPktIndex],4);			// storage in network byte order
	unsigned long nNextPktSize = ntohl( theNumber );// convert to host byte order

	int nBufBytesNeeded = *nPktIndex + nNextPktSize + sizeof(long);

	// If the next packet is the complete remainder of the recv() buffer	----> ( == )
	// or if the next packet is in the recv() buffer with more data after it----> ( <  )
	if (nBufBytesNeeded <= *pnBytesInBuf)
	{
		// we got the whole packet - decrypt, uncompress and return.
		*ppPkt = &buf1[*nPktIndex + sizeof(long)]; // 4 bytes of header, then data
		*nPktLen = nNextPktSize;

		// at this point *nPktLen is the entire packet in binary form (crypted and zipped)

		// decrypt the response into buf2
		if ( !PrepForClient(ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED,1,
						  de,*ppPkt,*nPktLen,buf2,nBuf2Bytes))
		{
			// if we get here something failed in the unzip/decrypt, 
			// most likely the data was crypted with an incorrect password.
			return -2;
		}

		// If the next packet is the complete remainder of the recv() buffer
		if (nBufBytesNeeded == *pnBytesInBuf)
		{
			*nPktIndex = -1; // last packet
			buf2[*nBuf2Bytes] = 0; 
		}
		else // the next packet is in the recv() buffer with more data after it
		{
			// the remote sent two small packets back to back and we got them in a single read.
			*nPktIndex = nBufBytesNeeded; 
		}
		
		// we recieved, decrypted, and uncompressed. The final length is [*nBuf2Bytes]
		return 1;  // Success
	}

	// otherwise, we need to revc() the remainder of this packet
	else
	{
		int rslt = readableTimeout(fd, nTimeout, 0); 
		if ( rslt > 0)
		{
			// slide the data to the front if the buffer to make room for this packet
			if (*nPktIndex > 0)
			{
				memmove(buf1,&buf1[*nPktIndex],*pnBytesInBuf - *nPktIndex); // ANSI <string.h>
				*pnBytesInBuf = *pnBytesInBuf - *nPktIndex;
				*nPktIndex = 0;
			}

			// test the buffer for necessary free space now
			if (MAX_DATA_CHUNK - *pnBytesInBuf < 1)
			{
				// not possible under normal conditions.
				//9=Internal Buffering Error
				GString strInfo;
				strInfo.LoadResource(g_pzCoreErrorSection, 9);
				InfoLog(strInfo);
				return -1;
			}

			// read more data from the server
			int nBytes = recv(fd, &buf1[*pnBytesInBuf], MAX_DATA_CHUNK - *pnBytesInBuf, 0 );
			if (nBytes > 0)
			{
				//if (g_DataTrace) DataTrace("raw rx", pNewDataBegin, nBytes);
				*pnBytesInBuf = *pnBytesInBuf + nBytes;
				// we have got [*pnBytesInBuf of nBufBytesNeeded] bytes at this point
				goto READ_FILE_PACKET_BEGIN;
			}
			else // nBytes = -1 || 0
			{
				// recv() failed (connection closed by remote)
				return -1;
			}
		}
		else if (rslt == -1)
		{
			// failure during wait (connection closed by remote)
			return -1;
		}
		else 
		{
			// rslt = 0, the time limit expired.
		}
		
		// no data or only a partial packet available - nothing to do.
		return 0;
	}
}

///////////////////////////////////////////////////////////////////////
// command	Param0		Param1		  Param2(cmd param 1)	Param3(cmd param2)
// --------------------------------------------------------------------
// mkd		RemoteIP	RemotePass	  remote path		
// rmd			""			""		  remote path
// del			""			""		  remote path/file
// ren			""			""		  remote path/file		remote file
// get			""			""		  remote path/file		local path/file	
// put			""			""		  remote path/file		local path/file	
// dir			""			""		  remote path
// 000		------------------ ENCRYPTED CMD --------------------------
// pxy		------------------ ENCRYPTED PROXY ------------------------
///////////////////////////////////////////////////////////////////////

// for example:
// get1.2.3.4&&mypassword&&c:\fully\qualified\path\TheFile.ext&&/usr/opt/TheLocalFileName.ext

int InitialCommandRead(int fd, char *buf1, int *nBuf1Len, GString &strCmd, GString &strConnectTo, GString &strDestPass, GString &strParam2, GString &strParam3, GString &strCmdThree, GString &strCmdFour, GString &strProxyTo)
{
	*nBuf1Len = 0;
	int nPUDMsgLen = -1;
	unsigned long lBeginReadTime = time(0); 
READ_WHOLE_COMMAND:
	int rslt = readableTimeout(fd, 5, 0);

	// prevents a 'denial of service attack' where  an attacker could hold the connection open
	// by sending data every few seconds, thereby never timing out, and holding a
	// connections hostage.  This cuts off any connection over 60 seconds after the accept()
	if ( time(0) - lBeginReadTime > 60)
	{
		// commands not terminated with "\r\n" will end up here, as we timeout 
		// waiting for the end of the message.
		// 40=Timeout reading file transfer command.  Did you terminate with [CRLF] aka'\r\n'?
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 40);
		InfoLog(strInfo);
		return 0;
	}

	if ( rslt == 0)
		goto READ_WHOLE_COMMAND;


	if ( rslt > 0)
	{
		// and no good command would overrun a buffer 
		if (MAX_DATA_CHUNK - *nBuf1Len < 1)
		{
			//41=InitialCommandRead buffer overrun attempt.
			GString strInfo;
			strInfo.LoadResource(g_pzCoreErrorSection, 41);
			InfoLog(strInfo);
			return 0;
		}

		int recvRslt = recv(fd, &buf1[*nBuf1Len], MAX_DATA_CHUNK - *nBuf1Len, 0 );
		if ( recvRslt == -1 )
		{
			return 0;
		}
		*nBuf1Len += recvRslt;
		buf1[*nBuf1Len] = 0;

		if ( *nBuf1Len < 3 )
			goto READ_WHOLE_COMMAND;

		
		if (strCmd.Length() != 3) // get the command name (PUT,GET,PUD,ALL .....)
		{
			GString tmpStrCmd(buf1,3);
			strCmd = tmpStrCmd;
		}

	
		// the PUD(Put User Data) is handled differently than all the other commands.
		// rather than being '\r\n' terminated as the data could have
		// '\r\n"'s in it, the length is sent, and used to read the whole command.
		if (  strCmd.CompareNoCase("pud") == 0 )
		{
			int NumericTerminationSearchBytes = (*nBuf1Len > 15) ? 15 : *nBuf1Len;
			if (nPUDMsgLen == -1)
			{
				for(int i=0;i<NumericTerminationSearchBytes; i++)
				{
					//the message looks like this: pud123&&user&&pass&&data
					if (buf1[i+3] == '&' && buf1[i+4] == '&')
					{
						// extract the size of message we expect to read.
						nPUDMsgLen = atoi(&buf1[3]); // length of the (&&user&&pass&&data)
						nPUDMsgLen += 3; // length of the "pud" command is always 3
						nPUDMsgLen += i; // length of ascii-numeric length 
					}
				}
			}

			// if we don't have the entire msg size yet, keep reading
			if (nPUDMsgLen == -1 || nPUDMsgLen < *nBuf1Len)
				goto READ_WHOLE_COMMAND;

		}

		// if this is a crypted command
		else if (buf1[0] == '0' && buf1[1] == '0' && buf1[2] == '0')
		{
			// MSG Format=000����~~www.Company.com|internal-pc###################### 
			// Where 000 is the command, ���� is a 4 byte packet length, ~~ is a two byte
			// length of a | seperated list of optional proxys then ######################
			// is the encrypted command than can only be decrypted by the last address
			// in the | seperated list that is ~~ bytes long.
			if ( *nBuf1Len < 7 )
				goto READ_WHOLE_COMMAND;
			
			// "Datatype Alignment information": although the data does not reside on
			// correct boundries in the protocol stream, this method of copying covers 
			// more platform bases, than simply casting  "(unsigned long *)&buf1[3]"
			unsigned long DataHeader = 0;
			memcpy(&DataHeader,&buf1[3],4);
			
			// now flip the byte ordering if necessary
			unsigned long nCmdMsgSize = ntohl( DataHeader );


			if (*nBuf1Len < nCmdMsgSize)
				goto READ_WHOLE_COMMAND;
			if (*nBuf1Len > nCmdMsgSize)
			{
				InfoLog("protocol error");
				return 0;
			}
		}
		else // this is an initial user request (generally from localhost or a secure network)
		{
			if ( memcmp("\r\n",&buf1[*nBuf1Len - 2],2) != 0 )
				goto READ_WHOLE_COMMAND;
			else
				// strip the [CRLF]
				buf1[*nBuf1Len - 2] = 0;
		}
	}
	else 
	{
		// readableTimeout failed/expired
		return 0;
	}

	GString tmpStrCmd(buf1,3);
	strCmd = tmpStrCmd;


	// there's nothing more to get if the command is crypted
	if ( strCmd.Compare("000") == 0 )
	{
	    // at this point buf1 contains a crypted command recvd() from a remote 5Loaves server.
		return 1; // success
	}

	// at this point buf1 contains a clear text command generally recvd() from local host.
	if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
	{
		GString str;
		str.Format("Command=[%s]\n",buf1);
		InfoLog(str);
	}


	GStringList lstParams("&&",&buf1[3]);
	GStringIterator itParams(&lstParams);
	
	int nLocalExec = ( strCmd.Compare("PUD") == 0 || strCmd.Compare("GUD") == 0) ? 1 : 0;
	if (!nLocalExec)
	{
		// **Param 0 - Destination
		//	SingleIP or	  InternetIP/InternalIP
		// "1.2.3.4" or  "12.12.12.12|192.168.1.100"
		if (!itParams()) 
			return 0; // too few params
		GString strDeliveryIPs(itParams++);
		GStringList lstDeliveryIPs("|",strDeliveryIPs);
		strConnectTo = lstDeliveryIPs.RemoveFirst();
		if ( lstDeliveryIPs.Size() > 0 )
		{
			strProxyTo = lstDeliveryIPs.Serialize("|");
		}
		
		// **Param 1 - Crypt Key
		if (!itParams()) 
			return 0; // too few params
		strDestPass = itParams++;
	}

	// **Param 2 - Command Param 1
	if (itParams()) 
		strParam2 = itParams++;

	// **Param 3 - Command Param 2
	if (itParams()) 
		strParam3 = itParams++;

	// **Param 4 - Command Param 3
	if (itParams()) 
		strCmdThree = itParams++;

	// **Param 5 - Command Param 4
	if (itParams()) 
		strCmdFour = itParams++;

	return 1; // success
}


int IsAllowedPath( const char *pzPath )
{
	GString strRoot( GetProfile().GetString("FileTransfer","Root",false) );
	if ( !strRoot.Length() ) // if no "root restrictions" are specified...
	{
		return 1; // OK
	}

	if ( strlen(pzPath) > strRoot.Length() - 1 )
	{
		GString strSub(pzPath,strRoot.Length());
		if (strSub.CompareNoCase((const char *)strRoot) == 0)
		{
			return 1; // OK
		}
		else
		{
			return 0; // FAIL
		}
	}
	return 0; // FAIL
}

class LocalData
{
public:
	int fd;
	char *buf1;
	char *buf2;
	CipherData *en;
	CipherData *de;
};

int LocalGet(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;
	
	
	GStringList lstLocalFiles("*",&buf2[3]);
	GStringIterator itLocalFiles(&lstLocalFiles);
	int nCurrentFileIndex = 0;
	while (itLocalFiles())
	{
		nCurrentFileIndex++;
		const char *pzLocalFileName = ReSlash(itLocalFiles++);

		if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
		{
			GString str;
			str.Format("GETting %d/%d[%s]\n",nCurrentFileIndex,lstLocalFiles.Size(),pzLocalFileName);
			InfoLog(str);
		}

		// Check to see that this file is within a restricted branch - if a restriction is specified.
		if ( !IsAllowedPath( pzLocalFileName ) )
		{
			GString strErr;
			strErr.Format("Error:Access denied to file[%s], Check the [FileTransfer] 'Root=' entry in the config file on[%s].\r\n",pzLocalFileName,(const char *)g_strThisHostName);
			DoFileTransferPacketSend(en,fd,(char *)(const char *)strErr,strErr.Length(),buf1,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED);
			continue;
		}
		int nOpenRetry = 0;
	RETRY_GET_OPEN:
		FILE *fp = fopen(pzLocalFileName,"rb");
		
		// if the file could be opened
		if (fp)
		{
			// get the size of the file
			fseek(fp,0,SEEK_END);
			long lFileBytes = ftell(fp);        // 4.2 gig file size limit in Version 1
			fseek(fp,0,SEEK_SET);				// when raised to a 64 bit value the max file size will be 16 terabytes 
			
			
			// start creating the data to send back
			sprintf(buf1,"OK %ld:",lFileBytes);
			long nTotalBytesIn = 0;

			// for 0 byte files, send the header only
			if (lFileBytes == 0) 
			{
				DoFileTransferPacketSend(en,fd,buf1,strlen(buf1),buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED);
			}
			while ( nTotalBytesIn < lFileBytes)
			{
				// nBytesToSend =  the header length (1st packet), or 0 for all subsequent packets
				int nBytesToSend = strlen(buf1); 
				int nBytesInThisRead = fread(&buf1[nBytesToSend],sizeof(char),MAX_RAW_CHUNK - nBytesToSend,fp);
				nTotalBytesIn  += nBytesInThisRead;
				nBytesToSend += nBytesInThisRead; // total length of data in buf1
				

				// sending this [nBytesToSend] chunk of bytes.
				if (DoFileTransferPacketSend(en,fd,buf1,nBytesToSend,buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
				{
					fclose(fp);
					return 1; // failed to send, connection closed by client.
				}

				buf1[0] = 0;
			}
			fclose(fp);

			// ***** wait for confirmation "OK" that the entire file was recvd() and written to disk ****
			
			// packet extraction variables 
			int nPktIndex = -1;
			char *pPacketBegin = 0;
			int nPacketLen = 0;
			int nBytesInBuf = 0;
			int nBuf2Bytes = 0;

			buf2[0] = 0;	buf2[1] = 0;
			nPktIndex = -1;
			int rslt = DoFileTransferPacketRead(fd, buf1, buf2, &nBuf2Bytes, de, &nPktIndex, &pPacketBegin, &nPacketLen, &nBytesInBuf);
			if (rslt == 0)
			{
				GString strLog;
				strLog.Format("LocalGet expected OK[%d]",SOCK_ERR);
				InfoLog(strLog);

				break;
			}
			else if (rslt == -2) // failure during wait (connection closed by server)
			{
				//16=Wrong password-key attempt?
				GString strInfo;
				strInfo.LoadResource(g_pzCoreErrorSection, 16);
				InfoLog(strInfo);
				break;
			}
			else if (rslt == -1) // failure during wait (connection closed by server)
			{
				// Timeout waiting for OK after LocalGet
				break;
			}
			if (buf2[0] != 'O' || buf2[1] != 'K')
			{
				// Expected OK got something else;
				break;
			}
			//  *****  Got the OK  *****
		}
		else
		{
			if (nOpenRetry++ < 3)
			{
				sched_yield();
				goto RETRY_GET_OPEN;
			}
			sprintf(buf2,"Error:Failed to open file[%s] code[%d] on [%s(%s)]\r\n",pzLocalFileName,errno,(const char *)g_strThisHostName, (const char *)g_strThisIPAddress);
			DoFileTransferPacketSend(en,fd,buf2,strlen(buf2),buf1,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED);
		}
	}
	return 1;
}



int LocalRmd(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;

	if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
	{
		GString str;
		str.Format("Removing [%s]",buf2);
		InfoLog(str);
	}

	GString strResult;
	int nRslt = rmdir(buf2);
	if (nRslt == 0)
	{
		strResult = "OK";
	}
	else
	{
		strResult.Format("Error:Failed to remove directory[%s], OS Reason[%d]\r\n",buf2,errno);
	}

	if (DoFileTransferPacketSend(en,fd,(char *)(const char *)strResult,strResult.Length(),buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
	{
		InfoLog("Local RMD send() failed");
		return 0; // failed to send.
	}
	return 1;

}



int LocalGud(int fd,char *buf1,char *buf2,const char *pzUser, const char *pzPass)
{
	
	GString strError;
	GString strUUDataFile(GetProfile().GetPath("FileTransfer","UserProfilePath",false));
	if (strUUDataFile.Length() == 0)
	{
		strError.Format("failed. No [UserProfilePath=] under [FileTransfer] on host[%s]",(const char *)g_strThisHostName);
	}
	strUUDataFile += pzUser;
	GString strUUData;
	strUUData.FromFile(strUUDataFile,0);
	if (strUUData.Length() == 0)
	{
		strError.Format("Failed to open user settings [%s] on [%s]",(const char *)strUUDataFile, (const char *)g_strThisHostName);
	}
	

	//uudecode
	BUFFER b;
	BufferInit(&b);
	unsigned int nDecoded;
	uudecode((char *)(const char *)strUUData, &b, &nDecoded, false);

	//decrypt
	CipherData de;
	InitializeCipherKey(&de,pzPass,0,DIR_DECRYPT);
	int nBitsCrypted;
	try
	{
		nBitsCrypted = blockDecrypt(&de.ci,&de.ki, (unsigned char *)b.pBuf,nDecoded *8,(unsigned char *)buf1);
	}
	catch(...) // this can happen when using a 128 key to undo 256 data
	{
		return 0;
	}
	if (nBitsCrypted != nDecoded *8)
	{
		return 0; 
	}
	// get the first byte of the last 128 bit block
	int chPad = (unsigned char)buf1[(nBitsCrypted/8) - 16] - 65;
	// the byte pad count must be less than a block size if the decrypt was a success
	if (chPad > 16)
	{
		// 16=Wrong password-key attempt?
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 16);
		InfoLog(strInfo);
		return 0;
	}
	// and the new size removes the last block and padding bits added to the second to last block.
	int nTrueLength = (nBitsCrypted/8) - 16 - chPad;
	int nValidDataTestHeader = strlen("valid-decrypt-test");

	// verify a correct decrypt
	if ( memcmp(buf1,"valid-decrypt-test",nValidDataTestHeader) == 0  )
	{
		nTrueLength -= nValidDataTestHeader;
	}
	else
	{
		// 16=Wrong password-key attempt?
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 16);
		InfoLog(strInfo);
		return 0;
	}

	//return results
	GString strSend;
	strSend.Format("%d:",nTrueLength);
	buf1[nValidDataTestHeader+nTrueLength] = 0; // null terminate
	strSend += &buf1[nValidDataTestHeader];


	if (send(fd, (const char *)strSend, strSend.Length(), 0) == -1)
	{ 
		return 0;
	}
	return 1;
}


int LocalPud(int fd,char *buf1,char *buf2,const char *pzUser, const char *pzPass, const char *pzData)
{

	GString strData("valid-decrypt-test");
	strData += pzData;

	// *********** encrypt ***********

	// bytes required to pad the last block to a 128 bit boundry
	unsigned char nPad = (16 - strData.Length() % 16);
	nPad = (nPad == 16) ? 0 : nPad;
	// pad it
	for(int i=0; i<nPad; i++)
		strData += '.';
	strData += (char)(65 + nPad); // encode the pad count
	for(int ii=0; ii<15; ii++)
		strData += '.';

	CipherData en;
	InitializeCipherKey(&en,pzPass,0,DIR_ENCRYPT);
	int nBitsCrypted;
	try
	{
		nBitsCrypted = blockEncrypt(&en.ci,&en.ki, (unsigned char *)(const char *)strData,strData.Length()*8,(unsigned char *)buf1);
	}
	catch(...)
	{
		// crypt failed.
		return 0;
	}
	if (nBitsCrypted != strData.Length()*8)
	{
		// crypt failed
		return 0;
	}


	// uuencode it into a GString
	BUFFER b;
	BufferInit(&b);
	uuencode((unsigned char *)buf1, nBitsCrypted/8, &b);
	GString strEncoded((char *)b.pBuf, b.cLen);


	GString strError;
	GString strUUDataFile(GetProfile().GetPath("FileTransfer","UserProfilePath",false));
	if (strUUDataFile.Length() == 0)
	{
		strError.Format("failed. No [UserProfilePath=] under [FileTransfer] on host[%s]",(const char *)g_strThisHostName);
	}
	strUUDataFile += pzUser;
	try
	{
		strEncoded.ToFile(strUUDataFile);
	}catch(...)
	{
		return 0;
	}
	
	char OKBuf[32];
	strcpy(OKBuf,"OK");
	if (send(fd, OKBuf, 2, 0) == -1)
	{ 
		return 0;
	}

	return 1;
}



int LocalRen(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;


	GString strResult;
	char *pSep = strpbrk(buf2,"*");
	if (pSep)
	{
		*pSep = 0;

		if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
		{
			GString str;
			str.Format("Renaming[%s] to [%s]",buf2,pSep+1);
			InfoLog(str);
		}

		// no trailing slashes SOURCE
		if (  ( buf2[strlen(buf2)-1] == '/' || buf2[strlen(buf2)-1] == '\\')  )
		{
			buf2[strlen(buf2)-1] = 0; // shorten the input param by 1 byte
		}

		// no trailing slashes DESTINATION
		if (  ( buf2[strlen(pSep+1)-1] == '/' || buf2[strlen(pSep+1)-1] == '\\')  )
		{
			buf2[strlen(pSep+1)-1] = 0; // shorten the input param by 1 byte
		}



		int nRslt = rename(buf2,pSep+1);
		if (nRslt == 0)
		{
			strResult = "OK";
		}
		else
		{
			strResult.Format("Error:Failed to rename file[%s] to [%s], OS Reason[%d]\r\n",buf2,pSep+1,errno);
		}
	}
	else
	{
		strResult.Format("Error:Failed to rename file. Bad param[%s]\r\n",buf2);
	}
	if (DoFileTransferPacketSend(en,fd,(char *)(const char *)strResult,strResult.Length(),buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
	{
		return 0; // failed to send.
	}
	return 1;

}

void LocalDelTree(char *pzDirectory, GString &strResult)
{
	if ( pzDirectory[strlen(pzDirectory)-1] != g_chSlash )
	{
		char pzSlash[2];
		pzSlash[0] = g_chSlash;
		pzSlash[0] = 0;
		strcat(pzDirectory,pzSlash);
	}

	
	// recurse into and gather all the files and directories under this one.
	GStringList lstDirs;
	GStringList lstFiles;
	CDirectoryListing::RecurseFolder(pzDirectory,&lstDirs,&lstFiles);
	int nFailedFileCount = 0;
	int nFailedDirCount = 0;

	GString strError;


	// remove all those files
	GStringIterator it(&lstFiles);
	while (it())
	{
		const char *pF = it++;
		if ( unlink( pF ) != 0)
		{
			// optionally, try to grant write permissions before re-try.
			if ( GetProfile().GetBoolean("FileTransfer","OverrideOSPermissions",false) )
			{
#ifdef _WIN32
				_chmod( pF, _S_IWRITE );
#else
				chmod(	pF, 777 );     
#endif
				if ( unlink( pF ) == 0) // if it worked
				{
					continue;
				}
			}

			
			if (!nFailedFileCount)
				strError = "Error:Failed to delete file(s):\n" ;
			nFailedFileCount++;
			strError += pF;
			strError += "\n";
		}
	}


	// then remove the directories
	while (lstDirs.Size())
	{
		const char *pD = lstDirs.RemoveLast();
		if ( rmdir( pD ) != 0)
		{
			// optionally, try to grant write permissions before re-try.
			if ( GetProfile().GetBoolean("FileTransfer","OverrideOSPermissions",false) )
			{
#ifdef _WIN32
				_chmod( pD, _S_IWRITE );
#else
				chmod(	pD, 777);  
#endif
				if ( rmdir( pD ) == 0) // if it worked
				{
					continue;
				}
			}

			if (!nFailedDirCount)
			{
				strError += "Error:Failed to remove folder(s):\n";
			}
			nFailedDirCount++;
			strError += pD;
			strError += "\n";
		}
	}


	// if everything was deleted, this will now work.
	if (rmdir(pzDirectory) == 0)
	{
		strResult = "OK";
	}
	else if (nFailedFileCount || nFailedDirCount) 
	{
		strResult.Format("Error:%s\r\n", (const char *)strError);
	}
	else	// permissions problem, sharing problem, disk I/O problem .....
	{
		// Windows 22 = File open by another user.
		strResult.Format("Error:Failed to remove [%s], OS Reason[%d]\r\n",pzDirectory,errno);
	}
}

int SendCommandResults(char *workBuf, const char *pSend,int nToSend,CipherData *en,int fd)
{
	// chop up the results into MAX_RAW_CHUNK sized chunks and send them
	int nSent = 0;
	while (nToSend > 0)
	{
		int nChunkSize = (nToSend > MAX_RAW_CHUNK) ? MAX_RAW_CHUNK : nToSend;
		nToSend -= nChunkSize;

		if (DoFileTransferPacketSend(en,fd,(char *)(const char *)&pSend[nSent],nChunkSize,workBuf,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
		{
			break; // failed to send.
		}
		nSent += nChunkSize;
	}
	return (nToSend > 0) ? 0 : 1; // 0 is fail, 1 is success
}


int LocalDel(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;

	if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
	{
		GString str;
		str.Format("Deleting[%s]",buf2);
		InfoLog(str);
	}
	
	// no trailing slashes
	if (  ( buf2[strlen(buf2)-1] == '/' || buf2[strlen(buf2)-1] == '\\')  )
	{
		buf2[strlen(buf2)-1] = 0; // shorten the input param by 1 byte
	}


	GString strResult;
	int nRslt = unlink( buf2 );
	if (nRslt == 0)
	{
		strResult = "OK";
	}
	else
	{
		// if we tried to delete a directory with files in it we end up here.
		LocalDelTree(buf2,strResult);
	}
	return SendCommandResults(buf1,(const char *)strResult,strResult.Length(),en,fd);

}


int LocalMkd(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;
	GString strResult;

	if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
	{
		GString str;
		str.Format("Making folder[%s]",ReSlash(buf2));
		InfoLog(str);
	}


	int nAttempts = 0;
RETRY_MKDIR:
	nAttempts++;
	#ifdef _WIN32
		int nRslt = mkdir(ReSlash(buf2));
	#else
		int nRslt = mkdir(ReSlash(buf2),777);
	#endif
	if (nRslt == 0)
	{
		strResult = "OK";
	}
	else
	{
		if (nAttempts < 10)
		{
			sched_yield();
			goto RETRY_MKDIR; // Mircosoft needs this...  (errno == 2 but a retry fixes it)
		}
		strResult.Format("Error:Failed to make directory[%s] ErrorCode=%d\r\n",buf2,errno);
		InfoLog(strResult);
	}

	if (DoFileTransferPacketSend(en,fd,(char *)(const char *)strResult,strResult.Length(),buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
	{
		InfoLog("LocalMkd send() failed");
		return 0; // failed to send.
	}
	return 1;
}



int LocalAll(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;


	int bDirsAndFiles = 1;
	char *pzDirTerm = strpbrk(buf2,"*");
	if (pzDirTerm)
	{
		bDirsAndFiles = atoi(pzDirTerm+1);
		*pzDirTerm = 0;
	}


	// command starts with keyword "ALL" followed by the folder.
	// Example: "ALL/opt/usr[/]" or "ALLc:\mydir[\]"
	// user param (may/may not) end with a slash.  This adds one if needed.
	int nBuf2Size = strlen(buf2);
	if (!( buf2[nBuf2Size-1] == '/' || buf2[nBuf2Size-1] == '\\'))
	{
		buf2[nBuf2Size] = g_chSlash; // slash over the null
		buf2[nBuf2Size+1] = 0;		 // terminate [ nBuf2Size is now off by 1, but it's never used]
	}

	// Check to see that this file is within a restricted branch - if a restriction is specified.
	if ( !IsAllowedPath( buf2 ) )
	{
		GString strErr;
		strErr.Format("Error:Access denied to file[%s], Check the [FileTransfer] 'Root=' entry in the config file on[%s].\r\n",(const char *)buf2,(const char *)g_strThisHostName);
		InfoLog(strErr,0);
		DoFileTransferPacketSend(en,fd,(char *)(const char *)strErr,strErr.Length(),buf1,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED);
		return 0;
	}

	if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
	{
		//43=Listing nested directory structure[%s]
		GString strInfo;
		strInfo.LoadResource(g_pzCoreErrorSection, 43, buf2);
		InfoLog(strInfo);
	}


	GStringList strDirs;
	GStringList strFiles;
	if(bDirsAndFiles)
	{
		CDirectoryListing::RecurseFolder(buf2,&strDirs,&strFiles);
	}
	else // files only
	{
		CDirectoryListing::RecurseFolder(buf2,0,&strFiles);
	}
	
	// Build the data packet 
	GString strResults;
	
	if (strDirs.Size() == 0 && strFiles.Size() == 0) 
	{
		// The Java GUI does this.
		// someone called ALL with a file, not a directory
		// a call with a valid directory returns a minimum
		// of 1 (the empty direct passed in)
		
		// 0 dirs, 1 file, back at ya (the file passed in)
		strResults.Format("0*1*%s",buf2); 
	}
	else // 
	{
		if (bDirsAndFiles)
		{
			// include the queiried root dir also if dirs are requested
			strResults.Format("%d*%d*%s*%s*%s",strDirs.Size()+1,strFiles.Size(),buf2,strDirs.Serialize("*"),strFiles.Serialize("*"));
		}
		else
		{
			strResults.Format("%d*%s",strFiles.Size(),strFiles.Serialize("*"));
		}
	}

	// prepend the length in the transport packet
	GString strSendResults(strResults.Length() + 32);
	strSendResults.Format("OK %d:%s",strResults.Length(),(const char *)strResults);

	return SendCommandResults(buf1,strSendResults,strSendResults.Length(),en,fd);
}

int LocalDir(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;


	// command starts with keyword "dir" followed by 
	// Example: "dir/opt/usr[/]" or "dirc:\mydir[\]"
	// user param (may/may not) end with a slash.  This adds one if needed.
	int nbuf2Len = strlen(buf2);
	if (!( buf2[nbuf2Len-1] == '/' || buf2[nbuf2Len-1] == '\\'))
	{
		if (strpbrk(buf2,"/")) // unix
			strcat(buf2,"/");
		else				   // windows
			strcat(buf2,"\\");
	}


	// Check to see that this file is within a restricted branch - if a restriction is specified.
	if ( !IsAllowedPath( buf2 ) )
	{
		GString strErr;
		strErr.Format("Error:Access denied to file[%s], Check the [FileTransfer] 'Root=' entry in the config file on[%s].\r\n",(const char *)buf2,(const char *)g_strThisHostName);
		DoFileTransferPacketSend(en,fd,(char *)(const char *)strErr,strErr.Length(),buf1,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED);
		return 0;
	}


	if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
	{
		GString str;
		str.Format("Listing Directory[%s]",buf2);
		InfoLog(str);
	}


	GString strSendResults;
	try
	{
		// Sample listing 2 files + 1 directory = "file1.txt*[dir] Temp*file2.txt"
		GString strResults;
		CDirectoryListing dir(buf2, 3); // nMode = 1 files, 2 dirs, 3 both
		GStringIterator it(&dir);
		while (it())
		{
			strResults += it++;
			if (it())
				strResults += "*";
		}
		strSendResults.Format("OK %d:%s",strResults.Length(),(const char *)strResults);
	}
	catch( GenericException &rErr )
	{
		strSendResults.Format("Error:%s\r\n",rErr.GetDescription());
	}

	return SendCommandResults(buf1,strSendResults,strSendResults.Length(),en,fd);
}

int LocalPut(LocalData *pLD)
{
	int fd = pLD->fd;
	char *buf1 = pLD->buf1;
	char *buf2 = pLD->buf2;
	CipherData *en = pLD->en;
	CipherData *de = pLD->de;

	// send "OK" otherwise send "Error:description\r\n" indicating that we're ready to recv() file contents.
	char OKBuf[32];
	memcpy(OKBuf,"OK",2);
	if (DoFileTransferPacketSend(en,fd,OKBuf,2,buf1,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
	{
		// if we failed to ACK, the creation of strFileName, drop the connection,
		InfoLog("Local Put failed to send OK");
		return 0;
	}

	// command example  "put123:/opt/usr/myfile.ext*456/opt/usr/myfile2.ext"
	// command example2 "put123:/opt/usr/myfile.ext"
	// where 123 is the length of the first file and 456 is the length of the 2nd

	GStringList lstLocalFiles("*",&buf2[3]);
	GStringIterator itLocalFiles(&lstLocalFiles);
	int nFileIndex = 0;
	while (itLocalFiles())
	{
		nFileIndex++;
		const char *pzLocalFileName = itLocalFiles++;
		if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
		{
			GString str;
			str.Format("PUTting %d/%d[%s]\n",nFileIndex,lstLocalFiles.Size(),CDirectoryListing::LastLeaf( pzLocalFileName ));
			InfoLog(str);
		}

		unsigned long nTotalBytesExpected = atol( pzLocalFileName ); // colon terminates atol()
		char *pColon = strpbrk((char *)pzLocalFileName,":");
		if (pColon)
		{
			// packet extraction variables 
			int nPktIndex = -1;
			char *pPacketBegin = 0;
			int nPacketLen = 0;
			int nBytesInBuf = 0;
			int nBuf2Bytes = 0;
			int nOpenRetry = 0;
			int bRemoveFile = 0;
			GString strFileName(pColon+1);
			strFileName = ReSlash(strFileName);

	RETRY_PUT_OPEN:
			
			FILE *fp = fopen(strFileName,"wb"); // open local file
			if (fp)
			{
				unsigned long nTotalBytesWritten = 0;

				// while there is data to recv(), get it and write it to disk.
				while(nTotalBytesWritten < nTotalBytesExpected && !bRemoveFile /*&& nTotalBytesExpected > 0*/ )
				{
					int rslt = DoFileTransferPacketRead(fd, buf1, buf2, &nBuf2Bytes, de, &nPktIndex, &pPacketBegin, &nPacketLen, &nBytesInBuf);

					if (rslt == 0)
					{
						// no data or only a partial packet available - nothing to do.
						GString strLog;
						strLog.Format("LocalPut [%ld] written, [%ld]expected -- [%d]rslt  [%d]nBuf2Bytes   [%d]errno",nTotalBytesWritten,nTotalBytesExpected,rslt,nBuf2Bytes,SOCK_ERR);
						InfoLog(strLog);

						bRemoveFile = 1;
					}
					if (rslt == -1 || rslt == -2) 
					{
						// Sometimes Linux ends up here with SOCK_ERR == 9
						GString strLog;
						strLog.Format("LocalPut bails [%d:%d]",rslt,SOCK_ERR);
						InfoLog(strLog);

						// client aborted file transfer
						bRemoveFile = 1;
					}
					else if (nBuf2Bytes)
					{
						nTotalBytesWritten += fwrite(buf2,1,nBuf2Bytes,fp);
						if(nTotalBytesWritten > nTotalBytesExpected)
						{
							//9=Internal Buffering Error
							GString strInfo;
							strInfo.LoadResource(g_pzCoreErrorSection, 9);
							InfoLog(strInfo);
						}

						sched_yield();
					}
				}
				
				// done recv()ing [pzLocalFileName]
				fclose(fp);

				// if the file is not complete
				if (bRemoveFile)
				{
					// delete it
					unlink((const char *)strFileName);
					return 0;
				}

				memcpy(OKBuf,"OK",2);
				if (DoFileTransferPacketSend(en,fd,OKBuf,2,buf1,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
				{
					// if we failed to ACK, the creation of strFileName, drop the connection,
					return 0;
				}

			}
			else
			{
				// optionally, try to grant write permissions before re-try.
				if ( GetProfile().GetBoolean("FileTransfer","OverrideOSPermissions",false) )
				{
	#ifdef _WIN32
					_chmod( (const char *)strFileName, _S_IWRITE );
	#else
					chmod(	(const char *)strFileName, 777);  
	#endif
				}

				// and try to create the path, if the specified path did not exist
				if (nOpenRetry++ < 3)
				{
					CDirectoryListing::CreatePath(pColon+1, 1);
					sched_yield();
					goto RETRY_PUT_OPEN;
				}

				// failed to open this file
				GString strError;
				strError.Format("Error:Failed to open file [%s] code[%d] on[%s]\r\n",(const char *)strFileName, errno, (const char *)g_strThisHostName);
				DoFileTransferPacketSend(en,fd,(char *)(const char *)strError,strError.Length(),buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED);
				return 0; // end this connection
			}
		}
		else 
		{
			// the command is not formatted correctly.
			GString strError;
			strError.Format("Error:Expecting nnnn:path/filename got[%s]\r\n",pzLocalFileName);
			DoFileTransferPacketSend(en,fd,(char *)(const char *)strError,strError.Length(),buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED);
			return 0; // end this connection
		}
	}
	return 1; // success
}


int SendCompletionResults(int fd, const char *strResults, int nLength)
{
	int CompletionNotifyRetries = 0;
COMPLETEION_SEND_RETRY:
	writableTimeout(fd, 60, 0); 
	int nRslt = send(fd, strResults, nLength, 0);
	if ( nRslt == -1 )
	{
		// 10035 = Resource temporarily unavailable
		// 10054 = Connection reset by peer 
		if (SOCK_ERR == 10035 || SOCK_ERR == 10054)
		{
			while(CompletionNotifyRetries++ < 3)
				goto COMPLETEION_SEND_RETRY; 
		}
	}

	// if this send() fails, ignore.  The client asked us to do it, we did it, 
	// now he's not here for us to tell him that we're done.
	return nRslt;
}



int DoFileTransfer(int fd, char *buf1, char *buf2, char **ppInitialProxySend, int *pnInitialSendLen, char *pzProxyToIP)
{
	
	int fds; // the socket handle this command will be sent to
	int bCmdReady = 1;
	int nBuf1Len = 0;
	int nBuf2Len = 0;
	
	// used for the 'put' command
	FILE *fp = 0;
	unsigned long lFileBytes = 0;

	// used by all commands
	GString strCmd;
	GString strConnectTo;
	GString strProxyTo;
	GString strDestPass;
	GString strCmdParam1;
	GString strCmdParam2;
	GString strCmdParam3;
	GString strCmdParam4;

	if ( InitialCommandRead(fd, buf1, &nBuf1Len, strCmd, strConnectTo, strDestPass, strCmdParam1, strCmdParam2, strCmdParam3, strCmdParam4, strProxyTo) == 0)
	{
		InfoLog("InitialCommandRead failed");
		return 0;
	}

	CipherData en;
	if (strCmd.CompareNoCase("000") != 0)
		InitializeCipherKey(&en,strDestPass,0,DIR_ENCRYPT);


	// commands that take 1(remote) param
	if( strCmd.CompareNoCase("rmd") == 0  ||  strCmd.CompareNoCase("del") == 0 || 
		strCmd.CompareNoCase("mkd") == 0  ||  strCmd.CompareNoCase("get") == 0 || 
		strCmd.CompareNoCase("dir") == 0  ||  strCmd.CompareNoCase("all") == 0)
	{
		// prepare the command to be crypted and transferred to the machine that will exec it.
		sprintf(buf1,(const char *)strCmd);
		strcpy(&buf1[3],(const char *)strCmdParam1);
	}
	else if (strCmd.CompareNoCase("gud") == 0) // Get User Data
	{
		bCmdReady = 0;
		LocalGud(fd,buf1,buf2,strCmdParam1,strCmdParam2);
	}
	else if (strCmd.CompareNoCase("pud") == 0) // Put User Data
	{
		bCmdReady = 0;
		LocalPud(fd,buf1,buf2,strCmdParam2,strCmdParam3,strCmdParam4);
	}
	else if (strCmd.CompareNoCase("ren") == 0) // rename file or directory
	{
		sprintf(buf1,"%s%s*%s",(const char *)strCmd,(const char *)strCmdParam1,(const char *)strCmdParam2);
	}
	else if (strCmd.CompareNoCase("put") == 0) // put file
	{
		GStringList lstLocalFiles("*",strCmdParam2);
		GStringList lstRemoteFiles("*",strCmdParam1);
		GStringIterator itLocalFiles(&lstLocalFiles);
		GStringIterator itRemoteFiles(&lstRemoteFiles);
		strcpy(buf1,"put");
		int nbuf1Index = 3;
		while (itLocalFiles() && itRemoteFiles())
		{
			const char *pzLocalFileName = ReSlash(itLocalFiles++);
			const char *pzRemoteFileName = itRemoteFiles++;
			
			fp = fopen(pzLocalFileName,"rb");
			if (fp)
			{
				// get the size of the file
				fseek(fp,0,SEEK_END);
				lFileBytes = ftell(fp);     // 4.2 gig file size limit in Version 1
				fclose(fp);					// when raised to a 64 bit value the max file size will be 16 terabytes 
				sprintf(&buf1[nbuf1Index],"%ld:%s",lFileBytes,pzRemoteFileName);
				nbuf1Index += strlen(&buf1[nbuf1Index]);
				if (itLocalFiles()&& itRemoteFiles())
				{
					buf1[nbuf1Index] = '*';
					nbuf1Index++;
					
				}
			}
			else
			{
				sprintf(buf1,"Error:Failed to open local file %s\r\n.  Code[%d]",pzLocalFileName,errno);
				fclose(fp);
				break;
			}
		}
	}
	// an inbound request, encrypted by another	tunnel entry point
	else if (strCmd.CompareNoCase("000") == 0)
	{
		// if the nProxyListLen > 0, this message is destined for another machine
		short theNumber;							// storage on platform architecture byte boundry
		memcpy(&theNumber,&buf1[7],2);				// storage in network byte order
		short nProxyListLen = ntohs( theNumber );	// convert to host byte order

		if (nProxyListLen)
		{
			// this command in intended for another recipient, it will bounce across this 
			// connection securely, since we don't even have the key to decrypt this if we wanted to.

			// MSG Format=000����~~A|B###################### 
			unsigned long theNumber = 0;					// storage on platform architecture byte boundry
			memcpy(&theNumber,&buf1[3],4);					// storage in network byte order
			unsigned long nOldMsgSize = ntohl( theNumber ); // convert to host byte order

			// rebuild the 000 message with the updated proxy info('this host' popped off the list)
			GString strProxies(&buf1[9],nProxyListLen);
			GStringList lstDeliveryIPs("|",(const char *)strProxies);
			const char *pzPopped = lstDeliveryIPs.RemoveFirst();
			if (strlen(pzPopped) < 1024)
				strcpy(pzProxyToIP,pzPopped);

			short nNewIPListLength = 0;	// this is the list of forward proxies yet to execute
			if ( lstDeliveryIPs.Size() > 0 )
			{
				// we're proxying to another proxy
				strcpy(&buf2[9],lstDeliveryIPs.Serialize("|"));
				nNewIPListLength = strlen(&buf2[9]);
			}

			unsigned short sh = htons(nNewIPListLength); 
			memcpy(&buf2[7],&sh,sizeof(short));
			
			int nMsgSizeDiff = nProxyListLen - nNewIPListLength;
			unsigned long nNewMsgSize = nOldMsgSize - nMsgSizeDiff;
			unsigned long l = htonl(nNewMsgSize);
			memcpy(&buf2[3],&l,sizeof(long));
			memcpy(buf2,"000",3);

			// tack the updated header(000����~~A|B) before the encrypted data (############)
			int nNewHeaderLen = nNewIPListLength + 9;
			memcpy(&buf1[9+nProxyListLen-nNewHeaderLen],buf2,nNewHeaderLen);
			*ppInitialProxySend = &buf1[9+nProxyListLen-nNewHeaderLen];
			*pnInitialSendLen = nNewMsgSize;

			// return and open proxy - the data prepared in buf1 will be the first
			// data sent once the connection to pzProxyToIP has been established.
			return 0;
		}

		bCmdReady = 0;

		CipherData deLocal;
		InitializeCipherKey(&deLocal,GetProfile().GetString("FileTransfer","Password",false),0,DIR_DECRYPT);
		
		CipherData enLocal;
		InitializeCipherKey(&enLocal,GetProfile().GetString("FileTransfer","Password",false),0,DIR_ENCRYPT);

		// Decrypting command
		
		// MSG Format=000����~~www.Company.com|internal-pc###################### 
		// Where 000 is the command, ���� is a 4 byte packet length, ~~ is a two byte
		// length of a | seperated list of optional proxys then ######################
		// is the encrypted command that can only be decrypted by the last address
		// in the | seperated list that is ~~ bytes long.
		if ( PrepForClient(ATTRIB_CRYPTED_2FISH256/*|ATTRIB_COMPRESSED*/,1,
						  &deLocal,&buf1[9+nProxyListLen],nBuf1Len-(9+nProxyListLen),buf2,&nBuf2Len))
		{
			LocalData LD;
			LD.fd = fd;
			LD.buf1 = buf1;
			LD.buf2 = &buf2[3];
			LD.en = &enLocal;
			LD.de = &deLocal;

			
			GString strUnCryptedCmd(buf2,3);
			if (strUnCryptedCmd.CompareNoCase("put") == 0)
			{
				LD.buf2 = buf2;
				LocalPut(&LD);
			}
			else if (strUnCryptedCmd.CompareNoCase("get") == 0)
			{
				LD.buf2 = buf2;
				LocalGet(&LD);
			}
			else if (strUnCryptedCmd.CompareNoCase("dir") == 0)
			{
				LocalDir(&LD);
			}
			else if (strUnCryptedCmd.CompareNoCase("mkd") == 0)
			{
				LocalMkd(&LD);
			}
			else if (strUnCryptedCmd.CompareNoCase("rmd") == 0)
			{
				LocalRmd(&LD);
			}
			else if (strUnCryptedCmd.CompareNoCase("del") == 0)
			{
				LocalDel(&LD);
			}
			else if (strUnCryptedCmd.CompareNoCase("ren") == 0)
			{
				LocalRen(&LD);
			}
			else if (strUnCryptedCmd.CompareNoCase("all") == 0)
			{
				LocalAll(&LD);
			}
		}
		else
		{
			// if we get here something failed in the unzip/decrypt, most likely
			// the data was crypted with an incorrect password (they can't decrypt this error).
			GString strSendResults;
			strSendResults.Format("Error:Bad pass-key for[%s]\r\n",(const char *)g_strThisHostName);
			return SendCommandResults(buf1,strSendResults,strSendResults.Length(),&en,fd);
		}
	}
	else
	{
		GString strLog;
		strLog.Format("Unknown command[%s]",(const char *)strCmd);
		InfoLog(strLog);
		return 0;
	}


	// The user-command has been prepared in buf1.  Now encrypt, connect 
	// and send the request to the actual machine that will fulfill it or 
	// send it to the machine that will proxy it.  If InitialCommandRead()
	// read in a crypted "000" command then the command was either handled
	// in a Local*() handler, or proxied, but bCmdReady will be false now.  
	// bCmdReady is only true when a raw(uncrypted) command was recvd()
	// generally from a localhost connection.
	if (bCmdReady)
	{
		memcpy(buf2,"000",3); // encrypt into buf2
		unsigned short sh = htons(strProxyTo.Length());
		memcpy(&buf2[7],&sh,sizeof(short));
		strcpy(&buf2[9],(const char *)strProxyTo);

		if ( PrepForServer(ATTRIB_CRYPTED_2FISH256/*|ATTRIB_COMPRESSED*/,1,
						  &en,buf1,strlen(buf1),&buf2[9+strProxyTo.Length()],&nBuf2Len))
		{
			// MSG Format=000����~~www.Company.com|internal-pc######################  
			// Where 000 is the command, ���� is a 4 byte packet length, ~~ is a two byte
			// length of a | seperated list of optional proxys then ######################
			// is the encrypted command than can only be decrypted by the last address
			// in the | seperated list that is ~~ bytes long.
			nBuf2Len += 9+strProxyTo.Length(); // size of entire msg
			unsigned long l = htonl(nBuf2Len); // packet length in network byte order
			memcpy(&buf2[3],&l,sizeof(long));

		}
		else
		{
			// Prep for server failed
			return 0;
		}

		IOBlocking(fd, 1);	// Set non-blocking IO 
		int nConnectAttempts = 0;
RE_ATTEMPT_CONNECT:
		fds = DoConnect(strConnectTo, 10777);
		if (fds == -1)
		{
			while(nConnectAttempts++ < 3)
			{
				sched_yield();
				goto RE_ATTEMPT_CONNECT;
			}
			GString strError;
			strError.Format("Error:Failed to connect to [%s]\r\n",(const char *)strConnectTo);
			InfoLog(strError,0);

			nonblocksend(fd, strError, strError.Length());
			goto CLOSE_AND_EXIT_FILE_XFER;
		}
		
		// send the crypted command from buf2
		if (nonblocksend(fds, buf2, nBuf2Len) != nBuf2Len)
		{
			GString strError;
			strError.Format("Error:Failed to send to [%s] Code[%d]\r\n",(const char *)strConnectTo, SOCK_ERR);
			SendCompletionResults(fd, strError, strError.Length());
			goto CLOSE_AND_EXIT_FILE_XFER;
		}



		// packet extraction variables
		int nPktIndex = -1;
		char *pPacketBegin;
		int nPacketLen;
		int nBytesInBuf;
		int nBuf2Bytes;
		buf1[0] = 0;
		buf2[0] = 0;
		
		CipherData de;
		InitializeCipherKey(&de,strDestPass,0,DIR_DECRYPT);


		// read the response, (or first response packet depending on the command we just sent)
		int rslt = DoFileTransferPacketRead(fds, buf1, buf2, &nBuf2Bytes, &de, &nPktIndex, &pPacketBegin, &nPacketLen, &nBytesInBuf);
		if (rslt == 0)
		{
			sprintf(buf2,"Error:Timeout or communication failure [%d:%d]\r\n",SOCK_ERR,errno);
			SendCompletionResults(fd, buf2, strlen(buf2));
			goto CLOSE_AND_EXIT_FILE_XFER;
		}
		else if (rslt == -2) // bad pass-key
		{
			GString strError("Error:Bad pass-key or communication failure\r\n");
			SendCompletionResults(fd, strError, strError.Length());
			goto CLOSE_AND_EXIT_FILE_XFER;
		}
		else if (rslt < 0) // -1 connection closed by server
		{
			sprintf(buf2,"Error:Bad pass-key or communication failure [%d]\r\n",SOCK_ERR);
			SendCompletionResults(fd, buf2, strlen(buf2));
			goto CLOSE_AND_EXIT_FILE_XFER;
		}


		// process this packet that has been decrypted and uncompressed to buf2.
		
		// depending on the command we issued(strCmd) - now deal with the response
		// mkd, del, rmd, and ren all work the same.  Their results are proxied back
		// to the requestor.
		if (strCmd.CompareNoCase("mkd") == 0 || strCmd.CompareNoCase("del") == 0 || 
			strCmd.CompareNoCase("rmd") == 0 || strCmd.CompareNoCase("ren") == 0)
		{
			// buf2 either == "OK" or "Error:description" --- fwd it to the requestor
			SendCompletionResults(fd, buf2, strlen(buf2));
		}
		else if ( (strCmd.CompareNoCase("dir") == 0) || (strCmd.CompareNoCase("all") == 0)) 
		{
			GString strCommandResults;
			if (buf2[0] == 'O' && buf2[1] == 'K')
			{
				long nTotalBytesExpected = atol(&buf2[3]); // length starts after space "OK nnnnnnn:DATADATADATA"
				char *pColon = strpbrk(buf2,":");
				if (pColon)
				{
					strCommandResults = &buf2[3];
				}

				// read in the whole command results
				while ( strCommandResults.Length() <  nTotalBytesExpected )
				{
					int rslt = DoFileTransferPacketRead(fds, buf1, buf2, &nBuf2Bytes, &de, &nPktIndex, &pPacketBegin, &nPacketLen, &nBytesInBuf);
					if (rslt == 0)
					{
						// no data or only a partial packet available - nothing to do.
						InfoLog("Timeout expired in DIR sub-packet read.");
						goto CLOSE_AND_EXIT_FILE_XFER;
					}
					else if (rslt < 0) // failure during wait (connection closed by server)
					{
						InfoLog("DIR DoFileTransferPacketRead failed");
						goto CLOSE_AND_EXIT_FILE_XFER;
					}
					else
					{
						strCommandResults += buf2;
					}
				}
				
				// then send them back to the requestor in MAX_RAW_CHUNK sizes
				int nTotalToSend = strCommandResults.Length();
				const char *pDataToSend = strCommandResults;
				int nSent = 0;
				while (nSent < nTotalToSend)
				{
					int nBlockSize = (nSent + MAX_RAW_CHUNK > nTotalToSend) ? nTotalToSend - nSent : MAX_RAW_CHUNK;
					int nSendRetries = 0;
SHOULD_BLOCK_SEND:
					int nRslt = send(fd, &pDataToSend[nSent], nBlockSize, 0);
					if (nRslt == -1)
					{ 
						// 10035 = Resource temporarily unavailable
						// 10054 = Connection reset by peer 
						if (SOCK_ERR == 10035 || SOCK_ERR == 10054)
						{
							while(nSendRetries++ < 3)
								goto SHOULD_BLOCK_SEND; 
						}

						GString strLog;
						strLog.Format("dir send() failed [%d]",SOCK_ERR);
						InfoLog(strLog);

						goto CLOSE_AND_EXIT_FILE_XFER;
					}
					else
					{
						nSent += nRslt;
					}
				}
			}
			else 
			{
				// proxy back the error
				SendCompletionResults(fd, buf2, strlen(buf2));
			}
		}
		else if (strCmd.CompareNoCase("put") == 0)
		{
			GString strPutResults;
			//  if we got "OK", we know the remote host is ready to accept data, now send the contents
			if (buf2[0] == 'O' && buf2[1] == 'K')
			{
				CipherData en;
				InitializeCipherKey(&en,strDestPass,0,DIR_ENCRYPT);


				GStringList lstLocalFiles("*",strCmdParam2);
				GStringIterator itLocalFiles(&lstLocalFiles);
				strcpy(buf1,"put");
				int nbuf1Index = 3;
				int nFileIndex = 0;
				while (itLocalFiles())
				{
					nFileIndex++;
					const char *pzLocalFileName = itLocalFiles++;
		
					if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
					{
						GString str;
						str.Format("PUTting %d/%d[%s]\n",nFileIndex,lstLocalFiles.Size(),CDirectoryListing::LastLeaf( pzLocalFileName ));
						InfoLog(str);
					}
			


					fp = fopen(pzLocalFileName,"rb");
					fseek(fp,0,SEEK_END);
					lFileBytes = ftell(fp);     // 4.2 gig file size limit in Version 1 (16 terabytes version 2)
					fseek(fp,0,SEEK_SET);		



					long nTotalBytesIn = 0;
					while ( nTotalBytesIn < lFileBytes )
					{
						int nBytesToRead = (lFileBytes - nTotalBytesIn > MAX_RAW_CHUNK) ? MAX_RAW_CHUNK : lFileBytes - nTotalBytesIn;
						int nBytesInThisRead = fread(buf1,sizeof(char),nBytesToRead,fp);
						nTotalBytesIn  += nBytesInThisRead;

						if (DoFileTransferPacketSend(&en,fds,buf1,nBytesInThisRead,buf2,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
						{
							GString strTemp;
							strTemp.Format("Error:Failed to send data for file[%s] code[%d]\r\n",pzLocalFileName, SOCK_ERR);
							if ( strPutResults.Length() )
								strPutResults += "\n";
							strPutResults += strTemp;
							fclose(fp);
							break; // failed to send data, abort this and any remaining bulk puts
						}
						
						if (nTotalBytesIn)
						{
							int nPercentComplete = (nTotalBytesIn == lFileBytes) ? 100 : nTotalBytesIn / (lFileBytes / 100);
							if (nPercentComplete == 100)
							{
								// if we're 99.5 - 99.9% complete we round down to 99%.
								// 100% is special in that it means true completeness.
								if (nTotalBytesIn < lFileBytes)
									nPercentComplete = 99;
							}
						}	
						buf1[0] = 0;
					}
					fclose(fp);
					

					// wait for confirmation "OK" that the file was recvd() and written to disk
					
					// packet extraction variables
					buf2[0] = 0;	buf2[1] = 0;
					nPktIndex = -1;
					int rslt = DoFileTransferPacketRead(fds, buf1, buf2, &nBuf2Bytes, &de, &nPktIndex, &pPacketBegin, &nPacketLen, &nBytesInBuf);
					if (rslt < 1) // 0, -1, or -2
					{
						GString strTemp;
						if (strPutResults.Length()) 
							strPutResults += "\n";
						strTemp.Format("Error:Sent file[%s] but recvd() no confirmation code[%d:%d]\r\n",pzLocalFileName,rslt,SOCK_ERR);
						strPutResults += strTemp;
						break; // abort any pending puts also
					}
					
					// anything other than "OK" is an Error:description
					if ( buf2[0] != 'O' || buf2[1] != 'K' )
					{
						// buf2 is a null terminated string "Error:description"
						buf2[rslt] = 0;

						// append it to the 'errors that occured during this bulk operation'
						if (strPutResults.Length()) 
							strPutResults += "\n";
						strPutResults += buf2;
						
						// the 'put' failed, break out and send the error back.
						break;
					}
				}

				// we're done sending the file(s), now send the "Error:" or "OK" back to the requestor.

				// if there's 'still files to send', then we aborted them.
				while(itLocalFiles())
				{
					if (strPutResults.Length())
						strPutResults += "\n";
					strPutResults += "Error: aborted ";
					strPutResults += (const char *)itLocalFiles++;
				}

				// if there was no error, then everything worked.
				if (strPutResults.Length() == 0)
					strPutResults = "OK";
			}
			else 
			{
				// never started sending any data because we never got the "OK" to send a file.
				if (strlen(buf2))
					strPutResults += buf2;
				else
					strPutResults.Format("Error: PUT failed to recv() the OK to send[%d]\r\n",SOCK_ERR);
			}

			SendCompletionResults(fd, strPutResults, strPutResults.Length());
		}
		else if (strCmd.CompareNoCase("get") == 0)
		{
			if (buf2[0] == 'O' && buf2[1] == 'K')
			{
				long nTotalBytesExpected = atol(&buf2[3]); // length starts after space "OK nnnnnnn:"
				long nTotalBytesWritten = 0;
				char *pFileData = strpbrk(&buf2[3],":");
				if (!pFileData)
				{
					GString strErr;
					strErr.Format("Error:GET got unexpected[%s] looking for [OK nnnnnnn:datadata]\r\n",buf2);
					InfoLog(strErr,0);
					goto CLOSE_AND_EXIT_FILE_XFER;
				}

				*pFileData = 0;
				int nChunkLength = nBuf2Bytes - strlen(buf2) - 1;
				pFileData++; // point at the fist byte of file data

				// packet extraction variables
				int nPktIndex = -1;
				char *pPacketBegin;
				int nPacketLen;
				int nBytesInBuf;
				int nBuf2Bytes;
				CipherData de;
				InitializeCipherKey(&de,strDestPass,0,DIR_DECRYPT);


				// "GET" takes two params, the first is a * seperated list of
				// remote file names, that param was sent through to the destination
				// and will be serviced by LocalGet() the second param is a * seperated 
				// list of local file names.  Here we iterate through the second param, 
				// creating each file and recv()ing it's content - in the order that 
				// param1 requested the * seperated list of remote file names.
				GStringList lstLocalFiles("*",strCmdParam2);
				GStringIterator itLocalFiles(&lstLocalFiles);
				int nFileIndex = 0;
				while ( itLocalFiles() )
				{
					nFileIndex++; // index of this file in lstLocalFiles
					const char *pzLocalFileName = ReSlash(itLocalFiles++);

					if ( GetProfile().GetBoolean("FileTransfer","EnableLogging",false) )
					{
						GString str;
						str.Format("Getting %d/%d[%s]\n",nFileIndex,lstLocalFiles.Size(),CDirectoryListing::LastLeaf( pzLocalFileName ));
						InfoLog(str);
					}
					
					// given "/usr/bob/file.ext"  create [/usr] and [/usr/bob] if necessary
					CDirectoryListing::CreatePath(pzLocalFileName,1);

					FILE *fp = fopen(pzLocalFileName,"wb"); // open local file
					if (fp)
					{
						GString strGetError; // empty, or reason pzLocalFileName failed

						if (nTotalBytesExpected == 0)
						{
							goto FILE_WRITE_COMPLETE; // skip the recv() and write() loop for 0 byte files
						}
						while(nTotalBytesWritten < nTotalBytesExpected)
						{
							// write the current packet to disk
							nTotalBytesWritten += fwrite(pFileData,1,nChunkLength,fp);
							
							// if there is more file to get
							if (nTotalBytesWritten < nTotalBytesExpected)
							{
								// get the next piece of the file
								int rslt = DoFileTransferPacketRead(fds, buf1, buf2, &nBuf2Bytes, &de, &nPktIndex, &pPacketBegin, &nPacketLen, &nBytesInBuf);
								if (rslt < 1)
								{
									strGetError.Format("Error:Failed to Get[%s] - Timeout code[%d:%d]\r\n",(const char *)pzLocalFileName,rslt,SOCK_ERR);
									break;
								}
								else
								{
									// Set this data as the current packet
									pFileData = buf2;
									nChunkLength = nBuf2Bytes;
								}

								// Calculate the progress complete
								if (nTotalBytesExpected)
								{
									int nThisFilePercentComplete;
									if (nTotalBytesWritten > 0 && nTotalBytesExpected > 0)
										nThisFilePercentComplete = (nTotalBytesWritten == nTotalBytesExpected) ? 100 : nTotalBytesWritten / (nTotalBytesExpected / 100);
									else
										nThisFilePercentComplete = 0;

									//TODO: make this value available in a "status" command
								}
							}
							else //we have written the entire file
							{
FILE_WRITE_COMPLETE:
								// this file is complete
								fclose(fp);

								// send an OK back to indicate we recvd(), decrypted, decompressed, and committed to disk the entire file
								char OKBuf[32];
								memcpy(OKBuf,"OK",2);
								if (DoFileTransferPacketSend(&en,fds,OKBuf,2,buf1,60,ATTRIB_CRYPTED_2FISH128|ATTRIB_COMPRESSED) == 0)
								{
									// if we failed to ACK, the creation of strFileName, drop the connection,
									strGetError.Format("Error:Failed to send OK for file[%s] code[%d]\r\n",(const char *)pzLocalFileName,SOCK_ERR);
									break;
								}


								// if there are more files transfer, read the first packet of the next file
								if ( itLocalFiles() )
								{
									// read the response, (or first response packet depending on the command we just sent)
									int rslt = DoFileTransferPacketRead(fds, buf1, buf2, &nBuf2Bytes, &de, &nPktIndex, &pPacketBegin, &nPacketLen, &nBytesInBuf);

									if (rslt < 1)
									{
										strGetError.Format("Error:GETting subsequent files code[%d:%d] for[%s]\r\n",rslt,SOCK_ERR,pzLocalFileName);
										InfoLog(strGetError,0);
										break; // out of while(nTotalBytesWritten < nTotalBytesExpected)
									}
									if (buf2[0] == 'O' && buf2[1] == 'K')
									{
										// read the first chunk of the next file										
										nTotalBytesExpected = atol(&buf2[3]); // length starts after space "OK nnnnnnn:"
										nTotalBytesWritten = 0;
										pFileData = strpbrk(&buf2[3],":");
										if (!pFileData)
										{
											strGetError.Format("Error:GET got unexpected[%s] looking for [OK nnnnnnn:datadata]\r\n",buf2);
											InfoLog(strGetError,0);
											break;// out of while(nTotalBytesWritten < nTotalBytesExpected)
										}
										*pFileData = 0;
										nChunkLength = nBuf2Bytes - strlen(buf2) - 1;
										pFileData++; // point at the first byte of file data
									}
									else
									{
										strGetError.Format("Error[%s] recieving batched file[%s]\r\n",buf2,pzLocalFileName);
										InfoLog(strGetError,0);
										break;
									}

									break; // out of while(nTotalBytesWritten < nTotalBytesExpected)
								}
							}

						}//end-while(nTotalBytesWritten < nTotalBytesExpected)

						
						// if there was an error send it back to the localhost connection
						if ( strGetError.Length()) 
						{
							SendCompletionResults(fd, strGetError, strGetError.Length());
							break; 
						}
						// else if we're all done (not a multi-file-get operation)
						else if (!itLocalFiles()) 
						{
							strGetError = "OK";
							SendCompletionResults(fd, strGetError, strGetError.Length());
						}
					}
					else // could not open the file
					{
						GString strError;
						strError.Format("Error:Failed to open file[%s] on [%s] OS code[%d]\r\n",(const char *)pzLocalFileName,(const char *)g_strThisHostName, errno);
						SendCompletionResults(fd, strError, strError.Length());
						break;
					}
				}
			}
			else // proxy back the error that prevented us from ever starting the transfer
			{
				SendCompletionResults(fd, buf2, strlen(buf2));
			}
		}
	}
CLOSE_AND_EXIT_FILE_XFER:
	// close the handle after the request has been serviced.
	#ifdef _WIN32    
		closesocket(fds);
		closesocket(fd);
	#elif _HPUX
		shutdown(fds);
		shutdown(fd);
	#else
		close(fds);
		close(fd);
	#endif
	return 0;
}


// This thread services all connection types.
void *clientThread(void *arg)
{
	GString strTempDebug;
	
	ThreadData *td = (ThreadData *)arg;
	const char *pDebugFile = 0;

	unsigned long lBytesRemaining;
	char *pCommandParam;
	const char *pSent = 0;
	int nYielded = 0;
	long starttime = 0;
	const char *pzHome = 0;
	int bHelperThreadIsRunning = 0;
	int nCloseCode = 0;
	int nInitialSendLen = 0;
	char *pInitialProxySend = 0;
	ThreadConnectionData TCD;
	int nSize = 0;
	
	
	// Transactional Memory Management -  vs.  On-Demand Heap Management
	// Memory allocated here (at server startup) is re-used for each 
	// transaction.  This takes a great burdon off of the C++ runtime 
	// memory manager (generally poorly implemented).  GStrings are coded
	// to the same ideal - (see _strIsOnHeap in GString.h).  Avoiding the 
	// C++ memory manager makes a very measurable performance difference.
	// When this server was linked to an excellent memory manager
	// (used by Netscape and many highend database/server products) 
	// from [www.microquill.com] there was **no** improvement in performance
	// for most operations.
	char *sockBuffer = new char[MAX_DATA_CHUNK];
	char *cryptDest = new char[MAX_DATA_CHUNK];
	// stream read in (used for HTTP & TXML requests only)
	GString  *inboundXML = new GString ( MAX_DATA_CHUNK * 2 );
	SERVER_MEMORY_EXTENSION;


	// seed the system random number generator on each thread with a unique number
	g_nRrandSeed += (777 * (g_nRrandSeed % 100)) ;
	srand( g_nRrandSeed );


	GString strConnectedIPAddress;

	int nBufBytes;
	int nPacketIndex;
	int nPacketLen;
	char *pPacket;
	unsigned long lLastActivityTime;

	ExceptionHandlerScope durationOfThread;

MAIN_THREAD_BODY:

	XML_TRY
	{
		// pre-caches Transaction objects in the server build
		OBJECT_PRECACHE;

		pCommandParam = 0;
		lBytesRemaining = 0;
		pSent = 0;
		nYielded = 0;
		pzHome = 0;
		nBufBytes = 0;
		nPacketIndex = -1;
		nPacketLen = 0;
		pPacket = 0;
		lLastActivityTime = 0;
		bHelperThreadIsRunning = 0;
		
		if (g_TraceThread)
		{
			GString strLog;
			strLog.Format("Thread %d Waiting",td->nThreadID);
			InfoLog(strLog);
		}

		td->nThreadIsBusy = 0;
		// ----------------------------------------------------------------------
		// wait here until we accept a new connection and hand off to this thread
		// ----------------------------------------------------------------------
		if ( td->m_bUseThreads )
			pthread_cond_wait(&td->cond, &td->lock); 

		if ( g_ThreadPing )
		{
			GString strLog;
			strLog.Format("W%d    ",td->nThreadID);
			InfoLog(strLog,0);
			goto MAIN_THREAD_BODY;
		}
		// ----------------------------------------------------------------------
		// We have just begun to service a new request.  We are "On the clock" now.
		// ----------------------------------------------------------------------
		if (g_TraceThread)
		{
			GString strLog;
			strLog.Format("Thread %d Working",td->nThreadID);
			InfoLog(strLog);

			starttime = getTimeMS(); // Start the clock
		}

		if (g_ServerIsShuttingDown)
		{
			nCloseCode = 1;
			goto SHUTTING_DOWN;
		}

		
		// get the address of the connected client into strConnectedIPAddress;
		// this address cannot be trusted for any type of access restrictions.
		struct sockaddr_in other_addr;
		nSize = sizeof(sockaddr_in);
		getpeername(td->sockfd,(struct sockaddr *) &other_addr, (socklen_t *)&nSize);

		strConnectedIPAddress = inet_ntoa(other_addr.sin_addr);



		pInitialProxySend = 0;
		nInitialSendLen = 0;
		if (td->pTSD->nProtocol == 3) // file transfer
		{
			// Set non-blocking IO
			IOBlocking(td->sockfd, 1);
			DoFileTransfer(td->sockfd, sockBuffer, cryptDest, &pInitialProxySend, &nInitialSendLen, TCD.pzIP);
			if (nInitialSendLen)
			{
				td->pTSD->nProxyTimeout = 60;
				// now fall through and open a proxy to bounce this encrypyted data to [TCD.pzIP]
			}
			else
			{
				// otherwise close this connection, it's handled already.  Success or failure was 
				// sent in DoFileTransfer(), this goto is not an indication of error, only completion.
				goto SOCKET_ERR_ABORT;
			}
		}

		//////////////////////////////////////////////////////////////////////////		
		//		
		// Following this comment are the two main logic branches.
		// Branch 1 - is just a proxy - bouncing the request to another location
		// Branch 2 - reads HTTP headers, and generates an HTTP response 
		//
		//////////////////////////////////////////////////////////////////////////		
		

		// if (      tunnel(in)      or       proxy(out)         or        raw(bounce)       ) 
		if (td->pTSD->nProtocol == 2 || td->pTSD->nProtocol == 6 || td->pTSD->nProtocol == 3 ) 
		{
		//////////////////////////////////////////////////////////////////////////		
		// Branch 1 - is just a proxy - bouncing the request to another location
		//////////////////////////////////////////////////////////////////////////		

		int nProxyfd = -1;
		int nProxyClosed = 0;

		// Tie the threadData(td) of 'this' thread to the thread that will proxy on our behalf
		TCD.pTSD = td->pTSD;
		TCD.sockfd = td->sockfd;
		TCD.pnProxyClosed = &nProxyClosed; //&td->nProxyClosed;
		TCD.pcondHelper = &td->condHelper;
		TCD.nParentThreadID = td->nThreadID;
		TCD.pnProxyfd = &nProxyfd;
		TCD.plLastActivityTime = &lLastActivityTime;
		TCD.pbHelperThreadIsRunning = &bHelperThreadIsRunning;
		
		if ( td->pTSD->nProtocol == 6)
		{
			TCD.nAction = td->nAction;
			TCD.nConnectRouteSize = td->nConnectRouteSize;
			strcpy(TCD.pzRemoteKey,td->pzRemoteKey);
			strcpy(TCD.pzIP,td->pzIP);
			memcpy(TCD.pzConnectRoute,td->pzConnectRoute,TCD.nConnectRouteSize);
		}

		// the 16 bit flag field indicating data attributes
		unsigned short sAttribs = BuildAttributesHeader(td->pTSD->szConfigSectionName, td->pTSD->nProtocol, TCD.nAction);
		int bIsTunnel = td->pTSD->bIsTunnel;
		if ( td->pTSD->nProtocol == 6 )
		{
			if (TCD.nAction == 1)
				bIsTunnel = 1;
			else
				bIsTunnel = 0;
		}

		CipherData cdToServer;
		// if ciphering is enabled
		if ( ( sAttribs & ATTRIB_CRYPTED_2FISH128 ) || ( sAttribs & ATTRIB_CRYPTED_2FISH256 ) )
		{
			const char *pKey128 = GetProfile().GetString(td->pTSD->szConfigSectionName,"CipherPass128",false);
			const char *pKey256 = GetProfile().GetString(td->pTSD->szConfigSectionName,"CipherPass256",false);
			
			if ( td->pTSD->nProtocol == 6 )
			{
				if (TCD.nAction == 1) // entering a tunnel 
				{
					pKey128 = TCD.pzRemoteKey;
					pKey256 = 0;
				}
				else if (TCD.nAction == 3) // exiting the tunnel 
				{
					pKey128 = GetProfile().GetString("RemoteWorkstation","Password",false);
				}
			}
			
			if(bIsTunnel)
			{
				InitializeCipherKey(&cdToServer,pKey128,pKey256,DIR_ENCRYPT);
			}
			else
			{
				InitializeCipherKey(&cdToServer,pKey128,pKey256,DIR_DECRYPT);
			}
		}


		// assign the "pcd" so that worker thread can obtain a pointer to this 
		// key and use it rather than rebuilding the same key.  currently unused.
		TCD.pTSD->pcd = &cdToServer;	


		// Set non-blocking IO  
		IOBlocking(td->sockfd, 1);  

		int bHasAwakenHelper = 0;
		do
		{
			if (bHasAwakenHelper == 0)
			{
				if(g_DataTrace)
				{
					GString strMessage("\n\nWaking Helper");
					strMessage.ToFileAppend(g_DataTraceFile);
				}

				// awaken our helper thread and have it connect to the remote side of this hop
				AttachToClientThread(&TCD,g_ProxyPool,g_ProxyPoolSize,&g_nProxyPoolRoundRobin,ProxyHelperThread);
				bHasAwakenHelper = 1;
				nProxyClosed = 0;

				// hang here until the forwardTo connection has been established 
				timespec ts;
				ts.tv_sec=time(0) + td->pTSD->nProxyTimeout; 
				ts.tv_nsec=0;
				pthread_cond_timedwait(&td->condHelper, &td->lockHelper, &ts); 
				if (time(0) >= ts.tv_sec)
				{
					GString strLog;
					strLog.Format("timeout [%d] expired",td->pTSD->nProxyTimeout);
					InfoLog(strLog);

					nProxyClosed = 1;
					nCloseCode = 2;
					break;
				}
				if (nProxyClosed)
				{
					// the [balanced|roll-over] connection could not be established 
					nCloseCode = 3; // no alternate specified
					break;
				}

				// currently the only time this is ever used is when proxying data for the
				// File transfer service.  Rather than handling this client, the service
				// converted the connection type to "raw proxy" and set some data to be
				// sent as the first data transmission once the proxy connection is established.
				// this sends that initial transmission.
				if (nInitialSendLen)
				{
					if (nonblocksend(nProxyfd,pInitialProxySend,nInitialSendLen) != nInitialSendLen)
					{
						nProxyClosed = 1;
						nCloseCode = 9; 
					}
				}

			}

			// recv from: (bIsTunnel) ? client application : client tunnel
			// essentially---> int bPacketSync = ( (!bIsTunnel) && (bCipher || bZip || bSync) ) ? 1 : 0;
			int bPacketSync = ( (!bIsTunnel) && sAttribs ) ? 1 : 0;


			unsigned short sPktAttributes = 0;
			int rslt = ReadPacket(td->sockfd,15,0,&nBufBytes,sockBuffer,&pPacket,&nPacketLen, &nPacketIndex,&sPktAttributes,sAttribs,bPacketSync);
			if (nProxyClosed)
			{
				nCloseCode = 4; // closed by worker thread
				break;
			}
			if (rslt == 0)
			{
				// no data or only a partial packet available - nothing to do.
				if (lLastActivityTime)
				{
					if ( (time(0) - lLastActivityTime ) > td->pTSD->nProxyTimeout )
					{
						//Helper Thread Wait Expired
						nProxyClosed = 1;
						nCloseCode = 5; 
					}
				}
				else
				{
					lLastActivityTime = time(0);
				}
			}
			else if (rslt == -1) // failure during wait (connection closed by client)
			{
				nProxyClosed = 1;
				nCloseCode = 6; 
			}
			else // process this packet
			{
				lLastActivityTime = time(0);
				if (!bIsTunnel) // if we are about to decrypted/decompress
				{ 
					if (!VerifyAttributes(sAttribs,sPktAttributes))
					{
						nProxyClosed = 1;
						nCloseCode = 7; 
						break; // same as "goto SOCKET_ERR_ABORT;" just not as readable
					}
				}

				if (g_DataTrace) DataTrace("rx from c", pPacket, nPacketLen);
				char *pSendData = pPacket;
				int nSendDataLen = nPacketLen;

				// if we need to sub-packet or apply data attributes(Cipher || Zip || Sync)
				if ( sAttribs )
				{
					int newLength;
					if (bIsTunnel) // if we are about to encrypt/compress, make room for the packet header
					{
						cryptDest += 4; // move the pointer ahead 4 bytes
					}

					// compress/decompress/crypt/decrypt or custom manipulation
					if (PrepForServer(sAttribs,bIsTunnel,&cdToServer,pSendData,nSendDataLen,cryptDest,&newLength))
					{
						if (bIsTunnel) // if we just encrypted/compressed
						{
							cryptDest -= 4; // move the pointer back 4 bytes
							short sh = htons(newLength); // packet length in network byte order
							memcpy(&cryptDest[0],&sh,sizeof(short));
							sh = htons(sAttribs); // attributes in network byte order
							memcpy(&cryptDest[2],&sh,sizeof(short));
							newLength += 4; // account for the header in the packet size
						}
						pSendData = cryptDest;
						nSendDataLen = newLength;
					}
					else
					{
						//PrepForServer failed in Branch2
						nCloseCode = 8; 
						nProxyClosed = 1;
					}
				}

				if (g_DataTrace) DataTrace("tx to s", pSendData, nSendDataLen);

				if (nonblocksend(nProxyfd,pSendData, nSendDataLen) != nSendDataLen)
				{
					nProxyClosed = 1;
					nCloseCode = 9; 
				}
				ProxyLog(td->pTSD->szConfigSectionName, td->nParentThreadID, pSendData, nSendDataLen, "tx->s");
			}
		}while( !nProxyClosed );
		
		// this is where it would fall through to anyhow.  the goto is for readability.
		goto SOCKET_ERR_ABORT; 
		
		}
		else if (td->pTSD->nProtocol == 1 ||	// HTTP Server
				 td->pTSD->nProtocol == 4 ||	// TXML
				 td->pTSD->nProtocol == 5)		// Both TXML and HTTP
		{
		//////////////////////////////////////////////////////////////////////////		
		// Branch 2 - reads HTTP headers, and generates an HTTP response 
		//////////////////////////////////////////////////////////////////////////		
		int nKeepAlive = 25;
		int nContentLength;
		int nHeaderLength;
		int nBytes;
		int nCumulativeBytes;

		// Set non-blocking IO 
		IOBlocking(td->sockfd, 1);
KEEP_ALIVE: 
		nContentLength = 0;
		nHeaderLength = 0;
		nBytes = -1;
		nCumulativeBytes = 0;


		int nKeepAliveTimeOut = GetProfile().GetInt("HTTP","KeepAliveTimeOut",false);
		if (nKeepAliveTimeOut < 2 )
			nKeepAliveTimeOut = 10;

		do
		{
			int rslt = readableTimeout(td->sockfd, nKeepAliveTimeOut, 0);
			// failure during wait || time limit expired
			if (rslt == -1 || rslt == 0)
			{
				goto SOCKET_ERR_ABORT;
			}

			nBytes = recv(td->sockfd, sockBuffer, MAX_DATA_CHUNK, 0 );
			if (nBytes < 1)
			{
				// -1 the client aborted 
				goto SOCKET_ERR_ABORT;
			}
			sockBuffer[nBytes] = 0;

			// look for an HTTP GET, it MIGHT look something like this:
			// but it could be a simple CGI or ISAPI call as well.
			/*
				GET /Index.html HTTP/1.1
				Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword
				Accept-Language: en-us
				Accept-Encoding: gzip, deflate
				User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
				Host: 192.168.1.102
				Connection: Keep-Alive
			*/


			int bisHead = (memcmp(sockBuffer,"HEAD",4) == 0) ? 1 : 0;
			if ( memcmp(sockBuffer,"GET",3) == 0 || bisHead )
			{
				if (g_HTTPTrace)
				{
					InfoLog(sockBuffer);
				}

				// Check the path of the "file" that this HTTP get is looking for.
				// If we are looking for switchboard requests....
				if ( GetProfile().GetBoolean("SwitchBoardServer","Enable",false) )
				{
					// and the path matches the special path that identifies this as the switchboard ---
					const char *pzSwitchPath = GetProfile().GetPath("SwitchBoardServer","Name",false);
					if (pzSwitchPath)
					{
						// (do the path comparison - config setting vs. value from recv() )
						GString str(pzSwitchPath);
						char ch = sockBuffer[4 + str.Length()]; // save and null the first byte past the path
						sockBuffer[4 + str.Length()] = 0;
						// if the path to the "file" this HTTP GET is looking in matches
						// the "name" entry under [SwitchBoardServer], which might be a value
						// something like this "/MySwitchServerPath/"......
						if ( str.CompareNoCase(&sockBuffer[4]) == 0)
						{
							// then this is a server checking for a remote-waiting client connection on this machine.

							// restore first byte past the path since the comparison is done.
							sockBuffer[4 + str.Length()] = ch;

							char *pStart = &sockBuffer[4 + str.Length()];
							char *pEnd = strstr(pStart,"HTTP/");
							if (pEnd)
							{
								pEnd--;
								*pEnd = 0;
								
								// see if there is a client waiting to connect to this request
								if (g_SwitchBoard.HandOffConnection (td->sockfd, pStart))
								{
									// there was a client waiting, td->sockfd is now being serviced
									// by another thread, so this thread goes back to the pool.
									goto SHUTTING_DOWN;
								}
								else
								{
									// no connections are waiting, close this connection then go back to the pool
									goto SOCKET_ERR_ABORT;
								}
							}
						}
						
						// put [ch] back where the string was temporarily null terminated for the comparison.
						sockBuffer[4 + str.Length()] = ch;
					}
				}
				
				// look for something like this (a CGI or ISAPI call)
				// GET /XMLServer/UBT.dll?TransactXML=......"
				// the ? must be in the first line of the HTTP header
				char *p = 0;
				char *pGETEnd = strpbrk(sockBuffer,"\r\n");
				if (pGETEnd)
				{
					char pSave = *pGETEnd;
					*pGETEnd = 0;
					p = strpbrk(sockBuffer,"?");  
					*pGETEnd = pSave;
				}
				
				// if there's no ?, this is not a CGI or ISAPI call, maybe a basic HTTP GET
				// for a static document/image/javascript etc...
				if (!p) 
				{
					// If we're configured to look for HTTP GET's on this port go-on
					if (td->pTSD->nProtocol == 1 ||	td->pTSD->nProtocol == 5)
					{
						pzHome = GetProfile().GetString("HTTP","Home",false);

						// get the file (from memory or disk), assemble the HTTP response, and send it back.
						nKeepAlive = ReturnFile(td, pzHome, &sockBuffer[4] ,nBytes - 4,strConnectedIPAddress, nKeepAlive, bisHead);
						break;
					}

					GString strError;
					strError.Format("Received HTTP request[%s]\n Invalid request or server not configured for this request.",sockBuffer);
					throw GenericException(0,(const char *)strError);
				}
				
				// When posting data through a GET, from a browser, the 
				// URL Data has certain characters hex encoded (a space becomes %20 for example)
				// CGI or ISAPI calls get their param values unescaped at this point.
				int nNewDataLen = unEscapeData(p + 1,p + 1);
				
				// so that this value, now stored in [inboundXML] becomes the 'Request'
				// that may be dispatched into an externally executable handler (CGI or ISAPI)
				inboundXML->write(p + 1,nNewDataLen);
				
				break; // we got the whole message in a single I/O read
			}

			// parse the HTTP headers in the first read from the stream
			if (!nHeaderLength)
			{
				char *pContent= strstr(sockBuffer,"Content-Length:");
				if (pContent)
				{
					pContent += 15; // first byte of content length in the HTTP header
					nContentLength = atoi(pContent);
					pContent= strstr(pContent,"\r\n\r\n");
					if (pContent)
					{
						pContent += 4; // first byte of content data
						nHeaderLength = (pContent) - sockBuffer;
						
						// begin capturing data past the HTTP headers
						inboundXML->write(pContent,nBytes - (pContent - sockBuffer));
					}
				}

				if ( nHeaderLength < 1 || nContentLength < 1)
				{
					sockBuffer[nBytes] = 0;
					GString strHeaderError;
					strHeaderError.Format("Expected HTTP Headers, but found [%s]",sockBuffer);
					throw GenericException((const char *)strHeaderError, 0);
				}
			}
			else
			{
				inboundXML->write(sockBuffer,nBytes);
			}
			nCumulativeBytes += nBytes;
		}while( nCumulativeBytes < nHeaderLength + nContentLength  || !nCumulativeBytes );
		
		// there has to be some reasonable limit to prevent denial of service attack
		if (nKeepAlive > 0 && nKeepAlive != 25)
			goto KEEP_ALIVE;


		// UBT Server extension
#ifdef INTERNAL_UBT_BUILD
	    #include "ServerDispatch.cpp"
#endif

		} // end of branch2
		
	}
	XML_CATCH(ex)
	{
		GString strLog;
		strLog.Format("XML_CATCH=%s\n",ex.GetDescription());
		InfoLog(strLog);

		PROPIGATE_SERVER_EXCEPTION;
	}

SOCKET_ERR_ABORT:

#ifdef _WIN32    
	closesocket(td->sockfd);
#elif _HPUX
	shutdown(td->sockfd,2);
#else
	close(td->sockfd);
#endif
	// -------------------------------------------------------------------------------------
	// The connection is closed and the client has the response.  We are "off the clock" now
	// -------------------------------------------------------------------------------------
	if (starttime)
	{
		GString strLog;
		strLog.Format("Thread %d  Service time ~:%d",td->nThreadID, getTimeMS() - starttime);
		InfoLog(strLog);

	}
	if(g_ConnectionDataTrace)
	{
		GString strMessage;
		strMessage.Format("Closed Inbound Connection from [%d:%s] Code:[%d]",td->pTSD->nPort,(const char *)strConnectedIPAddress, nCloseCode);
		InfoLog(strMessage);
	}



SHUTTING_DOWN:

	SERVER_MEMORY_RESET;
	*inboundXML = "";
	sockBuffer[0] = 0;

	// decrement the connection resource monitor
	ConnectionCounter(strConnectedIPAddress,-1); 

	unsigned long timenow = time(0);
	while (bHelperThreadIsRunning)
	{
		if (time(0) - timenow > 10)
		{
			// This is a development safety net.  
			// Under no known conditions can this code be reached.
			InfoLog("Thread Hung.  You need to stop then restart this application to correct this problem");
			
			// Threads should not kill other threads.  Fix the problem.
		}
		sched_yield();
	}

	// don't free the memory, just go back and wait for another transaction.
	if (!g_ServerIsShuttingDown && td->m_bUseThreads )
	{
		goto MAIN_THREAD_BODY;
	}

	// release memory and shutdown.  The thread will exit.
	delete sockBuffer;
	delete inboundXML;
	SERVER_MEMORY_CLEANUP;
	if (td->m_bUseThreads)
	{
		pthread_exit(0);
	}
	return 0;
}

int g_listenThreadsRunning = 0;


int MessageRouteInfo(int fd,char *pzConnectRoute,char *pzIP,int *nAction, int *nConnectRouteSize,char *pzPassword)
{
	// Set blocking IO
	IOBlocking(fd, 0);
	int timeout=15;
	if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) != -1 )
	{
		int bytes = recv(fd, pzConnectRoute, 2, 0);
		if (bytes == 2)
		{
			short theNumber = 0;					// storage on platform architecture byte boundry
			memcpy(&theNumber,&pzConnectRoute[0],2);// storage in network byte order
			short nParams = ntohs( theNumber );     // convert to host byte order

			bytes = recv(fd, pzConnectRoute, 2, 0);
			if (bytes != 2)
			{
				return 0; // protocol error
			}
		
			memcpy(&theNumber,&pzConnectRoute[0],2);
			short nRouteMsgSize = ntohs( theNumber );

			bytes = recv(fd, pzConnectRoute, nRouteMsgSize, 0);
			if (bytes == nRouteMsgSize)
			{
				pzConnectRoute[bytes] = 0;

				////////////////////////// Parse the pzConnectRoute  /////////////////////////////////////////////
				//
				//	SingleIP   or	  InternetIP  |InternalIP		or  InternetIP  |InternalIP   |InternalSubnetIP
				// "1.2.3.4"   or     "12.12.12.12|192.168.1.100"   or  "12.12.12.12|192.168.1.100|10.0.0.1"
				//
				// the lstDeliveryIPs may also contain "external connects" followed by internal proxies....
				// like "12.12.12.12|~Seasoned|10.0.0.1"
				// this would send to 12.12.12.12, wait for a machine to pickup a connection "Seasoned" 
				// then from that machine proxy to 10.0.0.1.
				//
				// and IP's may be machine names as well.  Note that machine mames are resolved on the fly.
				// for example "www.UB2B.com|MYInternalServer" - in this case "MYInternalServer" is resolved 
				// from the machine "www.UB2B.com" so that is does not need to be a public DNS
				// 
				/////////////////////////////////////////////////////////////////////////////////////////////////
				GStringList lstDeliveryIPs("|",pzConnectRoute);

				if (nParams == 2)
				{
					bytes = recv(fd, pzConnectRoute, 2, 0);
					if (bytes != 2)
						return 0;

					short theNumber = 0;						// storage on platform architecture byte boundry
					memcpy(&theNumber,&pzConnectRoute[0],2);	// storage in network byte order
					short nPassSize = ntohs( theNumber );       // convert to host byte order
					bytes = recv(fd, pzPassword, nPassSize, 0);
					if (bytes != nPassSize)
						return 0;
					pzPassword[nPassSize] = 0;
				}
				else if (nParams != 1)
				{
					return 0; // protocol error
				}

				GString strFirstIP(lstDeliveryIPs.PeekFirst());
				if (strFirstIP.GetAt(0) == '~')
				{
					lstDeliveryIPs.RemoveFirst();		  //  remove the ~connectionName
					lstDeliveryIPs.AddFirst("127.0.0.1"); // 'this' will get popped off
					strFirstIP = "127.0.0.1";
				}
			

				if ( IsMe( strFirstIP ) )
				{
					if ( lstDeliveryIPs.Size() > 1 )
					{
						// pop this hop
						lstDeliveryIPs.RemoveFirst();

						strcpy(pzIP, lstDeliveryIPs.PeekFirst());


						strcpy(&pzConnectRoute[4],lstDeliveryIPs.Serialize("|"));
						unsigned short sh = htons(strlen(&pzConnectRoute[4])); 
						memcpy(&pzConnectRoute[2],&sh,sizeof(short));
						sh = htons(1); 
						memcpy(&pzConnectRoute[0],&sh,sizeof(short));
						*nConnectRouteSize = strlen(&pzConnectRoute[4]) + 4;
						*nAction = 2;
					}
					else
					{
						// destination (proxy-->server), decrypted from tunnel and serviced by this machine
						*nConnectRouteSize = 0;
						*nAction = 3;
						pzIP[0] = 0;
					}
				}
				else
				{
					// tunnel (client-->proxy), raw VNC into tunnel crypted and sent to proxy
					strcpy(pzIP,lstDeliveryIPs.PeekFirst());
					strcpy(&pzConnectRoute[4],lstDeliveryIPs.Serialize("|"));
					unsigned short sh = htons(strlen(&pzConnectRoute[4])); 
					memcpy(&pzConnectRoute[2],&sh,sizeof(short));
					sh = htons(1); 
					memcpy(&pzConnectRoute[0],&sh,sizeof(short));
					*nConnectRouteSize = strlen(&pzConnectRoute[4]) + 4;
					*nAction = 1;
				}
			}
			else
			{
				// protocol error. or timed out waiting for data
				return 0;
			}
		}
		else
		{
			// protocol error. or timed out waiting for data
			return 0;
		}
	}
	else
	{
		// setsockopt() failed
		return 0;
	}
	return 1;
}



void BuildConnectionPostData(GString &strDestination, const char *pzConnectionFileName, const char *pzCfgSection)
{
	// HTTP headers can be wrong.  Notice Netscape 6.2 incorrectly states from the HTTP 
	// headers that it's version 6.1.  Some software proxies/firewalls filter this data causing
	// a legitimate need to configure headers that imitate a browser, for that matter
	// there may also be a legitimate need to specify a host other than the actual host.
	// so... both are supported.
	

	// get the host 
	GString strHost;
	const char *pzStaticHost = GetProfile().GetString(pzCfgSection,"StaticHost",false);
	// if we're not specifically told to report the actual host 
	if ( !GetProfile().GetBoolean(pzCfgSection,"TrueHost",false) )
	{
		// and a static host has been specified
		if (pzStaticHost && pzStaticHost[0])
			strHost = pzStaticHost;
	}
	// otherwise report the actual host IP
	if (strHost.Length() == 0 )
	{
		strHost = g_strThisIPAddress;
	}

		
	int nIE = GetProfile().GetBoolean(pzCfgSection,"ImitateIE",false);
	int nNS = GetProfile().GetBoolean(pzCfgSection,"ImitateNS",false);

	// if the user does not specify what browser to look like, choose for them
	if (!nIE && !nNS)
	{
		nIE = 1;
	}

	if (nIE)
	{
		///////////////////////////////////////////////////////////////////////////////////////////////////
		//								Sample HTTP GET from IE 6.0
		///////////////////////////////////////////////////////////////////////////////////////////////////
		//GET /Index.html HTTP/1.1												0D0D0A
		//Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, */*			0D0D0A
		//Accept-Language: en-us												0D0D0A
		//Accept-Encoding: gzip, deflate										0D0D0A
		//User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)		0D0D0A
		//Host: 127.0.0.1														0D0D0A
		//Connection: Keep-Alive												0D0D0A0D0D0A
		strDestination.Format(
			"GET %s HTTP/1.1\r\r\n"
			"Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, */*\r\r\n"
			"Accept-Language: en-us\r\r\n"
			"Accept-Encoding: gzip, deflate\r\r\n"
			"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)\r\r\n"
			"Host: %s\r\r\n"
			"Connection: Keep-Alive\r\r\n\r\r\n",pzConnectionFileName, (const char *)strHost);
	}
	else // Netscape
	{
		///////////////////////////////////////////////////////////////////////////////////////////////////
		//				Sample HTTP GET from Netscape 6.2 (http headers say it's still 6.1)
		///////////////////////////////////////////////////////////////////////////////////////////////////
		//GET /index.html HTTP/1.1												0D0D0A
		//Host: 192.168.1.104													0D0D0A
		//User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:0.9.2) Gecko/20010726 Netscape6/6.1	0D0D0A
		//Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, image/png, image/jpeg, image/gif;q=0.2, text/plain;q=0.8, text/css, */*;q=0.1 0D0D0A
		//Accept-Language: en-us												0D0D0A			
		//Accept-Encoding: gzip,deflate,compress,identity						0D0D0A
		//Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66					0D0D0A
		//Keep-Alive: 300														0D0D0A
		//Connection: keep-alive												0D0D0A0D0D0A
		strDestination.Format(
			"GET %s HTTP/1.1\r\r\n"
			"Host: %s\r\r\n"
			"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:0.9.2) Gecko/20010726 Netscape6/6.1\r\r\n"
			"Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, image/png, image/jpeg, image/gif;q=0.2, text/plain;q=0.8, text/css, */*;q=0.1 \r\r\n"
			"Accept-Language: en-us	\r\r\n"
			"Accept-Encoding: gzip,deflate,compress,identity\r\r\n"
			"Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66\r\r\n"
			"Keep-Alive: 300\r\r\n"
			"Connection: keep-alive\r\r\n\r\r\n",pzConnectionFileName, (const char *)strHost);
	}
}

void *SwitchBoardPoll(void *arg)
{
	// first just get the nTotalPollEntryCount by iterating the config sections
	int nTotalPollEntryCount = 0;
	int nActivePollCount = 0;
	while(1)
	{
		GString strSection;
		strSection.Format("PollExternalConnect%d",nTotalPollEntryCount+1); // == "PollExternalConnect1" .. "PollExternalConnectN"
		const char *pzCountTest = GetProfile().GetString(strSection,"Server",false);
		if ( pzCountTest && pzCountTest[0] )
		{
			nTotalPollEntryCount++; // including disabled entries, they will be skipped later
		}
		else
		{
			break;
		}
	}

	unsigned int nLowestPollInterval = 0xFFFFFFFF;

	// loop through the config sections copying all data into thread startup structures.
	ThreadConnectionData *TCD = new ThreadConnectionData[nTotalPollEntryCount];
	for(int i=0; i<nTotalPollEntryCount;i++)
	{
		sprintf(TCD->pzPollSectionName, "PollExternalConnect%d",i+1);

		const char *pzIP = GetProfile().GetString(TCD->pzPollSectionName,"Server",false); 
		const char *pzFullPath = GetProfile().GetString(TCD->pzPollSectionName,"Name",false);  

		// if is not properly configured, or specifically disabled
		if (!pzIP || !pzIP[0] || !pzFullPath || !pzFullPath[0] ||
				!GetProfile().GetBoolean(TCD->pzPollSectionName,"Enable",false) )
		{
			TCD[i].sockfd = -7; // used below to skip this 'disabled' entry
			continue;
		}
		nActivePollCount++;

		TCD[i].pTSD = new ThreadStartupData;
		TCD[i].pTSD->nProxyTimeout = 120;
		
		// 1 HTTP, 2 Proxy/Tunnel, 3 File Xfer, 4 TXML, 5 TXML&HTTP, 6 Remote-WS, 7 FileXFerProxy
		TCD[i].pTSD->nProtocol = GetProfile().GetInt(TCD->pzPollSectionName,"Protocol",false);;

		// any non valid port number, since this connection really comes through port 80 on another machine
		// this is not used anywhere except in debug tracing, to distinguish 'listen' and 'poll'.
		// the thread that services the request is unaware of how the connection was initiated (poll-in vs listen-in).  
		TCD[i].pTSD->nPort = -777;
		strcpy(TCD[i].pTSD->szProxyToServer,pzIP);
		strcpy(TCD[i].pTSD->pzPostConnectPathAndName,pzFullPath); // like: /MySwitchPath/MyVNCSwitchName.html

		TCD[i].nPollInterval = GetProfile().GetInt(TCD->pzPollSectionName,"PollIntervalSeconds",false);;
		nLowestPollInterval = (nLowestPollInterval < TCD[i].nPollInterval) ? nLowestPollInterval : TCD[i].nPollInterval;
	}
	if (nLowestPollInterval == 0)// if the user has assigned no poll interval.....
		nLowestPollInterval = 1;

	
	if (nActivePollCount == 0)
	{
		// end this thread, there is nothing to poll
		return 0;
	}

	int bFoundConnection; // RE_POLL flag initialized below

	// loop through thread startup structures
	// end of 0-n for ever loop
	while (1){ for(int i=0; i<nTotalPollEntryCount;i++) 
	{
		if (TCD[i].sockfd == -7) // if this is not enabled, skip this one
			continue;
		
		// if it's not time to poll this entry, skip this one
		if (time(0) - TCD[i].lLastPoll < TCD[i].nPollInterval)
			continue;

		TCD[i].lLastPoll = time(0);
RE_POLL:
		bFoundConnection = 0;
		char Buf[3];

		TCD[i].sockfd = DoConnect(TCD[i].pTSD->szProxyToServer, 80);
		if (TCD[i].sockfd == -1)
		{
			// falls through to close this connection, CLOSE_POLL
		}
		else
		{
			// set the connection attributes
			IOBlocking(TCD[i].sockfd, 1);	// Set non blocking IO

			GString strConnectionName; // contains results of BuildConnectionPostData()
			
			// generate a "Browser Issued" GET command requesting a connection.
			BuildConnectionPostData(strConnectionName, TCD[i].pTSD->pzPostConnectPathAndName, TCD->pzPollSectionName);
			
			
			if (nonblocksend(TCD[i].sockfd, (const char *)strConnectionName, strConnectionName.Length()) == -1)
			{ 
				InfoLog("nonblocksend() failed on Poll Thread");
				continue;
			}
			
			// read 2 bytes - "OK" we connected otherwise "--" we did not
			int nBytes = nonblockrecv(TCD[i].sockfd, Buf, 2);
			if (nBytes != -1)
			{
				if (Buf[0] == 'O' && Buf[1] == 'K')
				{
					bFoundConnection = 1;
					if (TCD[i].pTSD->nProtocol == 6) // Remote Workstation
					{
						if (MessageRouteInfo(TCD[i].sockfd,TCD[i].pzConnectRoute,TCD[i].pzIP,&TCD[i].nAction,&TCD[i].nConnectRouteSize,TCD[i].pzRemoteKey))
						{
							// Set non-blocking IO
							IOBlocking(TCD[i].sockfd, 1);
							AttachToClientThread(&TCD[i],g_ThreadPool,g_ThreadPoolSize,&g_nThreadPoolRoundRobin,clientThread );
						}
					}
					else if (TCD[i].pTSD->nProtocol == 3) // File Transfer
					{
						AttachToClientThread(&TCD[i],g_ThreadPool,g_ThreadPoolSize,&g_nThreadPoolRoundRobin,clientThread );
					}
					else
					{
						GString strLog;
						strLog.Format("Protocol [%d] in PollExternalConnect must be 3 or 6",TCD[i].pTSD->nProtocol);
						InfoLog(strLog);
						goto CLOSE_POLL;
					}
					
					// complete this polling, dont close the connection, it is being serviced
					// by the clientThread that it was just attached to. 
					goto FINISH_POLL;
				}
				else
				{
					// there is no client waiting for the type of connection just requested.
					goto CLOSE_POLL;
				}
			}
			else
			{
				InfoLog("poll recv() failed");
			}
		}
CLOSE_POLL:
		#ifdef _WIN32
			closesocket(TCD[i].sockfd);
		#elif _HPUX
			shutdown(TCD[i].sockfd,2);
		#else // linux, sun, AIX
			close(TCD[i].sockfd); 
		#endif
FINISH_POLL:
		
		if (!bFoundConnection)
		{
			// no connection was found on this poll, sleep for the specified interval
			// then poll the next configured server.
			PortableSleep(nLowestPollInterval,0);
		}
		else
		{
			// we got one, so check for more of the same type, this is designed
			// primarily for the file transfer protocol since commands are often
			// batched by task, for example GET or PUT for every file in a folder.
			sched_yield();
			goto RE_POLL;
		}

	}} // end of 0-n for ever
}


void *listenThread(void *arg)
{
	GString strConnectedIPAddress;
	g_listenThreadsRunning++;
	
	ThreadConnectionData TCD;
	
	TCD.pTSD = (ThreadStartupData *)arg;

	int clilen;
	struct sockaddr_in cli_addr;
	int nCount = 0;
	long lLastPing = 0;
	
	listen(TCD.pTSD->sockfd, 5);


	// Set non-blocking IO
	IOBlocking(TCD.pTSD->sockfd, 1);

#ifdef _WIN32
	SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL); // = 1 point above nornal
#endif

	for( ;; )
	{
		nCount++;
		clilen = sizeof(cli_addr);

		int RetryForLinux = 0;
RETRY_FOR_LINUX:		
		int rslt = readableTimeout(TCD.pTSD->sockfd, 1/*seconds*/, 0/*microseconds*/);
		if ( rslt == -1)
		{
			if (RetryForLinux++ < 10)
			{
				sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();sched_yield();
				goto RETRY_FOR_LINUX;
			}
			
			GString strLog;
			strLog.Format("listen thread shutting down code[%d]",SOCK_ERR);
			InfoLog(strLog);
			goto LISTEN_THREAD_EXIT; 
		}
		if (g_ServerIsShuttingDown)
		{
			goto LISTEN_THREAD_EXIT; 
		}
		if ( rslt > 0)
		{
			TCD.sockfd = accept(TCD.pTSD->sockfd, (struct sockaddr *) &cli_addr, (socklen_t *)&clilen);
			if(TCD.sockfd < 0)
			{
				// a 10038 is possible here "Connection reset by Peer", but extremely rare.
				GString strLog;
				strLog.Format("accept() failed code[%d]",SOCK_ERR);
				InfoLog(strLog);

				continue;
			}


			// get the address of the connected client into strConnectedIPAddress;
			struct sockaddr_in other_addr;
			int nSize = sizeof(sockaddr_in);

			int nPeerRet = getpeername(TCD.sockfd,(struct sockaddr *) &other_addr, (socklen_t *)&nSize);
			if ( nPeerRet == 0 )
			{
				unsigned int nMaxC = GetProfile().GetInt("System","MaxOpenConnectionsPerIP",false);
				if (nMaxC == 0)
					nMaxC = 0xFFFFFFFF;

				strConnectedIPAddress = inet_ntoa(other_addr.sin_addr);
				if(g_ConnectionDataTrace)
				{
					GString strMessage;
					strMessage.Format("Inbound Connection on[%d] from[%s]",TCD.pTSD->nPort,(const char *)strConnectedIPAddress);
					InfoLog(strMessage);
				}

				if (ConnectionCounter(strConnectedIPAddress,1) > nMaxC) 
				{
					GString strLog;
					strLog.Format("Machine at[%s] attempted more than %d connections.",(const char *)strConnectedIPAddress,nMaxC);
					InfoLog(strLog);

					// decrement this connection
					ConnectionCounter(strConnectedIPAddress,-1);

					// we could send a "HTTP/1.1 5xx \n\n" like IIS or just quit 
					// wasting cycles on this guy by ending the connection right now.
					#ifdef _WIN32    
						closesocket(TCD.sockfd);
					#elif _HPUX
						shutdown(TCD.sockfd,2);
					#else // linux, sun, AIX
						close(TCD.sockfd);
					#endif
				}
				else //service the connection
				{
					if (TCD.pTSD->nProtocol == 6)
					{
						if (MessageRouteInfo(TCD.sockfd,TCD.pzConnectRoute,TCD.pzIP,&TCD.nAction,&TCD.nConnectRouteSize,TCD.pzRemoteKey))
						{
							// Set non-blocking IO
							IOBlocking(TCD.sockfd, 1);
							AttachToClientThread(&TCD,g_ThreadPool,g_ThreadPoolSize,&g_nThreadPoolRoundRobin,clientThread );
						}
						else
						{
						#ifdef _WIN32
							closesocket(TCD.sockfd);
						#elif _HPUX
							shutdown(TCD.sockfd,2);
						#else // linux, sun, AIX
							close(TCD.sockfd); 
						#endif
						}
					}
					else
					{
						AttachToClientThread(&TCD,g_ThreadPool,g_ThreadPoolSize,&g_nThreadPoolRoundRobin,clientThread );
					}
				}
			}
			else
			{
				GString strLog;
				strLog.Format("getpeername() failed code[%d]",SOCK_ERR);
				InfoLog(strLog);
			}
		}
		if (g_PingListenThread)
		{
			if (time(0) > lLastPing + 3 )
			{
				GString strLog;
				strLog.Format("%d    ",TCD.pTSD->nPort);
				InfoLog(strLog,0);
				lLastPing = time(0);
			}
		}

		if (g_ServerIsShuttingDown || g_listenerIsRebooting)
			break;

	}
LISTEN_THREAD_EXIT:
	g_listenThreadsRunning--;

	GString strLog;
	strLog.Format("No longer listening to port %d.",TCD.pTSD->nPort);
	InfoLog(strLog);



	#ifdef _WIN32    
		closesocket(TCD.pTSD->sockfd);
	#elif _HPUX
		shutdown(TCD.pTSD->sockfd,2);
	#else // linux, sun, AIX
		close(TCD.pTSD->sockfd);
	#endif

	pthread_exit(0);
	return 0;
}


ThreadStartupData g_TSD;

// nAction  1=Create socket and Bind  2=Start Listener Thread  3=Both
int startListeners(int nAction) 
{
	g_listenerIsRebooting = 0;

	ThreadStartupData *pTSD;
	struct sockaddr_in  serv_addr;

	// create and bind to a socket on each port	that this server handles
	if (nAction == 1 || nAction == 3)
	{
		pTSD = &g_TSD;
		while(pTSD)
		{
			if((pTSD->sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
			{
				InfoLog("Can't open socket. Check permissions. ");
				return 0;
			}
			memset((char *) &serv_addr, 0, sizeof(serv_addr));
			serv_addr.sin_family = AF_INET;
			serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
			serv_addr.sin_port = htons( pTSD->nPort );
			
			if(bind(pTSD->sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
			{
				GString strErr;
				strErr.Format("Cannot use port[%d] (in use?)                           [%d]\nMake sure the port is available and\nyou have permissions to open the resource.\n(root in UNIX, Administrator in NT).\n\nIf the server process was ended by a user interrupt (Ctrl-C)\nSome machines may take upto 5 minutes to fully release the port.\nAfter this port becomes free, issue the command CycleListen to reattempt.",pTSD->nPort, pTSD->nPort);
				InfoLog(strErr);
			}
			pTSD = pTSD->Next();
		}
	}
	if (nAction == 2 || nAction == 3)
	{
		// create a listener thread for each port we handle
		pTSD = &g_TSD;
		while(pTSD)
		{
			pthread_t listen_thr;
			pthread_create(&listen_thr,	NULL, listenThread, (void *)pTSD );
			pTSD = pTSD->Next();
		}
	}
	return 1;
}


//////////////////////////////////////////////////////////////////////////////
int server_start()
{
	// turn off stdout buffering so we never need to flush();
	setbuf (stdout, NULL);

	InfoLog("Server starting.......");

	char pzTime[128];
	struct tm *newtime;
	long ltime;
	time(&ltime);
	newtime = gmtime(&ltime);
	strftime(pzTime, 128, "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", newtime);
	InfoLog(pzTime);

try
{

	g_ThreadPoolSize = GetProfile().GetInt("System","Pool",false);
	if (g_ThreadPoolSize < 1)
		g_ThreadPoolSize = 30;

	g_ProxyPoolSize = GetProfile().GetInt("System","ProxyPool",false);
	if (g_ProxyPoolSize < 1)
		g_ProxyPoolSize = 20;


	// one time initialization of the socket library
#ifdef _WIN32
	WSADATA wsaData;
	WSAStartup(MAKEWORD( 2, 2 ),&wsaData );
	_pthread_processInitialize();
#endif
	g_LogStdOut = 1; // Startup info always goes to std out and the file


	GString strLog;
	strLog.Format("Using Config File:[%s]",GetProfile().LastLoadedConfigFile());
	InfoLog(strLog);
	InitRandomNumbers();

	// gather info about all listen threads that will be started
	ThreadStartupData *pTSD = &g_TSD;

	InfoLog("Gathering Port Startup Assignments:",0);

	//
	// The HTTP/TXML service can share a port or each be on it's own port.....
	//

	// HTTP Service
	if ( GetProfile().GetBoolean("HTTP","Enable",false) )
	{
		pTSD->nPort = GetProfile().GetInt("HTTP","Port",false);
		if (!pTSD->nPort)
			pTSD->nPort = 80;
		pTSD->nProtocol = 1;
		InfoLog("80   ",0);
	}
	// TXML Service
	if ( GetProfile().GetBoolean("TXML","Enable",false) )
	{
		if ( GetProfile().GetBoolean("HTTP","Enable",false) )
		{
			if (pTSD->nPort == GetProfile().GetInt("TXML","Port",false))
			{
				// Running TXML and HTTP on the same port (modify the current pTSD)
				pTSD->nProtocol = 5; 
			}
			else
			{
				// Create a new pTSD for the TXML port
				pTSD = pTSD->Next(1);
				pTSD->nPort = GetProfile().GetInt("TXML","Port",false);
				pTSD->nProtocol = 4;

				GString strLog;
				strLog.Format("%d   ",pTSD->nPort);
				InfoLog(strLog,0);

			}
		}
	}




	// http://members.cotse.com/dlf/man/ports/ports10000_up.htm
	// a nice block of open ports is: 10111, 10222, 10333, 10444, 10555, 10666, 10777, 10888 and 10999
	// those ports should provide any optional services.

	// File Transfer Service - Port 10777
	if ( GetProfile().GetBoolean("FileTransfer","Enable",false) )
	{
		pTSD = pTSD->Next(1);
		pTSD->nPort = 10777;
		InfoLog("10777   ",0);
		pTSD->nProtocol = 3;
	}

	// Remote Workstation Service - Port 10888(always open) && 10999(open when active)
	if ( GetProfile().GetBoolean("RemoteWorkstation","Enable",false) )
	{
		pTSD = pTSD->Next(1);
		pTSD->nPort = 10888;
		InfoLog("10888   ",0);
		pTSD->nProtocol = 6;
		pTSD->nProxyTimeout = 120;//GetProfile().GetBoolean("RemoteWorkstation","Timeout",false);
	}


	// loop through the [Tunnel1]...[TunnelN] and [Proxy1]..[ProxyN] sections in the config file
	int bAdvanceTSD = 1;
	for(int nTunnelOrProxy = 0; nTunnelOrProxy < 2; nTunnelOrProxy++)
	{
		const char *pSection = (nTunnelOrProxy) ? "Proxy" : "Tunnel";

		// Priming Section Read
		int nEntry = 1;
		GString strNextSection;
		strNextSection.Format("%s1",pSection); // == "Proxy1" or "Tunnel1"
		const char *pzLocalPort = GetProfile().GetString(strNextSection,"LocalPort",false);
			
		while(pzLocalPort && pzLocalPort[0])
		{
			if (GetProfile().GetBoolean(strNextSection,"Enable"))
			{
				if (bAdvanceTSD)
					pTSD = pTSD->Next(1);
				try // missing profile sections throw exceptions
				{
					pTSD->nProtocol = 2;
					pTSD->nPort = atoi(pzLocalPort);
					strcpy(pTSD->szProxyToServer, GetProfile().GetString(strNextSection,"RemoteMachine") );
					pTSD->nProxyToPort = atoi(GetProfile().GetString(strNextSection,"RemotePort")); 
					pTSD->nProxyTimeout = atoi(GetProfile().GetString(strNextSection,"Timeout"));
					pTSD->bIsTunnel = (nTunnelOrProxy) ? 0 : 1;

					// so the thread can go get more info from this section later
					strcpy(pTSD->szConfigSectionName,(const char *)strNextSection);

					GString strLog;
					strLog.Format("%d   ",pTSD->nPort);
					InfoLog(strLog,0);
					bAdvanceTSD = 1;
				}
				catch( GenericException &rErr )
				{
					// skip this one, it's not correctly setup in the config file.
					GString strLog;
					strLog.Format("Port[%s] not started.  Configuration error[%s]",pzLocalPort,rErr.GetDescription());
					InfoLog(strLog)
						;
					bAdvanceTSD = 0;
				}
			}

			// Next Section Read
			nEntry++;
			strNextSection.Format("%s%d",pSection,nEntry); // "ProxyN" or "TunnelN"
			pzLocalPort = GetProfile().GetString(strNextSection,"LocalPort",false);
		}
	}
	InfoLog("\nBinding to service ports:");

	// bind to a socket on each port that this server handles
	startListeners(1);

	InfoLog("Creating pools:");
	

	// create the worker threads 
	CreateThreadPool(&g_ThreadPool,g_ThreadPoolSize,clientThread);
	CreateThreadPool(&g_ProxyPool,g_ProxyPoolSize,ProxyHelperThread);

	InfoLog("Local Name resolution:");

	// obtain the local host name then DNS resolve it to an IP address
	if ( g_strThisIPAddress.Length() == 0 )
	{
		char pzHostName[512];
		gethostname(pzHostName,512); //uname() on some systems
		struct hostent *pHELocal = (struct hostent *)gethostbyname((const char *)pzHostName);
		struct sockaddr_in my_addr; 
		memcpy((char *)&(my_addr.sin_addr), pHELocal->h_addr,pHELocal->h_length); 
		memset(&(my_addr.sin_zero),0, 8);// zero the rest of the (unused) struct
		g_strThisIPAddress = inet_ntoa(my_addr.sin_addr);
		g_strThisHostName=pzHostName;
	}


	// create a listener thread for each port we handle
	InfoLog("Starting Listeners:");
	startListeners(2);

	pthread_t listen_thr;
	pthread_create(&listen_thr,	NULL, SwitchBoardPoll, (void *)0 );


}
catch( ... )
{
	return 0;
}
	g_LogStdOut = GetProfile().GetBoolean("System","StdOutLog",false);

	return 1;
}

void stopListeners()
{
	g_listenerIsRebooting = 1;

	// stop the listeners
	ThreadStartupData *pTSD = &g_TSD;
	while(pTSD)
	{
		#ifdef _WIN32    
			closesocket(pTSD->sockfd);
		#elif _HPUX
			shutdown(pTSD->sockfd,2);
		#else // linux, sun, AIX
			close(pTSD->sockfd);
		#endif
		pTSD = pTSD->Next();
	}
}

// maybe the server was started and only 4/5 of the desired listen ports
// were available.  The server starts with a warning.  Any time in the
// future when the desired port becomes available. Calling listenerReboot()
// will close all of the active listeners then re-attempt for 5/5.
// All active transactions will complete normally.  New connections will be
// briefly paused.
void listenerReboot()
{
	stopListeners();
	
	PortableSleep(3,0);

	startListeners(3);
}



// a diagnostic to briefly wake every thread and have it print it's threadID.
// fyi: each thread is assigned a numerical ID beginning at 1  (easier to look at than real thread handles)
void pingPools()
{
	int nPrevLogSetting = g_LogStdOut;
	g_LogStdOut = 1; 


	InfoLog("Main Pool: ",0);
	PingPool(g_ThreadPool,g_ThreadPoolSize);
	InfoLog("\n\nProxy Pool: ",0);
	PingPool(g_ProxyPool,g_ProxyPoolSize);

	g_PingListenThread = 1;
	InfoLog("\n\nListening on ports:[",0);
	PortableSleep(3,0);
	InfoLog("]");

	g_PingListenThread = 0;

	g_LogStdOut = nPrevLogSetting; 
}

// This routine was designed to verify algorithms after a new operating system port.
void server_build_check()
{
	printf("Testing\n");
	InfoLog("Starting build check");

	// view data sizes
	GString str;
	str.Format("Size of--- Short:%d   Integer:%d    Long:%d   VeryLong: %d",sizeof(short),sizeof(int),sizeof(long),sizeof(__int64));
	InfoLog(str);

	// view byte order (1-0-0-0 is Intel,  0-0-0-1 is most others)
	int i = 1;
	char *pi = (char *)&i;
	str = "Byte Order ";
	for(int nByteOrderIndex = 0; nByteOrderIndex < sizeof(int); nByteOrderIndex++)
	{
		str += (int)pi[nByteOrderIndex];
		str += "-";
	}
	InfoLog(str);



	///////////////////////////////////////// Make Test Data ///////////////////////////////////////////////
	char pData1[256];
	char pWorkBuffer[1024];
	char pDataMatch[256];
	memset(pData1,0,256);
	memset(pDataMatch,0,256);
	for(i=0; i<32; i++)
	{
		pData1[i] = 65+i;		// A thru a followed by repeating nulls
		pDataMatch[i] = 65+i;
	}

	///////////////////////////////////////// MD5 ////////////////////////////////////////////////////
	InfoLog("Testing MD5 Hash");
	Hash((void *)pData1, 32, (unsigned char *)pWorkBuffer, 128);
	InfoLog("Hash:",0);
	for(i=0;i<16;i++)
	{
		GString strLog;
		strLog.Format("%d-",(unsigned char)pWorkBuffer[i]);
		InfoLog(strLog,0);
	}


	///////////////////////////////// TwoFish Encrypt/Decrypt  ///////////////////////////////////////
	CipherData en;
	CipherData de;
	InfoLog("Init Cipher Keys");
	InitializeCipherKey(&en,"This is the pass-key",0,DIR_ENCRYPT);
	InitializeCipherKey(&de,"This is the pass-key",0,DIR_DECRYPT);
	int nBitsCrypted;
	try
	{												// uncrypted in				// crypted out
		InfoLog("Testing Encrypt");
		nBitsCrypted = blockEncrypt(&en.ci,&en.ki, (unsigned char *)pData1,32*8,(unsigned char *)pWorkBuffer);
		InfoLog("\nCrypted:",0);
		for(i=0;i<32;i++)
		{
			GString strLog;
			strLog.Format("%d-",(unsigned char)pWorkBuffer[i]);
			InfoLog(strLog,0);
		}
	}
	catch(...)
	{
		InfoLog("\nblockEncrypt failed.");
		return;
	}
	if (nBitsCrypted != 32*8)
	{
		InfoLog("\nblockEncrypt failed point 2.");
		return;
	}
	try
	{
		InfoLog("Testing Decrypt");
		memset(pData1,0,256);							// crypted in					 // uncrypted out	
		nBitsCrypted = blockDecrypt(&de.ci,&de.ki, (unsigned char *)pWorkBuffer,32*8,(unsigned char *)pData1);
		
		if (memcmp(pData1,pDataMatch,256) == 0)
		{
			InfoLog("\nDecrypted OK!");
		}
		else
		{
			InfoLog("\nDecrypted Failed!");
		}
	}
	catch(...) 
	{
		InfoLog("\nDecrypt Failed, point 1");
		return;
	}
	if (nBitsCrypted != 32*8)
	{
		InfoLog("\nDecrypt Failed, point 2");
		return;
	}


	
	/////////////////////////////// Zip/UnZip ////////////////////////////////////////////////////////
	InfoLog("Testing Compress");
	int nZippedLength = 256;
	int cRet = BZ2_bzBuffToBuffCompress( pWorkBuffer, // destination
						   (unsigned int*)&nZippedLength,	
						   pData1, // source
						   256,						// zip the nulls + the data
						   1, //blockSize100k, 
						   0,// verbosity, 
						   30);//workFactor )
	if (cRet != 0)
	{
		GString strLog;
		strLog.Format("Compress failed. code[%d]",cRet);
		InfoLog(strLog);
		return;
	}
	else
	{
		GString strLog;
		strLog.Format("Compressed 256 bytes into %d bytes OK.",nZippedLength);
		InfoLog(strLog);
	}


	InfoLog("Testing Decompress");
	int nUnZippedLength = 256;
	memset(pDataMatch,0,256);
	cRet = BZ2_bzBuffToBuffDecompress 
							   ( pDataMatch,		// destination
								 (unsigned int *)&nUnZippedLength,
								 pWorkBuffer,		// source
								 nZippedLength,
								 1,// or 0 small,
								 0);//verbosity )
	if (cRet != 0)
	{
		GString strLog;
		strLog.Format("Decompress failed. Code[%d]",cRet);
		InfoLog(strLog);
		return;
	}

	if (memcmp(pDataMatch,pData1,256) == 0)
	{
		InfoLog("Decompress OK.");
	}else
	{
		InfoLog("Decompress Failed!");
		return;
	}



	/////////////////////////////////// UUEN/DeCode /////////////////////////////////////////////
	// uuencode into a GString
	InfoLog("Testing UUEncode");
	BUFFER b;
	BufferInit(&b);
	uuencode((unsigned char *)pData1, 256, &b);
	GString strEncoded((char *)b.pBuf, b.cLen);
	
	GString strLog;
	strLog.Format("UUEncoded=%s",(const char *)strEncoded);
	InfoLog(strLog);


	InfoLog("Testing UUDecode");
	BUFFER b2;
	BufferInit(&b2);
	unsigned int nDecoded;
	uudecode((char *)(const char *)strEncoded, &b2, &nDecoded, false);
	if (memcmp(b2.pBuf,pData1,256) == 0 )
	{
		InfoLog("UUDecoded OK");
	}
	else
	{
		InfoLog("UUDecode Failed!");
		return;
	}

	InfoLog("Done Checking Build.");
}

void server_stop()
{
	// stop receiving new connections, end the listen threads
	g_ServerIsShuttingDown = 1;
	
	PortableSleep(2,0);

	// drain the pools
	DestroyThreadPool(g_ProxyPool,g_ProxyPoolSize);
	DestroyThreadPool(g_ThreadPool,g_ThreadPoolSize);

	PortableSleep(2,0);

	InfoLog("ShutDown Complete");
#ifdef _WIN32
	WSACleanup();
	_pthread_processTerminate();
#endif
}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Founder United Business Technologies
United States United States
http://about.me/brian.aberle
https://www.linkedin.com/in/brianaberle
http://SyrianRue.org/Brian

Comments and Discussions