/************************************************************************ Copyright (c) 2000 - 2000 Microsoft Corporation Module Name : logontable.cpp Abstract : Source file for the logon table. Author : Revision History : ***********************************************************************/ #include "stdafx.h" #include #include #include #if !defined(BITS_V12_ON_NT4) #include "logontable.tmh" #endif HRESULT DetectTerminalServer( bool * pfTS ); HRESULT GetUserToken( ULONG LogonId, PHANDLE pUserToken ); HRESULT WaitForUserToken( DWORD session, HANDLE * pToken ) { const MaxWait = 30 * 1000; const WaitInterval = 500; long StartTime = GetTickCount(); HRESULT Hr = E_FAIL; do { Hr = GetUserToken( session, pToken ); if ( SUCCEEDED( Hr ) ) return Hr; LogError("logon : unable to get token : %!winerr!", Hr ); Sleep( WaitInterval ); } while ( GetTickCount() - StartTime < MaxWait ); return Hr; } CLoggedOnUsers::CLoggedOnUsers( TaskScheduler & sched ) : m_TaskScheduler( sched ), m_SensNotifier( NULL ) { FILETIME time; GetSystemTimeAsFileTime( &time ); m_CurrentCookie = time.dwLowDateTime; if ( WINDOWS2000_PLATFORM == g_PlatformVersion ) { try { bool fTS; THROW_HRESULT( DetectTerminalServer( &fTS )); if (fTS) { m_SensNotifier = new CTerminalServerLogonNotification; LogInfo( "TS-enabled SENS notification activated" ); } else { m_SensNotifier = new CLogonNotification; LogInfo( "regular SENS notification activated" ); } } catch( ComError Error ) { if ( Error.Error() == TYPE_E_CANTLOADLIBRARY || Error.Error() == TYPE_E_LIBNOTREGISTERED ) { LogInfo( "SENS doesn't exist on this platform, skipping" ); return; } else { LogInfo("SENS object failed with %x", Error.Error() ); throw; } } } } CLoggedOnUsers::~CLoggedOnUsers() { if (m_SensNotifier) { m_SensNotifier->DeRegisterNotification(); m_SensNotifier->Release(); } } HRESULT CLoggedOnUsers::LogonSession( DWORD session ) { CUser * user = NULL; try { HANDLE Token = NULL; auto_HANDLE AutoToken; // // Get the user's token and SID, then create a user object. // THROW_HRESULT( WaitForUserToken( session, &Token )); ASSERT( Token ); // Token can't be NULL AutoToken = Token; user = new CUser( Token ); // // Add the user to our by-session and by-SID indexes. // HoldWriterLock lock ( m_TaskScheduler ); try { // Just in case...delete any previously recorded user. // LogoffSession( session ); // // Subtlety: if the node for m_ActiveSessions[ session ] doesn't exist, // then the first reference to it will cause a node to be allocated. This may // throw E_OUTOFMEMORY. // m_ActiveSessions[ session ] = user; m_ActiveUsers.insert( make_pair( user->QuerySid(), user ) ); } catch( ComError Error ) { m_ActiveSessions.erase( session ); throw; } Dump(); g_Manager->UserLoggedOn( user->QuerySid() ); return S_OK; } catch( ComError err ) { delete user; LogError("logon : returning error 0x%x", err.Error() ); Dump(); return err.Error(); } } HRESULT CLoggedOnUsers::LogoffSession( DWORD session ) { try { HoldWriterLock lock ( m_TaskScheduler ); CUser * user = m_ActiveSessions[ session ]; if (!user) return S_OK; bool b = m_ActiveUsers.RemovePair( user->QuerySid(), user ); ASSERT( b ); m_ActiveSessions.erase( session ); Dump(); if (false == g_Manager->IsUserLoggedOn( user->QuerySid() )) { g_Manager->UserLoggedOff( user->QuerySid() ); } user->DecrementRefCount(); return S_OK; } catch( ComError err ) { LogWarning("logoff : exception 0x%x thrown", err.Error()); Dump(); return err.Error(); } } CUser * CLoggedOnUsers::CUserList::FindSid( SidHandle sid ) { iterator iter = find( sid ); if (iter == end()) { return NULL; } return iter->second; } bool CLoggedOnUsers::CUserList::RemovePair( SidHandle sid, CUser * user ) { // // Find the user in the user list and delete it. // pair b = equal_range( sid ); for (iterator i = b.first; i != b.second; ++i) { if (i->second == user) { erase( i ); return true; } } return false; } CUser * CLoggedOnUsers::CUserList::RemoveByCookie( SidHandle sid, DWORD cookie ) { // // Find the user in the user list and delete it. // pair b = equal_range( sid ); for (iterator i = b.first; i != b.second; ++i) { CUser * user = i->second; if (user->GetCookie() == cookie) { erase( i ); return user; } } return NULL; } HRESULT CLoggedOnUsers::LogonService( HANDLE Token, DWORD * pCookie ) { CUser * user = NULL; try { user = new CUser( Token ); *pCookie = InterlockedIncrement( &m_CurrentCookie ); user->SetCookie( *pCookie ); HoldWriterLock lock ( m_TaskScheduler ); m_ActiveServiceAccounts.insert( make_pair( user->QuerySid(), user )); return S_OK; } catch( ComError err ) { delete user; LogError("logon service : returning error 0x%x", err.Error() ); return err.Error(); } } HRESULT CLoggedOnUsers::LogoffService( SidHandle Sid, DWORD Cookie ) { try { HoldWriterLock lock ( m_TaskScheduler ); CUser * user = m_ActiveServiceAccounts.RemoveByCookie( Sid, Cookie ); if (!user) { LogWarning("logoff : invalid cookie %d", Cookie); return E_INVALIDARG; } user->DecrementRefCount(); return S_OK; } catch( ComError err) { LogWarning("logoff : exception 0x%x thrown", err.Error()); return err.Error(); } } HRESULT CLoggedOnUsers::AddServiceAccounts() { HRESULT hr = S_OK; DWORD ignore; HoldWriterLock lock ( m_TaskScheduler ); // // Add the LOCAL_SYSTEM account. // HANDLE Token; if (OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, &Token )) { hr = LogonService( Token, &ignore ); CloseHandle( Token ); } else { hr = HRESULT_FROM_WIN32( GetLastError() ); } if (FAILED(hr)) { LogWarning( "failed to register LocalSystem : %!winerr!", hr ); return hr; } if (g_PlatformVersion >= WINDOWSXP_PLATFORM) { // // Add the LocalService account. // if (LogonUser( L"LocalService", L"NT AUTHORITY", L"", LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &Token)) { hr = LogonService( Token, &ignore ); CloseHandle( Token ); } else { hr = HRESULT_FROM_WIN32( GetLastError() ); } if (FAILED(hr)) { LogWarning( "failed to register LocalService : %!winerr!", hr ); if ( HRESULT_FROM_WIN32( ERROR_LOGON_FAILURE ) == hr ) LogWarning( "LocalService doesn't exist, skip it.\n"); else return hr; } // // Add the NetworkService account. // if (LogonUser( L"NetworkService", L"NT AUTHORITY", L"", LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &Token)) { hr = LogonService( Token, &ignore ); CloseHandle( Token ); } else { hr = HRESULT_FROM_WIN32( GetLastError() ); } if (FAILED(hr)) { LogWarning( "failed to register NetworkService : %!winerr!", hr ); if ( HRESULT_FROM_WIN32( ERROR_LOGON_FAILURE ) == hr ) LogWarning( "NetworkService doesn't exist, skip it.\n"); else return hr; } } // // done // return S_OK; } HRESULT CLoggedOnUsers::AddActiveUsers() { HRESULT hr = S_OK; WTS_SESSION_INFO * SessionInfo = 0; HANDLE Token; HoldWriterLock lock ( m_TaskScheduler ); // // Get the console token, if any, without using Terminal Services. // if ( SUCCEEDED( GetUserToken( 0, &Token) ) ) { CloseHandle( Token ); hr = LogonSession( 0 ); if (FAILED(hr)) { // ignore it and try to carry on... LogWarning( "service : unable to logon session zero : %!winerr!", hr ); } } #if !defined( BITS_V12_ON_NT4 ) // // The call may return FALSE, because Terminal Services is not always loaded. // DWORD SessionCount = 0; BOOL b = WTSEnumerateSessions( WTS_CURRENT_SERVER_HANDLE, 0, // reserved 1, // version 1 is the only supported v. &SessionInfo, &SessionCount ); if (b) { int i; for (i=0; i < SessionCount; ++i) { if (SessionInfo[i].SessionId == 0) { // console was handled by GetCurrentUserToken. continue; } if (SessionInfo[i].State == WTSActive || SessionInfo[i].State == WTSDisconnected) { LogInfo("service : logon session %d, state %d", SessionInfo[i].SessionId, SessionInfo[i].State ); hr = LogonSession( SessionInfo[i].SessionId ); if (FAILED(hr)) { // ignore it and try to carry on... LogWarning( "service : unable to logon session %d : %!winerr!", SessionInfo[i].SessionId, hr ); } } } } if (SessionInfo) { WTSFreeMemory( SessionInfo ); } // // Now that the current population is recorded, keep abreast of changes. // if (m_SensNotifier) { m_SensNotifier->SetEnableState( true ); } #endif return S_OK; } CUser * CLoggedOnUsers::FindUser( SidHandle sid, DWORD session ) { HoldReaderLock lock ( m_TaskScheduler ); CUser * user = 0; // // Look for a session with the right user. // if (session == ANY_SESSION) { user = m_ActiveUsers.FindSid( sid ); } else { try { user = m_ActiveSessions[ session ]; if (user && user->QuerySid() != sid) { user = 0; } } catch( ComError Error ) { user = 0; } } // // Look in the service account list, if the session is compatible. // if (!user && (session == 0 || session == ANY_SESSION)) { user = m_ActiveServiceAccounts.FindSid( sid ); } if (user) { user->IncrementRefCount(); } return user; } void CLoggedOnUsers::Dump() { HoldReaderLock lock ( m_TaskScheduler ); LogInfo("sessions:"); m_ActiveSessions.Dump(); LogInfo("users:"); m_ActiveUsers.Dump(); LogInfo("service accounts:"); m_ActiveServiceAccounts.Dump(); } void CLoggedOnUsers::CSessionList::Dump() { for (iterator iter = begin(); iter != end(); ++iter) { LogInfo(" session %d user %p", iter->first, iter->second); } } void CLoggedOnUsers::CUserList::Dump() { for (iterator iter = begin(); iter != end(); ++iter) { if (iter->second) { (iter->second)->Dump(); } } } CLoggedOnUsers::CUserList::~CUserList() { iterator iter; while ((iter=begin()) != end()) { delete iter->second; erase( iter ); } } void CUser::Dump() { LogInfo( " user at %p:", this); LogInfo( " %d refs, sid %!sid!", _ReferenceCount, _Sid.get()); } long CUser::IncrementRefCount() { long count = InterlockedIncrement( & _ReferenceCount ); LogRef("refs %d", count); return count; } long CUser::DecrementRefCount() { long count = InterlockedDecrement( & _ReferenceCount ); LogRef("refs %d", count); if (0 == count) { delete this; } return count; } CUser::CUser( HANDLE Token ) /*++ Routine Description: Initializes a new CUser. At entry: points to the user's SID. points to the user's token. It can be an impersonation or primary token. points to an error-return variable. At exit: The CUser is set up. The caller can delete and if it wants to. if an error occurs, it is mapped to an HRESULT and written to . otherwise <*phr> is untouched and the CUser is ready for use. --*/ { _ReferenceCount = 1; _Sid = CopyTokenSid( Token ); // // Copy the token. Whether the source is primary or impersonation, // the result will be a primary token. // if (!DuplicateHandle( GetCurrentProcess(), Token, GetCurrentProcess(), &_Token, TOKEN_ALL_ACCESS, FALSE, // not inheritable 0 // no funny options )) { HRESULT HrError = HRESULT_FROM_WIN32( GetLastError() ); LogError( "CUser: can't duplicate token %!winerr!", HrError ); throw ComError( HrError ); } } CUser::~CUser() { CloseHandle( _Token ); } HRESULT CUser::LaunchProcess( StringHandle CmdLine ) { DWORD s; PVOID EnvironmentBlock = 0; PROCESS_INFORMATION ProcessInformation; memset( &ProcessInformation, 0, sizeof( PROCESS_INFORMATION )); try { STARTUPINFO si; memset( &si, 0, sizeof( STARTUPINFO )); si.cb = sizeof(STARTUPINFO); CAutoString WritableCmdLine ( CopyString( CmdLine )); LogInfo( "creating process: %S", WritableCmdLine.get() ); if (!CreateEnvironmentBlock( &EnvironmentBlock, _Token, FALSE )) { ThrowLastError(); } if (!CreateProcessAsUser( _Token, NULL, // CmdLine contains the .exe name as well as the args WritableCmdLine.get(), 0, // no special security attributes 0, // no special thread attributes false, // don't inherit my handles NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, EnvironmentBlock, NULL, // default current directory &si, &ProcessInformation )) { ThrowLastError(); } DestroyEnvironmentBlock( EnvironmentBlock ); EnvironmentBlock = 0; CloseHandle( ProcessInformation.hThread ); ProcessInformation.hThread = 0; CloseHandle( ProcessInformation.hProcess ); ProcessInformation.hProcess = 0; LogInfo("success, pid is 0x%x", ProcessInformation.dwProcessId); // // We succeeded. // return S_OK; } catch ( ComError err ) { LogError("unable to create process, %x", err.Error() ); if (EnvironmentBlock) { DestroyEnvironmentBlock( EnvironmentBlock ); } return err.Error(); } } #if ENABLE_STL_LOCK_OVERRIDE /* This file implements the STL lockit class to avoid linking to msvcprt.dll. */ CCritSec CrtLock; #pragma warning(push) #pragma warning(disable:4273) // __declspec(dllimport) attribute overridden std::_Lockit::_Lockit() { CrtLock.WriteLock(); } std::_Lockit::~_Lockit() { CrtLock.WriteUnlock(); } #pragma warning(pop) #endif extern "C" { HANDLE GetCurrentUserTokenW( WCHAR Winsta[], DWORD DesiredAccess ); void * __RPC_USER MIDL_user_allocate(size_t size) { try { return new char[size]; } catch( ComError Error ) { return NULL; } } void __RPC_USER MIDL_user_free( void * block) { delete block; } } HRESULT GetUserToken( ULONG LogonId, PHANDLE pUserToken ) { // // This gets the token of the user logged onto the WinStation // if we are an admin caller. // if (LogonId == 0) { // don't need the TS API. *pUserToken = GetCurrentUserTokenW( L"WinSta0", TOKEN_ALL_ACCESS ); if (*pUserToken != NULL) { return S_OK; } // if not, try the TS API. } // // Use Terminal Services for non-console Logon IDs. // BOOL Result; ULONG ReturnLength; NTSTATUS Status; OBJECT_ATTRIBUTES ObjA; HANDLE ImpersonationToken; WINSTATIONUSERTOKEN Info; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; static PWINSTATIONQUERYINFORMATIONW pWinstationQueryInformation = 0; // // See if the entry point is loaded yet. // if (!pWinstationQueryInformation) { HMODULE module = LoadLibrary(_T("winsta.dll")); if (module == NULL) { HRESULT HrError = HRESULT_FROM_WIN32( GetLastError() ); ASSERT( S_OK != HrError ); LogInfo( "Load library of winsta failed, error %!winerr!", HrError ); return HrError; } pWinstationQueryInformation = (PWINSTATIONQUERYINFORMATIONW) GetProcAddress( module, "WinStationQueryInformationW" ); if (!pWinstationQueryInformation) { HRESULT HrError = HRESULT_FROM_WIN32( GetLastError() ); ASSERT( S_OK != HrError ); LogInfo( "GetProcAddress of WinStationQueryInformationW, error %!winerr!", HrError ); FreeLibrary(module); return HrError; } } // // Ask for the token. // Info.ProcessId = UlongToHandle(GetCurrentProcessId()); Info.ThreadId = UlongToHandle(GetCurrentThreadId()); Result = (*pWinstationQueryInformation)( SERVERNAME_CURRENT, LogonId, WinStationUserToken, &Info, sizeof(Info), &ReturnLength ); if( !Result ) { HRESULT HrError = HRESULT_FROM_WIN32( GetLastError() ); ASSERT( S_OK != HrError ); LogError("token : WinstationQueryInfo failed with %!winerr!%", HrError ); return HrError; } // // The token returned is a duplicate of a primary token. // *pUserToken = Info.UserToken; return S_OK; } BOOL SidToString( PSID sid, wchar_t buffer[], USHORT bytes ) { UNICODE_STRING UnicodeString; UnicodeString.Buffer = buffer; UnicodeString.Length = 0; UnicodeString.MaximumLength = bytes; NTSTATUS NtStatus; NtStatus = RtlConvertSidToUnicodeString( &UnicodeString, sid, FALSE ); if (!NT_SUCCESS(NtStatus)) { LogWarning( "RtlConvertSid failed %x", NtStatus); StringCbCopy( buffer, bytes, L"(conversion failed)" ); return FALSE; } buffer[ UnicodeString.Length ] = 0; return TRUE; } HRESULT SetStaticCloaking( IUnknown *pUnk ) { // Sets static cloaking on the current object so that we // should always impersonate the current context. // Also sets the impersonation level to identify. HRESULT Hr = S_OK; IClientSecurity *pSecurity = NULL; OLECHAR *ServerPrincName = NULL; try { Hr = pUnk->QueryInterface( __uuidof( IClientSecurity ), (void**)&pSecurity ); if (Hr == E_NOINTERFACE) { // // This is not a proxy; the client is in the same apartment as we are. // Identity issn't an issue, because the client already has access to system // credentials. // return S_OK; } DWORD AuthnSvc, AuthzSvc; DWORD AuthnLevel, ImpLevel, Capabilites; THROW_HRESULT( pSecurity->QueryBlanket( pUnk, &AuthnSvc, &AuthzSvc, &ServerPrincName, &AuthnLevel, NULL, // Don't need impersonation handle since were setting that NULL, // don't need indenty handle since were setting that &Capabilites ) ); THROW_HRESULT( pSecurity->SetBlanket( pUnk, AuthnSvc, AuthzSvc, ServerPrincName, AuthnLevel, RPC_C_IMP_LEVEL_IDENTIFY, NULL, // COM use indentity from token #if !defined( BITS_V12_ON_NT4 ) EOAC_STATIC_CLOAKING // The point of the exercise #else 0 #endif ) ); } catch( ComError Error ) { Hr = Error.Error(); } CoTaskMemFree( ServerPrincName ); SafeRelease( pSecurity ); return Hr; } HRESULT DetectTerminalServer( bool * pfTS ) { // // Test for Terminal Services. // INT * pConnectState; DWORD size; if (WTSQuerySessionInformation( WTS_CURRENT_SERVER, 0, WTSConnectState, reinterpret_cast(&pConnectState), &size)) { LogInfo("TS test: TS is installed"); *pfTS = true; WTSFreeMemory( pConnectState ); return S_OK; } else if (GetLastError() == ERROR_APP_WRONG_OS) { LogInfo("TS test: no TS"); *pfTS = false; return S_OK; } else { DWORD s = GetLastError(); LogError("TS test returned %d", s); return HRESULT_FROM_WIN32( s ); } }