// wmsoaplistener.cpp: implementation of the wmsoaplistener class.
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2003 Robert Cunnings. All rights reserved.
//
// You may copy, modify, distribute or publish this code free of charge.
//
// NO WARRANTY: This software is provided AS IS, without warranty of any kind.
//
//////////////////////////////////////////////////////////////////////
//
// WMSOAPListener class - a class for HTTP protocol support.
// Uses Winsock2, supports HTTP Version 1.0.
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "WMSOAPListener.h"
#include "WMSOAPClientConn.h"
#include "process.h"
#include "base64.h"
#include "wmwsdlparser.h"
#include "XMLDocument.h"
#include "charencode.h"

#define SO_CONNECT_TIME 0x700C

DWORD WINAPI ClientThreadProc(LPVOID lpvParam)
{
	BOOL bRet;
	DWORD dwNumBytes;
	ULONG uKey;
	OVERLAPPED *pOverlapped;
	WMSOAPListener *pListener = (WMSOAPListener *)lpvParam;

	CoInitialize(NULL);
	while (true)
	{
		bRet = GetQueuedCompletionStatus(pListener->m_hCompletionPort,
											&dwNumBytes,
											&uKey,
											&pOverlapped,
											INFINITE);
		if (bRet == FALSE || bRet == ERROR_SUCCESS)
		{
			continue;
		}
		pListener->m_arrClients[uKey]->OnIOCompletion(dwNumBytes);
	}
	CoUninitialize();
	return 0;
}

DWORD WINAPI ListenThreadProc(LPVOID lpvParam)
{
	WMSOAPListener *pListener = (WMSOAPListener *)lpvParam;
	pListener->m_nNumListeningSockets = 0;
	// iterate through available protocols
	for (int i = 0; i < pListener->m_nNumProtocols; i++)
	{
		LPWSAPROTOCOL_INFO pInfo;
		SOCKET s;
		// get one
		pInfo = &pListener->m_pAvailProtocols[i];
		if (pInfo == NULL)
		{
			continue;
		}
		// see if it meets our expectations
		if ((pInfo->dwServiceFlags1 & XP1_CONNECTIONLESS) ||
				(!(pInfo->dwServiceFlags1 & XP1_GUARANTEED_DELIVERY)) ||
				(!(pInfo->dwServiceFlags1 & XP1_GUARANTEED_ORDER)) ||
				(!(pInfo->iAddressFamily == AF_INET)))
		{
			continue;
		}
		// create the socket
		s = WSASocket(0, 0, 0, pInfo, 0, WSA_FLAG_OVERLAPPED);
		if (s == INVALID_SOCKET)
		{
			continue;
		}
		// 10-5-2001
		// structure to hold inet address on stack
		// replaces heap allocated buffer sized per pInfo->iMaxSockAddr value
		// Thanks to Jeremy Frens for pointing out resulting bug
		// when Winsock Proxy Client is in use on same machine
		SOCKADDR_IN addr;
		// initialize address to INADDR_ANY for call to bind()
		memset(&addr, 0, sizeof(SOCKADDR_IN));
		// assign family and port
		addr.sin_family = (u_short)pInfo->iAddressFamily;
		addr.sin_port = htons((u_short)pListener->m_nPort);
        if (pListener->m_strBindAddress.empty() == false)
        {
            // bind to IP address specified in config file
            addr.sin_addr.S_un.S_addr = inet_addr(pListener->m_strBindAddress.c_str());
        }
		// bind
		int nResult = bind(s, (LPSOCKADDR)&addr, sizeof(SOCKADDR_IN));
		if (nResult == SOCKET_ERROR)
		{
			closesocket(s);     
			continue;
		}
		// create event object
		pListener->m_hListenEventObj[pListener->m_nNumListeningSockets] = CreateEvent(NULL, FALSE, FALSE, NULL);
		if (pListener->m_hListenEventObj[pListener->m_nNumListeningSockets] == NULL)
		{
			closesocket(s);     
			continue;
		}
		// turn on notifications
		nResult = WSAEventSelect(s, pListener->m_hListenEventObj[pListener->m_nNumListeningSockets], FD_ACCEPT);
		if (nResult == SOCKET_ERROR)
		{
			CloseHandle(pListener->m_hListenEventObj[pListener->m_nNumListeningSockets]);
			closesocket(s);     
			continue;
		}
		// now listen
		nResult = listen(s, SOMAXCONN);
		if (nResult == SOCKET_ERROR)
		{
			CloseHandle(pListener->m_hListenEventObj[pListener->m_nNumListeningSockets]);
			closesocket(s);     
			continue;
		}
		// fill in socket handle
		pListener->m_ListeningSockets[pListener->m_nNumListeningSockets].hSocket = s;
		// fill in protocol info
		pListener->m_ListeningSockets[pListener->m_nNumListeningSockets].ProtoInfo = pInfo;
		if (++pListener->m_nNumListeningSockets == MAX_LISTENING_SOCKETS)
		{
			break;
		}
	}
	while (true)
	{
		DWORD dwIndex;
		dwIndex = WaitForMultipleObjects(pListener->m_nNumListeningSockets,
											pListener->m_hListenEventObj,
											FALSE,
											INFINITE);
		pListener->OnAccept(dwIndex - WAIT_OBJECT_0);
	}
	return 0;
}

