/*++ Copyright (c) 1989 Microsoft Corporation Module Name: smloop.c Abstract: Session Manager Listen and API loops Author: Mark Lucovsky (markl) 04-Oct-1989 Revision History: --*/ #include "smsrvp.h" #include // Only for the interlocked functions. #define SM_WORKER_THREADS_LIMIT 4 ULONG_PTR SmUniqueProcessId; LONG SmpCurrentThreadsLimit = SM_WORKER_THREADS_LIMIT; LONG SmWorkerThreadsAvailable = 0; LONG SmTotalApiThreads = 0; PSMP_CLIENT_CONTEXT SmpDeferredFreeList = NULL; NTSTATUS SmpHandleConnectionRequest( IN HANDLE ConnectionPort, IN PSBAPIMSG Message ); PSMAPI SmpApiDispatch[SmMaxApiNumber] = { SmpCreateForeignSession, SmpSessionComplete, SmpTerminateForeignSession, SmpExecPgm, SmpLoadDeferedSubsystem, SmpStartCsr, SmpStopCsr }; #if DBG PSZ SmpApiName[ SmMaxApiNumber+1 ] = { "SmCreateForeignSession", "SmSessionComplete", "SmTerminateForeignSession", "SmExecPgm", "SmLoadDeferedSubsystem", "SmStartCsr", "SmStopCsr", "Unknown Sm Api Number" }; #endif // DBG EXCEPTION_DISPOSITION DbgpUnhandledExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ); VOID SmpFlushDeferredList() { PSMP_CLIENT_CONTEXT Head = SmpDeferredFreeList; SmpDeferredFreeList = NULL; while (Head != NULL) { PSMP_CLIENT_CONTEXT ClientContext = Head; NTSTATUS Status; Head = Head->Link; if (ClientContext->ClientProcessHandle) { Status = NtClose( ClientContext->ClientProcessHandle ); ASSERT(NT_SUCCESS(Status)); } Status = NtClose( ClientContext->ServerPortHandle ); ASSERT(NT_SUCCESS(Status)); RtlFreeHeap( SmpHeap, 0, ClientContext ); } SmpCurrentThreadsLimit = SM_WORKER_THREADS_LIMIT; } VOID SmpPushDeferredClientContext( PSMP_CLIENT_CONTEXT ClientContext ) { PSMP_CLIENT_CONTEXT CapturedHead; do { CapturedHead = SmpDeferredFreeList; ClientContext->Link = CapturedHead; } while ( InterlockedCompareExchangePointer(&SmpDeferredFreeList, ClientContext, CapturedHead) != CapturedHead ); SmpCurrentThreadsLimit = 1; } NTSTATUS SmpApiLoop ( IN PVOID ThreadParameter ) /*++ Routine Description: This is the main Session Manager API Loop. It services session manager API requests. Arguments: ThreadParameter - Supplies a handle to the API port used to receive session manager API requests. Return Value: None. --*/ { PSMAPIMSG SmApiReplyMsg; SMMESSAGE_SIZE MsgBuf; PSMAPIMSG SmApiMsg; NTSTATUS Status; HANDLE ConnectionPort; PSMP_CLIENT_CONTEXT ClientContext; PSMPKNOWNSUBSYS KnownSubSys; PROCESS_BASIC_INFORMATION ProcessInfo; InterlockedIncrement(&SmTotalApiThreads); RtlSetThreadIsCritical(TRUE, NULL, TRUE); NtQueryInformationProcess( NtCurrentProcess(), ProcessBasicInformation, &ProcessInfo, sizeof(ProcessInfo), NULL ); SmUniqueProcessId = ProcessInfo.UniqueProcessId; ConnectionPort = (HANDLE) ThreadParameter; SmApiMsg = (PSMAPIMSG)&MsgBuf; SmApiReplyMsg = NULL; try { for(;;) { { LONG CapturedThreads; do { CapturedThreads = SmWorkerThreadsAvailable; if (CapturedThreads >= SmpCurrentThreadsLimit) { if (SmApiReplyMsg) { while (1) { Status = NtReplyPort (ConnectionPort, (PPORT_MESSAGE) SmApiReplyMsg); if (Status == STATUS_NO_MEMORY) { LARGE_INTEGER DelayTime; KdPrint (("SMSS: Failed to reply to calling thread, retrying.\n")); DelayTime.QuadPart = Int32x32To64 (5000, -10000); NtDelayExecution (FALSE, &DelayTime); continue; } break; } } InterlockedDecrement(&SmTotalApiThreads); RtlSetThreadIsCritical(FALSE, NULL, TRUE); RtlExitUserThread(STATUS_SUCCESS); // // RtlExitUserThread never returns // } } while ( InterlockedCompareExchange(&SmWorkerThreadsAvailable, CapturedThreads + 1, CapturedThreads) != CapturedThreads); } if (SmTotalApiThreads == 1) { SmpFlushDeferredList(); } while (1) { Status = NtReplyWaitReceivePort (ConnectionPort, &ClientContext, (PPORT_MESSAGE) SmApiReplyMsg, (PPORT_MESSAGE) SmApiMsg); // // If we get an out of memory error then just wait instead of looping // if (Status == STATUS_NO_MEMORY) { LARGE_INTEGER DelayTime; if (SmApiReplyMsg != NULL) { KdPrint (("SMSS: Failed to reply to calling thread, retrying.\n")); } DelayTime.QuadPart = Int32x32To64 (5000, -10000); NtDelayExecution (FALSE, &DelayTime); continue; } break; } // // Launching at the same time a several subsystems can deadlock smss // if it has only two worker threads. // We create more threads if there is no server thread available // if (InterlockedDecrement(&SmWorkerThreadsAvailable) == 0) { NTSTATUS st = RtlCreateUserThread( NtCurrentProcess(), NULL, FALSE, 0L, 0L, 0L, SmpApiLoop, (PVOID) ThreadParameter, NULL, NULL ); } if (!NT_SUCCESS (Status)) { SmApiReplyMsg = NULL; continue; } else if ( SmApiMsg->h.u2.s2.Type == LPC_CONNECTION_REQUEST ) { SmpHandleConnectionRequest( ConnectionPort, (PSBAPIMSG) SmApiMsg ); SmApiReplyMsg = NULL; } else if ( SmApiMsg->h.u2.s2.Type == LPC_PORT_CLOSED ) { if (ClientContext) { SmpPushDeferredClientContext(ClientContext); } SmApiReplyMsg = NULL; } else { if ( !ClientContext ) { SmApiReplyMsg = NULL; continue; } KnownSubSys = ClientContext->KnownSubSys; SmApiMsg->ReturnedStatus = STATUS_PENDING; if ((ULONG) SmApiMsg->ApiNumber >= (ULONG) SmMaxApiNumber ) { Status = STATUS_NOT_IMPLEMENTED; } else { switch (SmApiMsg->ApiNumber) { case SmExecPgmApi : Status = (SmpApiDispatch[SmApiMsg->ApiNumber])( SmApiMsg, ClientContext, ConnectionPort); break; case SmLoadDeferedSubsystemApi : Status = (SmpApiDispatch[SmApiMsg->ApiNumber])( SmApiMsg, ClientContext, ConnectionPort); break; case SmStopCsrApi : case SmStartCsrApi : // // These Api's can only be called from a system process // if (ClientContext->SecurityContext == UNKNOWN_CONTEXT) { // // Initialize the client security context // ClientContext->SecurityContext = SmpClientSecurityContext ((PPORT_MESSAGE)SmApiMsg, ClientContext->ServerPortHandle); } if (ClientContext->SecurityContext == SYSTEM_CONTEXT) { Status = (SmpApiDispatch[SmApiMsg->ApiNumber])( SmApiMsg, ClientContext, ConnectionPort); } else { #if DBG KdPrint(("SMSS: Calling Sm Terminal Server Api from Non-System context.Status = STATUS_ACCESS_DENIED\n")); #endif Status = STATUS_ACCESS_DENIED; } break; case SmCreateForeignSessionApi : case SmSessionCompleteApi : case SmTerminateForeignSessionApi : if (!KnownSubSys) { Status = STATUS_INVALID_PARAMETER; } else { Status = (SmpApiDispatch[SmApiMsg->ApiNumber])( SmApiMsg, ClientContext, ConnectionPort); } break; } } SmApiMsg->ReturnedStatus = Status; SmApiReplyMsg = SmApiMsg; } } } except (DbgpUnhandledExceptionFilter( GetExceptionInformation() )) { ; } // // Make the compiler happy // return STATUS_UNSUCCESSFUL; } NTSTATUS SmpHandleConnectionRequest( IN HANDLE ConnectionPort, IN PSBAPIMSG Message ) /*++ Routine Description: This routine handles connection requests from either known subsystems, or other clients. Other clients are admin processes. The protocol for connection from a known subsystem is: capture the name of the subsystem's Sb API port Accept the connection Connect to the subsystems Sb API port Store the communication port handle in the known subsystem database signal the event associated with the known subsystem The protocol for others is to simply validate and accept the connection request. Arguments: Return Value: None. --*/ { NTSTATUS st; HANDLE CommunicationPort; REMOTE_PORT_VIEW ClientView; PSBCONNECTINFO ConnectInfo; ULONG ConnectInfoLength; PSMPKNOWNSUBSYS KnownSubSys, KnownSubSys2; BOOLEAN Accept; UNICODE_STRING SubSystemPort; SECURITY_QUALITY_OF_SERVICE DynamicQos; PSMP_CLIENT_CONTEXT ClientContext; OBJECT_ATTRIBUTES ObjA; HANDLE ClientProcessHandle=NULL; ULONG MuSessionId = 0; // // Set up the security quality of service parameters to use over the // sb API port. Use the most efficient (least overhead) - which is dynamic // rather than static tracking. // DynamicQos.ImpersonationLevel = SecurityIdentification; DynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; DynamicQos.EffectiveOnly = TRUE; Accept = TRUE; // Assume we will accept // // Get MuSessionId of the client if session manager is not connecting to itself // if ( (ULONG_PTR) Message->h.ClientId.UniqueProcess == SmUniqueProcessId ) { KnownSubSys = NULL; ClientProcessHandle = NULL; } else { InitializeObjectAttributes( &ObjA, NULL, 0, NULL, NULL ); st = NtOpenProcess( &ClientProcessHandle, PROCESS_QUERY_INFORMATION, &ObjA, &Message->h.ClientId ); if (NT_SUCCESS (st)) { SmpGetProcessMuSessionId( ClientProcessHandle, &MuSessionId ); } else { Accept = FALSE; } } ConnectInfo = &Message->ConnectionRequest; KnownSubSys = SmpLocateKnownSubSysByCid(&Message->h.ClientId); if ( (KnownSubSys) && (Accept == TRUE) ) { KnownSubSys2 = SmpLocateKnownSubSysByType(MuSessionId, ConnectInfo->SubsystemImageType); if (KnownSubSys2 == KnownSubSys ) { Accept = FALSE; KdPrint(("SMSS: Connection from SubSystem rejected\n")); KdPrint(("SMSS: Image type already being served\n")); } else { KnownSubSys->ImageType = ConnectInfo->SubsystemImageType; } if (KnownSubSys2) { RtlEnterCriticalSection( &SmpKnownSubSysLock ); SmpDeferenceKnownSubSys(KnownSubSys2); RtlLeaveCriticalSection( &SmpKnownSubSysLock ); } } if (Accept) { ClientContext = RtlAllocateHeap(SmpHeap, MAKE_TAG( SM_TAG ), sizeof(SMP_CLIENT_CONTEXT)); if ( ClientContext ) { ClientContext->ClientProcessHandle = ClientProcessHandle; ClientContext->KnownSubSys = KnownSubSys; // // The sm apis used by Terminal Server to start and stop CSR // do not get called from known subsystems and are restricted // to system processes only. // ClientContext->SecurityContext = UNKNOWN_CONTEXT; ClientContext->ServerPortHandle = NULL; } else { Accept = FALSE; } } ClientView.Length = sizeof(ClientView); st = NtAcceptConnectPort( &CommunicationPort, ClientContext, (PPORT_MESSAGE)Message, Accept, NULL, &ClientView ); if ( Accept ) { if (NT_SUCCESS (st)) { if (ClientContext) { ClientContext->ServerPortHandle = CommunicationPort; } if ( KnownSubSys ) { KnownSubSys->SmApiCommunicationPort = CommunicationPort; } st = NtCompleteConnectPort(CommunicationPort); if (!NT_SUCCESS(st)) { if ( KnownSubSys ) { KnownSubSys->SmApiCommunicationPort = NULL; RtlEnterCriticalSection( &SmpKnownSubSysLock ); SmpDeferenceKnownSubSys(KnownSubSys); RtlLeaveCriticalSection( &SmpKnownSubSysLock ); } return st; } // // Connect Back to subsystem. // if ( KnownSubSys ) { ConnectInfo->EmulationSubSystemPortName[ sizeof (ConnectInfo->EmulationSubSystemPortName)/sizeof (WCHAR) - 1] = '\0'; RtlCreateUnicodeString( &SubSystemPort, ConnectInfo->EmulationSubSystemPortName ); ConnectInfoLength = sizeof( *ConnectInfo ); st = NtConnectPort( &KnownSubSys->SbApiCommunicationPort, &SubSystemPort, &DynamicQos, NULL, NULL, NULL, NULL, NULL ); if ( !NT_SUCCESS(st) ) { KdPrint(("SMSS: Connect back to Sb %wZ failed %lx\n",&SubSystemPort,st)); } RtlFreeUnicodeString( &SubSystemPort ); NtSetEvent(KnownSubSys->Active,NULL); } } else { if (ClientProcessHandle) { NtClose( ClientProcessHandle ); } RtlFreeHeap( SmpHeap, 0, ClientContext ); } } else { if (ClientProcessHandle) { NtClose( ClientProcessHandle ); } } if (KnownSubSys) { RtlEnterCriticalSection( &SmpKnownSubSysLock ); SmpDeferenceKnownSubSys(KnownSubSys); RtlLeaveCriticalSection( &SmpKnownSubSysLock ); } return st; } PSMPKNOWNSUBSYS SmpLocateKnownSubSysByCid( IN PCLIENT_ID ClientId ) /*++ Routine Description: This function scans the known subsystem table looking for a matching client id (just UniqueProcess portion). If found, than the connection request is from a known subsystem and accept is always granted. Otherwise, it must be an administrative process. Arguments: ClientId - Supplies the ClientId whose UniqueProcess field is to be used in the known subsystem scan. Return Value: NULL - The ClientId does not match a known subsystem. NON-NULL - Returns the address of the known subsystem. --*/ { PSMPKNOWNSUBSYS KnownSubSys = NULL; PLIST_ENTRY Next; // // Acquire known subsystem lock. // RtlEnterCriticalSection(&SmpKnownSubSysLock); Next = SmpKnownSubSysHead.Flink; while ( Next != &SmpKnownSubSysHead ) { KnownSubSys = CONTAINING_RECORD(Next,SMPKNOWNSUBSYS,Links); Next = Next->Flink; if ( (KnownSubSys->InitialClientId.UniqueProcess == ClientId->UniqueProcess) && !KnownSubSys->Deleting ) { SmpReferenceKnownSubSys(KnownSubSys); break; } else { KnownSubSys = NULL; } } // // Unlock known subsystems. // RtlLeaveCriticalSection(&SmpKnownSubSysLock); return KnownSubSys; } PSMPKNOWNSUBSYS SmpLocateKnownSubSysByType( IN ULONG MuSessionId, IN ULONG ImageType ) /*++ Routine Description: This function scans the known subsystem table looking for a matching image type. Arguments: ImageType - Supplies the image type whose subsystem is to be located. Return Value: NULL - The image type does not match a known subsystem. NON-NULL - Returns the address of the known subsystem. --*/ { PSMPKNOWNSUBSYS KnownSubSys = NULL; PLIST_ENTRY Next; // // Aquire known subsystem lock // RtlEnterCriticalSection(&SmpKnownSubSysLock); Next = SmpKnownSubSysHead.Flink; while ( Next != &SmpKnownSubSysHead ) { KnownSubSys = CONTAINING_RECORD(Next,SMPKNOWNSUBSYS,Links); Next = Next->Flink; if ( (KnownSubSys->ImageType == ImageType) && !KnownSubSys->Deleting && (KnownSubSys->MuSessionId == MuSessionId) ) { SmpReferenceKnownSubSys(KnownSubSys); break; } else { KnownSubSys = NULL; } } // // Unlock known subsystems. // RtlLeaveCriticalSection(&SmpKnownSubSysLock); return KnownSubSys; } ENUMSECURITYCONTEXT SmpClientSecurityContext ( IN PPORT_MESSAGE Message, IN HANDLE ServerPortHandle ) /*++ Routine Description: Impersonate the client and check if it is running under system security context Arguments: PPORT_MESSAGE - LPC message pointer ServerPortHandle - LPC Port Handle Return Value: SYSTEM_CONTEXT - Client is running under system LUID NONSYSTEM_CONTEXT - Failure or client is not running under system LUID --*/ { NTSTATUS NtStatus ; HANDLE ImpersonationToken; HANDLE TokenHandle; TOKEN_STATISTICS TokenStatisticsInformation; ULONG Size; ENUMSECURITYCONTEXT retval = NONSYSTEM_CONTEXT; LUID SystemAuthenticationId = SYSTEM_LUID; NtStatus = NtImpersonateClientOfPort(ServerPortHandle, Message); if (!NT_SUCCESS(NtStatus)) { #if DBG KdPrint(( "SMSS: NtImpersonateClientOfPort failed: 0x%lX\n", NtStatus)) ; #endif return NONSYSTEM_CONTEXT ; } // // Get the Token Handle. // if (NT_SUCCESS(NtOpenThreadToken (NtCurrentThread(), TOKEN_IMPERSONATE | TOKEN_QUERY, FALSE, &TokenHandle) == FALSE)) { if (NT_SUCCESS(NtQueryInformationToken( TokenHandle, TokenStatistics, &TokenStatisticsInformation, sizeof(TokenStatisticsInformation), &Size ))) { if ( RtlEqualLuid ( &TokenStatisticsInformation.AuthenticationId, &SystemAuthenticationId ) ) { retval = SYSTEM_CONTEXT; } } NtClose(TokenHandle); } else { #if DBG KdPrint(( "SMSS: OpenThreadToken failed\n")) ; #endif } // //Revert to Self // ImpersonationToken = 0; NtStatus = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &ImpersonationToken, sizeof(HANDLE)); #if DBG if (!NT_SUCCESS(NtStatus)) { KdPrint(( "SMSS: NtSetInformationThread : %lx\n", NtStatus)); } #endif // DBG return retval; }