/********************************************************************++ * Copyright (c) Microsoft Corporation. All rights reserved. * --********************************************************************/ using System.Management.Automation.Tracing; using System.IO; using System.Threading; using System.Security.Principal; using System.Management.Automation.Internal; using Microsoft.Win32.SafeHandles; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting.Server { internal abstract class OutOfProcessMediatorBase { #region Protected Data protected TextReader originalStdIn; protected OutOfProcessTextWriter originalStdOut; protected OutOfProcessTextWriter originalStdErr; protected OutOfProcessServerSessionTransportManager sessionTM; protected OutOfProcessUtils.DataProcessingDelegates callbacks; protected static object SyncObject = new object(); protected object _syncObject = new object(); protected string _initialCommand; protected ManualResetEvent allcmdsClosedEvent; // Thread impersonation. protected WindowsIdentity _windowsIdentityToImpersonate; /// /// Count of commands in progress /// protected int _inProgressCommandsCount = 0; protected PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource(); protected bool _exitProcessOnError; #endregion #region Constructor protected OutOfProcessMediatorBase(bool exitProcessOnError) { _exitProcessOnError = exitProcessOnError; // Set up data handling callbacks. callbacks = new OutOfProcessUtils.DataProcessingDelegates(); callbacks.DataPacketReceived += new OutOfProcessUtils.DataPacketReceived(OnDataPacketReceived); callbacks.DataAckPacketReceived += new OutOfProcessUtils.DataAckPacketReceived(OnDataAckPacketReceived); callbacks.CommandCreationPacketReceived += new OutOfProcessUtils.CommandCreationPacketReceived(OnCommandCreationPacketReceived); callbacks.CommandCreationAckReceived += new OutOfProcessUtils.CommandCreationAckReceived(OnCommandCreationAckReceived); callbacks.ClosePacketReceived += new OutOfProcessUtils.ClosePacketReceived(OnClosePacketReceived); callbacks.CloseAckPacketReceived += new OutOfProcessUtils.CloseAckPacketReceived(OnCloseAckPacketReceived); callbacks.SignalPacketReceived += new OutOfProcessUtils.SignalPacketReceived(OnSignalPacketReceived); callbacks.SignalAckPacketReceived += new OutOfProcessUtils.SignalAckPacketReceived(OnSignalAckPacketReceived); allcmdsClosedEvent = new ManualResetEvent(true); } #endregion #region Data Processing handlers protected void ProcessingThreadStart(object state) { try { #if !CORECLR // CurrentUICulture is not available in Thread Class in CSS // WinBlue: 621775. Thread culture is not properly set // for local background jobs causing experience differences // between local console and local background jobs. Thread.CurrentThread.CurrentUICulture = Microsoft.PowerShell.NativeCultureResolver.UICulture; Thread.CurrentThread.CurrentCulture = Microsoft.PowerShell.NativeCultureResolver.Culture; #endif string data = state as string; OutOfProcessUtils.ProcessData(data, callbacks); } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); PSEtwLog.LogOperationalError( PSEventId.TransportError, PSOpcode.Open, PSTask.None, PSKeyword.UseAlwaysOperational, Guid.Empty.ToString(), Guid.Empty.ToString(), OutOfProcessUtils.EXITCODE_UNHANDLED_EXCEPTION, e.Message, e.StackTrace); PSEtwLog.LogAnalyticError( PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, Guid.Empty.ToString(), Guid.Empty.ToString(), OutOfProcessUtils.EXITCODE_UNHANDLED_EXCEPTION, e.Message, e.StackTrace); // notify the remote client of any errors and fail gracefully if (_exitProcessOnError) { originalStdErr.WriteLine(e.Message + e.StackTrace); Environment.Exit(OutOfProcessUtils.EXITCODE_UNHANDLED_EXCEPTION); } } } protected void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) { string streamTemp = System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STREAM_ID_STDIN; if (stream.Equals(DataPriorityType.PromptResponse.ToString(), StringComparison.OrdinalIgnoreCase)) { streamTemp = System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE; } if (Guid.Empty == psGuid) { lock (_syncObject) { sessionTM.ProcessRawData(rawData, streamTemp); } } else { // this is for a command AbstractServerTransportManager cmdTM = null; lock (_syncObject) { cmdTM = sessionTM.GetCommandTransportManager(psGuid); } if (null != cmdTM) { // not throwing when there is no associated command as the command might have // legitimately closed while the client is sending data. however the client // should die after timeout as we are not sending an ACK back. cmdTM.ProcessRawData(rawData, streamTemp); } else { // There is no command transport manager to process the input data. // However, we still need to acknowledge to the client that this input data // was received. This can happen with some cmdlets such as Select-Object -First // where the cmdlet completes before all input data is received. originalStdOut.WriteLine(OutOfProcessUtils.CreateDataAckPacket(psGuid)); } } } protected void OnDataAckPacketReceived(Guid psGuid) { throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, OutOfProcessUtils.PS_OUT_OF_PROC_DATA_ACK_TAG); } protected void OnCommandCreationPacketReceived(Guid psGuid) { lock (_syncObject) { sessionTM.CreateCommandTransportManager(psGuid); if (_inProgressCommandsCount == 0) allcmdsClosedEvent.Reset(); _inProgressCommandsCount++; tracer.WriteMessage("OutOfProcessMediator.OnCommandCreationPacketReceived, in progress command count : " + _inProgressCommandsCount + " psGuid : " + psGuid.ToString()); } } protected void OnCommandCreationAckReceived(Guid psGuid) { throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, OutOfProcessUtils.PS_OUT_OF_PROC_COMMAND_ACK_TAG); } protected void OnSignalPacketReceived(Guid psGuid) { if (psGuid == Guid.Empty) { throw new PSRemotingTransportException(PSRemotingErrorId.IPCNoSignalForSession, RemotingErrorIdStrings.IPCNoSignalForSession, OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_TAG); } else { // this is for a command AbstractServerTransportManager cmdTM = null; try { lock (_syncObject) { cmdTM = sessionTM.GetCommandTransportManager(psGuid); } // dont throw if there is no cmdTM as it might have legitimately closed if (null != cmdTM) { cmdTM.Close(null); } } finally { // Always send ack signal to avoid hang in client. originalStdOut.WriteLine(OutOfProcessUtils.CreateSignalAckPacket(psGuid)); } } } protected void OnSignalAckPacketReceived(Guid psGuid) { throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_ACK_TAG); } protected void OnClosePacketReceived(Guid psGuid) { PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource(); if (psGuid == Guid.Empty) { tracer.WriteMessage("BEGIN calling close on session transport manager"); bool waitForAllcmdsClosedEvent = false; lock (_syncObject) { if (_inProgressCommandsCount > 0) waitForAllcmdsClosedEvent = true; } // Wait outside sync lock if required for all cmds to be closed // if (waitForAllcmdsClosedEvent) allcmdsClosedEvent.WaitOne(); lock (_syncObject) { tracer.WriteMessage("OnClosePacketReceived, in progress commands count should be zero : " + _inProgressCommandsCount + ", psGuid : " + psGuid.ToString()); if (sessionTM != null) { // it appears that when closing PowerShell ISE, therefore closing OutOfProcServerMediator, there are 2 Close command requests // changing PSRP/IPC at this point is too risky, therefore protecting about this duplication sessionTM.Close(null); } tracer.WriteMessage("END calling close on session transport manager"); sessionTM = null; } } else { tracer.WriteMessage("Closing command with GUID " + psGuid.ToString()); // this is for a command AbstractServerTransportManager cmdTM = null; lock (_syncObject) { cmdTM = sessionTM.GetCommandTransportManager(psGuid); } // dont throw if there is no cmdTM as it might have legitimately closed if (null != cmdTM) { cmdTM.Close(null); } lock (_syncObject) { tracer.WriteMessage("OnClosePacketReceived, in progress commands count should be greater than zero : " + _inProgressCommandsCount + ", psGuid : " + psGuid.ToString()); _inProgressCommandsCount--; if (_inProgressCommandsCount == 0) allcmdsClosedEvent.Set(); } } // send close ack originalStdOut.WriteLine(OutOfProcessUtils.CreateCloseAckPacket(psGuid)); } protected void OnCloseAckPacketReceived(Guid psGuid) { throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, OutOfProcessUtils.PS_OUT_OF_PROC_CLOSE_ACK_TAG); } #endregion #region Methods protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper) { PSSenderInfo senderInfo; #if !UNIX WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity("", true, currentIdentity.Name, null), currentIdentity); senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); #else PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity("", true, "", null), null); senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); #endif OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper); ServerRemoteSession srvrRemoteSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo, _initialCommand, tm, configurationName); return tm; } protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string configurationName = null) { _initialCommand = initialCommand; sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper); try { do { string data = originalStdIn.ReadLine(); lock (_syncObject) { if (sessionTM == null) { sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper); } } if (string.IsNullOrEmpty(data)) { lock (_syncObject) { // give a chance to runspace/pipelines to close (as it looks like the client died // interminently) sessionTM.Close(null); sessionTM = null; } throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, string.Empty); } // process data in a thread pool thread..this way Runspace, Command // data can be processed concurrently. #if CORECLR ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data); #else Utils.QueueWorkItemWithImpersonation( _windowsIdentityToImpersonate, new WaitCallback(ProcessingThreadStart), data); #endif } while (true); } catch (Exception e) { CommandProcessorBase.CheckForSevereException(e); PSEtwLog.LogOperationalError( PSEventId.TransportError, PSOpcode.Open, PSTask.None, PSKeyword.UseAlwaysOperational, Guid.Empty.ToString(), Guid.Empty.ToString(), OutOfProcessUtils.EXITCODE_UNHANDLED_EXCEPTION, e.Message, e.StackTrace); PSEtwLog.LogAnalyticError( PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, Guid.Empty.ToString(), Guid.Empty.ToString(), OutOfProcessUtils.EXITCODE_UNHANDLED_EXCEPTION, e.Message, e.StackTrace); if (_exitProcessOnError) { // notify the remote client of any errors and fail gracefully originalStdErr.WriteLine(e.Message); Environment.Exit(OutOfProcessUtils.EXITCODE_UNHANDLED_EXCEPTION); } } } #endregion #region Static Methods internal static void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs args) { // args can never be null. Exception exception = (Exception)args.ExceptionObject; // log the exception to crimson event logs PSEtwLog.LogOperationalError(PSEventId.AppDomainUnhandledException, PSOpcode.Close, PSTask.None, PSKeyword.UseAlwaysOperational, exception.GetType().ToString(), exception.Message, exception.StackTrace); PSEtwLog.LogAnalyticError(PSEventId.AppDomainUnhandledException_Analytic, PSOpcode.Close, PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, exception.GetType().ToString(), exception.Message, exception.StackTrace); } #endregion } internal sealed class OutOfProcessMediator : OutOfProcessMediatorBase { #region Private Data private static OutOfProcessMediator s_singletonInstance; #endregion #region Constructors /// /// The mediator will take actions from the StdIn stream and responds to them. /// It will replace StdIn,StdOut and StdErr stream with TextWriter.Null's. This is /// to make sure these streams are totally used by our Mediator. /// private OutOfProcessMediator() : base(true) { // Create input stream reader from Console standard input stream. // We don't use the provided Console.In TextReader because it can have // an incorrect encoding, e.g., Hyper-V Container console where the // TextReader has incorrect default console encoding instead of the actual // stream encoding. This way the stream encoding is determined by the // stream BOM as needed. originalStdIn = new StreamReader(Console.OpenStandardInput(), true); // replacing StdIn with Null so that no other app messes with the // original stream. Console.SetIn(TextReader.Null); // replacing StdOut with Null so that no other app messes with the // original stream originalStdOut = new OutOfProcessTextWriter(Console.Out); Console.SetOut(TextWriter.Null); // replacing StdErr with Null so that no other app messes with the // original stream originalStdErr = new OutOfProcessTextWriter(Console.Error); Console.SetError(TextWriter.Null); } #endregion #region Static Methods /// /// internal static void Run(string initialCommand) { lock (SyncObject) { if (null != s_singletonInstance) { Dbg.Assert(false, "Run should not be called multiple times"); return; } s_singletonInstance = new OutOfProcessMediator(); } #if !CORECLR // AppDomain is not available in CoreCLR // Setup unhandled exception to log events AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); #endif s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer()); } #endregion } internal sealed class SSHProcessMediator : OutOfProcessMediatorBase { #region Private Data private static SSHProcessMediator s_singletonInstance; #endregion #region Constructors private SSHProcessMediator() : base(true) { #if !UNIX var inputHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Input); originalStdIn = new StreamReader( new FileStream(new SafeFileHandle(inputHandle, false), FileAccess.Read)); var outputHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Output); originalStdOut = new OutOfProcessTextWriter( new StreamWriter( new FileStream(new SafeFileHandle(outputHandle, false), FileAccess.Write))); var errorHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Error); originalStdErr = new OutOfProcessTextWriter( new StreamWriter( new FileStream(new SafeFileHandle(errorHandle, false), FileAccess.Write))); #else originalStdIn = new StreamReader(Console.OpenStandardInput(), true); originalStdOut = new OutOfProcessTextWriter( new StreamWriter(Console.OpenStandardOutput())); originalStdErr = new OutOfProcessTextWriter( new StreamWriter(Console.OpenStandardError())); #endif } #endregion #region Static Methods /// /// /// /// internal static void Run(string initialCommand) { lock (SyncObject) { if (s_singletonInstance != null) { Dbg.Assert(false, "Run should not be called multiple times"); return; } s_singletonInstance = new SSHProcessMediator(); } PSRemotingCryptoHelperServer cryptoHelper; #if !UNIX cryptoHelper = new PSRemotingCryptoHelperServer(); #else cryptoHelper = null; #endif s_singletonInstance.Start(initialCommand, cryptoHelper); } #endregion } internal sealed class NamedPipeProcessMediator : OutOfProcessMediatorBase { #region Private Data private static NamedPipeProcessMediator s_singletonInstance; private readonly RemoteSessionNamedPipeServer _namedPipeServer; #endregion #region Properties internal bool IsDisposed { get { return _namedPipeServer.IsDisposed; } } #endregion #region Constructors private NamedPipeProcessMediator() : base(false) { } private NamedPipeProcessMediator( RemoteSessionNamedPipeServer namedPipeServer) : base(false) { if (namedPipeServer == null) { throw new PSArgumentNullException("namedPipeServer"); } _namedPipeServer = namedPipeServer; // Create transport reader/writers from named pipe. originalStdIn = namedPipeServer.TextReader; originalStdOut = new OutOfProcessTextWriter(namedPipeServer.TextWriter); originalStdErr = new NamedPipeErrorTextWriter(namedPipeServer.TextWriter); // Flow impersonation if requested. WindowsIdentity currentIdentity = null; try { currentIdentity = WindowsIdentity.GetCurrent(); } catch (System.Security.SecurityException) { } _windowsIdentityToImpersonate = ((currentIdentity != null) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) ? currentIdentity : null; } #endregion #region Static Methods internal static void Run( string initialCommand, RemoteSessionNamedPipeServer namedPipeServer) { lock (SyncObject) { if (s_singletonInstance != null && !s_singletonInstance.IsDisposed) { Dbg.Assert(false, "Run should not be called multiple times, unless the singleton was disposed."); return; } s_singletonInstance = new NamedPipeProcessMediator(namedPipeServer); } #if !CORECLR // AppDomain is not available in CoreCLR AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); #endif s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer(), namedPipeServer.ConfigurationName); } #endregion } internal sealed class NamedPipeErrorTextWriter : OutOfProcessTextWriter { #region Private Members private const string _errorPrepend = "__NamedPipeError__:"; #endregion #region Properties internal static string ErrorPrepend { get { return _errorPrepend; } } #endregion #region Constructors internal NamedPipeErrorTextWriter( TextWriter textWriter) : base(textWriter) { } #endregion #region Base class overrides internal override void WriteLine(string data) { string dataToWrite = (data != null) ? _errorPrepend + data : null; base.WriteLine(dataToWrite); } #endregion } internal sealed class HyperVSocketMediator : OutOfProcessMediatorBase { #region Private Data private static HyperVSocketMediator s_instance; private readonly RemoteSessionHyperVSocketServer _hypervSocketServer; #endregion #region Properties internal bool IsDisposed { get { return _hypervSocketServer.IsDisposed; } } #endregion #region Constructors private HyperVSocketMediator() : base(false) { _hypervSocketServer = new RemoteSessionHyperVSocketServer(false); originalStdIn = _hypervSocketServer.TextReader; originalStdOut = new OutOfProcessTextWriter(_hypervSocketServer.TextWriter); originalStdErr = new HyperVSocketErrorTextWriter(_hypervSocketServer.TextWriter); } #endregion #region Static Methods internal static void Run( string initialCommand, string configurationName) { lock (SyncObject) { s_instance = new HyperVSocketMediator(); } #if !CORECLR // AppDomain is not available in CoreCLR AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); #endif s_instance.Start(initialCommand, new PSRemotingCryptoHelperServer(), configurationName); } #endregion } internal sealed class HyperVSocketErrorTextWriter : OutOfProcessTextWriter { #region Private Members private const string _errorPrepend = "__HyperVSocketError__:"; #endregion #region Properties internal static string ErrorPrepend { get { return _errorPrepend; } } #endregion #region Constructors internal HyperVSocketErrorTextWriter( TextWriter textWriter) : base(textWriter) { } #endregion #region Base class overrides internal override void WriteLine(string data) { string dataToWrite = (data != null) ? _errorPrepend + data : null; base.WriteLine(dataToWrite); } #endregion } }