VOID CALLBACK ListenerTimerProc(HWND hWnd, UINT uMsg, UINT uID, DWORD dwTickCount)
{
	_AtlModule.m_pSOAPListener->OnWatchdogTimer();
}

void WMSOAPListener::OnWatchdogTimer()
{
	EnterCriticalSection(&m_CSClients);
	for (int i = 0; i < m_nNumClients; i++)
	{
		WMSOAPClientConn *pClient = m_arrClients[i];
		if (pClient != NULL)
		{
			int nSize, nTime;
			SOCKET s = pClient->m_Connection.m_hSocket;
			if (s != INVALID_SOCKET)
			{
				nSize = sizeof(nTime);
				if (getsockopt(s, SOL_SOCKET, SO_CONNECT_TIME, (char *)&nTime, &nSize) != SOCKET_ERROR)
				{
					if (nTime > m_nClientTimeout)
					{
						pClient->Terminate();
						_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Client connection timeout"));
					}
				}
			}
		}
	}
	if (m_bFlushWSDLCache == true && m_dwNumConnections == 0)
	{
		FlushWSDLCache();
	}
	m_LogMgr.FlushLogFiles();
	LeaveCriticalSection(&m_CSClients);
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

WMSOAPListener::WMSOAPListener()
{
	m_nNumProtocols = 0;
	m_nPort = 80;
	m_nNumWorkThreads = 8;
	m_nNumClients = 5;
	m_nClientTimeout = 20000;
	m_hCompletionPort = NULL;
	m_bResolveClientname = false;
	m_uWatchDogTimerID = 0;
	m_uWatchDogTimerInterval = 10000;
	m_dwNumConnections = 0;
	m_dwNumConnectTotal = 0;
	m_dwNumBytesSent = 0;
	m_dwNumHTTPErrors = 0;
	m_dwNumInternalErrors = 0;
	m_dwNumNetworkErrors = 0;
	m_uLastTickCount = GetTickCount();
	m_bFlushWSDLCache = false;
	InitializeCriticalSection(&m_CSClients);
	InitializeCriticalSection(&m_CSProperties);
}

WMSOAPListener::~WMSOAPListener()
{
	Stop();
	DeleteCriticalSection(&m_CSClients);
	DeleteCriticalSection(&m_CSProperties);
	// empty WSDL cache
	map<string, WSDLCacheEntry *>::iterator iM;
	for (iM = m_mapWSDLCacheEntries.begin();
			iM != m_mapWSDLCacheEntries.end(); iM++)
	{
		WSDLCacheEntry* pEntry = (*iM).second;
		if (pEntry->pIWSDLParser != NULL)
		{
			pEntry->pIWSDLParser->Release();
		}
		delete pEntry;
	}
    // empty SOAP service description map
    map<string, SOAPSvcDesc *>::iterator iM2;
	for (iM2 = m_mapSOAPSvcDescriptions.begin();
			iM2 != m_mapSOAPSvcDescriptions.end(); iM2++)
	{
		SOAPSvcDesc* pDesc = (*iM2).second;
		if (pDesc != NULL)
		{
    		delete pDesc;
		}
	}
}

//////////////////////////////////////////////////////////////////////
// Methods
//////////////////////////////////////////////////////////////////////

void WMSOAPListener::UpdateNumBytesSent(DWORD dwNumBytes)
{
	m_dwNumBytesSent += dwNumBytes;
}

DWORD WMSOAPListener::GetNumBytesSent()
{
	return m_dwNumBytesSent;
}

void WMSOAPListener::IncNumHTTPErrors()
{
	m_dwNumHTTPErrors++;
}

DWORD WMSOAPListener::GetNumHTTPErrors()
{
	return m_dwNumHTTPErrors;
}

void WMSOAPListener::IncNumInternalErrors()
{
	m_dwNumInternalErrors++;
}

DWORD WMSOAPListener::GetNumInternalErrors()
{
	return m_dwNumInternalErrors;
}

void WMSOAPListener::IncNumNetworkErrors()
{
	m_dwNumNetworkErrors++;
}

DWORD WMSOAPListener::GetNumNetworkErrors()
{
	return m_dwNumNetworkErrors;
}

DWORD WMSOAPListener::GetNumConnectTotal()
{
	return m_dwNumConnectTotal;
}
// server settings
int WMSOAPListener::GetListenPort()
{
	return m_nPort;
}

void WMSOAPListener::SetListenPort(int nPort)
{
	m_nPort = nPort;
}

int WMSOAPListener::GetNumWorkThreads()
{
	return m_nNumWorkThreads;
}

void WMSOAPListener::SetNumWorkThreads(int nNumThreads)
{
	m_nNumWorkThreads = nNumThreads;
}

int WMSOAPListener::GetNumClientConn()
{
	return m_nNumClients;
}

void WMSOAPListener::SetNumClientConn(int nNumClients)
{
	m_nNumClients = nNumClients;
}

int WMSOAPListener::GetClientTimeout()
{
	return m_nClientTimeout;
}

void WMSOAPListener::SetClientTimeout(int nNumSecs)
{
	m_nClientTimeout = nNumSecs;
}

ULONG WMSOAPListener::GetWatchdogTimer()
{
	return m_uWatchDogTimerInterval;
}

void WMSOAPListener::SetWatchdogTimer(ULONG uNumSecs)
{
	m_uWatchDogTimerInterval = uNumSecs;
}

BOOL WMSOAPListener::Start()
{
	BOOL bRtn = TRUE;
	// load various properties
	if (bRtn == TRUE && LoadSettings() == FALSE)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Attempt to read configuration file failed!"));
		bRtn = FALSE;
	}
	// intialize Winsock
	if (bRtn == TRUE && InitWinsock2() == FALSE)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Attempt to intialize Winsock failed!"));
		bRtn = FALSE;
	}
	// find protocols
	if (bRtn == TRUE && GetAvailProtocols() == FALSE)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Attempt to enumerate protocols failed!"));
		bRtn = FALSE;
	}
	// start listening
	if (bRtn == TRUE && StartListening() == FALSE)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Attempt to start listening failed!"));
		bRtn = FALSE;
	}
	// open log files
	if (bRtn == TRUE && m_LogMgr.OpenLogFiles() == FALSE)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Attempt to open log files failed!"));
		bRtn = FALSE;
	}
	Sleep(5000);
	if (bRtn)
	{
		char szName[128];
		HRESULT hResult = gethostname(szName, 128);
		if (hResult == SOCKET_ERROR)
		{
			m_strListenerName = "Host name not found!";
		}
		else
		{
			m_strListenerName = szName;
		}
		_AtlModule.LogEvent(/*EVENTLOG_INFORMATION_TYPE,*/ _T("Host: %s... Number of listening sockets: %d"), szName, m_nNumListeningSockets);
	}
	return bRtn;
}

