// This file is the metadata version of the key storage object for the w3 server.
// it knows nothing about the LSA storage and only interacts with the metabase. Likewise,
// it does not convert old keyset.exe keys into a newer format. Any old LSA keys should have
// automatically converted to the new metabase format by the setup utility.

// file created 4/1/1997 by BoydM

#include "stdafx.h"
#include "KeyObjs.h"

#include "iiscnfgp.h"
#include "wrapmb.h"

#include "cmnkey.h"
#include "mdkey.h"
#include "mdserv.h"

#include "crackcrt.h"

#include "resource.h"

#include "ListRow.h"
#include "bindsdlg.h"

IMPLEMENT_DYNCREATE(CMDKey, CKey);
#define MAX_LEN                 (METADATA_MAX_NAME_LEN * sizeof(WCHAR))

#define		DEFAULT_PORT		443

extern HINSTANCE	g_hInstance;

//--------------------------------------------------------
CMDKey::CMDKey() :
		m_fUpdateKeys( FALSE ),
		m_fUpdateFriendlyName( FALSE ),
		m_fUpdateIdent( FALSE ),
		m_fUpdateBindings( FALSE ),
        m_pService( NULL )
	{}

//--------------------------------------------------------
CMDKey::CMDKey(CMDKeyService*  pService) :
		m_fUpdateKeys( FALSE ),
		m_fUpdateFriendlyName( FALSE ),
		m_fUpdateIdent( FALSE ),
		m_fUpdateBindings( FALSE ),
        m_pService( pService )
	{}

//--------------------------------------------------------
CMDKey::~CMDKey()
	{}

//-------------------------------------------------------------
void CMDKey::SetName( CString &szNewName )
	{
	CCmnKey::SetName( szNewName );
	m_fUpdateFriendlyName = TRUE;
	}

//-------------------------------------------------------------
// update the key's caption
void CMDKey::UpdateCaption( void )
	{
	// specify the resources to use
	HINSTANCE hOldRes = AfxGetResourceHandle();
	AfxSetResourceHandle( g_hInstance );

	CString	sz;
	// the caption is based on the name of the key
	CString szCaption = m_szName;

	// now we tack on info about the server it is attached to
	szCaption += _T(" <");
	switch( m_rgbszBindings.GetSize() )
		{
		case 0:		// there are no bindings, do nothing
			sz.Empty();
			break;
		case 1:		// if there is only one binding, use it in the brackets
			sz = m_rgbszBindings[0];
			// actually, we need to see if it is the non-localized default string
			if ( sz == MDNAME_DEFAULT )
				sz.LoadString( IDS_DEFAULT );	// load the localized default string
			break;
		default:	// there are multiple bindings, say so
			sz.LoadString( IDS_MULTIPLE_BINDINGS );
			break;
		};
	// close the brackets
	szCaption += sz;
	szCaption += _T(">");

	// and setup the caption
	m_szItemName = szCaption;
	FSetCaption(szCaption);

	// update the icon too
	UpdateIcon();

	// restore the resources
	AfxSetResourceHandle( hOldRes );
	}

//-------------------------------------------------------------
		// make a copy of the key
//-------------------------------------------------------------
CKey* CMDKey::PClone( void )
	{
	CMDKey*	pClone = NULL;

	// TRY to make a new key object
	try
		{
		pClone = new CMDKey(m_pService);

		// copy over all the data
		pClone->CopyDataFrom( this );
		}
	catch( CException e )
		{
		// if the object had been made, delete it
		if ( pClone )
			delete pClone;
		return NULL;
		}

	return (CKey*)pClone;
	}

//-------------------------------------------------------------
// copy the members from a key into this key
//-------------------------------------------------------------
void CMDKey::CopyDataFrom( CKey* pKey )
	{
	// copy over the base data
	CKey::CopyDataFrom( pKey );

	// if the key we are copying from is a MD key, copy over
	// the w3MD specific information as well
	if ( pKey->IsKindOf(RUNTIME_CLASS(CMDKey)) )
		{
		CMDKey* pMDKey = (CMDKey*)pKey;
		m_szName = pMDKey->m_szName;
		}
	else
		{
		m_szName = pKey->m_szItemName;
		}
	}


