2025-04-27 07:49:33 -04:00

931 lines
26 KiB
C++

/*++
Copyright (c) 1996-1997 Microsoft Corporation
Module Name:
Mid.cxx
Abstract:
Implements the CMid class.
Author:
SatishT 04-13-96
--*/
#include<or.hxx>
//
// declare the resolver handle cache privately held by COrBindingIterator
//
TCSafeResolverHashTable<CResolverHandle>
COrBindingIterator::ResolverHandles(ResolverHandleCacheSize);
//
// CRpcssHandle methods
//
BOOL
CRpcssHandle::TryDynamic()
{
if (_fDynamic)
{
return FALSE;
}
else
{
RPC_STATUS status = RpcBindingReset(_hOR);
if (status == RPC_S_OK)
{
_fDynamic = TRUE;
return TRUE;
}
else
{
return FALSE;
}
}
}
BOOL
CRpcssHandle::TryUnsecure()
{
if (!_fSecure)
{
return FALSE;
}
else
{
RPC_STATUS status = RpcBindingSetAuthInfoA(_hOR,
NULL,
RPC_C_AUTHN_LEVEL_NONE,
0,
0,
0);
if (status == RPC_S_OK)
{
_fSecure = FALSE;
return TRUE;
}
else
{
return FALSE;
}
}
}
ORSTATUS
CRpcssHandle::Reset(RPC_BINDING_HANDLE hIn)
{
ASSERT(hIn != NULL);
Clear();
RPC_STATUS status = RpcBindingCopy(hIn,&_hOR);
if (status == RPC_S_OK)
{
// BUGBUG: As usual, this is very NT specific
status = RpcBindingSetAuthInfoA(_hOR,
(UCHAR*)"Default",
RPC_C_AUTHN_LEVEL_CONNECT,
RPC_C_AUTHN_WINNT,
0,
0);
if (status != RPC_S_OK)
{
ComDebOut((DEB_OXID,"OR: RpcBindingSetAuthInfo failed for OR handle with %d\n",
status));
// Just fall back on unsecure.
TryUnsecure();
status = RPC_S_OK;
}
}
return status;
}
//
// BindingIterator methods
//
COrBindingIterator::COrBindingIterator(CMid *pMid )
: _pMid(pMid), _bIter(pMid->_iStringBinding,pMid->_dsa)
{
ASSERT(!_pMid->IsLocal()); // should never call for local
_pCurrentHandle = ResolverHandles.Lookup(CIdKey(pMid->GetMID()));
}
CResolverHandle * COrBindingIterator::First()
/*++
Method Description:
Gets the first possible RPC binding handle to the remote machine.
This is successful only if we have an uncleared (and hence presumably
working) resolver handle in the static table of handles in this class.
If not, we defer to COrBindingIterator::Next().
Arguments:
None
Return Value:
NULL - resource allocation or connection failure
non-NULL - A binding to the machine.
--*/
{
if (_pCurrentHandle && !_pCurrentHandle->IsEmpty())
{
return _pCurrentHandle; // Already have one, so try it
}
else
{
return Next();
}
}
CResolverHandle * COrBindingIterator::Next()
/*++
Method Description:
Gets the next possible RPC binding handle to the remote machine.
We get here only if First finds no usable handle in ResolverHandles
which happens only if
1. At the first contact with this OR.
2. At subsequent contacts, a current handle failed (some service needed by
that protseq may have failed, or we have a network partition).
This includes the possibility that we never successfully talk to this OR.
Arguments:
None
Return Value:
NULL - resource allocation or connection failure
non-NULL - A binding to the machine.
--*/
{
_pMid->_fBindingWorking = FALSE; // no, just to be double sure
if (_pCurrentHandle) // get rid of it since it is apparently not working
{
ResolverHandles.Remove(*_pCurrentHandle);
_pCurrentHandle = NULL;
}
PWSTR pwstrT;
RPC_BINDING_HANDLE hMachine = NULL;
while((hMachine == NULL) && (pwstrT = _bIter.Next()) != NULL)
{
// ronans - dcomhttp CODEWORK - generalize this to make list of client side protocols
// which may be used even if machine cannot act as server on these protocols
if (IsMemberOf(*pwstrT,*gpcRemoteProtseqs,gpRemoteProtseqIds) ||
((*pwstrT == ID_DCOMHTTP) && gpfClientHttp && *gpfClientHttp))
{
hMachine = GetBindingToOr(pwstrT);
}
}
if (NULL != hMachine) // did we get anything?
{
ComDebOut((DEB_OXID,"OR: COrBindingIterator::Next %ls\n", pwstrT));
_pCurrentHandle = new CResolverHandle(_pMid->GetMID());
if (_pCurrentHandle != NULL)
{
_pCurrentHandle->Reset(hMachine); // OK we have it, copy it in
RpcBindingFree(&hMachine); // and free the original
_pMid->_iStringBinding = _bIter.Index(); // remember where we are
ResolverHandles.Add(_pCurrentHandle); // cache the handle in table
}
}
return _pCurrentHandle;
}
//
// CMid methods
//
// private method used in the constructor and elsewhere to try
// contacting the target resolver to establish the correct binding to use
void CMid::ResetBinding()
{
ComDebOut((DEB_OXID,"OR: enter CMid::ResetBinding\n"));
COrBindingIterator bindIter(this);
CResolverHandle *pOrHandle;
for (pOrHandle = bindIter.First();
pOrHandle != NULL;
pOrHandle = bindIter.Next()
)
{
RPC_BINDING_HANDLE hRemoteOr = pOrHandle->GetRpcHandle();
// Use unsecure handle since this is address resolution
// we are not spoofing proof anyway
RPC_STATUS status = RpcBindingSetAuthInfoA(
hRemoteOr,
NULL,
RPC_C_AUTHN_LEVEL_NONE,
0,
0,
0
);
{
CTempReleaseSharedMemory temp;
status = ::ServerAlive(hRemoteOr); // try a ping
}
if (status == RPC_S_OK)
{
_fBindingWorking = TRUE; // mark this Mid as functional
break;
}
_fBindingWorking = FALSE; // mark this Mid as NOT functional
}
if (!gfThisIsRPCSS && pOrHandle) // don't stick RPCSS with this handle
{
pOrHandle->Clear();
}
}
// define the static members for page-based allocation
DEFINE_PAGE_ALLOCATOR(CMid)
CMid::CMid(
DUALSTRINGARRAY *pdsa,
ORSTATUS& status,
USHORT wProtSeq, // if the SCM tells us what to use
MID mid, // sometimes needed for the local MID
BOOL fCheckNetAddress // Should I ping for multinet resolver?
) :
_id(mid),
_iStringBinding(0),
_iSecurityBinding(0),
_fBindingWorking(FALSE),
_dsa(pdsa,TRUE,status),
_dwExpirationTime(0),
_sequenceNum(0),
_setID(0),
_cFailedPings(0),
_fPingThreadIsInside(FALSE)
{
if (wProtSeq > 0)
{
PWSTR pwstr = FindMatchingProtseq(wProtSeq,pdsa->aStringArray);
if (NULL != pwstr)
{
_iStringBinding = pwstr - pdsa->aStringArray;
}
}
if (fCheckNetAddress)
{
// We need to figure out which address works for us
ResetBinding();
}
}
BOOL
CMid::HasExpired()
{
BOOL fResult = FALSE;
// First check if a ping thread died while calling PingServer
// on this MID, or pinging has been unsuccessful long enough
if (_fPingThreadIsInside || _cFailedPings >= 3)
{
fResult = TRUE; // this isn't working
}
else
{
BOOL fUseless =
!IsLocal() && // not local
References() == 1 && // no Oxids
_pingSet.IsEmpty() && // nothing to ping
_addOidList.IsEmpty() && // nothing to add
_dropOidList.IsEmpty(); // nothing to drop
if (fUseless && _dwExpirationTime > 0)
{
fResult = (CTime() - CTime(_dwExpirationTime)) > BaseTimeoutInterval;
}
else if (fUseless)
{
_dwExpirationTime = GetTickCount();
}
else
{
_dwExpirationTime = 0;
}
}
// If the CMid has expired, it will be removed from the MidTable and Oids
// and Oxids in it will never be run down, so they should be run down here
// As far as the resolver is concerned, the Oids and Oxids belonging
// to this Mid no longer exist
if (fResult)
{
COxidList DisownList;
COxidTableIterator OxidTableIter(*gpOxidTable);
COxid *pOxid;
// First gather all Oxids at this Mid
while (pOxid = OxidTableIter.Next())
{
if (pOxid->GetMid() == this)
{
DisownList.Insert(pOxid);
}
}
// Now disown them
COxidListIterator DisownIter;
DisownIter.Init(DisownList);
while (pOxid = DisownIter.Next())
{
ASSERT(!pOxid->IsLocal());
ASSERT(pOxid->GetProcess() == gpPingProcess);
gpPingProcess->DisownOxid(pOxid,FALSE);
}
// Now we can clean up the _dropOidList
COidListIterator OidListIter;
OidListIter.Init(_dropOidList);
COid *pOid, *pOidRemoved;
while (pOid = OidListIter.Next())
{
// every Oid in this has been Disowned by its Oxid
// as the following ASSERT says
ASSERT(pOid->GetOxid()->DisownOid(pOid) == NULL);
pOidRemoved = gpOidTable->Remove(*pOid);
// the pOid should still have been in the gpOidTable
ASSERT(pOid==pOidRemoved || NULL==pOidRemoved);
}
// OK, now get rid of all these Oids
_pingSet.RemoveAll();
_addOidList.Clear();
_dropOidList.Clear();
// And release other resources, if any
COrBindingIterator::ResolverHandles.Remove(CIdKey(GetMID()));
}
return fResult;
}
// The invariant is that each remote Oid is in exactly one of the
// corresponding Mid's data structures: _pingSet, _addOidList, _dropOidList
// Remember that it is possible for a COid object to be run down and
// another COid object with the same OID/MID combination to be created
// and used later if the ping following the rundown fails. In order to avoid
// getting two COid objects with the same OID/MID, we do not remove a remote COid
// from the gpOidTable until it has been dropped from the Mid's data structures
ORSTATUS
CMid::AddClientOid(COid *pOid)
{
ORSTATUS status = OR_OK;
// this way, we have the same number of refs on the Oid
// as we would if we were already pinging it. The ref is
// held by the _addOidList instead of the _pingSet
if (_pingSet.Lookup(*pOid) == NULL)
{
status = _addOidList.Insert(pOid);
}
if (status == OR_I_DUPLICATE)
{
status = OR_OK;
}
// if we were planning to drop it, cease and desist
// this should only happen if a _dropOidList gets carried over to
// the next ping period because a ping fails
// If this is in the _dropOidList it was not in the _pingSet
// and therefore must have gotten added to the _addOidList
if (status == OR_OK) // be cautious
{
_dropOidList.Remove(*pOid);
}
return status;
}
ORSTATUS
CMid::DropClientOid(COid *pOid)
{
ORSTATUS status = _dropOidList.Insert(pOid);
if (status == OR_I_DUPLICATE)
{
status = OR_OK;
}
// The only way this is called is if the PingThread decided to
// run this Oid down, which means no one is using it, and in fact
// hasn't been using it for a BaseTimeoutInterval
if (status == OR_OK) // be cautious
{
COid *pAdd = _addOidList.Remove(*pOid); // at most one of these ops
COid *pPing = _pingSet.Remove(*pOid); // will actually work
ASSERT(pAdd==NULL || pPing==NULL);
}
return status;
}
RPC_STATUS
NegotiateDCOMVersion(
IN OUT COMVERSION *pVersion
)
/*++
Routine Description:
// Called when we receive a COMVERSION from a remote machine
// to determine which DCOM protocol level to talk.
Arguments:
pVersion - version of the remote machine. Modified if necessary
by this routine to be the lower of the two versions.
Return Value:
OR_OK
--*/
{
if (pVersion->MajorVersion == COM_MAJOR_VERSION)
{
if (pVersion->MinorVersion > COM_MINOR_VERSION)
{
// since the client has a lower minor version number,
// use the lower of the two.
pVersion->MinorVersion = COM_MINOR_VERSION;
}
return OR_OK;
}
return RPC_E_VERSION_MISMATCH;
}
ORSTATUS
CMid::ResolveRemoteOxid(
IN OXID Oxid,
OUT OXID_INFO *poxidInfo
)
{
ComDebOut((DEB_OXID,"OR: enter CMid::ResolveRemoteOxid\n"));
// Remote OXID, call ResolveOxid
ORSTATUS status = RPC_S_INVALID_BINDING;
USHORT tmpProtseq;
RPC_BINDING_HANDLE hRemoteOr;
poxidInfo->psa = NULL;
COrBindingIterator bindIter(this);
CResolverHandle *pOrHandle;
for (pOrHandle = bindIter.First();
pOrHandle != NULL;
pOrHandle = bindIter.Next()
)
{
RPC_BINDING_HANDLE hRemoteOr = pOrHandle->GetRpcHandle();
tmpProtseq = ProtseqOfServer();
if (tmpProtseq == 0)
{
status = RPC_S_INVALID_BINDING;
break;
}
poxidInfo->dwTid = poxidInfo->dwPid = 0; // marks a remote OXID
BOOL fRetry = FALSE;
do
{
{
CTempReleaseSharedMemory temp;
// try calling ResolveOxid2 first, if that fails,
// try ResolveOxid.
status = ResolveOxid2(
hRemoteOr,
&Oxid,
1,
&tmpProtseq,
&poxidInfo->psa,
&poxidInfo->ipidRemUnknown,
&poxidInfo->dwAuthnHint,
&poxidInfo->version
);
if (status == RPC_S_PROCNUM_OUT_OF_RANGE)
{
// must be a downlevel server (COMVERSION == 5.1), try calling on
// the old ResolveOXID method.
poxidInfo->version.MajorVersion = COM_MAJOR_VERSION;
poxidInfo->version.MinorVersion = COM_MINOR_VERSION_1;
poxidInfo->dwFlags = 0;
status = ::ResolveOxid(
hRemoteOr,
&Oxid,
1,
&tmpProtseq,
&poxidInfo->psa,
&poxidInfo->ipidRemUnknown,
&poxidInfo->dwAuthnHint
);
}
if (status == OR_OK)
{
status = NegotiateDCOMVersion(&poxidInfo->version);
}
}
switch (status)
{
case RPC_S_UNKNOWN_IF:
fRetry = pOrHandle->TryDynamic();
hRemoteOr = pOrHandle->GetRpcHandle();
continue;
case ERROR_ACCESS_DENIED:
case RPC_S_UNKNOWN_AUTHN_SERVICE:
case RPC_S_UNKNOWN_AUTHN_LEVEL:
case RPC_S_INVALID_AUTH_IDENTITY:
case RPC_S_SEC_PKG_ERROR:
fRetry = pOrHandle->TryUnsecure();
hRemoteOr = pOrHandle->GetRpcHandle();
continue;
default:
fRetry = FALSE;
}
}
while (fRetry);
if ((status == OR_OK) || (status == OR_BADOXID))
{
_fBindingWorking = TRUE; // mark this Mid as functional
break;
}
_fBindingWorking = FALSE; // mark this Mid as NOT functional
}
if (status == OR_OK)
{
ASSERT(poxidInfo->psa && "Remote resolve succeeded but no bindings returned");
}
return status;
}
void
CMid::ClearSet(COidList &dropList)
{
COid *pOid;
// This set is no longer valid -- clear current set
// but remove only dropped Oids from the global table
// because we know those have been run down and are
// not in use on this machine
_pingSet.RemoveAll();
// Since this is called only from Pingserver, the ping thread
// should not have been able to add anything to the _dropOidList
ASSERT(_dropOidList.IsEmpty());
COidListIterator Oids;
Oids.Init(dropList);
while(pOid = Oids.Next())
{
// every Oid in this has been Disowned by its Oxid
// as the following ASSERT says
ASSERT(pOid->GetOxid()->DisownOid(pOid) == NULL);
COid* pOidRemoved = gpOidTable->Remove(*pOid);
ASSERT(pOidRemoved==pOid || NULL==pOidRemoved);
}
dropList.Clear();
_setID = 0;
_sequenceNum = 0;
_cFailedPings = 0;
}
ORSTATUS
CMid::PingServer()
{
ComDebOut((DEB_OXID,"OR: enter CMid::PingServer\n"));
// Setting this flag lets us detect the situation
// where the PingThread terminates abnormally
SetFlagForScope PingThread(_fPingThreadIsInside);
if (_addOidList.IsEmpty() && _dropOidList.IsEmpty() && _pingSet.IsEmpty())
{
return OR_OK; // nothing to do
}
ORSTATUS status;
RPC_BINDING_HANDLE hRemoteOr;
COrBindingIterator bindIter(this);
CResolverHandle *pOrHandle;
if (!_addOidList.IsEmpty() || !_dropOidList.IsEmpty())
{
// need complex ping
USHORT cAddToSet = _addOidList.Size();
USHORT cDelFromSet = _dropOidList.Size();
COidList addOidListSent, dropOidListSent;
OID *aAddToSet = (OID*) alloca(sizeof(OID)*cAddToSet);
OID *aDelFromSet = (OID*) alloca(sizeof(OID)*cDelFromSet);
COidListIterator AddIter;
AddIter.Init(_addOidList);
USHORT i;
COid *pOid;
for (pOid = AddIter.Next(), i=0; pOid != NULL; pOid = AddIter.Next())
{
aAddToSet[i++] = pOid->GetOID();
ASSERT(_pingSet.Lookup(*pOid)==NULL); // should not be already in Ping set
}
COidListIterator DropIter;
DropIter.Init(_dropOidList);
for (pOid = DropIter.Next(), i=0; pOid != NULL; pOid = DropIter.Next())
{
aDelFromSet[i++] = pOid->GetOID();
}
// Transfer the current _addOidList and _dropOidList and set them to
// empty since we are releasing the shared memory lock.
_addOidList.Transfer(addOidListSent);
_dropOidList.Transfer(dropOidListSent);
// Now release the lock and ping.
status = RPC_S_INVALID_BINDING; // In case bindIter fails to deliver
for (pOrHandle = bindIter.First();
pOrHandle != NULL;
pOrHandle = bindIter.Next()
)
{
RPC_BINDING_HANDLE hRemoteOr = pOrHandle->GetRpcHandle();
BOOL fRetry = FALSE;
do
{
{
CTempReleaseSharedMemory temp;
status = ::ComplexPing(
hRemoteOr,
&_setID,
++_sequenceNum,
cAddToSet,
cDelFromSet,
aAddToSet,
aDelFromSet,
&_pingBackoffFactor
);
}
switch (status)
{
case RPC_S_UNKNOWN_IF:
fRetry = pOrHandle->TryDynamic();
hRemoteOr = pOrHandle->GetRpcHandle();
continue;
case ERROR_ACCESS_DENIED:
case RPC_S_UNKNOWN_AUTHN_SERVICE:
case RPC_S_UNKNOWN_AUTHN_LEVEL:
case RPC_S_INVALID_AUTH_IDENTITY:
case RPC_S_SEC_PKG_ERROR:
fRetry = pOrHandle->TryUnsecure();
hRemoteOr = pOrHandle->GetRpcHandle();
continue;
default:
fRetry = FALSE;
}
}
while (fRetry);
if (status == OR_OK || status == OR_BADOID || status == OR_BADSET)
{
_fBindingWorking = TRUE; // mark this Mid as functional
break;
}
_fBindingWorking = FALSE; // mark this Mid as NOT functional
}
// If we added some Oids while we were dropping them, we want
// to keep the Oids around in the gpOidTable and elsewhere
dropOidListSent.Remove(_addOidList);
switch (status)
{
case OR_OK:
case OR_BADOID: // we don't know which one, and anyway, it doesn't matter
{ // this scope is just for OidListIter
// use an iterator instead of Pop to avoid the situation
// where the Pop causes the ref count to drop to zero
// causing a crash immediately thereafter
// We need this defensive change because all the race
// conditions involved here are not yet understood
COidListIterator OidListIter;
OidListIter.Init(addOidListSent);
while (pOid = OidListIter.Next())
{
status = _pingSet.Add(pOid);
ASSERT(status != OR_I_DUPLICATE);
if (status != OR_OK)
{
break;
}
}
// If we added some Oids during this ping that were already
// added as part of the ping, we don't want to add them again
_addOidList.Remove(addOidListSent);
// we can finally get rid of these Oids altogether
OidListIter.Init(dropOidListSent);
while (pOid = OidListIter.Next())
{
COid* pOidRemoved = gpOidTable->Remove(*pOid);
ASSERT(pOidRemoved==pOid || NULL==pOidRemoved);
}
_cFailedPings = 0;
}
break;
case OR_BADSET:
// this ping set is invalid, clear it.
ClearSet(dropOidListSent);
_cFailedPings = 0;
break;
default: // RPC failure
// we could not have dropped anything new since
// this is the thread that does rundown for remote Oids
ASSERT(_dropOidList.IsEmpty());
dropOidListSent.Transfer(_dropOidList); // try again next time
_addOidList.Merge(addOidListSent); // try again next time
_cFailedPings++;
}
// the addOidListSent and dropOidListSent will be cleared automagically
// at the end of this scope by their destructors
if (_pingSet.IsEmpty())
{
_setID = 0; // server OR will delete the set
_sequenceNum = 0;
}
}
else if (_setID != 0)
{
// need simple ping
{
// no retries for simple pinging
CResolverHandle *_pCurrentHandle =
COrBindingIterator::ResolverHandles.Lookup(CIdKey(GetMID()));
RPC_BINDING_HANDLE hRemoteOr = _pCurrentHandle ?
_pCurrentHandle->GetRpcHandle() :
NULL;
if (hRemoteOr != NULL)
{
CTempReleaseSharedMemory temp;
status = ::SimplePing(
hRemoteOr,
&_setID
);
}
else
{
status = OR_BADSET;
}
}
switch (status)
{
case OR_BADSET:
// this ping set is invalid, clear it.
ClearSet(COidList());
// fall through
case OR_OK:
_cFailedPings = 0;
_fBindingWorking = TRUE; // mark this Mid as functional
break;
default: // RPC failure
_cFailedPings++;
_fBindingWorking = FALSE; // mark this Mid as NOT functional
}
}
return status;
}