Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1029 lines
30 KiB

/*++
Copyright (c) 1995-1996 Microsoft Corporation
Module Name:
OrClnt.cxx
Abstract:
Object resolver client side class implementations. CClientOxid, CClientOid,
CClientSet classes are implemented here.
Author:
Mario Goertzel [MarioGo]
Revision History:
MarioGo 04-03-95 Combined many smaller .cxx files
MarioGo 01-05-96 Locally unique IDs
--*/
#include<or.hxx>
extern
error_status_t
ComplexPingInternal(
IN handle_t hRpc,
IN SETID *pSetId,
IN USHORT SequenceNum,
IN ULONG cAddToSet,
IN ULONG cDelFromSet,
IN OID AddToSet[],
IN OID DelFromSet[],
OUT USHORT *pPingBackoffFactor
);
class CTestBindingPPing : public CParallelPing
{
public:
CTestBindingPPing(WCHAR *pBindings) :
_pBindings(pBindings)
{}
BOOL NextCall(PROTSEQINFO *pProtseqInfo)
{
if (*_pBindings)
{
pProtseqInfo->pvUserInfo = _pBindings;
pProtseqInfo->hRpc = TestBindingGetHandle(_pBindings);
_pBindings = OrStringSearch(_pBindings, 0) + 1;
return TRUE;
}
else
{
return FALSE;
}
}
void ReleaseCall(PROTSEQINFO *pProtseqInfo)
{
if (pProtseqInfo->hRpc)
{
RpcBindingFree(&pProtseqInfo->hRpc);
}
}
private:
WCHAR * _pBindings;
};
//
// CClientOid methods
//
CClientOid::~CClientOid()
{
ASSERT(gpClientLock->HeldExclusive());
ASSERT(!In());
ASSERT(Out());
ASSERT(_pOxid);
ASSERT(_pSet);
_pOxid->Release();
_pSet->Release();
gpClientOidTable->Remove(this);
}
//
// CClientOxid methods.
//
ORSTATUS
CClientOxid::GetInfo(
IN BOOL fApartment,
OUT OXID_INFO *pInfo
)
/*++
Routine Description:
Returns the OXID_INFO structure for this oxid.
The gpClientLock is held and there should also be
a reference held by the calling routine upon entry to
this method.
Arguments:
fApartment - TRUE iif the client is apartment model.
pInfo - Will contain the standard info, a single _expanded_
string binding and complete security bindings.
MIDL_user_allocated.
Return Value:
OR_NOMEM - Unable to allocate a parameter.
OR_OK - Normally.
--*/
{
USHORT protseq;
PWSTR pwstrT;
ORSTATUS status = OR_OK;
DUALSTRINGARRAY *psa;
BOOL bNoEndpoint = FALSE;
ASSERT(dsaValid(_oxidInfo.psa));
if (0 == _wProtseq)
{
// Local server
protseq = ID_LPC;
pwstrT = FindMatchingProtseq(protseq, _oxidInfo.psa->aStringArray);
ASSERT(pwstrT != 0);
if (0 != pwstrT)
{
psa =
GetStringBinding(pwstrT,
_oxidInfo.psa->aStringArray + _oxidInfo.psa->wSecurityOffset);
if (0 == psa)
{
status = OR_NOMEM;
}
}
else
{
status = OR_BADOXID;
}
}
else
{
// Remote server, find a string binding to use.
psa = 0;
PWSTR pwstrBinding = 0;
// First, check if there is a known good binding to use.
if (_iStringBinding != 0xFFFF)
{
pwstrBinding = &_oxidInfo.psa->aStringArray[_iStringBinding];
}
else
{
pwstrT = NULL;
if (_pMachineName)
{
pwstrT = FindMatchingProtseq(_pMachineName,
_wProtseq,
_oxidInfo.psa->aStringArray);
}
if (pwstrT)
{
pwstrBinding = pwstrT;
_iStringBinding = (USHORT)(pwstrT - _oxidInfo.psa->aStringArray);
}
else
{
// no stringbinding for the Protseq and machine name we succeeded on
// previously. Ping all these guys in parallel and see if any work
CTestBindingPPing ping(_oxidInfo.psa->aStringArray);
RPC_STATUS rpcstatus = RPC_S_OK;
gpClientLock->UnlockExclusive();
for (;;)
{
rpcstatus = ping.Ping();
if ( RPC_S_UNKNOWN_IF == rpcstatus )
{
if ( ! bNoEndpoint )
{
for ( ULONG ProtseqIndex = 0; ProtseqIndex < ping.HandleCount(); ProtseqIndex++ )
{
RPC_BINDING_HANDLE tmpBinding;
rpcstatus = RpcBindingCopy( ping.Info(ProtseqIndex)->hRpc, &tmpBinding);
if (rpcstatus != RPC_S_OK)
break;
RpcBindingFree( &(ping.Info(ProtseqIndex)->hRpc));
rpcstatus = RpcBindingReset(tmpBinding);
if (rpcstatus != RPC_S_OK)
{
RpcBindingFree(&tmpBinding);
break;
}
ping.Info(ProtseqIndex)->hRpc = tmpBinding;
}
if (rpcstatus == RPC_S_OK)
{
bNoEndpoint = TRUE;
continue;
}
}
}
break;
}
gpClientLock->LockExclusive();
if (rpcstatus == RPC_S_OK)
{
pwstrBinding = (WCHAR*) ping.GetWinner()->pvUserInfo;
_iStringBinding = (USHORT)(pwstrBinding - _oxidInfo.psa->aStringArray);
}
ping.Reset();
}
}
if (0 != pwstrBinding)
{
// Found a binding
ASSERT(pwstrBinding == &_oxidInfo.psa->aStringArray[_iStringBinding]);
psa = GetStringBinding(pwstrBinding,
_oxidInfo.psa->aStringArray + _oxidInfo.psa->wSecurityOffset);
if (0 == psa)
{
status = OR_NOMEM;
}
}
else
{
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_WARNING_LEVEL,
"OR: Unable to find a binding for oxid %p (to %S)\n",
this,
_oxidInfo.psa->aStringArray + 1));
if (status == OR_OK)
status = OR_BADOXID;
}
}
if (status == OR_OK)
{
// copy all the data into the OXID_INFO
memcpy(pInfo, &_oxidInfo, sizeof(_oxidInfo));
pInfo->psa = psa;
}
return(status);
}
ORSTATUS
CClientOxid::UpdateInfo(OXID_INFO *pInfo)
{
DUALSTRINGARRAY *pdsaT;
ASSERT(pInfo);
ASSERT(gpClientLock->HeldExclusive());
if (pInfo->psa)
{
ASSERT(dsaValid(pInfo->psa));
pdsaT = new(sizeof(USHORT) * pInfo->psa->wNumEntries) DUALSTRINGARRAY;
if (!pdsaT)
{
return(OR_NOMEM);
}
dsaCopy(pdsaT, pInfo->psa);
delete _oxidInfo.psa;
}
else
{
pdsaT = _oxidInfo.psa;
}
// copy in the new data
memcpy(&_oxidInfo, pInfo, sizeof(_oxidInfo));
_oxidInfo.psa = pdsaT;
ASSERT(dsaValid(_oxidInfo.psa));
return(OR_OK);
}
void
CClientOxid::Reference()
/*++
Routine Description:
As as CReferencedObject::Reference except that it knows to
pull the oxid out of the plist when the refcount was 0.
Arguments:
None
Return Value:
None
--*/
{
BOOL fRemove = (this->References() == 0);
// We may remove something from a PList more then once;
// it won't hurt anything. This avoids trying to remove
// more often then necessary.
this->CReferencedObject::Reference();
if (fRemove)
{
CPListElement * t = Remove();
ASSERT(t == &this->_plist || t == 0);
}
}
DWORD
CClientOxid::Release()
/*++
Routine Description:
Overrides CReferencedObject::Release since OXIDs must wait for
a timeout period before being deleted.
Arguments:
None
Return Value:
0 - object fully released.
non-zero - object nt fully released by you.
--*/
{
ASSERT(gpClientLock->HeldExclusive());
LONG c = CReferencedObject::Dereference();
if (c == 0)
{
Insert();
}
ASSERT(c >= 0);
return(c);
}
//
// CClientSet methods
//
ORSTATUS
CClientSet::RegisterObject(CClientOid *pOid)
/*++
Routine Description:
Adds a new oid to the set of oids owned by this set.
Arguments:
pOid - A pointer to the OID to add to the set. The caller gives
his reference to this set.
Return Value:
None
--*/
{
ORSTATUS status;
ASSERT(gpClientLock->HeldExclusive());
ASSERT(_blistOids.Member(pOid) == FALSE);
status = _blistOids.Insert(pOid);
if (status == OR_OK)
{
ObjectUpdate(pOid);
_cFailedPings = 0;
}
VALIDATE((status, OR_NOMEM, 0));
return(status);
}
ORSTATUS
CClientSet::PingServer()
/*++
Routine Description:
Performs a nice simple ping of the remote set.
Note:
Exactly and only one thread may call this method on
a given instance of a CClientSet at a time.
No lock held when called.
Overview of state transitions on a CClientOid during
a complex ping:
In() Out() Actions before ping; after ping
FALSE FALSE A ; C A U
FALSE TRUE R ; R U
TRUE FALSE N ; N
TRUE TRUE R ; C R U
Before:
A - Added to list of IDs to be added.
N - Ignored
R - Added to list of IDs to be removed.
// States may change during the call.
After:
C - Check if ping call was successful. If not, skip next action.
R - If the Out() state is still TRUE, remove it.
N - ignored
A - Set In() state to TRUE
U - If Out() state changed during the call, set _fChange.
If three pings fail in a row, all Out()==TRUE OIDs are
actually Released() and no new pings are made until ObjectUpdate()
is called again.
Arguments:
None
Return Value:
OR_OK - Pinged okay
OR_NOMEM - Resource allocation failed
OR_I_PARTIAL_UPDATE - Pinged okay, but more pings
are needed to fully update the remote set.
Other - Error from RPC.
--*/
{
ORSTATUS status;
ULONG cAdds = 0;
ULONG cDels = 0;
ULONG i;
WCHAR* pPrincipal = NULL;
WCHAR* pMachineNameFromBindings = NULL;
DWORD cFailedUnsecureCPings = 0;
CToken *pToken;
if (_fSecure)
{
pToken = (CToken *)Id2();
ASSERT(pToken != 0);
pToken->Impersonate();
}
if (_fChange)
{
USHORT wBackoffFactor;
OID *aAdds = 0;
OID *aDels = 0;
CClientOid **apoidAdds;
CClientOid **apoidDels = 0;
CClientOid *pOid;
gpClientLock->LockShared();
// Since we own a shared lock, nobody can modify the contents
// of the set or change the references on an OID in the set
// while we do this calculation.
ASSERT(_fChange);
_fChange = FALSE;
DWORD debugSize = _blistOids.Size();
ASSERT(debugSize);
CBListIterator oids(&_blistOids);
while (pOid = (CClientOid *)oids.Next())
{
if (pOid->Out() == FALSE)
{
if (pOid->In() == FALSE)
{
// Referenced and not in set, add it.
cAdds++;
}
}
else
{
// Not referenced, remove it.
cDels++;
}
}
ASSERT(debugSize == _blistOids.Size());
oids.Reset(&_blistOids);
DWORD cbAlloc = (sizeof(OID) * (cAdds + cDels)) +
(sizeof(CClientOid*) * (cAdds + cDels));
PVOID pvMem;
// Alloc no more than 16k on the stack.
if (cbAlloc < 0x4000)
{
pvMem = NULL;
aAdds = (OID *) alloca(cbAlloc);
}
else
{
pvMem = PrivMemAlloc(cbAlloc);
aAdds = (OID *) pvMem;
}
if (!aAdds)
{
gpClientLock->UnlockShared();
if (_fSecure)
{
pToken = (CToken *)Id2();
ASSERT(pToken != 0);
pToken->Revert();
}
return OR_NOMEM;
}
apoidAdds = (CClientOid **) ( aAdds + cAdds);
aDels = (OID *)( apoidAdds + cAdds);
apoidDels = (CClientOid **)(aDels + cDels);
DWORD debugAdds = cAdds;
DWORD debugDels = cDels;
cAdds = cDels = 0;
while (pOid = (CClientOid *)oids.Next())
{
if (pOid->Out() == FALSE)
{
if (pOid->In() == FALSE)
{
// Referenced and not yet added
aAdds[cAdds] = pOid->Id();
apoidAdds[cAdds] = pOid;
cAdds++;
}
}
else
{
aDels[cDels] = pOid->Id();
apoidDels[cDels] = pOid;
cDels++;
}
}
ASSERT(debugSize == _blistOids.Size());
ASSERT(debugAdds == cAdds);
ASSERT(debugDels == cDels);
gpClientLock->UnlockShared();
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_INFO_LEVEL,
"OR: Pinging set %p on %S, (%d, %d)\n",
this,
_pMid->PrintableName(),
cAdds,
cDels));
// Allocate a connection if needed
if ( FALSE == _pMid->IsLocal()
&& 0 == _hServer )
{
_hServer = _pMid->GetBinding();
if (!_hServer)
{
_iBinding = 0;
status = OR_NOMEM;
}
else
{
if (_pMid->IsSecure())
{
// set security on the binding handle.
_fSecure = TRUE;
RPC_SECURITY_QOS qos;
pMachineNameFromBindings = ExtractMachineName( _pMid->GetStringBinding() );
if (pMachineNameFromBindings)
{
pPrincipal = new WCHAR[lstrlenW(pMachineNameFromBindings) +
(sizeof(RPCSS_SPN_PREFIX) / sizeof(WCHAR)) + 1];
if (pPrincipal)
{
lstrcpyW(pPrincipal, RPCSS_SPN_PREFIX);
lstrcatW(pPrincipal, pMachineNameFromBindings);
}
delete pMachineNameFromBindings;
}
USHORT wAuthnSvc = _pMid->GetAuthnSvc();
qos.Version = RPC_C_SECURITY_QOS_VERSION;
qos.Capabilities = RPC_C_QOS_CAPABILITIES_DEFAULT;
qos.IdentityTracking = RPC_C_QOS_IDENTITY_DYNAMIC;
qos.ImpersonationType = RPC_C_IMP_LEVEL_IDENTIFY;
// AuthnSvc is unsigned long and 0xFFFF will get 0 extended
status = RpcBindingSetAuthInfoEx(_hServer,
pPrincipal,
RPC_C_AUTHN_LEVEL_CONNECT,
wAuthnSvc != 0xFFFF ? wAuthnSvc
: RPC_C_AUTHN_DEFAULT,
NULL,
0,
&qos);
if (status != RPC_S_OK)
_fSecure = FALSE;
delete pPrincipal;
}
else
{
_fSecure = FALSE;
status = OR_OK;
}
}
}
else
{
status = OR_OK;
}
if (OR_OK == status)
{
if (_pMid->IsLocal())
{
// For local pings, do it all in one call.
for (;;)
{
_sequence++;
status = ComplexPingInternal(
_hServer,
&_setid,
_sequence,
cAdds,
cDels,
aAdds,
aDels,
&wBackoffFactor
);
if (status == OR_BADSET)
{
// Restart loop, allocating new set
ASSERT(_setid);
_sequence = 0;
_setid = 0;
continue;
}
else if (status == OR_BADOID)
{
// This is really okay, all Dels must be deleted,
// and if the add failed now, it will always fail.
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_WARNING_LEVEL,
"OR: Client specified unknown OID(s). %p %p %p\n",
this,
aAdds,
apoidAdds));
status = OR_OK;
}
break;
}
}
else
{
//
// _ComplexPing is typed as taking USHORT's for the count of Adds and
// Dels -- astoundingly there are people in the world who seem to be
// using more object references than a USHORT can hold. Rather than
// changing the protocol, we separate the adds\dels into USHRT_MAX
// size chunks.
//
// However, this is not necessary for local pings, which is why that case
// is handled separately above.
//
const ULONG MAX_PING_CHUNK_SIZE = USHRT_MAX;
ULONG cAddsTotal = 0;
ULONG cDelsTotal = 0;
ULONG cCallsNeeded;
cCallsNeeded = max((ULONG)ceil((double)cAdds / (double)MAX_PING_CHUNK_SIZE),
(ULONG)ceil((double)cDels / (double)MAX_PING_CHUNK_SIZE));
// Loop the necessary # of times. Certain conditions can cause us
// re-start the loop, eg security errors, or bad set errors.
for (i = 0; i < cCallsNeeded; i++)
{
// Figure out how many adds\dels we are doing on this
// iteration. Remember that the # of adds\dels are
// independent of each other.
USHORT cAddsPerCall;
USHORT cDelsPerCall;
OID* pAddsPerCall;
OID* pDelsPerCall;
if (cAddsTotal < cAdds)
{
// More adds to do.
cAddsPerCall = (USHORT)(min(cAdds - (i * MAX_PING_CHUNK_SIZE),
MAX_PING_CHUNK_SIZE));
}
else
cAddsPerCall = 0; // done with adds
if (cDelsTotal < cDels)
{
// More dels to do.
cDelsPerCall = (USHORT)(min(cDels - (i * MAX_PING_CHUNK_SIZE),
MAX_PING_CHUNK_SIZE));
}
else
cDelsPerCall = 0; // done with dels
// Setup pointers
pAddsPerCall = (cAddsPerCall > 0) ?
aAdds + (i * MAX_PING_CHUNK_SIZE) : NULL;
pDelsPerCall = (cDelsPerCall > 0) ?
aDels + (i * MAX_PING_CHUNK_SIZE) : NULL;
ASSERT(_hServer);
ASSERT((cAddsPerCall > 0) || (cDelsPerCall > 0));
ASSERT(pAddsPerCall || pDelsPerCall);
// Update totals
cAddsTotal += cAddsPerCall;
cDelsTotal += cDelsPerCall;
_sequence++;
status = ComplexPing(
_hServer,
&_setid,
_sequence,
cAddsPerCall,
cDelsPerCall,
pAddsPerCall,
pDelsPerCall,
&wBackoffFactor
);
if (status == OR_OK)
{
// Keep going -- might have more chunks to process
}
else if (status == OR_BADOID)
{
// This is really okay, all Dels must be deleted,
// and if the add failed now, it will always fail.
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_WARNING_LEVEL,
"OR: Client specified unknown OID(s). %p %p %p\n",
this,
aAdds,
apoidAdds));
status = OR_OK;
}
else if (status == OR_NOMEM
|| status == RPC_S_OUT_OF_RESOURCES
|| status == RPC_S_SERVER_TOO_BUSY)
{
// On these errors we quit immediately. No retry attempts
// even if there are further chunks to process.
break;
}
else if (status == RPC_S_ACCESS_DENIED ||
status == RPC_S_SEC_PKG_ERROR)
{
_fSecure = FALSE;
// if unsecure pings fail more than once,
// then most likely the server rebooted
// and the setid was allocated to a different user
// on another machine. Not much we can do.
// Or, this is a DDOS attack.
// Bail in either case.
if (cFailedUnsecureCPings++ > 3)
{
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_WARNING_LEVEL,
"OR: Server %S (set %p) has failed 3 complex pings...\n",
_pMid->PrintableName(),
this));
RPC_STATUS mystatus = RpcBindingFree(&_hServer);
ASSERT(mystatus == RPC_S_OK && _hServer == 0);
_sequence--;
break;
}
status = RpcBindingSetAuthInfo(_hServer,
0,
RPC_C_AUTHN_LEVEL_NONE,
RPC_C_AUTHN_NONE,
0,
0);
if (status == RPC_S_OK)
{
// Restart loop using unsecure calls
i = -1; // restart loop from beginning
cAddsTotal = 0;
cDelsTotal = 0;
}
}
else if (status == OR_BADSET)
{
// Set invalid; reallocate (don't free the binding).
ASSERT(_setid);
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_WARNING_LEVEL,
"OR: Set %p invalid; recreating..\n",
this));
_setid = 0;
_sequence = 0;
cAddsTotal = 0;
cDelsTotal = 0;
i = -1; // reset loop
}
else
{
// Assume communication failure, free binding, and exit the
// loop (we'll re-allocate a new binding on the next ping)
RPC_STATUS mystatus = RpcBindingFree(&_hServer);
ASSERT(mystatus == RPC_S_OK && _hServer == 0);
_sequence--;
break;
}
}
}
}
pToken->Revert();
gpClientLock->LockExclusive();
this->Reference(); // Keep set alive until we finish
if (status == OR_OK)
{
// Success, process the adds
for (i = 0; i < cAdds; i++)
{
pOid = apoidAdds[i];
pOid->Added();
if (FALSE != pOid->Out())
{
// NOT referenced now, make sure it gets deleted next period.
ObjectUpdate(pOid);
}
}
// Process deletes.
for (i = 0; i < cDels; i++)
{
pOid = apoidDels[i];
pOid->Deleted();
if (FALSE != pOid->Out())
{
// Well what do you yah know, we can _finally_ delete an oid.
CClientOid *pT = (CClientOid *)_blistOids.Remove(pOid);
ASSERT(pT == pOid);
DWORD t = pOid->Release();
ASSERT(t == 0);
}
else
{
// We deleted from the set but now somebody is referencing it.
// Make sure we re-add it next time.
ObjectUpdate(pOid);
}
}
_cFailedPings = 0;
}
else
{
_fChange = TRUE;
}
DWORD c = this->Release();
if (c)
{
ASSERT(_blistOids.Size());
this->Insert();
}
else
{
ASSERT(cAdds == 0 && cDels != 0);
}
// Set (this) pointer maybe invalid
gpClientLock->UnlockExclusive();
if (pvMem)
PrivMemFree(pvMem);
}
else
{
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_INFO_LEVEL,
"OR: Pinging set %p on %S.\n",
this,
_pMid->PrintableName()));
ASSERT(_setid != 0);
if (_pMid->IsLocal())
{
ASSERT(_cFailedPings == 0);
ASSERT(_hServer == 0);
status = _SimplePing(0, &_setid);
// Somewhat overactive assert. We usually hit this assert when
// the machine is overstressed and the worker threads get
// behind. Sometimes it can also be a sign of a hung process
// on the machine. Commenting it out until we can figure
// out a way to make it more discerning.
//ASSERT(status == OR_OK);
}
else
{
ASSERT(_hServer);
if (_cFailedPings <= 3)
{
status = SimplePing(_hServer, &_setid);
if (status != OR_OK)
{
_cFailedPings++;
if (_cFailedPings > 3)
{
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_WARNING_LEVEL,
"OR: Server %S (set %p) has failed 3 pings...\n",
_pMid->PrintableName(),
this));
}
}
else
{
_cFailedPings = 0;
}
}
else
{
status = OR_OK;
}
}
this->Insert();
pToken->Revert();
}
// Set (this) maybe invalid.
#if DBG
if (status != OR_OK)
{
KdPrintEx((DPFLTR_DCOMSS_ID,
DPFLTR_WARNING_LEVEL,
"OR: ping %p failed %d\n",
this,
status));
}
#endif
return(status);
}