//-------------------------------------------------------------
void CMDKey::OnProperties()
	{
	// specify the resources to use
	HINSTANCE hOldRes = AfxGetResourceHandle();
	AfxSetResourceHandle( g_hInstance );


    // if this key does not have a signed certificate, do not allow the user
    // to make any bindings to it
    if ( !m_pCertificate )
        {
        AfxMessageBox( IDS_DONT_BIND_UNSIGNED );
        // restore the resources
        AfxSetResourceHandle( hOldRes );
        return;
        }

	// the properties of the w3 key invove its ip address relationship
	CBindingsDlg	dlg( m_pService->m_pszwMachineName );

	// give it this key
	dlg.m_pKey = this;

	// set the instance members of the dialog
	// run the dialog
	if ( dlg.DoModal() == IDOK )
		{
		// cause the name to rebuild
		UpdateCaption();

		// set it dirty
		SetDirty( TRUE );
		}

	// restore the resources
	AfxSetResourceHandle( hOldRes );
	}

//--------------------------------------------------------
// add a binding to the binding list
void CMDKey::AddBinding( LPCSTR psz )
	{
	CString	szBinding = psz;

	// filter out disabled or incomplete key bindings
	if ( (szBinding.Find(MDNAME_INCOMPLETE) >= 0) ||
			(szBinding.Find(MDNAME_DISABLED) >= 0) )
		{
		return;
		}
	
	// add the binding to the list
	m_rgbszBindings.Add( psz );

	// update the caption if we need to
	switch( m_rgbszBindings.GetSize() )
		{
		case 1:	// the display changes on these
		case 2:
			UpdateCaption();
		};
	}

//--------------------------------------------------------
// is a given binding already associated with the key
void CMDKey::RemoveBinding( CString szBinding )
	{
	DWORD	nItems, iItem;

	// scan the binding list
	nItems = m_rgbszBindings.GetSize();
	for ( iItem = 0; iItem < nItems; iItem++ )
		{
		// look for the binding
		if ( szBinding == m_rgbszBindings[iItem] )
			{
			// found it!
			m_rgbszBindings.RemoveAt( iItem );
			m_fUpdateBindings = TRUE;

			// update the caption if we need to
			switch( m_rgbszBindings.GetSize() )
				{
				case 0:	// the display changes on these
				case 1:
					UpdateCaption();
				};
			}
		}
	}

//--------------------------------------------------------
// is a given binding already associated with the key
BOOL CMDKey::FContainsBinding( CString szBinding )
	{
	DWORD	nItems, iItem;

	// scan the binding list
	nItems = m_rgbszBindings.GetSize();
	for ( iItem = 0; iItem < nItems; iItem++ )
		{
		// look for the binding
		if ( szBinding == m_rgbszBindings[iItem] )
			{
			// found it!
			return TRUE;
			}
		}

	// we did not find the binding
	return FALSE;
	}

//--------------------------------------------------------
BOOL CMDKey::FGetIdentString( CString &szIdent )
    {
    // make sure the cert is there
    if ( !m_pCertificate || !m_cbCertificate )
        return FALSE;
    return FGetIdentString( m_pCertificate, m_cbCertificate, szIdent );
    }

//--------------------------------------------------------
BOOL CMDKey::FGetIdentString( PVOID pCert, DWORD cbCert, CString &szIdent )
    {
	// declare the cracker object
	CCrackedCert	cracker;
	// crack the cert
	if ( cracker.CrackCert( (PUCHAR)pCert, cbCert ) )
		{
		DWORD*	pdw = cracker.PGetSerialNumber();
		szIdent.Format( "%d:%d:%d:%d", pdw[0], pdw[1], pdw[2], pdw[3] );
		// success
		return TRUE;
		}
    return FALSE;
    }


