/*++ Copyright (c) 1995-1997 Microsoft Corporation Module Name: servers.cxx Abstract: Author: Revision History: --*/ #include "act.hxx" // Maximum number of times we will let the server tell us we are busy #define MAX_BUSY_RETRIES 3 // Maximum number of times we will let the server tell us it has rejected // the call, and the sleep time between retries. #define MAX_REJECT_RETRIES 10 #define DELAYTIME_BETWEEN_REJECTS 1500 // 1.5 seconds extern InterfaceData *AllocateAndCopy(InterfaceData *pifdIn); BOOL CServerList::InList( IN CServerListEntry * pServerListEntry ) { CListElement * pEntry; for ( pEntry = First(); pEntry; pEntry = pEntry->Next() ) if ( pEntry == (CListElement *) pServerListEntry ) return TRUE; return FALSE; } CServerListEntry::CServerListEntry( IN CServerTableEntry * pServerTableEntry, IN CProcess * pServerProcess, IN IPID ipid, IN UCHAR Context, IN UCHAR State, IN UCHAR SubContext ) { _pServerTableEntry = pServerTableEntry; _pServerTableEntry->Reference(); // This process was already validated in ServerRegisterClsid. _pServerProcess = ReferenceProcess( pServerProcess, TRUE ); _ipid = ipid; _hRpc = 0; _Context = Context; _SubContext = SubContext; _State = State; _NumCalls = 0; _lThreadToken = 0; _lSingleUseStatus = SINGLE_USE_AVAILABLE; _dwServerFaults = 0; // // Get a unique registration number without taking a global lock. // Remember that gRegisterKey uses interlocked increment for ++. // for (;;) { _RegistrationKey = (DWORD) gRegisterKey; gRegisterKey++; if ( (_RegistrationKey + 1) == (DWORD) gRegisterKey ) break; } } CServerListEntry::~CServerListEntry() { ASSERT( (Previous() == NULL) && (Next() == NULL) ); ASSERT(_lSingleUseStatus == SINGLE_USE_AVAILABLE || _lSingleUseStatus == SINGLE_USE_TAKEN); if (_hRpc) { RPC_STATUS status = RpcBindingFree(&_hRpc); ASSERT(status == RPC_S_OK); _hRpc = NULL; } ReleaseProcess( _pServerProcess ); _pServerTableEntry->Release(); } HANDLE CServerListEntry::RpcHandle() { HANDLE hRpc; RPC_STATUS status; if (_hRpc) return _hRpc; hRpc = _pServerProcess->GetBindingHandle(); if (!hRpc) return NULL; status = RpcBindingSetObject(hRpc, (GUID *)&_ipid); if (status == RPC_S_OK) { // Set mutual auth and IMPERSONATE RPC_SECURITY_QOS_V3 qos; ZeroMemory(&qos, sizeof(RPC_SECURITY_QOS_V3)); qos.Version = RPC_C_SECURITY_QOS_VERSION_3; qos.Capabilities = RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH | RPC_C_QOS_CAPABILITIES_LOCAL_MA_HINT; qos.IdentityTracking = RPC_C_QOS_IDENTITY_DYNAMIC; qos.ImpersonationType = RPC_C_IMP_LEVEL_IMPERSONATE; qos.AdditionalSecurityInfoType = 0; qos.Sid = _pServerProcess->GetToken()->GetSid(); status = RpcBindingSetAuthInfoEx(hRpc, NULL, // pass sid in QOS instead RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_WINNT, NULL, RPC_C_AUTHZ_NONE, (RPC_SECURITY_QOS*)&qos); } if (status == RPC_S_OK) { if (InterlockedCompareExchangePointer((void**)&_hRpc, hRpc, NULL) == NULL) { hRpc = NULL; } } if (hRpc) { status = RpcBindingFree(&hRpc); ASSERT(status == RPC_S_OK); hRpc = NULL; } return _hRpc; } BOOL CServerListEntry::Match( IN CToken * pToken, IN BOOL bRemoteActivation, IN BOOL bClientImpersonating, IN WCHAR* pwszWinstaDesktop, IN BOOL bSurrogate, IN LONG lThreadToken, IN LONG lSessionID, IN DWORD pid, IN DWORD dwProcessReqType, IN DWORD dwFlags ) { ASSERT(_lSingleUseStatus == SINGLE_USE_AVAILABLE || _lSingleUseStatus == SINGLE_USE_TAKEN); // If server is suspended, don't use it. if ((_State & SERVERSTATE_SUSPENDED) && !(dwFlags & MATCHFLAG_ALLOW_SUSPENDED)) return FALSE; // Is the process represented by this entry retired or suspended? if (!_pServerProcess->AvailableForActivations()) return FALSE; // If looking for a surrogate, only allow surrogates if (bSurrogate && !(_State & SERVERSTATE_SURROGATE)) return FALSE; // Did a custom activator specify a specific process to use? if (dwProcessReqType == PRT_USE_THIS) { // The rule here is, if we are not the specified process, then we // bail. If we are the right guy, then that's great but then we // still need to perform all subsequent checks below. A custom // activator may not override our normal security checks. if (_pServerProcess->GetPID() != pid) return FALSE; } // Is this a single-use registration that has already been // consumed - if so, don't use. We will check this again // at the bottom after all other checks have been made. But // doing so here saves us time if there are a lot of these // servers coming and going. if (_State & SERVERSTATE_SINGLEUSE) { if (_lSingleUseStatus == SINGLE_USE_TAKEN) return FALSE; } // If server is running as a service, no need to go further if (SERVER_SERVICE == _Context) return TRUE; // If server is a runas, might need to do more checking (ie, for // interactive user servers) below if (SERVER_RUNAS == _Context) goto EndMatch; // // If we reached here then we are an activate-as-activator server (at // least til we pass the EndMatch label down below) // ASSERT(_Context == SERVER_ACTIVATOR && "Unexpected server context"); // // If the client is anonymous, then forget it // if (!pToken) return FALSE; // Notes on activator-as-activator client\server identity & desktop matching: // // -- If client was remote, then only client & server identity needs to match // -- If client was local, and not impersonating, then both client & server // identity and client\server desktop need to match // -- If client was local, and impersonating, then client & server identity // need to match, and token luid's need to match. // // The reason for this is that apps from NT4 and before expect that act-as- // activator servers will always run on the client's desktop. If the server // has the same identity as the client, this is fine. Trouble arises when the // client is impersonating, and we put the server on the client's desktop - // sometimes the newly-launched server will not have permissions to the client's // desktop, and hence dies a quick death. The above rules are meant to // work around this, and allow us to simulataneously accommodate both legacy apps // and new apps that activate objects while impersonating. if (!bRemoteActivation && !bClientImpersonating) { ASSERT(pwszWinstaDesktop); if (!pwszWinstaDesktop) return FALSE; if (lstrcmpW(pwszWinstaDesktop, _pServerProcess->WinstaDesktop()) != 0 ) return FALSE; } else if (!bRemoteActivation) { // By matching luids in the local\impersonation case, we can // indirectly try to enforce desktops that way. if (S_OK != pToken->MatchTokenLuid(_pServerProcess->GetToken())) return FALSE; } // // If the client isn't us, then forget it (remember, at this point we're still // an activate-as-activator server) // if ( S_OK != pToken->MatchToken2(_pServerProcess->GetToken(), FALSE) ) { //DbgPrint("RPCSS: ServerListEntry %p: Token did not match.\n", this); return FALSE; } // // If the client is less trusted than us, then forget it as well. // if ( gbSAFERAAAChecksEnabled ) { if (S_FALSE == pToken->CompareSaferLevels(_pServerProcess->GetToken())) { //DbgPrint("RPCSS: ServerListEntry %p: SAFER level did not match.\n", this); return FALSE; } } EndMatch: BOOL bRet = FALSE; if (lThreadToken == _lThreadToken) { CToken* pServerToken = _pServerProcess->GetToken(); ASSERT(pServerToken && "_pServerProcess did not have an associated token reference"); if (pServerToken != NULL) { if (_SubContext == SUB_CONTEXT_RUNAS_INTERACTIVE) { if (lSessionID != INVALID_SESSION_ID) { // User specified a destination session to use; check that this // server is in that session bRet = (pServerToken->MatchSessionID(lSessionID) == S_OK); } else { // No dest. session specified. The only thing left to check is // if the user is local then the server we select should be in // that user's session. If the user is not local, then this // server must be in session 0 to be a match if (pToken == NULL) { // anonymous client; only thing to make sure is that server // is running in session zero bRet = (pServerToken->GetSessionId() == 0); } else { // else the server and client better be in the same session; note // that if the client is from off-machine, then his session will // be zero. bRet = (S_OK == pToken->MatchTokenSessionID(pServerToken)); } } } else { // Else the server is either a pure run-as server, or an activate-as-activator // server. In either case, it is adequate for the activation. bRet = TRUE; } } } // If this server registered as single-use, we need to make sure // no one else uses it. The previous implementation would take // a write lock here and completely remove the entry from the list, // but I like this method better since it's more concurrent. if (bRet && (_State & SERVERSTATE_SINGLEUSE)) { LONG lICERet; lICERet = InterlockedCompareExchange(&_lSingleUseStatus, SINGLE_USE_TAKEN, SINGLE_USE_AVAILABLE); if (lICERet != SINGLE_USE_AVAILABLE) { // Can't use this one, somebody else grabbed it bRet = FALSE; } } return bRet; } BOOL CServerListEntry::CallServer( IN PACTIVATION_PARAMS pActParams, OUT HRESULT * phr ) /*-- Notes: jsimmons 02/10/01 -- I changed this function to return TRUE on most error paths. The semantic meaning of this function's return value is "TRUE if we called a server or encountered a fatal error trying to do so". Return values of FALSE are interpreted by the caller to mean "it's okay if I do a retry". My belief is that we should not be doing retrys of any kind after most errors -- ie, a memory allocation failure should stop us dead in our tracks. --*/ { HANDLE hRpc; DWORD BusyRetries; DWORD RejectRetries; BOOL fDone; DWORD CreateInstanceFlags; RPC_STATUS status = RPC_S_OK; HRESULT hr; hRpc = RpcHandle(); if (!hRpc) { *phr = E_OUTOFMEMORY; return TRUE; } BusyRetries = 0; RejectRetries = 0; CreateInstanceFlags = 0; #ifdef SERVER_HANDLER if ( pActParams->ClsContext & CLSCTX_ESERVER_HANDLER ) { CreateInstanceFlags |= CREATE_EMBEDDING_SERVER_HANDLER; if ( gbDisableEmbeddingServerHandler ) { CreateInstanceFlags |= DISABLE_EMBEDDING_SERVER_HANDLER; pActParams->pInstantiationInfo->SetInstFlag(CreateInstanceFlags); } } #endif // SERVER_HANDLER // Tell the server that we are using dynamic cloaking. pActParams->ORPCthis->flags |= ORPCF_DYNAMIC_CLOAKING; if (_State & SERVERSTATE_SURROGATE) { pActParams->pInstantiationInfo->SetIsSurrogate(); } if (pActParams->MsgType == GETPERSISTENTINSTANCE) { ASSERT(pActParams->pInstanceInfo != NULL); if (pActParams->pIFDROT) { InterfaceData *newIfd = AllocateAndCopy((InterfaceData*)pActParams->pIFDROT); if (newIfd == NULL) { *phr = E_OUTOFMEMORY; return TRUE; } pActParams->pInstanceInfo->SetIfdROT((MInterfacePointer*)newIfd); } } // In case treat as changed the clsid set it now ASSERT(pActParams->pInstantiationInfo); pActParams->pInstantiationInfo->SetClsid(pActParams->Clsid); do { MInterfacePointer *pIFDIn, *pIFDOut=NULL; DWORD destCtx = MSHCTX_LOCAL; *phr = ActPropsMarshalHelper(pActParams->pActPropsIn, IID_IActivationPropertiesIn, destCtx, MSHLFLAGS_NORMAL, &pIFDIn); if (FAILED(*phr)) { return TRUE; } // // Always impersonate the client before calling the server. // pActParams->pToken->Impersonate(); switch (pActParams->MsgType) { case GETCLASSOBJECT: status = RPC_S_OK; RpcTryExcept { *phr = LocalGetClassObject( hRpc, pActParams->ORPCthis, pActParams->Localthis, pActParams->ORPCthat, pIFDIn, &pIFDOut); } RpcExcept(1) { status = RpcExceptionCode(); } RpcEndExcept; break; case GETPERSISTENTINSTANCE: case CREATEINSTANCE: status = RPC_S_OK; RpcTryExcept { *phr = LocalCreateInstance( hRpc, pActParams->ORPCthis, pActParams->Localthis, pActParams->ORPCthat, NULL, //No punk outer from here pIFDIn, &pIFDOut); } RpcExcept(1) { status = RpcExceptionCode(); } RpcEndExcept; break; default: ASSERT(0 && "Unknown activation type"); *phr = E_UNEXPECTED; break; } //Switch // // Un-impersonate // pActParams->pToken->Revert(); MIDL_user_free(pIFDIn); if ((*phr == S_OK) && (status == RPC_S_OK )) { // AWFUL HACK ALERT: This is too hacky even for the SCM ActivationStream ActStream((InterfaceData*) (((BYTE*)pIFDOut)+48)); pActParams->pActPropsOut = new ActivationPropertiesOut(FALSE /* fBrokenRefCount */ ); if (pActParams->pActPropsOut != NULL) { IActivationPropertiesOut *dummy; hr = pActParams->pActPropsOut->UnmarshalInterface(&ActStream, IID_IActivationPropertiesOut, (LPVOID*)&dummy); if (FAILED(hr)) { pActParams->pActPropsOut->Release(); pActParams->pActPropsOut = NULL; *phr = hr; } else dummy->Release(); } else *phr = E_OUTOFMEMORY; MIDL_user_free(pIFDOut); } // Determine if we need to retry the call. Assume not. fDone = TRUE; if (status == RPC_S_SERVER_TOO_BUSY) { // server RPC was busy, should we retry? if (BusyRetries++ < MAX_BUSY_RETRIES) fDone = FALSE; } else if (status == RPC_E_CALL_REJECTED) { // Take Note: this is somewhat broken, but was added as a hotfix for // Word Insert Excel'97 with Addins, where Excel registers it's CF // early then rejects calls until the addin's are ready, which could // take any amount of time. We give 3 tries, with a sleep between them // to give the app some CPU time and emulate the delay than a client // message filter would do. if (RejectRetries++ < MAX_REJECT_RETRIES) { Sleep(DELAYTIME_BETWEEN_REJECTS); fDone = FALSE; } } } while ( !fDone ); // A RpcStatus of ERROR_ACCESS_DENIED means the server will never // accept calls from this user. Don't retry the activation. if (status == ERROR_ACCESS_DENIED || status == E_ACCESSDENIED) { *phr = HRESULT_FROM_WIN32(status); return TRUE; } // // We get a non-zero rpcstat if there was a communication problem // with the server. We get CO_E_SERVER_STOPPING if a server // consumes its own single use registration or was in the process of // revoking its registration when we called. // else if ( (status != RPC_S_OK) || (*phr == CO_E_SERVER_STOPPING) ) { if ( status != RPC_S_OK ) { // // Some rpc errors are of the 8001xxxx variety. We shouldn't do // the hresult conversion of these. // if ( HRESULT_FACILITY( status ) == FACILITY_RPC ) *phr = status; else *phr = HRESULT_FROM_WIN32(status); } // Decide whether to retry the activation. return RetryableError(*phr) ? FALSE : TRUE; } return TRUE; } BOOL CServerListEntry::ServerDied() /*-- ServerDied Used by callers to determine if the server process handle has been signalled. --*/ { BOOL fServerDied = FALSE; HANDLE hProcess = _pServerProcess->GetProcessHandle(); if (hProcess) { DWORD dwRet = WaitForSingleObject(hProcess, 0); if (dwRet == WAIT_OBJECT_0) { fServerDied = TRUE; } // else assume still alive on all other return values } return fServerDied; } BOOL CServerListEntry::RetryableError(HRESULT hr) /*-- Returns TRUE if the error is such that the caller should attempt a retry of the activation, or FALSE otherwise. --*/ { BOOL fRetry = TRUE; switch (hr) { case RPC_E_SYS_CALL_FAILED: // RPC_E_SYS_CALL_FAILED is not used by rpc, only ole32, and it // is not one we should be doing retrys on. fRetry = FALSE; break; default: fRetry = TRUE; break; } return fRetry; }