// --------------------------------------------------------------------------
// 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(<ime);
newtime = gmtime(<ime);
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(<ime);
newtime = gmtime(<ime);
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(<ime);
newtime = gmtime(<ime);
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(<ime);
newtime = gmtime(<ime);
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(<ime);
newtime = gmtime(<ime);
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(<ime);
newtime = gmtime(<ime);
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
}