// wmsoapsvccp.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "wmsoapsvccp.h"
#include "CWSSPropSheet.h"
#include "..\soap_source\WMMD5.h"
#include "..\soap_source\bytearrayconv.h"
#include "..\soap_source\hexcode.h"
#include "..\soap_source\WMSOAPClient.h"
#include "..\soap_source\charencode.h"
#include "..\wmsoapstruct\wmsoapstruct.h"
#include "..\soap_source\XMLDocFragment.h"
#include "..\soap_source\XMLDocument.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

static wchar_t *g_pwszSoapAuth = L"http://soap-authentication.org/digest/2001/10/";

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	//{{AFX_DATA(CAboutDlg)
	enum { IDD = IDD_ABOUTBOX };
	//}}AFX_DATA

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CAboutDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	//{{AFX_MSG(CAboutDlg)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
	//{{AFX_DATA_INIT(CAboutDlg)
	//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CAboutDlg)
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CAboutDlg)
		// No message handlers
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CWSSCPApp

BEGIN_MESSAGE_MAP(CWSSCPApp, CWinApp)
	//{{AFX_MSG_MAP(CWSSCPApp)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG
//	ON_COMMAND(ID_HELP, CWinApp::OnHelp)
	ON_COMMAND(ID_HELP, CWSSCPApp::OnHelpButton)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CWSSCPApp construction

