windows-nt/Source/XPSP1/NT/inetsrv/iis/ui/admin/lbcfg/natservercomputer.cpp

700 lines
21 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
#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;
}