//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: class.cxx // // Contents: Server table entry implementation // // History: VinayKr Created // 06-Nov-98 TarunA Launch and shutdown logic change // 24-Feb-99 TarunA Fix custom surrogate launch // 17-Jun-99 a-sergiv Support for filtering RPCSS event logs // //-------------------------------------------------------------------------- #include "act.hxx" CServerTable * gpClassTable = NULL; CServerTable * gpProcessTable = NULL; CSharedLock * gpClassLock = 0; CSharedLock * gpProcessLock = 0; CInterlockedInteger gRegisterKey( 1 ); // // Default number of milliseconds to wait for a server to register its // class factory before giving up and forcefully terminating it. // DWORD gServerStartTimeout = 120000; // // Default number of milliseconds to wait for a class factory registration // after we detect that the launched server has died (for supporting cases // where the server we launch turns around and launches another, then dies // immediately) // DWORD gServerStartSecondChanceTimeout = 30000; // // Default number of milliseconds to wait for an NT service server to register // before we wake up and query its status. At most we wait for // gNTServiceMaxTimeouts of these periods (see code in WaitForService below). // DWORD gNTServiceInterrogatePeriod = 30000; DWORD gNTServiceMaxTimeouts = 4; // // Default number of milliseconds that we block while waiting for a dllhost // server to register. There is a startup protocol here - we block for // thirty seconds, then check the state of the launched process. // DWORD gDllhostServerStartTimeout = 30000; // // Maxiumum number of milliseconds we will wait while waiting for a dllhost // server to register. There is a protocol yes, but this is the upper time // bound no matter what. // DWORD gDllhostMaxServerStartTimeout = 90000; // // Default number of milliseconds to wait for a dllhost to finish // long-running initialization. // DWORD gDllhostInitializationTimeout = 90000; void GetSessionIDFromActParams(IActivationPropertiesIn* pActPropsIn, LONG* plSessionID) { *plSessionID = INVALID_SESSION_ID; if (pActPropsIn != NULL) { ISpecialSystemProperties *pSp = NULL; HRESULT hr; hr = pActPropsIn->QueryInterface(IID_ISpecialSystemProperties, (void**) &pSp); if (SUCCEEDED(hr) && pSp != NULL) { ULONG ulSessionID; BOOL bUseConsole; hr = pSp->GetSessionId(&ulSessionID, &bUseConsole); if (SUCCEEDED(hr)) { if (!bUseConsole && (ulSessionID != INVALID_SESSION_ID)) { // Just use whatever was there *plSessionID = (LONG) ulSessionID; } else if (bUseConsole) { // Little bit of magic here regarding how to find the // currently active NT console session ID. *plSessionID = (USER_SHARED_DATA->ActiveConsoleId); } } pSp->Release(); pSp = NULL; } } } //+------------------------------------------------------------------------- // // CServerTable::GetOrCreate // // A somewhat degenerate combination of Lookup and Create -- only creates // empty entries -- in particular without hooking up process entry if the // entry being created is a class entry -- used only to create process entries // and to create class entries for unsolicited CoRegisterClassObject calls // //-------------------------------------------------------------------------- CServerTableEntry * CServerTable::GetOrCreate( IN GUID & ServerGuid ) { CServerTableEntry * pEntry; pEntry = Lookup( ServerGuid ); if ( pEntry ) return pEntry; else return Create(ServerGuid); } //+------------------------------------------------------------------------- // // CServerTable::Create // // Creates a new CServerTableEntry, or returns an existing one. // //-------------------------------------------------------------------------- CServerTableEntry * CServerTable::Create( IN GUID & ServerGuid ) { BOOL fEntryFoundInTable = FALSE; _pServerTableLock->LockExclusive(); // Must use CGuidTable::Lookup method because it doesn't take // a shared lock. Already have the exclusive lock. CServerTableEntry *pNewEntry = (CServerTableEntry *) CGuidTable::Lookup( &ServerGuid ); if (pNewEntry) { pNewEntry->Reference(); // Found an entry, take a reference on it fEntryFoundInTable = TRUE; } else { LONG Status = ERROR_SUCCESS; pNewEntry = new CServerTableEntry( Status, &ServerGuid, _EntryType ); if ( ! pNewEntry ) { _pServerTableLock->UnlockExclusive(); return NULL; } if ( Status != ERROR_SUCCESS ) { _pServerTableLock->UnlockExclusive(); pNewEntry->Release(); return NULL; } } if (!fEntryFoundInTable) { Add( pNewEntry ); } _pServerTableLock->UnlockExclusive(); return pNewEntry; } //+------------------------------------------------------------------------- // // CServerTable::Lookup // //-------------------------------------------------------------------------- CServerTableEntry * CServerTable::Lookup( IN GUID & ServerGuid ) { CServerTableEntry * pServerTableEntry; _pServerTableLock->LockShared(); pServerTableEntry = (CServerTableEntry *) CGuidTable::Lookup( &ServerGuid ); if ( pServerTableEntry ) pServerTableEntry->Reference(); _pServerTableLock->UnlockShared(); return pServerTableEntry; } //+------------------------------------------------------------------------- // // CServerTableEntry::CServerTableEntry // //-------------------------------------------------------------------------- CServerTableEntry::CServerTableEntry( OUT LONG& Status, IN GUID * pServerGUID, IN EnumEntryType EntryType ) : CGuidTableEntry( pServerGUID ), _EntryType(EntryType) ,_ServerLock( Status ), _lThreadToken(0), _hProcess(0), _pProcess(NULL) ,_pvRunAsHandle(NULL), _bSuspendedClsid(FALSE), _bSuspendedApplication(FALSE), _dwProcessId(0) { switch (_EntryType) { case ENTRY_TYPE_CLASS: _pParentTable = gpClassTable; break; case ENTRY_TYPE_PROCESS: _pParentTable = gpProcessTable; break; default: if (Status == ERROR_SUCCESS) Status = E_INVALIDARG; } _pParentTableLock = _pParentTable->_pServerTableLock; } //+------------------------------------------------------------------------- // // CServerTableEntry::~CServerTableEntry // //-------------------------------------------------------------------------- CServerTableEntry::~CServerTableEntry() { ASSERT(_pParentTableLock->HeldExclusive()); // Remove ourselves from the table - this will be a no-op if we // we were never actually added. _pParentTable->Remove(this); if(NULL != _pProcess) { ReleaseProcess(_pProcess); } ASSERT(_pvRunAsHandle == NULL); ASSERT(_hProcess == NULL); } //+------------------------------------------------------------------------- // // CServerTableEntry::Release // //-------------------------------------------------------------------------- DWORD CServerTableEntry::Release() { DWORD Refs; CSharedLock* pParentTableLock = _pParentTableLock; CairoleDebugOut((DEB_SCM, "Releasing ServerTableEntry at 0x%p\n", this)); pParentTableLock->LockExclusive(); Dereference(); if ( 0 == (Refs = References()) ) { delete this; } pParentTableLock->UnlockExclusive(); return Refs; } //+------------------------------------------------------------------------- // // CServerTableEntry::RegisterServer // //-------------------------------------------------------------------------- HRESULT CServerTableEntry::RegisterServer( IN CProcess * pServerProcess, IN IPID ipid, IN CClsidData * pClsidData, OPTIONAL IN CAppidData * pAppidData, IN UCHAR ServerState, OUT DWORD * pRegistrationKey ) { CServerListEntry * pEntry; CSurrogateListEntry * pSurrogateEntry; UCHAR Context = 0; UCHAR SubContext = 0; if (pClsidData) { if ( (pClsidData->ServerType() == SERVERTYPE_SERVICE) || (pClsidData->ServerType() == SERVERTYPE_COMPLUS_SVC) ) { Context = SERVER_SERVICE; } else if ( pClsidData->HasRunAs() ) { Context = SERVER_RUNAS; if ( pClsidData->IsInteractiveUser() ) SubContext = SUB_CONTEXT_RUNAS_INTERACTIVE; else SubContext = SUB_CONTEXT_RUNAS_THIS_USER; } // Special case of COM+ library class being loaded by VB for debugging else if ( pClsidData->ServerType() == SERVERTYPE_COMPLUS && pClsidData->IsInprocClass() ) { Context = SERVER_RUNAS; } } if ( _EntryType == ENTRY_TYPE_PROCESS ) { // This is the COM+/COM surrogate case ASSERT(NULL == pClsidData && "Process Entry Given Clsid data"); ASSERT(NULL != pAppidData && "Process Entry Not Given Appid data"); if (pAppidData->Service()) { Context = SERVER_SERVICE; } else if ((pAppidData->IsInteractiveUser()) || pAppidData->RunAsUser()) { Context = SERVER_RUNAS; } } // If none of the above applied, Context will be zero at this point -- this // essentially means the server should be treated as an activate-as-activator // server. However, if Context is zero SubContext had better also be zero, // and so we assert that here. if (Context == 0) { Context = SERVER_ACTIVATOR; ASSERT(SubContext == 0); } pEntry = new CServerListEntry( this, pServerProcess, ipid, Context, ServerState, SubContext); if ( ! pEntry ) return E_OUTOFMEMORY; // Add registration to the process object -- but only if we are a class entry; // otherwise this is a registration for a surrogate server if ( _EntryType == ENTRY_TYPE_CLASS ) { pEntry->AddToProcess( Guid() ); } // SetSuspendedFlagOnNewServer(pServerProcess); // // NOTE: The entry should be inserted in the lists only after // all the registration specific stuff has been done. This will // ensure that anyone outside this procedure sees the complete // entry and not some half-baked one // ServerLock()->LockExclusive(); // Check if process handle exists implying that a // launch is in progress for this class. Try to // register runas,process, and threadtoken with // the newly created entry // Note that it is essential for the server lock to // be held since _hProcess can change otherwise. if (_hProcess || _pProcess) RegisterHandles(pEntry, pServerProcess); _ServerList.Insert( pEntry ); ServerLock()->UnlockExclusive(); // // It's possible that a process could register itself as a surrogate but // there's no CLSID info in the registry. We might want to disallow // this kind of register. // // Note: We allow class registration if appid is not present but don't // allow surrogate registration since it'll never be found // The right thing to do would be to reject the call completely when // an appid is not present but we will take the path of least resistance // and preserve legacy if ( pClsidData && (ServerState & SERVERSTATE_SURROGATE) && pClsidData->AppidString() ) { pSurrogateEntry = new CSurrogateListEntry( pClsidData->AppidString(), pEntry ); if ( ! pSurrogateEntry ) { pEntry->CReferencedObject::Release(); return E_OUTOFMEMORY; } gpSurrogateList->Insert( pSurrogateEntry ); } *pRegistrationKey = pEntry->RegistrationKey(); return S_OK; } //+------------------------------------------------------------------------- // // CServerTableEntry::RevokeServer -- version 1 for class entries // // CODE WORK: Consider defining two separate classes for class and // process entries so we can avoid this sort of thing // //-------------------------------------------------------------------------- void CServerTableEntry::RevokeServer( IN CProcess * pServerProcess, IN DWORD RegistrationKey ) { ASSERT(_EntryType == ENTRY_TYPE_CLASS); ServerLock()->LockExclusive(); CServerListEntry * pEntry = (CServerListEntry *) _ServerList.First(); for ( ; pEntry; pEntry = (CServerListEntry *) pEntry->Next() ) { if ( (RegistrationKey == pEntry->_RegistrationKey) && (pServerProcess == pEntry->_pServerProcess) ) break; } if ( pEntry ) _ServerList.Remove( pEntry ); ServerLock()->UnlockExclusive(); if ( pEntry ) { pEntry->RemoveFromProcess(); pEntry->Release(); } } //+------------------------------------------------------------------------- // // CServerTableEntry::RevokeServer -- version 2 for process entries // //-------------------------------------------------------------------------- void CServerTableEntry::RevokeServer( IN ScmProcessReg * pScmProcessReg ) { ASSERT(_EntryType == ENTRY_TYPE_PROCESS); ServerLock()->LockExclusive(); CServerListEntry * pEntry = (CServerListEntry *) _ServerList.First(); for ( ; pEntry; pEntry = (CServerListEntry *) pEntry->Next() ) { if ( (pScmProcessReg->RegistrationToken == pEntry->_RegistrationKey) ) break; } if ( pEntry ) _ServerList.Remove( pEntry ); ServerLock()->UnlockExclusive(); if ( pEntry ) { pEntry->RemoveFromProcess(); pEntry->Release(); } } //+------------------------------------------------------------------------- // // CServerTableEntry::LookupServer // //-------------------------------------------------------------------------- BOOL CServerTableEntry::LookupServer( IN CToken * pClientToken, IN BOOL bRemoteActivation, IN BOOL bClientImpersonating, IN WCHAR * pwszWinstaDesktop, IN DWORD dwFlags, IN LONG lThreadToken, IN LONG lSessionID, IN DWORD pid, IN DWORD dwProcessReqType, OUT CServerListEntry ** ppServerListEntry) { CServerListEntry * pEntry; *ppServerListEntry = NULL; // If we are suspended, no need to go any further if (IsSuspended()) return FALSE; ServerLock()->LockShared(); pEntry = (CServerListEntry *) _ServerList.First(); for ( ; pEntry; pEntry = (CServerListEntry *) pEntry->Next() ) { if ( pEntry->Match( pClientToken, bRemoteActivation, bClientImpersonating, pwszWinstaDesktop, FALSE, lThreadToken, lSessionID, pid, dwProcessReqType, dwFlags ) ) { pEntry->Reference(); break; } } ServerLock()->UnlockShared(); *ppServerListEntry = pEntry; return (pEntry != 0); } //+------------------------------------------------------------------------- // // CServerTableEntry::LookupServer // //-------------------------------------------------------------------------- BOOL CServerTableEntry::LookupServer( IN DWORD RegistrationKey, CServerListEntry ** ppServerListEntry ) { CServerListEntry * pEntry; ServerLock()->LockShared(); pEntry = (CServerListEntry *) _ServerList.First(); for ( ; pEntry; pEntry = (CServerListEntry *) pEntry->Next() ) { if ( RegistrationKey == pEntry->_RegistrationKey ) break; } if ( pEntry ) pEntry->Reference(); ServerLock()->UnlockShared(); *ppServerListEntry = pEntry; return (pEntry != 0); } //+------------------------------------------------------------------------- // // CServerTableEntry::UnsuspendServer // //-------------------------------------------------------------------------- void CServerTableEntry::UnsuspendServer( IN DWORD RegistrationKey ) { CServerListEntry * pEntry; LookupServer( RegistrationKey, &pEntry ); if ( pEntry ) { pEntry->_State &= ~SERVERSTATE_SUSPENDED; pEntry->Release(); } } //+------------------------------------------------------------------------- // CServerTableEntry::ServerExists //+------------------------------------------------------------------------- HRESULT CServerTableEntry::ServerExists( IN ACTIVATION_PARAMS * pActParams, BOOL *pfExist) { LONG lSessionID = INVALID_SESSION_ID; ServerLock()->LockShared(); GetSessionIDFromActParams(pActParams->pActPropsIn, &lSessionID); CServerListEntry *pEntry = (CServerListEntry *) _ServerList.First(); for ( ; pEntry; pEntry = (CServerListEntry *) pEntry->Next() ) { if ( pEntry->Match( pActParams->pToken, pActParams->RemoteActivation, pActParams->bClientImpersonating, pActParams->pProcess ? pActParams->pProcess->WinstaDesktop() : NULL , FALSE, 0, lSessionID )) { break; } } ServerLock()->UnlockShared(); *pfExist = (pEntry != NULL); return S_OK; } //+------------------------------------------------------------------------- // // CServerTableEntry::CallRunningServer // Synopsis: The behavior of CallRunningServer is best described by this matrix // // If the call is made successfully then we return // If CallServer() returns error then we take the following action // ---------------------------------------------------------------------------- // | | RPC_E_SERVERFAULT | CO_E_SERVER_STOPPING | Other Errors | // ---------------------------------------------------------------------------- // | COM | Kill the process | Remove the list entry |Remove the list entry| // | | Remove the list entry | Try launching another |Try launching another| // | | Try launching another | process |process | // | | process | | | // | | | | | // ----------------------------------------------------------------------------- // | COM+| Kill the process | Set a timeout period |If process exists | // | | Remove the list entry | If the server |then | // | | Return the error | stops before timeout | return error | // | | | then |else | // | | | try launching | Remove the entry | // | | | another process | try launching | // | | | else | another process | // | | | return error | | // ----------------------------------------------------------------------------- //-------------------------------------------------------------------------- BOOL CServerTableEntry::CallRunningServer( IN ACTIVATION_PARAMS * pActParams, IN DWORD dwFlags, IN LONG lLaunchThreadToken, IN CClsidData * pClsidData, OUT HRESULT * phr ) { BOOL bStatus; LONG lSessionID = INVALID_SESSION_ID; CServerListEntry* pEntry; GetSessionIDFromActParams(pActParams->pActPropsIn, &lSessionID); for (;;) { BOOL bTerminatedServer; bStatus = LookupServer( pActParams->pToken, pActParams->RemoteActivation, pActParams->bClientImpersonating, pActParams->pProcess ? pActParams->pProcess->WinstaDesktop() : NULL , dwFlags, lLaunchThreadToken, lSessionID, pActParams->dwPID, pActParams->dwProcessReqType, &pEntry); if (!bStatus) { ASSERT(!pEntry); return FALSE; } *phr = S_OK; // // If the entry is "initializing", then it isn't really ready yet. // Wait for it to become so. // if (pEntry->IsInitializing()) { *phr = WaitForInitCompleted(pEntry, pClsidData); if (FAILED(*phr)) { if (*phr == CO_E_SERVER_STOPPING) bStatus = FALSE; else bStatus = TRUE; } } // // Returns TRUE if we successfully make a call to this server, or // if an unrecoverable non-server-related error is encountered // (e.g, an out-of-mem failure while allocating data before or // after the call). // // The final HRESULT is not necessarily S_OK. // if (SUCCEEDED(*phr)) bStatus = pEntry->CallServer(pActParams, phr); if (!bStatus) { // We were unable to talk to the server, or got back // CO_E_SERVER_STOPPING. // Retrieve the process handle, if we have one HANDLE hProcess = pEntry->GetProcess()->GetProcessHandle(); bTerminatedServer = FALSE; // Try to kill the server if it returned RPC_E_SERVERFAULT (this // means that we caught an unhandled exception somewhere in the // server, which we don't really appreciate.) if(*phr == RPC_E_SERVERFAULT) { if(hProcess) { bTerminatedServer = TerminateProcess(hProcess, 0); } // Remember that the server blew up on us. This is mostly // for debugging purposes pEntry->IncServerFaults(); } // // Determine if we need to remove the server entry. We will do this if // a) we killed the server process up above; or b) the server returned // CO_E_SERVER_STOPPING; or c) the server process is dead already. // if (bTerminatedServer || (*phr == CO_E_SERVER_STOPPING) || pEntry->ServerDied()) { BOOL bRemoved; // // Remove this server entry if it's still in the list. // ServerLock()->LockExclusive(); bRemoved = _ServerList.InList(pEntry); if (bRemoved) _ServerList.Remove(pEntry); ServerLock()->UnlockExclusive(); // Need to remove this entry from the surrogate list as // well, if applicable (void)gpSurrogateList->RemoveMatchingEntry(pEntry); if (bRemoved) { pEntry->RemoveFromProcess(); pEntry->Release(); } } // 6ba => we should not retry calls into the server else if (RPC_S_SERVER_UNAVAILABLE == HRESULT_CODE(*phr)) { bStatus = TRUE; } } // Now allow threads other than launching one to access // this entry. Exit since we matched launched server // regardless of result since we know that we will // not get any more matches. if (lLaunchThreadToken) { pEntry->SetThreadToken(0); pEntry->Release(); return bStatus; } pEntry->Release(); if ( bStatus ) return TRUE; } } //+------------------------------------------------------------------------- // // CServerTableEntry::StartServerAndWait // //-------------------------------------------------------------------------- HRESULT CServerTableEntry::StartServerAndWait( IN ACTIVATION_PARAMS * pActParams, IN CClsidData * pClsidData, IN LONG &lLaunchThreadToken ) { SC_HANDLE hService; HRESULT hr; BOOL bStatus; BOOL bServiceAlreadyRunning = FALSE; DWORD dwActvFlags; if ( ! pClsidData->LaunchAllowed( pActParams->pToken, pActParams->ClsContext ) ) return E_ACCESSDENIED; // Check CreateProcessMemoryGate // [a-sergiv (Sergei O. Ivanov) 11/1/99 Implemented Memory Gates in COM Base if(pActParams->pActPropsIn) { BOOL bResult = TRUE; IClassClassicInfo *pClassicInfo = NULL; hr = pActParams->pActPropsIn->GetClassInfo(IID_IClassClassicInfo, (void**) &pClassicInfo); if(SUCCEEDED(hr) && pClassicInfo) { IResourceGates *pResGates = NULL; hr = pClassicInfo->GetProcess(IID_IResourceGates, (void**) &pResGates); pClassicInfo->Release(); if(SUCCEEDED(hr) && pResGates) { hr = pResGates->Test(CreateProcessMemoryGate, &bResult); pResGates->Release(); if(SUCCEEDED(hr) && !bResult) { // The gate said NO! return E_OUTOFMEMORY; } } } } hService = 0; // NOTE: _hProcess, _dwProcessId, _pProcess and all other process specific // variables that are part of CServerTableEntry are used only during the // launch. Once the launch is completed either they are transferred to // process specific entries or discarded. See CServerTableEntry::RegisterHandles // for an example. _dwProcessId = 0; // _hProcess and _pProcess variables are released and set to // null whether // (1) This is the first launch // OR // (2) We succeeded in an earlier launch or failed ASSERT((0 == _hProcess) && (NULL == _pProcess)); // // Get the register event for the server we're about to launch. Note // there exists a problem in that we may launch a server, but another // server may register the desired clsid\appid at about the same time. // I think this is just something we have to live with, especially since // there are servers in the world that don't register with us, but instead // turn around and launch another process to do the registration. So, // even if we have a process handle it would be impossible to always // correlate from register event back to the launching table entry. // CNamedObject* pRegisterEvent = pClsidData->ServerRegisterEvent(); if (!pRegisterEvent) return E_OUTOFMEMORY; if (pClsidData->ServerType() == SERVERTYPE_SURROGATE) { ASSERT(_EntryType == ENTRY_TYPE_CLASS); LPWSTR pwszAppidString = pClsidData->AppidString(); CSurrogateListEntry * pSurrogateListEntry = NULL; if(pwszAppidString) { pSurrogateListEntry = gpSurrogateList->Lookup( pActParams->pToken, pActParams->RemoteActivation, pActParams->bClientImpersonating, pActParams->pProcess ? pActParams->pProcess->WinstaDesktop() : NULL , pwszAppidString ); } if ( pSurrogateListEntry ) { // If we find a surrogate entry and we are inside a launch // server then that implies that a surrogate was launched for // a different class' activation. We have a process entry for // this surrogate and we set it here. Later when RegisterServer // gets called we use the process object to set the state. // For all other cases, the state is set using the process handle. _pProcess = pSurrogateListEntry->Process(); ASSERT(NULL !=_pProcess); // We will release this on every path below ReferenceProcess( _pProcess, TRUE ); // Remember the launching thread's token _lThreadToken = lLaunchThreadToken; // Load the dll that will service the activation request bStatus = pSurrogateListEntry->LoadDll( pActParams, &hr ); pSurrogateListEntry->Release(); // A status of TRUE implies that we successfully talked to the // server if (bStatus) { // A successful hresult implies that the server was able to // load the dll successfully and hence we expect it to // register back if(SUCCEEDED(hr)) { // Wait for the loaded object server to register back bStatus = WaitForSingleObject( pRegisterEvent->Handle(), 30000 ); } // If the server failed to load the dll or it registered back // within the timeout then we return immediately if ( FAILED(hr) || (bStatus == WAIT_OBJECT_0) ) { ReleaseProcess(_pProcess); _pProcess = NULL; pRegisterEvent->Release(); return hr; } } // If a launched server failed to register back in the given // timeout or we were not able to talk to the server then we // continue and try to launch a new process ReleaseProcess(_pProcess); _pProcess = NULL; } } if ((pClsidData->ServerType() == SERVERTYPE_SERVICE) || (pClsidData->ServerType() == SERVERTYPE_COMPLUS_SVC)) { // Set the token here as service does not register back // with us and so it cannot be set at registration time // unlike the activator server case below hr = pClsidData->LaunchService( pActParams->pToken, pActParams->ClsContext, &hService ); if (hr == HRESULT_FROM_WIN32(ERROR_SERVICE_ALREADY_RUNNING)) { hr = S_OK; bServiceAlreadyRunning = TRUE; } } else { //When a "RunAs Interactive User" server is launched from a Hydra session, //the server runs in the Hydra session of the caller. In this case, //we want to call LaunchActivator server instead of LaunchRunAsServer //so that the server runs in the appropriate Hydra session. _lThreadToken = lLaunchThreadToken; if ( pClsidData->HasRunAs()) { hr = pClsidData->LaunchRunAsServer( pActParams->pToken, pActParams->RemoteActivation, pActParams->pActPropsIn, pActParams->ClsContext, &_hProcess, &_dwProcessId, &_pvRunAsHandle); } else { // Check to see if the client requested "protected" activation. If // so, we refuse to launch a server using their token. hr = pActParams->pActPropsIn->GetActivationFlags(&dwActvFlags); if (FAILED(hr) || (dwActvFlags & ACTVFLAGS_DISABLE_AAA)) { hr = E_ACCESSDENIED; } // If client came in unsecure, reject the activation if (pActParams->UnsecureActivation) { hr = E_ACCESSDENIED; } if (SUCCEEDED(hr)) { hr = pClsidData->LaunchActivatorServer( pActParams->pToken, pActParams->pEnvBlock, pActParams->EnvBlockLength, pActParams->RemoteActivation, pActParams->bClientImpersonating, pActParams->pProcess ? pActParams->pProcess->WinstaDesktop() : NULL, pActParams->ClsContext, &_hProcess, &_dwProcessId); } } } if (FAILED(hr)) { ASSERT(_pvRunAsHandle == NULL); _lThreadToken = 0; pRegisterEvent->Release(); return hr; } // we got this far so it is time to wait for the server/service we just // launched to register itself with us appropriately and in a timely fashion // Retry once if we fail to get desktop resources the first time BOOL fSuccessful = FALSE; for (int i=0; i<2; i++) { ULONG winerr=0; fSuccessful = FALSE; switch (pClsidData->ServerType()) { case SERVERTYPE_SERVICE: case SERVERTYPE_COMPLUS_SVC: ASSERT(hService && "Waiting for service with NULL handle"); fSuccessful = WaitForService(hService, pRegisterEvent->Handle(), bServiceAlreadyRunning); CloseServiceHandle( hService ); hService = 0; break; case SERVERTYPE_COMPLUS: case SERVERTYPE_DLLHOST: fSuccessful = WaitForDllhostServer(pRegisterEvent->Handle(), pActParams, winerr, lLaunchThreadToken); break; default: fSuccessful = WaitForLocalServer(pRegisterEvent->Handle(), winerr); break; } if ( (i==0) && _pvRunAsHandle && (!fSuccessful ) && (winerr == ERROR_WAIT_NO_CHILDREN)) { RunAsInvalidateAndRelease(_pvRunAsHandle); _pvRunAsHandle = NULL; hr = pClsidData->LaunchRunAsServer( pActParams->pToken, pActParams->RemoteActivation, pActParams->pActPropsIn, pActParams->ClsContext, &_hProcess, &_dwProcessId, &_pvRunAsHandle); if (FAILED(hr)) { ASSERT(_pvRunAsHandle == NULL); pRegisterEvent->Release(); return hr; } continue; } break; } if ( !fSuccessful ) { GUID ServerGuid = Guid(); LogRegisterTimeout( &ServerGuid, pActParams->ClsContext, pActParams->pToken ); hr = CO_E_SERVER_EXEC_FAILURE; } else { hr = S_OK; } // Ensure that we are serializing around // registration which takes an exclusive // before modifying this and is the only // one that needs to look at or modify it. if (_hProcess) { ServerLock()->LockShared(); if (_hProcess) { CloseHandle(_hProcess); _hProcess = 0; } ServerLock()->UnlockShared(); } // If we still have the token set the callers also to // NULL because we might have succeeded the launch but // the server registering back was different from // the one launched(this should be very rare). if (_lThreadToken) { _lThreadToken = 0; lLaunchThreadToken = 0; } if (_pvRunAsHandle) { ASSERT(pClsidData->HasRunAs()); RunAsRelease(_pvRunAsHandle); _pvRunAsHandle = NULL; } pRegisterEvent->Release(); return hr; } //+------------------------------------------------------------------------- // // CServerTableEntry::RegisterHandles // // Synopsis Register either the RunAs or the process handle with the // CProcess object. Also register Single use Thread token. // // NOTE: this is called by RegisterServer //-------------------------------------------------------------------------- BOOL CServerTableEntry::RegisterHandles(IN CServerListEntry * pEntry, IN CProcess *pServerProcess) { BOOL bStatus = TRUE; // Are we interested in registering the process handle if(_hProcess || _pProcess) { ASSERT(pServerProcess); // We can have either a process handle if this entry is the one // which was the first to launch the server or we can have a process // object if the server has already been launched. The two cases are // mutually exclusive and we assert that here. ASSERT((_hProcess && (NULL == _pProcess)) || (0 == _hProcess && (NULL != _pProcess))); if(_hProcess) bStatus = pServerProcess->SetProcessHandle(_hProcess,_dwProcessId); // If a FALSE status is returned then the PID of the process launched // by us did not match the PID of the process registered. // The only cases where this will be FALSE are if a server is // started by hand at the same time as a system launch or the // process registering was launched by the one we launched. // This should almost never happen if (!bStatus) goto ExitRegisterHandles; // If the PID matched then we are sure that this register event // matches the server we launched // If we had a process match save thread token if // it was passed since this thread launched the server if (_lThreadToken) { pEntry->SetThreadToken(_lThreadToken); _lThreadToken = NULL; } // Are we interested in registering the RunAs handle ? if (_pvRunAsHandle) { // Pass our winsta reference over to process entry which will // release it when it goes away so we can be rid of // responsibilities for it. RunAsSetWinstaDesktop(_pvRunAsHandle, pServerProcess->WinstaDesktop()); pServerProcess->SetRunAsHandle(_pvRunAsHandle); // Given Handle away so remove our reference _pvRunAsHandle = NULL; } } ExitRegisterHandles: return bStatus; } //+------------------------------------------------------------------------- // // CServerTableEntry::WaitForLocalServer // //-------------------------------------------------------------------------- BOOL CServerTableEntry::WaitForLocalServer( IN HANDLE hRegisterEvent, IN ULONG &winerr ) { DWORD Status = WAIT_OBJECT_0+1; BOOL bStatus = FALSE; HANDLE Handles[2]; Handles[0] = hRegisterEvent; Handles[1] = _hProcess; // Wait for process and register events Status = WaitForMultipleObjects( 2, Handles, FALSE, gServerStartTimeout ); if ( Status == (WAIT_OBJECT_0 + 1) ) { // Launched Process went away but this could be the case // where we have a launched server that starts another server // which registers back(Lotus or something ??) // So null the handle and wait for the register event for // a much shorter time GetExitCodeProcess(_hProcess, &winerr); HANDLE hProcess; CloseHandle( _hProcess ); _hProcess = 0; Status = WaitForSingleObject( hRegisterEvent, gServerStartSecondChanceTimeout ); } if ( Status != WAIT_OBJECT_0 && _hProcess ) TerminateProcess( _hProcess, 0 ); if ( Status == WAIT_OBJECT_0 ) return TRUE; else return FALSE; } //+------------------------------------------------------------------------- // // CServerTableEntry::WaitForDllhostServer // //-------------------------------------------------------------------------- BOOL CServerTableEntry::WaitForDllhostServer( IN HANDLE hRegisterEvent, IN ACTIVATION_PARAMS *pActParams, IN ULONG &winerr, IN LONG lThreadToken ) { DWORD Status = WAIT_OBJECT_0+1; DWORD WaitedMilliSecondsToStart = 0; CServerListEntry * pEntry = NULL; BOOL bStatus; CProcess *pProcess=NULL; HANDLE Handles[2]; Handles[0] = hRegisterEvent; Handles[1] = _hProcess; LONG lSessionID = INVALID_SESSION_ID; GetSessionIDFromActParams(pActParams->pActPropsIn, &lSessionID); for (;;) { // Wait for process and register events Status = WaitForMultipleObjects( 2, Handles, FALSE, gDllhostServerStartTimeout ); if ( ( Status == WAIT_OBJECT_0 ) || ( Status == (WAIT_OBJECT_0 + 1 ))) break; if (pEntry == NULL) { bStatus = LookupServer( pActParams->pToken, pActParams->RemoteActivation, pActParams->bClientImpersonating, pActParams->pProcess ? pActParams->pProcess->WinstaDesktop() : NULL , MATCHFLAG_ALLOW_SUSPENDED, // Dllhost is suspended now, we still want to find it! lThreadToken, lSessionID, 0, // no need for a pid here PRT_IGNORE, // no need for prt here &pEntry); if (!bStatus) pEntry = NULL; else { pProcess = pEntry->_pServerProcess; ASSERT(pProcess != NULL); } } ScmProcessReg *pScmProcessReg=NULL; if (pProcess) { pScmProcessReg = pProcess->GetProcessReg(); } // If the server died in the middle of an activationand context rundown ocured, then // pScmProcessReg is NULL if (! pScmProcessReg ) { // ProcessActivatorStarted not called yet WaitedMilliSecondsToStart += gDllhostServerStartTimeout; if (WaitedMilliSecondsToStart >= gDllhostMaxServerStartTimeout) break; else continue; } // ProcessActivatorStarted has been called, how long since last call from // initial process activator? // handles wraparound CTime CurrentTime; DWORD WaitedMilliSecondsToReady = CurrentTime - pScmProcessReg->TimeOfLastPing; if (WaitedMilliSecondsToReady >= gDllhostMaxServerStartTimeout) break; else continue; } if (pEntry) pEntry->Release(); if ( Status == WAIT_OBJECT_0 ) return TRUE; else { GetExitCodeProcess(_hProcess, &winerr); TerminateProcess( _hProcess, 0 ); return FALSE; } } //+------------------------------------------------------------------------- // // CServerTableEntry::WaitForService // //-------------------------------------------------------------------------- BOOL CServerTableEntry::WaitForService( IN SC_HANDLE hService, IN HANDLE hRegisterEvent, IN BOOL bServiceAlreadyRunning ) { SERVICE_STATUS ServiceStatus; DWORD Status = WAIT_FAILED; BOOL bStatus; BOOL bKeepLooping; BOOL bStopService = FALSE; for (DWORD i = 0; i < gNTServiceMaxTimeouts; i++) { Status = WaitForSingleObject( hRegisterEvent, gNTServiceInterrogatePeriod ); if (Status == WAIT_OBJECT_0) { // register event was signalled, just return break; } else { // Assume we are done bKeepLooping = FALSE; bStatus = ControlService( hService, SERVICE_CONTROL_INTERROGATE, &ServiceStatus ); if (bStatus) { switch ( ServiceStatus.dwCurrentState ) { case SERVICE_STOPPED : case SERVICE_STOP_PENDING : // weirdness break; case SERVICE_RUNNING : case SERVICE_START_PENDING : case SERVICE_CONTINUE_PENDING : // the service is taking its own sweet time. Keep // looping to give it more time. bKeepLooping = TRUE; break; case SERVICE_PAUSE_PENDING : case SERVICE_PAUSED : // more weirdness if (!bServiceAlreadyRunning) bStopService = TRUE; break; } } //else quit if (!bKeepLooping) break; } } if ( bStopService ) { (void) ControlService( hService, SERVICE_CONTROL_STOP, &ServiceStatus ); } if ( Status == WAIT_OBJECT_0 ) return TRUE; else return FALSE; } //+------------------------------------------------------------------------- // // CServerTableEntry::WaitForInitCompleted // // Wait for an initializing server (dllhost) to finish initialization. // //-------------------------------------------------------------------------- HRESULT CServerTableEntry::WaitForInitCompleted( IN CServerListEntry *pEntry, IN CClsidData *pClsidData ) { ASSERT(pClsidData); // Should NOT call this without registration info. if (NULL == pClsidData) { // If no clsid data, then we'll just need to return the // "I'm initializing still" error code, since we have no // way of waiting. I don't think this should ever happen, // though. return CO_E_SERVER_INIT_TIMEOUT; } CNamedObject* pInitEvent = pClsidData->ServerInitializedEvent(); if (!pInitEvent) { return E_OUTOFMEMORY; } HRESULT hr = S_OK; if (pEntry->IsInitializing()) { HANDLE hProcess = pEntry->_pServerProcess->GetProcessHandle(); DWORD dwRet; if (hProcess) { HANDLE handles[2] = {pInitEvent->Handle(), hProcess}; dwRet = WaitForMultipleObjects(2, handles, FALSE, gDllhostInitializationTimeout); } else { dwRet = WaitForSingleObject(pInitEvent->Handle(), gDllhostInitializationTimeout); } if (dwRet == WAIT_TIMEOUT) { hr = CO_E_SERVER_INIT_TIMEOUT; } else if (dwRet == WAIT_OBJECT_0) { hr = S_OK; } else if (dwRet == WAIT_OBJECT_0+1) { hr = CO_E_SERVER_STOPPING; } else { hr = HRESULT_FROM_WIN32(GetLastError()); } } pInitEvent->Release(); return hr; } //+------------------------------------------------------------------------- // // CServerTableEntry::GetServerListWithLock // // Returns a pointer to this entry's server list. Takes shared lock before // returning. Caller is expected to call ReleaseSharedListLock to release // the lock // //-------------------------------------------------------------------------- CServerList* CServerTableEntry::GetServerListWithSharedLock() { ServerLock()->LockShared(); return &_ServerList; } //+------------------------------------------------------------------------- // // CServerTableEntry::ReleaseSharedListLock // // Releases a shared lock on this entry's list lock. // //-------------------------------------------------------------------------- void CServerTableEntry::ReleaseSharedListLock() { ServerLock()->UnlockShared(); } // // CServerTableEntry::SuspendClass // // Turns on the suspended flag for this entry. // void CServerTableEntry::SuspendClass() { ASSERT(!_bSuspendedClsid && !_bSuspendedApplication && _EntryType == ENTRY_TYPE_CLASS); _bSuspendedClsid = TRUE; SetSuspendOnAllServers(TRUE); } // // CServerTableEntry::UnsuspendClass // // Turns off the suspended flag for this entry. // void CServerTableEntry::UnsuspendClass() { ASSERT(_bSuspendedClsid && !_bSuspendedApplication && _EntryType == ENTRY_TYPE_CLASS); _bSuspendedClsid = FALSE; SetSuspendOnAllServers(FALSE); } // // CServerTableEntry::RetireClass // // Retires all currently registered servers for // this clsid. // void CServerTableEntry::RetireClass() { ASSERT(_EntryType == ENTRY_TYPE_CLASS); RetireAllServers(); } // // CServerTableEntry::SuspendApplication // // Turns on the suspended flag for this entry. // void CServerTableEntry::SuspendApplication() { ASSERT(!_bSuspendedClsid && !_bSuspendedApplication && _EntryType == ENTRY_TYPE_PROCESS); _bSuspendedClsid = TRUE; } // // CServerTableEntry::UnsuspendApplication // // Turns on the suspended flag for this entry. // void CServerTableEntry::UnsuspendApplication() { ASSERT(!_bSuspendedClsid && _bSuspendedApplication && _EntryType == ENTRY_TYPE_PROCESS); _bSuspendedClsid = FALSE; } // // CServerTableEntry::RetireApplication // // Marks as retired all servers registered with // this entry. // void CServerTableEntry::RetireApplication() { ASSERT(_EntryType == ENTRY_TYPE_PROCESS); RetireAllServers(); } // // CServerTableEntry::IsSuspended // // Returns TRUE if servers of this type are // currently suspended, FALSE otherwise. // BOOL CServerTableEntry::IsSuspended() { ASSERT(_EntryType == ENTRY_TYPE_CLASS || _EntryType == ENTRY_TYPE_PROCESS); if (_EntryType == ENTRY_TYPE_CLASS) return _bSuspendedClsid; else return _bSuspendedApplication; } // // CServerTableEntry::SetSuspendOnAllServers // // Sets the suspended bit on all currently registered servers. // void CServerTableEntry::SetSuspendOnAllServers(BOOL bSuspended) { CServerListEntry * pEntry; ServerLock()->LockShared(); for (pEntry = (CServerListEntry *) _ServerList.First(); pEntry; pEntry = (CServerListEntry *) pEntry->Next() ) { if (bSuspended) pEntry->Suspend(); else pEntry->Unsuspend(); } ServerLock()->UnlockShared(); } // // CServerTableEntry::RetireAllServers // // Marks as retired all currently registered servers. // void CServerTableEntry::RetireAllServers() { CServerListEntry * pEntry; ServerLock()->LockShared(); for (pEntry = (CServerListEntry *) _ServerList.First(); pEntry; pEntry = (CServerListEntry *) pEntry->Next() ) { pEntry->Retire(); } ServerLock()->UnlockShared(); } // // CServerTableEntry::SetSuspendedFlagOnNewServer // // Helper function for when a new class factory registration/app // comes along. If we are currently suspended, then we need to // pass that state on to the new server. If we're not suspended, // then there's nothing to do. // void CServerTableEntry::SetSuspendedFlagOnNewServer(CProcess* pprocess) { if ( (_EntryType == ENTRY_TYPE_CLASS) && _bSuspendedClsid) { pprocess->Suspend(); } else if ( (_EntryType == ENTRY_TYPE_PROCESS) && _bSuspendedApplication) { pprocess->Suspend(); } } //+------------------------------------------------------------------------- // // SCMRemoveRegistration Version 1: for class entries representing traditional // local servers including custom surrogate servers. // // Called from CProcess::RevokeClassRegs() in the resolver. // //-------------------------------------------------------------------------- void SCMRemoveRegistration( CProcess * pProcess, GUID & Guid, DWORD Reg ) { CServerTableEntry * pClassTableEntry = NULL; pClassTableEntry = gpClassTable->Lookup( Guid ); if ( pClassTableEntry ) { pClassTableEntry->RevokeServer( pProcess, Reg ); pClassTableEntry->Release(); } } //+------------------------------------------------------------------------- // // SCMRemoveRegistration Version 2: for process entries representing // new unified surrogate servers. // // Called from CProcess::RevokeClassRegs() in the resolver. // //-------------------------------------------------------------------------- void SCMRemoveRegistration( ScmProcessReg * pScmProcessReg ) { CServerTableEntry * pProcessTableEntry = NULL; pProcessTableEntry = gpProcessTable->Lookup( pScmProcessReg->ProcessGUID ); if ( pProcessTableEntry ) { pProcessTableEntry->RevokeServer( pScmProcessReg ); pProcessTableEntry->Release(); } } void SCMProcessCleanup( const CProcess *pProcess ) { CSurrogateListEntry *pSurrogateEntry = gpSurrogateList->Lookup(pProcess); if (pSurrogateEntry) { gpSurrogateList->Remove(pSurrogateEntry); pSurrogateEntry->Release(); } }