void WMSOAPListener::Stop()
{
	// kill timer
	if (m_uWatchDogTimerID != 0)
	{
		KillTimer(NULL, m_uWatchDogTimerID);
	}
	// close listening sockets
    for (int i = 0; i < m_nNumListeningSockets; i++)
	{
		closesocket(m_ListeningSockets[i].hSocket);
		CloseHandle(m_hListenEventObj[i]);
	}
	// done with IO completion port
	if (m_hCompletionPort != NULL)
	{
		CloseHandle(m_hCompletionPort);
	}
	// done with clients
	for (i = 0; i < m_nNumClients; i++)
	{
		WMSOAPClientConn *pClient = m_arrClients[i];
		delete pClient;
	}
	// cleanup Winsock
	WSACleanup();
}

BOOL WMSOAPListener::InitWinsock2()
{
    int           nError;              // catches return value of WSAStartup 
    WORD          wVersionRequested;   // passed to WSAStartup 
    WSADATA       WsaData;             // receives data from WSAStartup 
    BOOL          bRtn = TRUE;         // return value 
 
    // Start WinSock 2.  If it fails, we don't need to call 
    // WSACleanup(). 
    wVersionRequested = MAKEWORD(VERSION_MAJOR, VERSION_MINOR); 
    nError = WSAStartup(wVersionRequested, &WsaData); 
    if (nError)
	{ 
        bRtn = FALSE; 
    }
	else
	{ 
        // Now confirm that the WinSock 2 DLL supports the exact version 
        // we want. If not, make sure to call WSACleanup(). 
        if (LOBYTE(WsaData.wVersion) != VERSION_MAJOR || 
            HIBYTE(WsaData.wVersion) != VERSION_MINOR)
		{ 
            WSACleanup(); 
            bRtn = FALSE; 
        } 
    } 
    return(bRtn); 
 
}

BOOL WMSOAPListener::GetAvailProtocols()
{
	DWORD dwNumBytes = 0;
	m_nNumProtocols = WSAEnumProtocols(NULL, NULL, &dwNumBytes);
	if (m_nNumProtocols == SOCKET_ERROR && WSAGetLastError() != WSAENOBUFS)
	{
		return FALSE;
	}
	m_pAvailProtocols = (LPWSAPROTOCOL_INFO)malloc(dwNumBytes);
	if (m_pAvailProtocols == NULL)
	{
		return FALSE;
	}
	m_nNumProtocols = WSAEnumProtocols(NULL, m_pAvailProtocols, &dwNumBytes);
	if (m_nNumProtocols == SOCKET_ERROR)
	{
		return FALSE;
	}
	return TRUE;
}