//--------------------------------------------------------
// does a given key name exist already in the metabase?
BOOL CMDKey::FGetIdentString( CWrapMetaBase* pWrap, PCHAR pszObj, CString &szIdent )
	{
	BOOL		fAnswer = FALSE;
    DWORD		cbData;
    PVOID		pData;
    CString     sz;

    // if this is an incomplete key, fail
    if ( _tcsncmp(MDNAME_INCOMPLETE, pszObj, _tcslen(MDNAME_INCOMPLETE)) == 0 )
        return FALSE;

	// try and ready the cached ident directly.
	BOOL f = pWrap->GetString( pszObj, MD_SSL_IDENT, IIS_MD_UT_SERVER,
            sz.GetBuffer(MAX_LEN), MAX_LEN);
    sz.ReleaseBuffer();
    if ( f )
		{
		// good. It was cached.
		szIdent = sz;
        m_szIdent = szIdent;
		return TRUE;
		}

	// drat. We haven't cached the ident for this key before. we need to get it. This
	// means loading the certificate - and cracking it to get the serial number. If there
	// is no certificate (an incomplete key) we return false.
    pData = pWrap->GetData( pszObj, MD_SSL_PUBLIC_KEY, IIS_MD_UT_SERVER,
                BINARY_METADATA, &cbData, 0 );
	// we got the certificate and can now crack it
    if ( pData )
        {
        m_fUpdateIdent = FGetIdentString( (PUCHAR)pData, cbData, szIdent );
        fAnswer = m_fUpdateIdent;
		// cache the ident in memory. It will get written out on Commit
		m_szIdent = szIdent;
/*
		// declare the cracker object
		CCrackedCert	cracker;
		// crack the cert
		if ( cracker.CrackCert( (PUCHAR)pData, cbData ) )
			{
			DWORD*	pdw = cracker.PGetSerialNumber();
			szIdent.Format( "%d:%d:%d:%d", pdw[0], pdw[1], pdw[2], pdw[3] );
			// cache the ident in memory. It will get written out on Commit
			m_szIdent = szIdent;
			m_fUpdateIdent = TRUE;
			// success
			fAnswer = TRUE;
			}
*/
		// free the buffer
		pWrap->FreeWrapData( pData );
		}
	else
		{
		// we did not get the certificate - return FALSE
		// fAnswer is already set to false
		}

	// return the answer;
	return fAnswer;
	}

//--------------------------------------------------------
BOOL CMDKey::FWriteKey( CWrapMetaBase* pWrap, DWORD iKey, CStringArray* prgbszTracker )
	{
	BOOL		f;
	DWORD		nBindings = m_rgbszBindings.GetSize();
	CString		szBinding;
	BOOL		fUpdateAll = FALSE;

	// if there are no assigned bindings, the key still gets stored with a object
	// name in the format of "disabled{iKey}". and if it is incomplete, then store
	// it with the name "incomplete{iKey}" Because the iKey can change and we don't
	// want any conflicts, re-write them
	if ( nBindings == 0 )
		{
		// build the binding name as appropriate
		if ( m_pCertificate )
			szBinding.Format( "%s%d", MDNAME_DISABLED, iKey );
		else
			szBinding.Format( "%s%d", MDNAME_INCOMPLETE, iKey );

		// set the update flag
		m_fUpdateBindings = TRUE;
		}

	// NOTE: pWrap has already been opened to /LM/W3Svc/SSLKeys
	// if the key is not dirty, its easy
	if ( !m_fUpdateKeys && !m_fUpdateFriendlyName && !m_fUpdateIdent && !m_fUpdateBindings && !FGetDirty() )
		{
		// add names of its bindings so it doesn't get deleted
		DWORD	iBinding;
		for ( iBinding = 0; iBinding < nBindings; iBinding++ )
			prgbszTracker->Add( m_rgbszBindings[iBinding] );
		return TRUE;
		}

	// handle no bindings as a special case first
	if ( nBindings == 0 )
		{
		// tell the server about it
		prgbszTracker->Add((LPCSTR)szBinding);
		// ok. Create the key in the metabase.
		f = pWrap->AddObject( szBinding );
		// and save the data
		f = FWriteData( pWrap, szBinding, TRUE );
		// clear the dirty flag and exit
		SetDirty( FALSE );
		return TRUE;
		}

	// there are bindings to be saved... loop though them and update each
	DWORD	iBinding;
	for ( iBinding = 0; iBinding < nBindings; iBinding++ )
		{
		// get the binding name
		szBinding = m_rgbszBindings[iBinding];


// test code
if ( szBinding.IsEmpty() )
AfxMessageBox( "Empty Binding Alert!" );


		// now that we know where to save it, add the name to list of saved
		// objects being kept track of by the server object - (This is so that
		// the server knows what's been added)
		prgbszTracker->Add((LPCSTR)szBinding);

		// ok. Create the key in the metabase. Really, we may only need to do this
		// if m_fUpdateBindings is set - if the object is new - update all the data
		fUpdateAll = pWrap->AddObject( szBinding ) || m_fUpdateBindings;

		// write out the data
		FWriteData( pWrap, szBinding, fUpdateAll );
		}

	// clear the flags
	m_fUpdateKeys = FALSE;
	m_fUpdateFriendlyName = FALSE;
	m_fUpdateIdent = FALSE;
	m_fUpdateBindings = FALSE;

	// clear the dirty flag
	SetDirty( FALSE );

	return TRUE;
	}

