|
|
#include "stdafx.h"
#include "natobjs.h"
#include "resource.h"
#include "ConnectDlg.h"
#include <wincrypt.h>
HRESULT CreateMD5Hash( IN PBYTE pbData, IN DWORD cbData, OUT PBYTE pbHashBuffer, IN OUT DWORD *pdwHashBufferSize );
//-------------------------------------------------------------------------
CNATServerComputer::CNATServerComputer( LPCTSTR pszComputerName ) { m_csNatComputer = pszComputerName; ZeroMemory( m_hash, sizeof(m_hash) ); }
//-------------------------------------------------------------------------
CNATServerComputer::~CNATServerComputer() { // clean up the lists
EmptySiteComputers(); EmptyGroups(); }
//-------------------------------------------------------------------------
// Edit the properties of this server computer
BOOL CNATServerComputer::OnProperties() { AfxMessageBox(IDS_NODENAME); return FALSE; }
//-------------------------------------------------------------------------
// get the number of groups associated with this machine
DWORD CNATServerComputer::GetNumGroups() { return m_rgbGroups.GetSize(); }
//-------------------------------------------------------------------------
// Get a group reference
CNATGroup* CNATServerComputer::GetGroup( IN DWORD iGroup ) { return m_rgbGroups[iGroup]; }
//-------------------------------------------------------------------------
// Technically, the GetGroupName function is unecessary because
// you get get the group class pointer, then call GetName on it, but it is really
// handy to just do it here. Cleans up the snapin part of the code.
BOOL CNATServerComputer::GetGroupName( IN DWORD iGroup, OUT CString &csName ) { // just get the group and turn around the name
CNATGroup* pGroup = GetGroup( iGroup ); if ( !pGroup ) return FALSE; // get the name
csName = pGroup->GetName(); return TRUE; }
//-------------------------------------------------------------------------
// call this to automatically verify that the target NAT computers config info
// hasn't changed. If it has, it prompts the user and lets them continue or refresh.
BOOL CNATServerComputer::VerifyHashIsOK() { BYTE hash[HASH_BYTE_SIZE]; DWORD cbHash = sizeof(hash);
// get the has of the current state of the server. Passing in a null pointer
// causes the routine to go out and get a new copy of the blob
if ( !GetNATStateHash( NULL, 0, hash, &cbHash ) ) return FALSE;
// if the two hashes are the same, then the blob has not changed on us
return ( memcmp(hash, m_hash, HASH_BYTE_SIZE) == 0 ); }
//-------------------------------------------------------------------------
// rebuild all the data based on a new blob from the NAT machine
void CNATServerComputer::Refresh() { DWORD num, i; CString szName, szIP; LPWSTR pszwName, pszwIP; BOOL f;
// empty any current entries in the lists
EmptySiteComputers(); EmptyGroups();
// Get the blob data from the DCOM object and put it in the wrapper thing
CIPMap IpMap;
// get the data
LPBYTE pData = NULL; DWORD cbData = 0; // throws up its own errors
pData = PGetNATStateBlob( &cbData ); if ( !pData ) return;
// unserialize the data into the wrapper class
if ( !IpMap.Unserialize(&pData, &cbData) ) { goto error; }
// start by reading in all the site computers from the wrapper
num = IpMap.ComputerCount(); for ( i = 0; i < num; i++ ) { // the computer name from the wrapper
if ( !IpMap.EnumComputer(i, &pszwName) ) { ASSERT( FALSE ); continue; } szName = pszwName;
// check if the computer name is visible
BOOL bVisible = CanSeeComputer( (LPCTSTR)szName );
// create the computer object
CNATSiteComputer* pSC = new CNATSiteComputer( (LPCTSTR)szName, bVisible ); ASSERT( pSC ); if ( !pSC ) goto error;
// add the computer to the site computer array
m_rgbSiteComputers.Add( pSC ); }
// now read the groups. In each group, build its site array
num = IpMap.IpPublicCount(); for ( i = 0; i < num; i++ ) { DWORD dwSticky;
// the computer name from the wrapper
if ( !IpMap.EnumIpPublic(i, &pszwIP, &pszwName, &dwSticky) ) { ASSERT( FALSE ); continue; } szIP = pszwIP; szName = pszwName;
// create the group object
CNATGroup* pG = new CNATGroup( this, (LPCTSTR)szIP, (LPCTSTR)szName, dwSticky ); ASSERT( pG ); if ( !pG ) goto error;
// add the computer to the site computer array
m_rgbGroups.Add( pG );
// now, for this group, add the sites that are associated with it. This means
// looping through all the computer objects and looking for ones that match this one
DWORD numComputers = m_rgbSiteComputers.GetSize(); DWORD iComp; for ( iComp = 0; iComp < numComputers; iComp++ ) { szIP.Empty(); // if there is a match, add it as a site
if ( IpMap.GetIpPrivate(iComp, i, &pszwIP, &pszwName) ) { szIP = pszwIP; szName = pszwName; }
// if there is a match, then there will be data in szIP
if ( !szIP.IsEmpty() ) { // we got a match. We can now create a new site object and add it to the group
pG->AddSite( m_rgbSiteComputers[iComp], (LPCTSTR)szIP, (LPCTSTR)szName ); } } }
// success!
goto cleanup;
// failure :-(
error: // tell the user about it
AfxMessageBox( IDS_BADBLOB );
// empty any current entries in the lists
EmptySiteComputers(); EmptyGroups();
// cleanup
cleanup: if ( pData ) GlobalFree( pData ); }
//-------------------------------------------------------------------------
// commit the current state of the data back to the NAT machine
void CNATServerComputer::Commit() { #define ICOMP_ERR 0xFFFFFFFF
CStoreXBF xbfStorage;
// start the commit process by verifying that no other admin has changed the
// state of the server since we last refreshed. If then have, we have to tell
// the user and let them choose to continue commiting (losing the current state
// of the server) or to cancel the current operation and automatically refresh
// to the new state of the server.
if ( !VerifyHashIsOK() ) { if ( AfxMessageBox( IDS_VERIFYFAILED ) == IDNO ) { Refresh(); return; } }
// we are committing. The process involves creating a new interface wrapper.
// building up all the values, then writing out the blob to the DCOM layer.
CIPMap IpMap;
// first, add all the SiteComputer objects to the map object. While doing this, only
// actually add ones that have a refcount associated with them. (cleans up items that)
// are no longer used. Also, set the actual index on the object for use later on
DWORD num = m_rgbSiteComputers.GetSize(); DWORD i, j; DWORD iComp = 0;
// loop through the computers and add each one.
for ( i = 0; i < num; i++ ) { CNATSiteComputer* pSC = m_rgbSiteComputers[i]; ASSERT( pSC );
// if the SC has a refcount, add it to the interface builder
if ( pSC->m_refcount ) { // set its internal iComp member variable
pSC->m_iComp = iComp;
// Add the SC to the interface builder
if ( !IpMap.AddComputer((LPTSTR)(LPCTSTR)pSC->m_csName) ) goto error; } else // the refcount is null
{ // set its internal iComp member variable to invalid
pSC->m_iComp = ICOMP_ERR; } }
// loop through the groups and add those to the interface builder
num = m_rgbGroups.GetSize();
// loop through the computers and add each one.
for ( i = 0; i < num; i++ ) { CNATGroup* pG = m_rgbGroups[i]; ASSERT( pG );
// add it to the interface builder
if ( !IpMap.AddIpPublic((LPTSTR)pG->GetIP(), (LPTSTR)pG->GetName(), pG->GetSticky()) ) goto error;
// we also need to add the sites to the group
DWORD numSites = pG->GetNumSites(); for ( j = 0; j < numSites; j++ ) { CNATSite* pS = pG->GetSite( j ); ASSERT( pS ); ASSERT( pS->m_pSiteComputer ); ASSERT( pS->m_pSiteComputer->m_refcount > 0 ); ASSERT( pS->m_pSiteComputer->m_iComp != ICOMP_ERR );
// map the site to the group in the interface builder
if ( !IpMap.SetIpPrivate( pS->m_pSiteComputer->m_iComp, i, (LPTSTR)pS->GetIP(), (LPTSTR)pS->GetName() ) ) goto error; } }
// serialize it all into a buffer for the COM call
if ( !IpMap.Serialize(&xbfStorage) ) goto error;
// Set the blob into the DCOM layer. - It puts up its own error
// message if there is a failure talking to the DCOM object
if ( SetStateBlob( xbfStorage.GetBuff(), xbfStorage.GetUsed() ) ) { DWORD cbHash = HASH_BYTE_SIZE; // update the hash
GetNATStateHash( xbfStorage.GetBuff(), xbfStorage.GetUsed(), m_hash, &cbHash ); }
// return normally (success)
return;
error: // shouldn't ever get here
ASSERT( FALSE ); AfxMessageBox( IDS_BUILDBLOBERROR ); }
//-------------------------------------------------------------------------
// empties and frees all the site computer objects in the list
void CNATServerComputer::EmptySiteComputers() { DWORD numSiteComputers = m_rgbSiteComputers.GetSize();
// loop the array and free all the group objects
for ( DWORD i = 0; i < numSiteComputers; i++ ) { delete m_rgbSiteComputers[i]; }
// empty the array itself
m_rgbSiteComputers.RemoveAll(); }
//-------------------------------------------------------------------------
// empties and frees all the groups/sites in the groups list
void CNATServerComputer::EmptyGroups() { DWORD numGroups = m_rgbGroups.GetSize();
// loop the array and free all the group objects
for ( DWORD i = 0; i < numGroups; i++ ) { delete m_rgbGroups[i]; }
// empty the array itself
m_rgbGroups.RemoveAll(); }
//-------------------------------------------------------------------------
// add a new group to the server computer (called by the UI). This
// may or may not prompt the user with UI and returns the pointer
// to the new group after adding it to the group list
CNATGroup* CNATServerComputer::NewGroup() { // for now, make a group with just all the defaults
CNATGroup* pG = new CNATGroup( this ); if ( pG == NULL ) { AfxMessageBox( IDS_LOWMEM ); return NULL; }
// Ask the user to edit the group's properties. Do not add it if they cancel
if ( !pG->OnProperties() ) { delete pG; return NULL; }
// add the object to the end of the array
m_rgbGroups.Add( pG ); return pG; }
//-------------------------------------------------------------------------
// add a new computer to the sites list. - Note: if the computer
// already exists in the sites list, it will just return a reference
// to the existing computer. This routine prompts the user to choose
// a computer to add. Returns FALSE if it fails.
CNATSiteComputer* CNATServerComputer::NewComputer() { // Ask the user for the machine to connect to.
CConnectDlg dlgConnect; if ( dlgConnect.DoModal() == IDCANCEL ) return NULL;
// check if it is visible
BOOL bVisible = CanSeeComputer( (LPCTSTR)dlgConnect.m_cstring_name );
// for now, make a group with just all the defaults
CNATSiteComputer* pSC = new CNATSiteComputer( (LPCTSTR)dlgConnect.m_cstring_name, bVisible ); if ( pSC == NULL ) { AfxMessageBox( IDS_LOWMEM ); return NULL; }
// add the object to the end of the array
m_rgbSiteComputers.Add( pSC ); return pSC; }
//-------------------------------------------------------------------------
// adds an existing computer to the list - to be called during a refresh.
// this checks the visiblity of the machine on the net as it adds it
void CNATServerComputer::AddSiteComputer( LPWSTR pszwName ) { // check if the named computer is visible on the network
BOOL bVisible = CanSeeComputer( pszwName );
// create the new Site Computer object
CNATSiteComputer* pSC = new CNATSiteComputer( pszwName, bVisible ); if ( pSC == NULL ) { AfxMessageBox( IDS_LOWMEM ); return; }
// add the object to the end of the array
m_rgbSiteComputers.Add( pSC ); }
//-------------------------------------------------------------------------
// adds an existing group to the list - to be called during a refresh.
void CNATServerComputer::AddGroup( LPWSTR pszwIPPublic, LPWSTR pszwName, DWORD dwSticky, DWORD type ) { // create the new Site Computer object
CNATGroup* pG = new CNATGroup( this, pszwIPPublic, pszwName, dwSticky, type ); if ( pG == NULL ) { AfxMessageBox( IDS_LOWMEM ); return; }
// add the object to the end of the array
m_rgbGroups.Add( pG ); }
//-------------------------------------------------------------------------
// gets the hash of the NAT data blob. If a null pointer to the blob
// is passed in, then it dynamically gets the blob from the server
// or you can pass in a specific blob. This is to be used to check if
// the state of the server has changed since the data was last loaded.
// The hash is obtained from the crypto code so it is really good. The
// buffer for the hash should be 128 bits in length. Using an MD5 hash.
BOOL CNATServerComputer::GetNATStateHash( IN LPBYTE pData, IN DWORD cbData, OUT LPBYTE pHash, IN OUT DWORD* pcbHash ) { HRESULT hRes; LPBYTE pInternalData = NULL;
// if no data buffer was specified, then we should get a new state blob from the server
if ( !pData ) { pInternalData = PGetNATStateBlob( &cbData ); if ( !pInternalData ) return FALSE; pData = pInternalData; }
// get the hash
hRes = CreateMD5Hash( pData, cbData, pHash, pcbHash );
// if we allocated an internal buffer, free it now
if ( pInternalData ) GlobalFree( pInternalData );
return SUCCEEDED( hRes ); }
//-------------------------------------------------------------------------
// access the server and retrieve the state blob. Use GetLastError to see
// what went wrong if the returned result is NULL. Pass in the dword pointed
// to by pcbData to get the required size.
LPBYTE CNATServerComputer::PGetNATStateBlob( OUT DWORD* pcbData ) { HRESULT hRes; IMSIisLb* pIisLb; LPBYTE pData = NULL;
// get the interface to the NAT server
hRes = GetNATInterface( &pIisLb ); if ( FAILED(hRes) ) return pData;
// the first call gets the amout of required space for the blob
DWORD dwcb; hRes = pIisLb->GetIpList( 0, NULL, &dwcb );
// if it fails, with anything except too small a buffer, then fail
if ( FAILED(hRes) && (hRes != ERROR_INSUFFICIENT_BUFFER) ) goto cleanup;
// allocate space to receive the blob
pData = (LPBYTE)GlobalAlloc( GPTR, dwcb ); if ( !pData ) { AfxMessageBox( IDS_LOWMEM ); goto cleanup; }
// get the blob
hRes = pIisLb->GetIpList( dwcb, pData, &dwcb );
// if it fails, with anything except too small a buffer, then fail
if ( FAILED(hRes) ) { GlobalFree( pData ); pData = NULL; }
// cleanup
cleanup: pIisLb->Release();
return NULL; }
//-------------------------------------------------------------------------
// access the server and set the state blob. Use GetLastError to see what went
// wrong if the returned result is FALSE
BOOL CNATServerComputer::SetStateBlob( IN LPBYTE pData, IN DWORD cbData ) { HRESULT hRes; IMSIisLb* pIisLb;
// get the interface to the NAT server
hRes = GetNATInterface( &pIisLb ); if ( FAILED(hRes) ) return FALSE;
// set the data blob to the server.
hRes = pIisLb->SetIpList( cbData, (PUCHAR)pData );
// cleanup
pIisLb->Release();
return FALSE; }
//-------------------------------------------------------------------------
// open the DCOM interface to the target NAT machine.
HRESULT CNATServerComputer::GetNATInterface( IMSIisLb** ppIisLb ) { COSERVERINFO csiMachineName; LPSTR pszMachineName = NULL; IClassFactory* pcsfFactory = NULL; HRESULT hRes = 0;
//fill the structure for CoCreateInstanceEx
ZeroMemory( &csiMachineName, sizeof(csiMachineName) ); csiMachineName.pwszName = NULL;
// get the class factory
hRes = CoGetClassObject(CLSID_MSIisLb, CLSCTX_SERVER, &csiMachineName, IID_IClassFactory, (void**) &pcsfFactory);
if ( SUCCEEDED( hRes ) ) { // the instance of the load balancing interface
hRes = pcsfFactory->CreateInstance(NULL, IID_IMSIisLb, (void **)ppIisLb); // clean up the class factory
pcsfFactory->Release(); }
// if there was an error, tell the user
if ( FAILED( hRes ) ) { AfxMessageBox( IDS_NOLBCONNECT ); }
return hRes; }
//-------------------------------------------------------------------------
// utility to check if the computer is visible on the net
BOOL CNATServerComputer::CanSeeComputer( LPCTSTR pszname ) { // if no name is passed in, then it is the local machine. return true
if ( (pszname == NULL) || (*pszname == 0 ) ) return TRUE;
// until I can figure out a way to ping the address via TCP/IP,
// attempt to connect to registry.
HKEY hkResult; LONG err; err = RegConnectRegistry( pszname, // address of name of remote computer
HKEY_LOCAL_MACHINE, // predefined registry handle
&hkResult // address of buffer for remote registry handle
); // clean up
if ( err == ERROR_SUCCESS ) CloseHandle( hkResult ); // return whether or not it worked
return ( err == ERROR_SUCCESS ); }
//-------------------------------------------------------------------------
// function is courtesy Alex Mallet (amallet)
HRESULT CreateMD5Hash( IN PBYTE pbData, IN DWORD cbData, OUT PBYTE pbHashBuffer, IN OUT DWORD *pdwHashBufferSize ) /*++
Routine Description:
Creates MD5 hash of data
Arguments:
pbData - buffer of data to be hashed cbData - size of data to be hashed pbHashBuffer - buffer to receive hash pdwHashBufferSize - size of pbHashBuffer
Returns:
HRESULT indicating success/failure
--*/
{ HCRYPTPROV hProv = NULL; HCRYPTHASH hHash = NULL; HRESULT hRes = S_OK;
//
// Get a handle to the CSP that will create the
// hash
if ( !CryptAcquireContext( &hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) { hRes = RETURNCODETOHRESULT( GetLastError() ); goto EndCreateHash; }
//
// Get a handle to an MD5 hash object
//
if ( !CryptCreateHash( hProv, CALG_MD5, 0, 0, &hHash ) ) { hRes = RETURNCODETOHRESULT( GetLastError() ); goto EndCreateHash; }
//
// Hash the data
//
if ( !CryptHashData( hHash, pbData, cbData, 0 ) ) { hRes = RETURNCODETOHRESULT( GetLastError() ); goto EndCreateHash; }
//
// Retrieve the hash
//
if ( !CryptGetHashParam( hHash, HP_HASHVAL, pbHashBuffer, pdwHashBufferSize, 0 ) ) { hRes = RETURNCODETOHRESULT( GetLastError() ); goto EndCreateHash; }
EndCreateHash: //
//Cleanup
//
if ( hHash ) { CryptDestroyHash( hHash ); }
if ( hProv ) { CryptReleaseContext( hProv, 0 ); }
return hRes; }
|