BOOL WMSOAPListener::StartListening()
{
	// create completion port
	m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, m_nNumWorkThreads);
	if (m_hCompletionPort == NULL)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "Attempt to create IO Completion Port failed!");
		return FALSE;
	}
	// create thread pool
	for (int i = 0; i < m_nNumWorkThreads; i++)
	{
		DWORD dwThreadID;
		HANDLE hThreadHandle;

		hThreadHandle = CreateThread(NULL, 0, ClientThreadProc, LPVOID(this), 0, &dwThreadID);
		if (hThreadHandle == NULL)
		{
			_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "Attempt to create Worker thread failed!");
			return FALSE;
		}
	}
	// create client object pool
	for (i = 0; i < m_nNumClients; i++)
	{
		WMSOAPClientConn *pClient = new WMSOAPClientConn(this, i);
		if (pClient == NULL)
		{
			_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "Attempt to create Client object failed!");
			return FALSE;
		}
		pClient->InitializeStateMachine();
		m_arrClients.push_back(pClient);
		m_dequeClients.push_back((DWORD)i);
	}
	// create listener thread
	DWORD ListenThreadID;
	HANDLE hThread = BEGINTHREADEX(NULL,
									0,
									ListenThreadProc,
									LPVOID(this),
									0,
									&ListenThreadID);
	if (hThread == NULL)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "Attempt to create Listener thread failed!");
		return FALSE;
	}
	// close handle
	CloseHandle(hThread);
	// start watchdog timer
	m_uWatchDogTimerID = SetTimer(NULL, WATCHDOG_TIMER_ID, m_uWatchDogTimerInterval * 1000L, ListenerTimerProc);
	if (m_uWatchDogTimerID == 0)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "Attempt to create Watchdog Timer failed!");
		return FALSE;
	}
	return TRUE;
}

void WMSOAPListener::OnAccept(DWORD dwIndex)
{
	// accept connection
	SOCKET s = WSAAccept(m_ListeningSockets[dwIndex].hSocket, NULL, NULL, NULL, (DWORD)NULL);
	if (s == INVALID_SOCKET)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "Attempt to accept connection failed!");
		return;
	}
	EnterCriticalSection(&m_CSClients);
	// at full capacity?
	if (IsClientAvail() == FALSE)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "At full capacity...refused connection!");
		closesocket(s);
		LeaveCriticalSection(&m_CSClients);
		return;
	}
	// get a client
	ULONG uClientIndex = DeQueueClient();
	if (CreateIoCompletionPort((HANDLE)s, m_hCompletionPort, uClientIndex, m_nNumWorkThreads) == NULL)
	{
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "Failed to intialize Client!");
		closesocket(s);
		ReQueueClient(uClientIndex);
		LeaveCriticalSection(&m_CSClients);
		return;
	}
	WMSOAPClientConn *pClient = m_arrClients[uClientIndex];
	// update counters
	m_dwNumConnections++;
	m_dwNumConnectTotal++;
	// start up client
	pClient->Initialize(s);
	LeaveCriticalSection(&m_CSClients);
}

void WMSOAPListener::ReQueueClient(ULONG uIndex)
{
	// put index back into deque
	EnterCriticalSection(&m_CSClients);
	m_dequeClients.push_back(uIndex);
	m_dwNumConnections--;
	LeaveCriticalSection(&m_CSClients);
}

ULONG WMSOAPListener::DeQueueClient()
{
	ULONG uClientIndex = m_dequeClients.front();
	m_dequeClients.pop_front();
	return uClientIndex;
}

BOOL WMSOAPListener::IsClientAvail()
{
	return (m_dequeClients.size() > 0 ? TRUE : FALSE);
}