//--------------------------------------------------------
// write out the data portion to a particular binding
BOOL CMDKey::FWriteData( CWrapMetaBase* pWrap, CString szBinding, BOOL fWriteAll )
	{
	BOOL f;
	// write all the parts of the key - start with the certificate

	// start with the secure parts
	if ( m_fUpdateKeys || fWriteAll )
		{
		if ( m_pCertificate )
			f = pWrap->SetData( szBinding, MD_SSL_PUBLIC_KEY, IIS_MD_UT_SERVER, BINARY_METADATA,
										m_pCertificate, m_cbCertificate,
										METADATA_SECURE );

		// write out the private key
		if ( m_pPrivateKey )
			f = pWrap->SetData( szBinding, MD_SSL_PRIVATE_KEY, IIS_MD_UT_SERVER, BINARY_METADATA,
										m_pPrivateKey, m_cbPrivateKey,
										METADATA_SECURE );

		// write out the password - treat is ast secure binary data
		if ( !m_szPassword.IsEmpty() )
			f = pWrap->SetData( szBinding, MD_SSL_KEY_PASSWORD, IIS_MD_UT_SERVER, BINARY_METADATA,
										(PVOID)(LPCSTR)m_szPassword, m_szPassword.GetLength()+1,
										METADATA_SECURE );

		// write out the request
		if ( m_pCertificateRequest )
			f = pWrap->SetData( szBinding, MD_SSL_KEY_REQUEST, IIS_MD_UT_SERVER, BINARY_METADATA,
										m_pCertificateRequest, m_cbCertificateRequest,
										METADATA_SECURE );
		}

	// write out the cached serial number
	if ( m_fUpdateIdent || m_fUpdateKeys || fWriteAll )
		if ( !m_szIdent.IsEmpty() )
			{
			f = pWrap->SetString( szBinding, MD_SSL_IDENT, IIS_MD_UT_SERVER,
								m_szIdent, METADATA_SECURE );
			}

	// write out the friendly name of the key
	if ( m_fUpdateFriendlyName || fWriteAll )
		f = pWrap->SetString( szBinding, MD_SSL_FRIENDLY_NAME, IIS_MD_UT_SERVER,
									m_szName, 0 );

	return TRUE;
	}