CWSSCPApp::CWSSCPApp()
{
	// TODO: add construction code here,
	// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CWSSCPApp object

CWSSCPApp theApp;

char *g_pszUserAgent = "WMSS Control Panel/3.2 (C++ SOAP client library)";

/////////////////////////////////////////////////////////////////////////////
// CWSSCPApp initialization

BOOL CWSSCPApp::InitInstance()
{
	AfxEnableControlContainer();

	// Standard initialization
	// If you are not using these features and wish to reduce the size
	//  of your final executable, you should remove from the following
	//  the specific initialization routines you do not need.

#ifdef _AFXDLL
	Enable3dControls();			// Call this when using MFC in a shared DLL
#else
	Enable3dControlsStatic();	// Call this when linking to MFC statically
#endif

	// initialize COM
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

	CWSSPropSheet dlg(_T("White Mesa SOAP Server Control Panel v3.2"));
	m_pMainWnd = &dlg;
	int nResponse = dlg.DoModal();
	if (nResponse == IDOK)
	{
		// TODO: Place code here to handle when the dialog is
		//  dismissed with OK
	}
	else if (nResponse == IDCANCEL)
	{
		// TODO: Place code here to handle when the dialog is
		//  dismissed with Cancel
	}
	m_SOAPClient.Cleanup();
	// uninitialize COM
	CoUninitialize();

	// Since the dialog has been closed, return FALSE so that we exit the
	//  application, rather than start the application's message pump.
	return FALSE;
}

void CWSSCPApp::OnHelpButton()
{
	 CAboutDlg dlg;
	 dlg.DoModal();
}

void CWSSCPApp::BuildRequestHeaders(bool bInitialMsg)
{
	if (bInitialMsg == true)
	{
        // send no header to elicit a challenge from server
        return;
    }
	// fill up name array
	BSTR bstrNames[5];
	bstrNames[0] = SysAllocString(L"Nonce");
	bstrNames[1] = SysAllocString(L"Auth");
	bstrNames[2] = SysAllocString(L"UserID");
	bstrNames[3] = SysAllocString(L"Realm");
	bstrNames[4] = SysAllocString(L"ClientNonce");

	// instantiate struct object
	Iwmsoapstruct *pIwmsoapstructOut;
	HRESULT res = CoCreateInstance( CLSID_wmsoapstruct,
									NULL, CLSCTX_SERVER,
									IID_Iwmsoapstruct,
									(void **) &pIwmsoapstructOut);
	if (FAILED(res))
	{
		return;
	}
	VARIANT_BOOL bResult;
	_variant_t varTemp;
	for (int j = 0;	j < 5; j++)
	{
		switch (j)
		{
			case 0:
				// Server nonce
				if (bInitialMsg == false)
				{
                    varTemp = m_strSN.c_str();
					pIwmsoapstructOut->SetMember(bstrNames[j], varTemp, &bResult);
				}
				break;
			case 1:
				// Auth token
				if (bInitialMsg == false)
				{
				    BuildCN();
					BuildResponse();
					varTemp.parray = ByteArrayFromBuffer(m_Auth, MD5_HASH_SIZE);
					varTemp.vt = VT_ARRAY | VT_UI1;
					pIwmsoapstructOut->SetMember(bstrNames[j], varTemp, &bResult);
				}
				break;
			case 2:
				// User ID
				varTemp = m_strUID.c_str();
				pIwmsoapstructOut->SetMember(bstrNames[j], varTemp, &bResult);
				break;
			case 3:
				// Realm
				varTemp = m_strRealm.c_str();
				pIwmsoapstructOut->SetMember(bstrNames[j], varTemp, &bResult);
				break;
			case 4:
				// Client nonce
                if (bInitialMsg == true)
                {
				    BuildCN();
                }
				// set struct member
				varTemp = m_strCN.c_str();
				pIwmsoapstructOut->SetMember(bstrNames[j], varTemp, &bResult);
				break;
		}
	}
	// now get IDispatch pointer
	IDispatch *pIDispatchOut;
	res = pIwmsoapstructOut->QueryInterface(IID_IDispatch, (void **)&pIDispatchOut);
	if (FAILED(res))
	{
		for (int j = 0; j < 5; j++)
		{
			SysFreeString(bstrNames[j]);
		}
		pIwmsoapstructOut->Release();
		return;
	}
	// release
	pIwmsoapstructOut->Release();
	// set up variant
	_variant_t varContent;
	varContent.vt = VT_DISPATCH;
	varContent.pdispVal = pIDispatchOut;
	// set header
    wstring wstrName;
	if (bInitialMsg == true)
	{
        wstrName = L"InitChallenge";
    }
    else
	{
        wstrName = L"ClientAuth";
    }
	m_SOAPClient.SetRequestHeaderEntry( wstring(g_pwszSoapAuth),
										wstring(L""),
										wstrName,
										varContent,
										true);
	// done with names
	for (j = 0; j < 5; j++)
	{
		SysFreeString(bstrNames[j]);
	}
}

void CWSSCPApp::GetResponseHeaders()
{
	// initialize
	m_bStaleNonce = false;
	m_strAuthStatus.erase();
	// store status code
	_variant_t varContent;
	wstring wstrHeaderNS(g_pwszSoapAuth);
	if ((m_SOAPClient.GetResponseHeaderEntry(wstrHeaderNS, wstring(L"Challenge"), varContent) == true
		|| m_SOAPClient.GetResponseHeaderEntry(wstrHeaderNS, wstring(L"NextChallenge"), varContent) == true)
			&& (varContent.vt & VT_TYPEMASK) == VT_DISPATCH)
	{
		// fill up name array
		BSTR bstrNames[5];
		bstrNames[0] = SysAllocString(L"Status");
		bstrNames[1] = SysAllocString(L"Nonce");
		bstrNames[2] = SysAllocString(L"Realm");
		bstrNames[3] = SysAllocString(L"ClientNonce");
		bstrNames[4] = SysAllocString(L"ServerAuth");
		// get struct object
		IDispatch *pIDispatch = varContent.pdispVal;
		Iwmsoapstruct *pIwmsoapstruct;
		HRESULT res = pIDispatch->QueryInterface(IID_Iwmsoapstruct, (void **)&pIwmsoapstruct);
		if (FAILED(res))
		{
			return;
		}
		_variant_t varTemp;
		VARIANT_BOOL bResult;
		CString strTemp;
		// now iterate over struct members
		for (int j = 0;	j < 5; j++)
		{
			// get member
			pIwmsoapstruct->GetMember(bstrNames[j], &varTemp, &bResult);

			switch (j)
			{
				case 0:
					// Status
					if (bResult == VARIANT_TRUE)
					{
						strTemp = varTemp.bstrVal;
						m_strAuthStatus = (LPCSTR)strTemp;
						// check for stale server nonce error
                        if (m_strAuthStatus == "Unauthenticated.ExpiredNonce")
						{
							m_bStaleNonce = true;
						}
					}
					break;
				case 1:
					// Server nonce
					if (bResult == VARIANT_TRUE)
					{
						strTemp = varTemp.bstrVal;
						m_strSN = (LPCSTR)strTemp;
					}
					break;
				case 2:
					// Realm
					if (bResult == VARIANT_TRUE)
					{
						strTemp = varTemp.bstrVal;
						m_strRealm = (LPCSTR)strTemp;
					}
					break;
				case 3:
					// Client nonce (reflected from server)
					// we don't need it... it is stored internally
					break;
				case 4:
					// Reverse auth token
					if (bResult == VARIANT_TRUE)
					{
						void *pBuffer;
						unsigned long lSize;
						if (BufferFromByteArray(varTemp.parray,
												&pBuffer,
												&lSize) == true)
						{
							memmove(m_RAuth, pBuffer, MD5_HASH_SIZE);
							free(pBuffer);
						}
					}
					break;
			}
			varTemp.Clear();
		}
		// release interface ptr
		pIwmsoapstruct->Release();
		// done with names
		for (j = 0; j < 5; j++)
		{
			SysFreeString(bstrNames[j]);
		}
	}
}

void CWSSCPApp::BuildResponse()
{
	// response = MD5( MD5(uid:realm:pwd):SN:CN )
	string strTemp1 = m_strUID;
	strTemp1 += ":";
    strTemp1 += m_strRealm;
	strTemp1 += ":";
	strTemp1 += m_strPassword;
	string strTemp2;

	WMMD5 md5;
	md5.Update((unsigned char *)strTemp1.c_str(), strTemp1.length());
	md5.FinalizeHexEncoded(strTemp2);

	strTemp1 = strTemp2;
	strTemp1 += ":";
    strTemp1 += m_strSN;
	strTemp1 += ":";
    strTemp1 += m_strCN;

	md5.Update((unsigned char *)strTemp1.c_str(), strTemp1.length());
	md5.Finalize((unsigned long *)m_Auth);
}

void CWSSCPApp::BuildCN()
{
    unsigned char CN[MD5_HASH_SIZE];
    DWORD dwTicks = GetTickCount();
	WMMD5 md5;
	md5.Update((unsigned char *)&dwTicks, sizeof(DWORD));
	md5.Finalize((unsigned long *)CN);
	WMHexCode hexCode;
    m_strCN.erase();
	hexCode.Encode(CN, MD5_HASH_SIZE, m_strCN);
}

bool CWSSCPApp::AuthServer()
{
	// response = MD5( MD5(uid:realm:pwd):SN:CN )
	string strTemp1 = m_strUID;
	strTemp1 += ":";
    strTemp1 += m_strRealm;
	strTemp1 += ":";
	strTemp1 += m_strPassword;
	string strTemp2;
	WMMD5 md5;
	md5.Update((unsigned char *)strTemp1.c_str(), strTemp1.length());
	md5.FinalizeHexEncoded(strTemp2);

	strTemp1 = strTemp2;
	strTemp1 += ":";
    strTemp1 += m_strSN;
	strTemp1 += ":";
    strTemp1 += m_strCN;

	md5.Update((unsigned char *)strTemp1.c_str(), strTemp1.length());
	unsigned char Expected[MD5_HASH_SIZE];
	md5.Finalize((unsigned long *)Expected);
	if (memcmp(Expected, m_RAuth, MD5_HASH_SIZE) != 0)
	{
		return false;
	}
	return true;
}

bool CWSSCPApp::ReadSettings()
{
    WMXMLDocument doc;
    char szBuffer[256];
    GetModuleFileName(NULL, szBuffer, 256);
    szBuffer[255] = 0;
    string strDefault = szBuffer;
    string::size_type pos = strDefault.rfind("\\");
    strDefault = strDefault.substr(0, pos);
    strDefault += "\\cp-config.xml";
    if (doc.Read(strDefault) == false)
    {
        return false;
    }
    // cache filename
    m_strConfigFilePath = strDefault;
    // get data
    map<wstring, wstring> mapNS;
    WMXMLElement *pValue;
    string strTemp;
    if (doc.SelectElems(wstring(L"/hostname"), mapNS) == true)
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(strTemp, pValue) == false)
        {
            // bad data
            return false;
        }
        m_strHost = strTemp.c_str();
    }
    if (doc.SelectElems(wstring(L"/portNbr"), 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"/svcURI"), mapNS) == false)
    {
        m_strSvcURI = "/WMSOAPSvrCP";
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_strSvcURI, pValue) == false)
        {
            // bad data
            return false;
        }
    }
    if (doc.SelectElems(wstring(L"/wsdlServiceName"), mapNS) == false)
    {
        m_strServiceName = "WMSOAPSvrMgr";
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(strTemp, pValue) == false)
        {
            // bad data
            return false;
        }
        m_strServiceName = strTemp.c_str();
    }
    if (doc.SelectElems(wstring(L"/wsdlPortName"), mapNS) == false)
    {
        m_strPortName = "WMSOAPSvrMgrPort";
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(strTemp, pValue) == false)
        {
            // bad data
            return false;
        }
        m_strPortName = strTemp.c_str();
    }
    if (doc.SelectElems(wstring(L"/viewBuffers"), mapNS) == false)
    {
        // default to false
        m_bViewBuffers = false;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeBoolean(m_bViewBuffers, pValue) == false)
        {
            // bad data
            return false;
        }
    }
    if (doc.SelectElems(wstring(L"/useProxy"), mapNS) == false)
    {
        // default to false
        m_bUseProxy = false;
    }
    else
    {
        pValue = doc.NextElem();
        if (doc.DeserializeBoolean(m_bUseProxy, pValue) == false)
        {
            // bad data
            return false;
        }
    }
    if (doc.SelectElems(wstring(L"/proxyHost"), mapNS) == true)
    {
        pValue = doc.NextElem();
        if (doc.DeserializeString(m_strProxyHost, pValue) == false)
        {
            // bad data
            return false;
        }
    }
    if (doc.SelectElems(wstring(L"/proxyPort"), mapNS) == true)
    {
        pValue = doc.NextElem();
        if (doc.DeserializeInt(m_nProxyPort, pValue) == false)
        {
            // bad data
            return false;
        }
    }
    return true;
}

bool CWSSCPApp::WriteSettings()
{
    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"hostname"));
    doc.SerializeString(string((LPCSTR)m_strHost), pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"portNbr"));
    doc.SerializeInt(m_nPort, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"svcURI"));
    doc.SerializeString(m_strSvcURI, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"wsdlServiceName"));
    doc.SerializeString(string((LPCSTR)m_strServiceName), pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"wsdlPortName"));
    doc.SerializeString(string((LPCSTR)m_strPortName), pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"viewBuffers"));
    doc.SerializeBoolean(m_bViewBuffers, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"useProxy"));
    doc.SerializeBoolean(m_bUseProxy, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"proxyHost"));
    doc.SerializeString(m_strProxyHost, pChild);
    pChild = doc.CreateChildElem(pRoot, wstring(L""), wstring(L""), wstring(L"proxyPort"));
    doc.SerializeInt(m_nProxyPort, pChild);
    // write it out
    doc.Write(m_strConfigFilePath);
    return true;
}