BOOL WMSOAPListener::LoadSettings()
{
    WMXMLDocument doc;
    // read in configuration file
    char szBuffer[256];
    GetModuleFileName(NULL, szBuffer, 256);
    szBuffer[255] = 0;
    string strPath = szBuffer;
    string::size_type pos = strPath.rfind("\\");
    strPath = strPath.substr(0, pos);
    strPath += "\\wmss-config.xml";
    if (doc.Read(strPath) == false)
    {
        return FALSE;
    }
    m_strConfigFilePath = strPath;
    map<wstring, wstring> mapNS;
    WMXMLElement *pValue;
    if (doc.SelectElems(wstring(L"/listenAddress"), mapNS) == true)
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_strBindAddress, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/listenPort"), mapNS) == false)
    {
        // default to 80
        m_nPort = 80;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeInt(m_nPort, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/numWorkThreads"), mapNS) == false)
    {
        // default to 8
        m_nNumWorkThreads = 8;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeInt(m_nNumWorkThreads, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/maxClients"), mapNS) == false)
    {
        // default to 16
        m_nNumClients = 16;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeInt(m_nNumClients, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/clientTimeout"), mapNS) == false)
    {
        // default to 60 seconds
        m_nClientTimeout = 60;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeInt(m_nClientTimeout, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/watchdogInterval"), mapNS) == false)
    {
        // default to 30 seconds
        m_uWatchDogTimerInterval = 30;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeUInt(m_uWatchDogTimerInterval, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/wsdlCacheSize"), mapNS) == false)
    {
        // default to 20
        m_nWSDLFileCacheSize = 20;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeInt(m_nWSDLFileCacheSize, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/docBasePath"), mapNS) == false)
    {
        // default
        char szBuffer[256];
        GetModuleFileName(NULL, szBuffer, 256);
        szBuffer[255] = 0;
        m_strDocBasePath = szBuffer;
        string::size_type pos = m_strDocBasePath.rfind("\\");
        m_strDocBasePath = m_strDocBasePath.substr(0, pos);
        m_strDocBasePath += "\\docroot\\";
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_strDocBasePath, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/defaultDocument"), mapNS) == false)
    {
        // default
        m_strDocDefault = "default.htm";
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_strDocDefault, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/enableServerLog"), mapNS) == false)
    {
        // default to false
        m_LogMgr.m_bServerLogEnable = false;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeBoolean(m_LogMgr.m_bServerLogEnable, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/serverLogPath"), mapNS) == true)
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_LogMgr.m_strServerLogPath, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/enableTraceLog"), mapNS) == false)
    {
        // default to false
        m_LogMgr.m_bSOAPTraceLogEnable = false;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeBoolean(m_LogMgr.m_bSOAPTraceLogEnable, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/traceLogPath"), mapNS) == true)
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_LogMgr.m_strSOAPTraceLogPath, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/enableUserAgentLog"), mapNS) == false)
    {
        // default to false
        m_LogMgr.m_bUserAgentLogEnable = false;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeBoolean(m_LogMgr.m_bUserAgentLogEnable, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/userAgentLogPath"), mapNS) == true)
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_LogMgr.m_strUserAgentLogPath, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    if (doc.SelectElems(wstring(L"/logRotationInterval"), mapNS) == true)
    {
        pValue = doc.NextElem();
        unsigned int uHours;
        if (doc.DeserializeUInt(uHours, pValue) == false)
        {
            // bad data 
            return FALSE;
        }
        // avoid overflowing unsigned int holding msec
        if (uHours > 1193) uHours = 1193;
        m_LogMgr.m_uRotationPeriod = uHours * 3600000;
    }
    if (doc.SelectElems(wstring(L"/enableNameLookup"), mapNS) == false)
    {
        // default to false
        m_bResolveClientname = false;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeBoolean(m_bResolveClientname, pValue) == false)
        {
            // bad data
            return FALSE;
        }
    }
    // load MIME type mappings
    if (doc.SelectElems(wstring(L"/mimeMappings/item"), mapNS) == false)
    {
		// fill in a few defaults
		m_mapMIMETypes.insert(map<string, string>::value_type(".GIF", "image/gif"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".HTM", "text/html"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".HTML", "text/html"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".TXT", "text/plain"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".WSDL", "text/xml"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".XML", "text/xml"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".XSD", "text/xml"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".JPG", "image/jpeg"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".JPEG", "image/jpeg"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".DOC", "application/msword"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".PDF", "application/pdf"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".RTF", "application/rtf"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".TIF", "image/tiff"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".TIFF", "image/tiff"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".ZIP", "application/zip"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".MPG", "video/mpeg"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".MPEG", "video/mpeg"));
		m_mapMIMETypes.insert(map<string, string>::value_type(".PS", "application/postscript"));
    }
    else
    {
        for (WMXMLElement *pValue = doc.NextElem(); pValue != NULL; pValue = doc.NextElem())
        {
            WMXMLElement *pExt = pValue->GetChildElement(wstring(L""), wstring(L"extension"));
            WMXMLElement *pType = pValue->GetChildElement(wstring(L""), wstring(L"mimeType"));
            if (pExt == NULL || pType == NULL)
            {
                continue;
            }
            string strTemp;
            if (doc.DeserializeString(strTemp, pExt) == false)
            {
                // bad data
                return FALSE;
            }
            string::size_type pos;
            for (pos = 0; pos < strTemp.length(); pos++)
            {
                strTemp[pos] = toupper(strTemp[pos]);
            }
		    string strKey = strTemp;
            if (doc.DeserializeString(strTemp, pType) == false)
            {
                // bad data
                return FALSE;
            }
		    m_mapMIMETypes.insert(map<string, string>::value_type(strKey, strTemp));
        }
    }
    // load any service definitions
    if (doc.SelectElems(wstring(L"/endpointList/endpoint"), mapNS) == true)
    {
        for (WMXMLElement *pValue = doc.NextElem(); pValue != NULL; pValue = doc.NextElem())
        {
            string strReqURI, strWsdlDocURL;
            wstring wstrWsdlSvc, wstrWsdlPort, wstrBodyProc, wstrNextHopURL, wstrNodeURI;
            bool bEnabled = false;
            bool bLoopbackEnabled = false;
            bool bExplicitTypingEnabled = false;
            bool bNodeTypeEnabled = false;
            list<wstring> listHeaderProcs;
            list<wstring> listRoles;

            WMXMLElement *pReqURI = pValue->GetChildElement(wstring(L""), wstring(L"requestURI"));
            WMXMLElement *pWsdlDocURL = pValue->GetChildElement(wstring(L""), wstring(L"wsdlDocURL"));
            WMXMLElement *pWsdlSvc = pValue->GetChildElement(wstring(L""), wstring(L"wsdlService"));
            WMXMLElement *pWsdlPort = pValue->GetChildElement(wstring(L""), wstring(L"wsdlPort"));
            WMXMLElement *pBodyProc = pValue->GetChildElement(wstring(L""), wstring(L"bodyProcessingProgId"));
            if (pReqURI == NULL || pWsdlDocURL == NULL || pWsdlSvc == NULL || pWsdlPort == NULL || pBodyProc == NULL)
            {
                // mandatory elements
                continue;
            }
            if (doc.DeserializeString(strReqURI, pReqURI) == false)
            {
                // bad data
                continue;
            }
            if (doc.DeserializeString(strWsdlDocURL, pWsdlDocURL) == false)
            {
                // bad data
                continue;
            }
            if (doc.DeserializeString(wstrWsdlSvc, pWsdlSvc) == false)
            {
                // bad data
                continue;
            }
            if (doc.DeserializeString(wstrWsdlPort, pWsdlPort) == false)
            {
                // bad data
                continue;
            }
            if (doc.DeserializeString(wstrBodyProc, pBodyProc) == false)
            {
                // bad data
                continue;
            }
            WMXMLElement *pHeaderProc = pValue->GetChildElement(wstring(L""), wstring(L"headerProcessingProgIds"));
            if (pHeaderProc != NULL)
            {
                ChildElementsList::iterator iL;
                for (iL = pHeaderProc->GetChildElements().begin(); iL != pHeaderProc->GetChildElements().end(); iL++)
                {
                    WMXMLElement *pItem = *iL;
                    if (pItem->GetLocalName() == L"progId")
                    {
                        wstring wstrTemp;
                        if (doc.DeserializeString(wstrTemp, pItem) == false)
                        {
                            // bad data
                            continue;
                        }
                        listHeaderProcs.push_back(wstrTemp);
                    }
                }
            }
            WMXMLElement *pRoles = pValue->GetChildElement(wstring(L""), wstring(L"roles"));
            if (pRoles != NULL)
            {
                ChildElementsList::iterator iL;
                for (iL = pRoles->GetChildElements().begin(); iL != pRoles->GetChildElements().end(); iL++)
                {
                    WMXMLElement *pItem = *iL;
                    if (pItem->GetLocalName() == L"uri")
                    {
                        wstring wstrTemp;
                        if (doc.DeserializeString(wstrTemp, pItem) == false)
                        {
                            // bad data
                            continue;
                        }
                        listRoles.push_back(wstrTemp);
                    }
                }
            }
            WMXMLElement *pNodeURI = pValue->GetChildElement(wstring(L""), wstring(L"nodeURI"));
            if (pNodeURI != NULL)
            {
                if (doc.DeserializeString(wstrNodeURI, pNodeURI) == false)
                {
                    // bad data
                    continue;
                }
            }
            WMXMLElement *pNextHopURL = pValue->GetChildElement(wstring(L""), wstring(L"nextHopURL"));
            if (pNextHopURL != NULL)
            {
                if (doc.DeserializeString(wstrNextHopURL, pNextHopURL) == false)
                {
                    // bad data
                    continue;
                }
            }
            WMXMLElement *pEnabled = pValue->GetChildElement(wstring(L""), wstring(L"enabled"));
            if (pEnabled != NULL)
            {
                if (doc.DeserializeBoolean(bEnabled, pEnabled) == false)
                {
                    // bad data
                    continue;
                }
            }
            WMXMLElement *pExpTypingEnabled = pValue->GetChildElement(wstring(L""), wstring(L"enableExplicitTyping"));
            if (pExpTypingEnabled != NULL)
            {
                if (doc.DeserializeBoolean(bExplicitTypingEnabled, pExpTypingEnabled) == false)
                {
                    // bad data
                    continue;
                }
            }
            WMXMLElement *pNodeTypeEnabled = pValue->GetChildElement(wstring(L""), wstring(L"enableNodeType"));
            if (pNodeTypeEnabled != NULL)
            {
                if (doc.DeserializeBoolean(bNodeTypeEnabled, pNodeTypeEnabled) == false)
                {
                    // bad data
                    continue;
                }
            }
            WMXMLElement *pLoopbackEnabled = pValue->GetChildElement(wstring(L""), wstring(L"enableLoopback"));
            if (pLoopbackEnabled != NULL)
            {
                if (doc.DeserializeBoolean(bLoopbackEnabled, pLoopbackEnabled) == false)
                {
                    // bad data
                    continue;
                }
            }
            // see if this is URI given in form "/whatever/*" to 
            // indicate that extra path components may be present
            bool bExtraPathEnabled = false;
            int nLen = strReqURI.length();
            if (strReqURI[nLen - 1] == '*' && strReqURI[nLen - 2] == '/')
            {
                strReqURI = strReqURI.substr(0, nLen - 2);
                bExtraPathEnabled = true;
            }
            SOAPSvcDesc *pSvc = new SOAPSvcDesc(strReqURI,
                                                strWsdlDocURL,
                                                wstrWsdlSvc,
                                                wstrWsdlPort,
                                                wstrBodyProc,
                                                wstrNextHopURL,
                                                listRoles,
                                                wstrNodeURI,
                                                bEnabled,
                                                bExplicitTypingEnabled,
                                                bNodeTypeEnabled,
                                                bExtraPathEnabled,
                                                listHeaderProcs,
                                                bLoopbackEnabled);
            m_mapSOAPSvcDescriptions.insert(map<string, SOAPSvcDesc *>::value_type(strReqURI, pSvc));
        }
    }
	return TRUE;
}

void WMSOAPListener::StoreSettings()
{
    WMXMLDocument doc;
    WMXMLElement *pRoot = doc.CreateRootElem( wstring(L""), wstring(L""), wstring(L"configuration"));
    // server properties
    WMXMLElement *pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"listenAddress"));
    doc.SerializeString(m_strBindAddress, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"listenPort"));
    doc.SerializeInt(m_nPort, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"numWorkThreads"));
    doc.SerializeInt(m_nNumWorkThreads, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"maxClients"));
    doc.SerializeInt(m_nNumClients, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"clientTimeout"));
    doc.SerializeInt(m_nClientTimeout, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"watchdogInterval"));
    doc.SerializeUInt(m_uWatchDogTimerInterval, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"wsdlCacheSize"));
    doc.SerializeInt(m_nWSDLFileCacheSize, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"enableNameLookup"));
    doc.SerializeBoolean(m_bResolveClientname, pChild);
	// HTML props
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"docBasePath"));
    doc.SerializeString(m_strDocBasePath, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"defaultDocument"));
    doc.SerializeString(m_strDocDefault, pChild);
	// store logging props
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"enableServerLog"));
    doc.SerializeBoolean(m_LogMgr.m_bServerLogEnable, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"serverLogPath"));
    doc.SerializeString(m_LogMgr.m_strServerLogPath, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"enableTraceLog"));
    doc.SerializeBoolean(m_LogMgr.m_bSOAPTraceLogEnable, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"traceLogPath"));
    doc.SerializeString(m_LogMgr.m_strSOAPTraceLogPath, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"enableUserAgentLog"));
    doc.SerializeBoolean(m_LogMgr.m_bUserAgentLogEnable, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"userAgentLogPath"));
    doc.SerializeString(m_LogMgr.m_strUserAgentLogPath, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"logRotationInterval"));
    // convert from hours to msec
    unsigned int uHours = m_LogMgr.m_uRotationPeriod / 3600000;
    doc.SerializeUInt(uHours, pChild);
	// SOAP endpoints
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"endpointList"));
	map<string, SOAPSvcDesc *>::iterator iM;
	for (iM = m_mapSOAPSvcDescriptions.begin(); iM != m_mapSOAPSvcDescriptions.end(); iM++)
	{
        SOAPSvcDesc *svc = (*iM).second;
        WMXMLElement *pEndpoint = doc.CreateChildElem(pChild, wstring(L""), wstring(L""), wstring(L"endpoint"));
        WMXMLElement *pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"requestURI"));
        string strTemp = svc->m_strReqURI;
        if (svc->m_bExtraPathEnabled == true)
        {
            strTemp += "/*";
        }
        doc.SerializeString(strTemp, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"wsdlService"));
        doc.SerializeString(svc->m_wstrWsdlSvc, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"wsdlPort"));
        doc.SerializeString(svc->m_wstrWsdlPort, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"wsdlDocURL"));
        doc.SerializeString(svc->m_strWsdlDocURL, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"bodyProcessingProgId"));
        doc.SerializeString(svc->m_wstrBodyProcProgId, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"headerProcessingProgIds"));
        for (list<wstring>::iterator iL = svc->m_listHeaderProcProgIds.begin();
                                     iL != svc->m_listHeaderProcProgIds.end();
                                     iL++)
        {
            WMXMLElement *pItem = doc.CreateChildElem(pMember, wstring(L""), wstring(L""), wstring(L"progId"));
            doc.SerializeString(*iL, pItem);
        }
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"roles"));
        for (list<wstring>::iterator iL = svc->m_listRoles.begin();
                                     iL != svc->m_listRoles.end();
                                     iL++)
        {
            WMXMLElement *pItem = doc.CreateChildElem(pMember, wstring(L""), wstring(L""), wstring(L"uri"));
            doc.SerializeString(*iL, pItem);
        }
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"nodeURI"));
        doc.SerializeString(svc->m_wstrNodeURI, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"nextHopURL"));
        doc.SerializeString(svc->m_wstrNextHopURL, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"enabled"));
        doc.SerializeBoolean(svc->m_bEnabled, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"enableExplicitTyping"));
        doc.SerializeBoolean(svc->m_bExplicitTypingEnabled, pMember);
        pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"enableNodeType"));
        doc.SerializeBoolean(svc->m_bNodeTypeEnabled, pMember);

        if (svc->m_bLoopbackEnabled == true)
        {
            pMember = doc.CreateChildElem(pEndpoint, wstring(L""), wstring(L""), wstring(L"enableLoopback"));
            doc.SerializeBoolean(svc->m_bLoopbackEnabled, pMember);
        }
	}
	// MIME map
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"mimeMappings"));
    map<string, string>::iterator iM2;
	for (iM2 = m_mapMIMETypes.begin(); iM2 != m_mapMIMETypes.end(); iM2++)
	{
        WMXMLElement *pItem = doc.CreateChildElem(pChild, wstring(L""), wstring(L""), wstring(L"item"));
        WMXMLElement *pMember = doc.CreateChildElem(pItem, wstring(L""), wstring(L""), wstring(L"extension"));
        doc.SerializeString((*iM2).first, pMember);
        pMember = doc.CreateChildElem(pItem, wstring(L""), wstring(L""), wstring(L"mimeType"));
        doc.SerializeString((*iM2).second, pMember);
	}
    // write it out
    doc.Write(m_strConfigFilePath);
}