//--------------------------------------------------------
BOOL CMDKey::FLoadKey( CWrapMetaBase* pWrap, PCHAR pszObj )
	{
    DWORD		cbData;
    PVOID		pData;

	// start with the public key
    pData = pWrap->GetData( pszObj, MD_SSL_PUBLIC_KEY, IIS_MD_UT_SERVER,
                BINARY_METADATA, &cbData, 0 );
    if ( pData )
        {
        // set the data into place
		m_pCertificate = (PVOID)GlobalAlloc( GPTR, cbData );
		// if we got the pointer, copy the rest of the data into place
		if( m_pCertificate)
			{
			m_cbCertificate = cbData;
			CopyMemory( m_pCertificate, pData, cbData );
			}
		// free the buffer
		pWrap->FreeWrapData( pData );
		}
		
	// now the private key
    pData = pWrap->GetData( pszObj, MD_SSL_PRIVATE_KEY, IIS_MD_UT_SERVER,
                BINARY_METADATA, &cbData, 0 );
    if ( pData )
        {
        // set the data into place
		m_pPrivateKey = (PVOID)GlobalAlloc( GPTR, cbData );
		// if we got the pointer, copy the rest of the data into place
		if( m_pPrivateKey)
			{
			m_cbPrivateKey = cbData;
			CopyMemory( m_pPrivateKey, pData, cbData );
			}
		// free the buffer
		pWrap->FreeWrapData( pData );
		}
		
	// now the password key
    pData = pWrap->GetData( pszObj, MD_SSL_KEY_PASSWORD, IIS_MD_UT_SERVER,
                BINARY_METADATA, &cbData, 0 );
    if ( pData )
        {
        // set the data into place - relatively easy in this case
		m_szPassword = (LPCSTR)pData;
		// free the buffer
		pWrap->FreeWrapData( pData );
		}

	// now the request
    pData = pWrap->GetData( pszObj, MD_SSL_KEY_REQUEST, IIS_MD_UT_SERVER,
                BINARY_METADATA, &cbData, 0 );
    if ( pData )
        {
        // set the data into place
		m_pCertificateRequest = (PVOID)GlobalAlloc( GPTR, cbData );
		// if we got the pointer, copy the rest of the data into place
		if( m_pCertificateRequest)
			{
			m_cbCertificateRequest = cbData;
			CopyMemory( m_pCertificateRequest, pData, cbData );
			}
		// free the buffer
		pWrap->FreeWrapData( pData );
		}

	// finally, retrieve the friendly name
	BOOL f = pWrap->GetString( pszObj, MD_SSL_FRIENDLY_NAME, IIS_MD_UT_SERVER,
                m_szName.GetBuffer(MAX_LEN), MAX_LEN, 0);
    m_szName.ReleaseBuffer();
    if ( !f )
		m_szName.Empty();

	// make this item's metabase name the first name in the list
	AddBinding( pszObj );

	// Success
	return TRUE;
	}

//-------------------------------------------------------------
// install a cert
BOOL CMDKey::FInstallCertificate( PVOID pCert, DWORD cbCert, CString &szPass )
	{
    // first, we should test that the certificate and password are valid
    // for this particular key
	// cache the old certificate in case the new one fails
	DWORD	old_cbCertificate = m_cbCertificate;
	PVOID	old_pCertificate = m_pCertificate;

	// set the new one into place
	m_cbCertificate = cbCert;
	m_pCertificate = pCert;

	// verify the password - verify password puts up any error dialogs
	if ( !FVerifyValidPassword(szPass) )
		{
		// resore the old values
		m_cbCertificate = old_cbCertificate;
		m_pCertificate = old_pCertificate;

		// dispose of the new stuff
		GlobalFree( pCert );

		// return false
		return FALSE;
		}

    // now we need to see if this key has already been installed
    // get the identification string
    CString szIdentThis;
    if ( !FGetIdentString( pCert, cbCert, szIdentThis ) )
        return FALSE;

    // scan the existing keys, looking for one with the same ident string
    // if one is found, tell the user that it already exists and fail
    CString szIdentTest;
    CMDKey* pTestKey = m_pService->GetFirstMDKey();
    while ( pTestKey )
        {
        // if we are testing against this key, continue
        if ( pTestKey == this )
            goto GETNEXTKEY;

        // get the test ident string
        if ( !pTestKey->FGetIdentString( pTestKey->m_pCertificate,
                                pTestKey->m_cbCertificate, szIdentTest ) )
            goto GETNEXTKEY;

        // test the ident strings
        if ( szIdentThis == szIdentTest )
            {
            // the key already exists
            AfxMessageBox( IDS_DUPLICATE_CERT );
            return FALSE;
            }

GETNEXTKEY:
        // get the next key for the loop
        pTestKey = m_pService->GetNextMDKey(pTestKey);
        }

	// run the default action
	BOOL	fDefault = CKey::FInstallCertificate(pCert, cbCert, szPass);

	// set the update keys flag
	m_fUpdateKeys = TRUE;


    // if everything worked so far then check to see if there is a key
    // on this service with the default binding. If there isn't, then
    // set this key to have the default binding.
    if ( fDefault )
        {
        // load the default binding string
        CString szBinding;
        szBinding = MDNAME_DEFAULT;
        // if no key has the default binding, then make it so
        if ( !m_pService->FIsBindingInUse(szBinding) )
            {
			m_rgbszBindings.Add( MDNAME_DEFAULT );
            }
        }


    // if it worked, force the icon to change
    if ( fDefault )
        UpdateIcon();

	// return the default answer
	return fDefault;
	}