IDispatch *WMSOAPListener::FindWSDLCacheEntry(const string& strWSDLFileURL)
{
	EnterCriticalSection(&m_CSProperties);
	IDispatch *pIWSDLParser;
	map<string, WSDLCacheEntry *>::iterator iM;
	if ((iM = m_mapWSDLCacheEntries.find(strWSDLFileURL)) == m_mapWSDLCacheEntries.end())
	{
		pIWSDLParser = NULL;
	}
	else
	{
		WSDLCacheEntry* pEntry = (*iM).second;
		pIWSDLParser = pEntry->pIWSDLParser;
		pEntry->dwTickCount = GetTickCount();
	}
	LeaveCriticalSection(&m_CSProperties);
	return pIWSDLParser;
}

void WMSOAPListener::SetFlushWSDLCacheFlag()
{
	EnterCriticalSection(&m_CSProperties);
	m_bFlushWSDLCache = true;
	LeaveCriticalSection(&m_CSProperties);
}

void WMSOAPListener::FlushWSDLCache()
{
	EnterCriticalSection(&m_CSProperties);
	WSDLCacheEntry* pEntry;
		map<string, WSDLCacheEntry *>::iterator iM;
	for (iM = m_mapWSDLCacheEntries.begin();
			iM != m_mapWSDLCacheEntries.end(); iM++)
	{
		pEntry = (*iM).second;
		if (pEntry->pIWSDLParser != NULL)
		{
			pEntry->pIWSDLParser->Release();
		}
		delete pEntry;
		continue;
	}
	m_mapWSDLCacheEntries.clear();
    m_bFlushWSDLCache = false;
	_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "WSDL cache flushed");
	LeaveCriticalSection(&m_CSProperties);
}

bool WMSOAPListener::AddWSDLCacheEntry(const string& strWSDLFileURL,
										IDispatch *pIWSDLParser)
{
	if (pIWSDLParser == NULL)
    {
		return false;
    }
	EnterCriticalSection(&m_CSProperties);
	// check for clock rollover (every 49 days)
	unsigned int uTickCount = GetTickCount();
	if (m_uLastTickCount > uTickCount)
	{
		m_bFlushWSDLCache = true;
		LeaveCriticalSection(&m_CSProperties);
		return false;
	}
	WSDLCacheEntry* pEntry;
	if (m_mapWSDLCacheEntries.size() >= (unsigned int)m_nWSDLFileCacheSize)
	{
		// full...LRU entry will get dropped
		map<string, WSDLCacheEntry *>::iterator iM;
		map<string, WSDLCacheEntry *>::iterator iOldest;
		iOldest = m_mapWSDLCacheEntries.begin();
		WSDLCacheEntry* pOldestEntry = (*iOldest).second;
		for (iM = m_mapWSDLCacheEntries.begin();
				iM != m_mapWSDLCacheEntries.end(); iM++)
		{
			pEntry = (*iM).second;
			if (pEntry->dwTickCount < pOldestEntry->dwTickCount)
			{
				iOldest = iM;
				pOldestEntry = pEntry;
			}
		}
		_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ "WSDL cache limit hit...dropping LRU entry!");
		// now dump LRU entry
		if (pOldestEntry->pIWSDLParser != NULL)
		{
			pOldestEntry->pIWSDLParser->Release();
		}
		delete pOldestEntry;
		m_mapWSDLCacheEntries.erase(iOldest);
	}
	// update counter
	m_uLastTickCount = uTickCount;
	// create new entry
	bool bRtn = false;
	pEntry = new WSDLCacheEntry;
	if (pEntry != NULL)
	{
		pEntry->dwTickCount = uTickCount;
		pEntry->pIWSDLParser = pIWSDLParser;
		m_mapWSDLCacheEntries.insert(map<string, WSDLCacheEntry *>::value_type(strWSDLFileURL, pEntry));
		bRtn = true;
	}
	LeaveCriticalSection(&m_CSProperties);
	return bRtn;
}