diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index 02b0a2c03..d5c9e843b 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -155,6 +155,11 @@ namespace Microsoft.PowerShell get { return _namedPipeServerMode; } } + internal bool SSHServerMode + { + get { return _sshServerMode; } + } + internal bool ServerMode { get @@ -394,6 +399,10 @@ namespace Microsoft.PowerShell { _namedPipeServerMode = true; } + else if (MatchSwitch(switchKey, "sshservermode", "sshs")) + { + _sshServerMode = true; + } else if (MatchSwitch(switchKey, "configurationname", "config")) { ++i; @@ -935,6 +944,7 @@ namespace Microsoft.PowerShell private bool _socketServerMode; private bool _serverMode; private bool _namedPipeServerMode; + private bool _sshServerMode; private string _configurationName; private ConsoleHost _parent; private ConsoleHostUserInterface _ui; diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index da0df1159..4c4d8f57f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -249,6 +249,12 @@ namespace Microsoft.PowerShell s_cpp.ConfigurationName); exitCode = 0; } + else if (s_cpp.SSHServerMode) + { + ClrFacade.StartProfileOptimization("StartupProfileData-SSHServerMode"); + System.Management.Automation.Remoting.Server.SSHProcessMediator.Run(s_cpp.InitialCommand); + exitCode = 0; + } else if (s_cpp.SocketServerMode) { ClrFacade.StartProfileOptimization("StartupProfileData-SocketServerMode"); diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs index 15dbd3fce..5a972e73b 100644 --- a/src/System.Management.Automation/engine/hostifaces/Connection.cs +++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs @@ -436,7 +436,12 @@ namespace System.Management.Automation.Runspaces /// /// Runspace is based on a VM socket transport. /// - VMSocketTransport = 0x4 + VMSocketTransport = 0x4, + + /// + /// Runspace is based on SSH transport. + /// + SSHTransport = 0x8 } #endregion diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs index d2672445b..c40b6c674 100644 --- a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs +++ b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs @@ -663,6 +663,7 @@ namespace System.Management.Automation.Runspaces if ((!(connectionInfo is WSManConnectionInfo)) && (!(connectionInfo is NewProcessConnectionInfo)) && (!(connectionInfo is NamedPipeConnectionInfo)) && + (!(connectionInfo is SSHConnectionInfo)) && (!(connectionInfo is VMConnectionInfo)) && (!(connectionInfo is ContainerConnectionInfo))) { diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index 5a35801ce..715c216e3 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -974,6 +974,10 @@ namespace System.Management.Automation { returnCaps |= RunspaceCapability.VMSocketTransport; } + else if (_connectionInfo is SSHConnectionInfo) + { + returnCaps |= RunspaceCapability.SSHTransport; + } else { ContainerConnectionInfo containerConnectionInfo = _connectionInfo as ContainerConnectionInfo; diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs index d81dcd65e..688f370d0 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs @@ -263,7 +263,7 @@ namespace System.Management.Automation.Runspaces } else { - Name = AutoGenerateRunspaceName(); + Name = AutoGenerateRunspaceName(Id); remoteRunspace.PSSessionName = Name; } @@ -298,6 +298,15 @@ namespace System.Management.Automation.Runspaces return; } + // SSH session + SSHConnectionInfo sshConnectionInfo = remoteRunspace.ConnectionInfo as SSHConnectionInfo; + if (sshConnectionInfo != null) + { + ComputerType = TargetMachineType.RemoteMachine; + ConfigurationName = "DefaultShell"; + return; + } + // We only support WSMan/VM/Container sessions now. Dbg.Assert(false, "Invalid Runspace"); } @@ -310,9 +319,35 @@ namespace System.Management.Automation.Runspaces /// Generates and returns the runspace name /// /// auto generated name - private String AutoGenerateRunspaceName() + private string AutoGenerateRunspaceName(int id) { - return "Session" + Id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo); + string sessionIdStr = id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo); + + if (_remoteRunspace.ConnectionInfo is WSManConnectionInfo) + { + return "WinRM" + sessionIdStr; + } + else if (_remoteRunspace.ConnectionInfo is SSHConnectionInfo) + { + return "SSH" + sessionIdStr; + } + else if ((_remoteRunspace.ConnectionInfo is NamedPipeConnectionInfo) || + (_remoteRunspace.ConnectionInfo is ContainerConnectionInfo)) + { + return "NamedPipe" + sessionIdStr; + } + else if (_remoteRunspace.ConnectionInfo is NewProcessConnectionInfo) + { + return "Process" + sessionIdStr; + } + else if (_remoteRunspace.ConnectionInfo is VMConnectionInfo) + { + return "Socket" + sessionIdStr; + } + else + { + return "Session" + sessionIdStr; + } } /// @@ -360,7 +395,7 @@ namespace System.Management.Automation.Runspaces /// Runspace name internal static string ComposeRunspaceName(int id) { - return "Session" + id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo); + return "WinRM" + id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo); } #endregion diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index 7bd3f13af..01f8ad113 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -386,6 +386,8 @@ namespace Microsoft.PowerShell.Commands [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] public SwitchParameter AsJob { get @@ -443,6 +445,8 @@ namespace Microsoft.PowerShell.Commands [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] [Alias("HCN")] public SwitchParameter HideComputerName { @@ -505,6 +509,9 @@ namespace Microsoft.PowerShell.Commands [Parameter(Position = 1, Mandatory = true, ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)] + [Parameter(Position = 1, + Mandatory = true, + ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [ValidateNotNull] [Alias("Command")] public override ScriptBlock ScriptBlock @@ -548,6 +555,9 @@ namespace Microsoft.PowerShell.Commands [Parameter(Position = 1, Mandatory = true, ParameterSetName = FilePathContainerIdParameterSet)] + [Parameter(Position = 1, + Mandatory = true, + ParameterSetName = FilePathSSHHostParameterSet)] [ValidateNotNull] [Alias("PSPath")] public override string FilePath @@ -649,6 +659,49 @@ namespace Microsoft.PowerShell.Commands set { base.RunAsAdministrator = value; } } + #region SSH Parameters + + /// + /// Host Name + /// + [ValidateNotNullOrEmpty()] + [Parameter(Position = 0, Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(Position = 0, Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + public override string HostName + { + get { return base.HostName; } + + set { base.HostName = value; } + } + + /// + /// User Name + /// + [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + [ValidateNotNullOrEmpty()] + public override string UserName + { + get { return base.UserName; } + + set { base.UserName = value; } + } + + /// + /// Key Path + /// + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + [ValidateNotNullOrEmpty()] + public override string KeyPath + { + get { return base.KeyPath; } + + set { base.KeyPath = value; } + } + + #endregion + #endregion Parameters #region Overrides @@ -929,6 +982,18 @@ namespace Microsoft.PowerShell.Commands } break; + case InvokeCommandCommand.SSHHostParameterSet: + case InvokeCommandCommand.FilePathSSHHostParameterSet: + { + var job = new PSRemotingJob(new string[] { this.HostName }, Operations, + ScriptBlock.ToString(), ThrottleLimit, _name); + job.PSJobTypeName = RemoteJobType; + job.HideComputerName = _hideComputerName; + this.JobRepository.Add(job); + WriteObject(job); + } + break; + case InvokeCommandCommand.SessionParameterSet: case InvokeCommandCommand.FilePathSessionParameterSet: { diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs index 51434b42c..166ed55fb 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -176,6 +176,11 @@ namespace Microsoft.PowerShell.Commands /// protected const string VMNameParameterSet = "VMName"; + /// + /// SSH host parameter set + /// + protected const string SSHHostParameterSet = "SSHHost"; + /// /// runspace parameter set /// @@ -673,6 +678,43 @@ namespace Microsoft.PowerShell.Commands } private string _thumbPrint = null; + #region SSHHostParameters + + /// + /// SSH Target Host Name + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + [ValidateNotNullOrEmpty()] + public virtual string HostName + { + get; + set; + } + + /// + /// SSH User Name + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + [ValidateNotNullOrEmpty()] + public virtual string UserName + { + get; + set; + } + + /// + /// SSH Key Path + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + [ValidateNotNullOrEmpty()] + public virtual string KeyPath + { + get; + set; + } + + #endregion + #endregion Properties #region Internal Static Methods @@ -865,6 +907,11 @@ namespace Microsoft.PowerShell.Commands /// protected const string FilePathContainerIdParameterSet = "FilePathContainerId"; + /// + /// SSH Host file path parameter set. + /// + protected const string FilePathSSHHostParameterSet = "FilePathSSHHost"; + #endregion #region Parameters @@ -1113,6 +1160,21 @@ namespace Microsoft.PowerShell.Commands } }// CreateHelpersForSpecifiedComputerNames + /// + /// Creates helper objects for host names for PSRP over SSH + /// remoting. + /// + protected void CreateHelpersForSpecifiedHostNames() + { + var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyPath); + var typeTable = TypeTable.LoadDefaultTypeFiles(); + var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace; + var pipeline = CreatePipeline(remoteRunspace); + + var operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline); + Operations.Add(operation); + } + /// /// Creates helper objects with the specified command for /// the specified remote runspaceinfo objects @@ -1720,6 +1782,11 @@ namespace Microsoft.PowerShell.Commands } break; + case PSExecutionCmdlet.SSHHostParameterSet: + case PSExecutionCmdlet.FilePathSSHHostParameterSet: + CreateHelpersForSpecifiedHostNames(); + break; + case PSExecutionCmdlet.FilePathSessionParameterSet: case PSExecutionCmdlet.SessionParameterSet: { diff --git a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs index 3aa13ab9e..da9e7afcc 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs @@ -297,6 +297,10 @@ namespace Microsoft.PowerShell.Commands case ContainerIdParameterSet: remoteRunspace = GetRunspaceForContainerSession(); break; + + case SSHHostParameterSet: + remoteRunspace = GetRunspaceForSSHSession(); + break; } // If runspace is null then the error record has already been written and we can exit. @@ -1239,6 +1243,20 @@ namespace Microsoft.PowerShell.Commands return remoteRunspace; } + /// + /// Create remote runspace for SSH session + /// + private RemoteRunspace GetRunspaceForSSHSession() + { + var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyPath); + var typeTable = TypeTable.LoadDefaultTypeFiles(); + var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace; + remoteRunspace.Open(); + remoteRunspace.ShouldCloseOnPop = true; + + return remoteRunspace; + } + #endregion #region Internal Methods diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs index 9edd6956b..2abe99ef7 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -238,6 +238,12 @@ namespace Microsoft.PowerShell.Commands } break; + case NewPSSessionCommand.SSHHostParameterSet: + { + remoteRunspaces = CreateRunspacesForSSHHostParameterSet(); + } + break; + default: { Dbg.Assert(false, "Missing paramenter set in switch statement"); @@ -1049,6 +1055,23 @@ namespace Microsoft.PowerShell.Commands return remoteRunspaces; }// CreateRunspacesWhenContainerParameterSpecified + /// + /// CreateRunspacesForSSHHostParameterSet + /// + /// + private List CreateRunspacesForSSHHostParameterSet() + { + var remoteRunspaces = new List(); + var sshConnectionInfo = new SSHConnectionInfo( + this.UserName, + this.HostName, + this.KeyPath); + var typeTable = TypeTable.LoadDefaultTypeFiles(); + remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace); + + return remoteRunspaces; + } + /// /// Helper method to either get a user supplied runspace/session name /// or to generate one along with a unique Id. diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index 142017f98..88c606f31 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -222,10 +222,10 @@ namespace System.Management.Automation.Remoting uint nDefaultTimeOut, SECURITY_ATTRIBUTES securityAttributes); - internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle) + internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle, bool inheritHandle = false) { SECURITY_ATTRIBUTES securityAttributes = new NamedPipeNative.SECURITY_ATTRIBUTES(); - securityAttributes.InheritHandle = false; + securityAttributes.InheritHandle = inheritHandle; securityAttributes.NLength = (int)Marshal.SizeOf(securityAttributes); securityAttributes.LPSecurityDescriptor = securityDescriptorPinnedHandle.AddrOfPinnedObject(); return securityAttributes; @@ -590,7 +590,7 @@ namespace System.Management.Automation.Remoting #region Private Methods - private static CommonSecurityDescriptor GetServerPipeSecurity() + internal static CommonSecurityDescriptor GetServerPipeSecurity() { // Built-in Admin SID SecurityIdentifier adminSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index ace6255ee..8c3ce5ad5 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -4,9 +4,12 @@ Copyright (c) Microsoft Corporation. All rights reserved. using System.Net; using System.Net.Sockets; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.IO.Pipes; +using System.ComponentModel; // Win32Exception using System.Management.Automation.Tracing; using System.Management.Automation.Remoting; using System.Management.Automation.Internal; @@ -14,6 +17,8 @@ using System.Management.Automation.Remoting.Client; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; +using System.Security.AccessControl; +using Microsoft.Win32.SafeHandles; using Dbg = System.Management.Automation.Diagnostics; using WSManAuthenticationMechanism = System.Management.Automation.Remoting.Client.WSManNativeApi.WSManAuthenticationMechanism; @@ -1555,7 +1560,6 @@ namespace System.Management.Automation.Runspaces /// public override string ComputerName { - // TODO: should this be different get { return "localhost"; } set { throw new NotImplementedException(); } } @@ -1821,6 +1825,487 @@ namespace System.Management.Automation.Runspaces #endregion } + /// + /// Class used to create a connection through an SSH.exe client to a remote host machine. + /// Connection information includes SSH target (user name and host machine) along with + /// client key used for key based user authorization. + /// + public sealed class SSHConnectionInfo : RunspaceConnectionInfo + { + #region Properties + + /// + /// User Name + /// + public string UserName + { + get; + private set; + } + + /// + /// Key Path + /// + private string KeyPath + { + get; + set; + } + + #endregion + + #region Constructors + + /// + /// Constructor + /// + private SSHConnectionInfo() + { } + + /// + /// Constructor + /// + /// User Name + /// Computer Name + /// Key Path + public SSHConnectionInfo( + string userName, + string computerName, + string keyPath) + { + if (userName == null) { throw new PSArgumentNullException("userName"); } + if (computerName == null) { throw new PSArgumentNullException("computerName"); } + + this.UserName = userName; + this.ComputerName = computerName; + this.KeyPath = keyPath; + } + + #endregion + + #region Overrides + + /// + /// Computer is always localhost. + /// + public override string ComputerName + { + get; + set; + } + + /// + /// Credential + /// + public override PSCredential Credential + { + get { return null; } + set { throw new NotImplementedException(); } + } + + /// + /// Authentication + /// + public override AuthenticationMechanism AuthenticationMechanism + { + get { return AuthenticationMechanism.Default; } + set { throw new NotImplementedException(); } + } + + /// + /// CertificateThumbprint + /// + public override string CertificateThumbprint + { + get { return string.Empty; } + set { throw new NotImplementedException(); } + } + + /// + /// Shallow copy of current instance. + /// + /// NamedPipeConnectionInfo + internal override RunspaceConnectionInfo InternalCopy() + { + SSHConnectionInfo newCopy = new SSHConnectionInfo(); + newCopy.ComputerName = this.ComputerName; + newCopy.UserName = this.UserName; + newCopy.KeyPath = this.KeyPath; + + return newCopy; + } + + /// + /// CreateClientSessionTransportManager + /// + /// + /// + /// + /// + internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) + { + return new SSHClientSessionTransportManager( + this, + instanceId, + cryptoHelper); + } + + #endregion + + #region Internal Methods + + /// + /// StartSSHProcess + /// + /// + internal System.Diagnostics.Process StartSSHProcess( + out StreamWriter stdInWriterVar, + out StreamReader stdOutReaderVar, + out StreamReader stdErrReaderVar) + { + string filePath = string.Empty; +#if !UNIX + var context = Runspaces.LocalPipeline.GetExecutionContextFromTLS(); + if (context != null) + { + var cmdInfo = context.CommandDiscovery.LookupCommandInfo("ssh.exe", CommandOrigin.Internal) as ApplicationInfo; + if (cmdInfo != null) + { + filePath = cmdInfo.Path; + } + } +#else + filePath = @"ssh"; +#endif + + // Extract an optional domain name if provided. + string domainName = null; + string userName = this.UserName; +#if !UNIX + var parts = this.UserName.Split(Utils.Separators.Backslash); + if (parts.Length == 2) + { + domainName = parts[0]; + userName = parts[1]; + } +#endif + + // Create client ssh process that hosts powershell.exe as a subsystem and is configured + // to be in server mode for PSRP over SSHD: + // powershell -Version 5.1 -sshs -NoLogo -NoProfile + // See sshd_configuration file, subsystems section and it will have this entry: + // Subsystem powershell C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Version 5.1 -sshs -NoLogo -NoProfile + string arguments; + if (!string.IsNullOrEmpty(this.KeyPath)) + { + arguments = (string.IsNullOrEmpty(domainName)) ? + string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -s powershell", this.KeyPath, userName, this.ComputerName) : + string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -s powershell", this.KeyPath, userName, domainName, this.ComputerName); + } + else + { + arguments = (string.IsNullOrEmpty(domainName)) ? + string.Format(CultureInfo.InvariantCulture, @"{0}@{1} -s powershell", userName, this.ComputerName) : + string.Format(CultureInfo.InvariantCulture, @"-l {0}@{1} {2} -s powershell", userName, domainName, this.ComputerName); + } + + System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo( + filePath, + arguments); + startInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(filePath); + startInfo.CreateNoWindow = true; + startInfo.UseShellExecute = false; + + return StartSSHProcessImpl(startInfo, out stdInWriterVar, out stdOutReaderVar, out stdErrReaderVar); + } + + #endregion + + #region SSH Process Creation + +#if UNIX + + /// + /// Create a process through managed APIs and return StdIn, StdOut, StdError reader/writers + /// This works for non-Windows platforms and is simpler. + /// + private static System.Diagnostics.Process StartSSHProcessImpl( + System.Diagnostics.ProcessStartInfo startInfo, + out StreamWriter stdInWriterVar, + out StreamReader stdOutReaderVar, + out StreamReader stdErrReaderVar) + { + startInfo.RedirectStandardInput = true; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + + System.Diagnostics.Process process = new Process(); + process.StartInfo = startInfo; + + process.Start(); + + stdInWriterVar = process.StandardInput; + stdOutReaderVar = process.StandardOutput; + stdErrReaderVar = process.StandardError; + + return process; + } + +#else + + /// + /// Create a process through native Win32 APIs and return StdIn, StdOut, StdError reader/writers + /// This needs to be done via Win32 APIs because managed code creates anonymous synchronous pipes + /// for redirected StdIn/Out and SSH (and PSRP) require asynchrous (overlapped) pipes, which must + /// be through named pipes. Managed code for named pipes is unreliable and so this is done via + /// P-Invoking native APIs. + /// + private static System.Diagnostics.Process StartSSHProcessImpl( + System.Diagnostics.ProcessStartInfo startInfo, + out StreamWriter stdInWriterVar, + out StreamReader stdOutReaderVar, + out StreamReader stdErrReaderVar) + { + Exception ex = null; + System.Diagnostics.Process sshProcess = null; + // + // These std pipe handles are bound to managed Reader/Writer objects and returned to the transport + // manager object, which uses them for PSRP communication. The lifetime of these handles are then + // tied to the reader/writer objects which the transport is responsible for disposing (see + // SSHClientSessionTransportManger and the CloseConnection() method. + // + SafePipeHandle stdInPipeServer = null; + SafePipeHandle stdOutPipeServer = null; + SafePipeHandle stdErrPipeServer = null; + try + { + sshProcess = CreateProcessWithRedirectedStd( + startInfo, + out stdInPipeServer, + out stdOutPipeServer, + out stdErrPipeServer); + } + catch (InvalidOperationException e) { ex = e; } + catch (ArgumentException e) { ex = e; } + catch (FileNotFoundException e) { ex = e; } + catch (System.ComponentModel.Win32Exception e) { ex = e; } + + if ((ex != null) || + (sshProcess == null) || + (sshProcess.HasExited == true)) + { + throw new InvalidOperationException(RemotingErrorIdStrings.CannotStartSSHClient, ex); + } + + // Create the std in writer/readers needed for communication with ssh.exe. + stdInWriterVar = null; + stdOutReaderVar = null; + stdErrReaderVar = null; + try + { + stdInWriterVar = new StreamWriter(new NamedPipeServerStream(PipeDirection.Out, true, true, stdInPipeServer)); + stdOutReaderVar = new StreamReader(new NamedPipeServerStream(PipeDirection.In, true, true, stdOutPipeServer)); + stdErrReaderVar = new StreamReader(new NamedPipeServerStream(PipeDirection.In, true, true, stdErrPipeServer)); + } + catch (Exception e) + { + CommandProcessorBase.CheckForSevereException(e); + if (stdInWriterVar != null) { stdInWriterVar.Dispose(); } else { stdInPipeServer.Dispose(); } + if (stdOutReaderVar != null) { stdInWriterVar.Dispose(); } else { stdOutPipeServer.Dispose(); } + if (stdErrReaderVar != null) { stdInWriterVar.Dispose(); } else { stdErrPipeServer.Dispose(); } + + throw; + } + + return sshProcess; + } + + /// + /// CreateProcessWithRedirectedStd + /// + private static Process CreateProcessWithRedirectedStd( + ProcessStartInfo startInfo, + out SafePipeHandle stdInPipeServer, + out SafePipeHandle stdOutPipeServer, + out SafePipeHandle stdErrPipeServer) + { + // + // Create named (async) pipes for reading/writing to std. + // + stdInPipeServer = null; + stdOutPipeServer = null; + stdErrPipeServer = null; + SafePipeHandle stdInPipeClient = null; + SafePipeHandle stdOutPipeClient = null; + SafePipeHandle stdErrPipeClient = null; + string randomName = System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName()); + + try + { + // Get default pipe security (Admin and current user access) + var securityDesc = RemoteSessionNamedPipeServer.GetServerPipeSecurity(); + + var stdInPipeName = @"\\.\pipe\StdIn" + randomName; + stdInPipeServer = CreateNamedPipe(stdInPipeName, securityDesc); + stdInPipeClient = GetNamedPipeHandle(stdInPipeName); + + var stdOutPipeName = @"\\.\pipe\StdOut" + randomName; + stdOutPipeServer = CreateNamedPipe(stdOutPipeName, securityDesc); + stdOutPipeClient = GetNamedPipeHandle(stdOutPipeName); + + var stdErrPipeName = @"\\.\pipe\StdErr" + randomName; + stdErrPipeServer = CreateNamedPipe(stdErrPipeName, securityDesc); + stdErrPipeClient = GetNamedPipeHandle(stdErrPipeName); + } + catch (Exception e) + { + CommandProcessorBase.CheckForSevereException(e); + + if (stdInPipeServer != null) { stdInPipeServer.Dispose(); } + if (stdInPipeClient != null) { stdInPipeClient.Dispose(); } + if (stdOutPipeServer != null) { stdOutPipeServer.Dispose(); } + if (stdOutPipeClient != null) { stdOutPipeClient.Dispose(); } + if (stdErrPipeServer != null) { stdErrPipeServer.Dispose(); } + if (stdErrPipeClient != null) { stdErrPipeClient.Dispose(); } + + throw; + } + + // Create process + PlatformInvokes.STARTUPINFO lpStartupInfo = new PlatformInvokes.STARTUPINFO(); + PlatformInvokes.PROCESS_INFORMATION lpProcessInformation = new PlatformInvokes.PROCESS_INFORMATION(); + int creationFlags = 0; + + try + { + var cmdLine = String.Format(CultureInfo.InvariantCulture, @"""{0}"" {1}", startInfo.FileName, startInfo.Arguments); + + lpStartupInfo.hStdInput = new SafeFileHandle(stdInPipeClient.DangerousGetHandle(), false); + lpStartupInfo.hStdOutput = new SafeFileHandle(stdOutPipeClient.DangerousGetHandle(), false); + lpStartupInfo.hStdError = new SafeFileHandle(stdErrPipeClient.DangerousGetHandle(), false); + lpStartupInfo.dwFlags = 0x100; + + // No new window: Inherit the parent process's console window + creationFlags = 0x00000000; + + // Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly. + creationFlags |= 0x00000004; + + PlatformInvokes.SECURITY_ATTRIBUTES lpProcessAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); + PlatformInvokes.SECURITY_ATTRIBUTES lpThreadAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); + bool success = PlatformInvokes.CreateProcess( + null, + cmdLine, + lpProcessAttributes, + lpThreadAttributes, + true, + creationFlags, + IntPtr.Zero, + startInfo.WorkingDirectory, + lpStartupInfo, + lpProcessInformation); + + if (!success) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + // At this point, we should have a suspended process. Get the .Net Process object, resume the process, and return. + Process result = Process.GetProcessById(lpProcessInformation.dwProcessId); + PlatformInvokes.ResumeThread(lpProcessInformation.hThread); + + return result; + } + catch (Exception e) + { + CommandProcessorBase.CheckForSevereException(e); + if (stdInPipeServer != null) { stdInPipeServer.Dispose(); } + if (stdInPipeClient != null) { stdInPipeClient.Dispose(); } + if (stdOutPipeServer != null) { stdOutPipeServer.Dispose(); } + if (stdOutPipeClient != null) { stdOutPipeClient.Dispose(); } + if (stdErrPipeServer != null) { stdErrPipeServer.Dispose(); } + if (stdErrPipeClient != null) { stdErrPipeClient.Dispose(); } + + throw; + } + finally + { + lpProcessInformation.Dispose(); + } + } + + private static SafePipeHandle GetNamedPipeHandle(string pipeName) + { + // Create pipe flags for asynchronous pipes. + uint pipeFlags = NamedPipeNative.FILE_FLAG_OVERLAPPED; + + // We want an inheritable handle. + PlatformInvokes.SECURITY_ATTRIBUTES securityAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); + + // Get handle to pipe. + var fileHandle = PlatformInvokes.CreateFileW( + pipeName, + NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, + 0, + securityAttributes, + NamedPipeNative.OPEN_EXISTING, + pipeFlags, + IntPtr.Zero); + + int lastError = Marshal.GetLastWin32Error(); + if (fileHandle == PlatformInvokes.INVALID_HANDLE_VALUE) + { + throw new System.ComponentModel.Win32Exception(lastError); + } + + return new SafePipeHandle(fileHandle, true); + } + + private static SafePipeHandle CreateNamedPipe( + string pipeName, + CommonSecurityDescriptor securityDesc) + { + // Create optional security attributes based on provided PipeSecurity. + NamedPipeNative.SECURITY_ATTRIBUTES securityAttributes = null; + GCHandle? securityDescHandle = null; + if (securityDesc != null) + { + byte[] securityDescBuffer = new byte[securityDesc.BinaryLength]; + securityDesc.GetBinaryForm(securityDescBuffer, 0); + securityDescHandle = GCHandle.Alloc(securityDescBuffer, GCHandleType.Pinned); + securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value, true); ; + } + + // Create async named pipe. + SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe( + pipeName, + NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED, + NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE, + 1, + 32768, + 32768, + 0, + securityAttributes); + + int lastError = Marshal.GetLastWin32Error(); + if (securityDescHandle != null) + { + securityDescHandle.Value.Free(); + } + + if (pipeHandle.IsInvalid) + { + throw new Win32Exception(lastError); + } + + return pipeHandle; + } + +#endif + + #endregion + } + /// /// The class that contains connection information for a remote session between a local host /// and VM. The local host can be a VM in nested scenario. diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs index 9ce961d9b..cad472de9 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs @@ -424,6 +424,7 @@ namespace System.Management.Automation.Remoting lock (_syncObject) { _writer.WriteLine(data); + _writer.Flush(); } } @@ -1403,6 +1404,174 @@ namespace System.Management.Automation.Remoting.Client #endregion } + internal sealed class SSHClientSessionTransportManager : OutOfProcessClientSessionTransportManagerBase + { + #region Data + + private SSHConnectionInfo _connectionInfo; + private Process _sshProcess; + private StreamWriter _stdInWriter; + private StreamReader _stdOutReader; + private StreamReader _stdErrReader; + private const string _threadName = "SSHTransport Reader Thread"; + + #endregion + + #region Constructors + + internal SSHClientSessionTransportManager( + SSHConnectionInfo connectionInfo, + Guid runspaceId, + PSRemotingCryptoHelper cryptoHelper) + : base(runspaceId, cryptoHelper) + { + if (connectionInfo == null) { throw new PSArgumentException("connectionInfo"); } + + _connectionInfo = connectionInfo; + } + + #endregion + + #region Overrides + + internal override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (isDisposing) + { + CloseConnection(); + } + } + + protected override void CleanupConnection() + { + CloseConnection(); + } + + /// + /// Create an SSH connection to the target host and set up + /// transport reader/writer. + /// + internal override void CreateAsync() + { + // Create the ssh client process with connection to host target. + _sshProcess = _connectionInfo.StartSSHProcess( + out _stdInWriter, + out _stdOutReader, + out _stdErrReader); + + _sshProcess.Exited += (sender, args) => + { + CloseConnection(); + }; + + // Create writer for named pipe. + stdInWriter = new OutOfProcessTextWriter(_stdInWriter); + + // Create reader thread and send first PSRP message. + StartReaderThread(_stdOutReader); + } + + #endregion + + #region Private Methods + + private void CloseConnection() + { + var stdInWriter = _stdInWriter; + if (stdInWriter != null) { stdInWriter.Dispose(); } + + var stdOutReader = _stdOutReader; + if (stdOutReader != null) { stdOutReader.Dispose(); } + + var stdErrReader = _stdErrReader; + if (stdErrReader != null) { stdErrReader.Dispose(); } + + var sshProcess = _sshProcess; + if ((sshProcess != null) && !sshProcess.HasExited) + { + _sshProcess = null; + try + { + sshProcess.Kill(); + } + catch (InvalidOperationException) { } + catch (NotSupportedException) { } + catch (System.ComponentModel.Win32Exception) { } + } + } + + private void StartReaderThread( + StreamReader reader) + { + Thread readerThread = new Thread(ProcessReaderThread); + readerThread.Name = _threadName; + readerThread.IsBackground = true; + readerThread.Start(reader); + } + + private void ProcessReaderThread(object state) + { + try + { + StreamReader reader = state as StreamReader; + Dbg.Assert(reader != null, "Reader cannot be null."); + + // Send one fragment. + SendOneItem(); + + // Start reader loop. + while (true) + { + string data = reader.ReadLine(); + if (data == null) + { + // End of stream indicates the target process was lost. + // Raise transport exception to invalidate the client remote runspace. + PSRemotingTransportException psrte = new PSRemotingTransportException( + PSRemotingErrorId.IPCServerProcessReportedError, + RemotingErrorIdStrings.IPCServerProcessReportedError, + RemotingErrorIdStrings.NamedPipeTransportProcessEnded); + RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, TransportMethodEnum.ReceiveShellOutputEx)); + break; + } + + if (data.StartsWith(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend, StringComparison.OrdinalIgnoreCase)) + { + // Error message from the server. + string errorData = data.Substring(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend.Length); + HandleErrorDataReceived(errorData); + } + else + { + // Normal output data. + HandleOutputDataReceived(data); + } + } + } + catch (ObjectDisposedException) + { + // Normal reader thread end. + } + catch (Exception e) + { + CommandProcessorBase.CheckForSevereException(e); + + if (e is ArgumentOutOfRangeException) + { + Dbg.Assert(false, "Need to adjust transport fragmentor to accomodate read buffer size."); + } + + string errorMsg = (e.Message != null) ? e.Message : string.Empty; + _tracer.WriteMessage("SSHClientSessionTransportManager", "StartReaderThread", Guid.Empty, + "Transport manager reader thread ended with error: {0}", errorMsg); + } + } + + #endregion + } + internal abstract class NamedPipeClientSessionTransportManagerBase : OutOfProcessClientSessionTransportManagerBase { #region Data @@ -1979,8 +2148,8 @@ namespace System.Management.Automation.Remoting.Server #region Constructors - internal OutOfProcessServerSessionTransportManager(OutOfProcessTextWriter outWriter, OutOfProcessTextWriter errWriter) - : base(BaseTransportManager.DefaultFragmentSize, new PSRemotingCryptoHelperServer()) + internal OutOfProcessServerSessionTransportManager(OutOfProcessTextWriter outWriter, OutOfProcessTextWriter errWriter, PSRemotingCryptoHelperServer cryptoHelper) + : base(BaseTransportManager.DefaultFragmentSize, cryptoHelper) { Dbg.Assert(null != outWriter, "outWriter cannot be null."); Dbg.Assert(null != errWriter, "errWriter cannot be null."); diff --git a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs index ac0a9fe94..1d19a2f1f 100644 --- a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs +++ b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs @@ -7,6 +7,7 @@ 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 @@ -296,14 +297,21 @@ namespace System.Management.Automation.Remoting.Server #region Methods - protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName) + 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); - PSSenderInfo senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); + 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); + OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper); ServerRemoteSession srvrRemoteSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo, _initialCommand, tm, configurationName); @@ -311,11 +319,11 @@ namespace System.Management.Automation.Remoting.Server return tm; } - protected void Start(string initialCommand, string configurationName = null) + protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string configurationName = null) { _initialCommand = initialCommand; - sessionTM = CreateSessionTransportManager(configurationName); + sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper); try { @@ -326,7 +334,7 @@ namespace System.Management.Automation.Remoting.Server { if (sessionTM == null) { - sessionTM = CreateSessionTransportManager(configurationName); + sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper); } } if (string.IsNullOrEmpty(data)) @@ -474,7 +482,76 @@ namespace System.Management.Automation.Remoting.Server // Setup unhandled exception to log events AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); #endif - s_singletonInstance.Start(initialCommand); + 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 @@ -552,7 +629,7 @@ namespace System.Management.Automation.Remoting.Server // AppDomain is not available in CoreCLR AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); #endif - s_singletonInstance.Start(initialCommand, namedPipeServer.ConfigurationName); + s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer(), namedPipeServer.ConfigurationName); } #endregion @@ -643,7 +720,7 @@ namespace System.Management.Automation.Remoting.Server AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); #endif - s_instance.Start(initialCommand, configurationName); + s_instance.Start(initialCommand, new PSRemotingCryptoHelperServer(), configurationName); } #endregion diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs index c7e070cc2..8b1c2c224 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs @@ -132,11 +132,12 @@ namespace System.Management.Automation.Remoting _senderInfo = senderInfo; _configProviderId = configurationProviderId; _initParameters = initializationParameters; - if (Platform.IsWindows) - { - _cryptoHelper = (PSRemotingCryptoHelperServer)transportManager.CryptoHelper; - _cryptoHelper.Session = this; - } +#if !UNIX + _cryptoHelper = (PSRemotingCryptoHelperServer)transportManager.CryptoHelper; + _cryptoHelper.Session = this; +#else + _cryptoHelper = null; +#endif Context = new ServerRemoteSessionContext(); SessionDataStructureHandler = new ServerRemoteSessionDSHandlerlImpl(this, transportManager); @@ -162,9 +163,9 @@ namespace System.Management.Automation.Remoting transportManager.ReceivedDataCollection.MaximumReceivedDataSize = null; } - #endregion Constructors +#endregion Constructors - #region Creation Factory +#region Creation Factory /// /// Creates a server remote session for the supplied @@ -240,9 +241,9 @@ namespace System.Management.Automation.Remoting return result; } - #endregion +#endregion - #region Overrides +#region Overrides /// /// This indicates the remote session object is Client, Server or Listener. @@ -457,9 +458,9 @@ namespace System.Management.Automation.Remoting SessionDataStructureHandler.StateMachine.RaiseEvent(args); } - #endregion Overrides +#endregion Overrides - #region Properties +#region Properties /// /// This property returns the ServerRemoteSessionContext object created inside @@ -473,9 +474,9 @@ namespace System.Management.Automation.Remoting /// internal ServerRemoteSessionDataStructureHandler SessionDataStructureHandler { get; } - #endregion +#endregion - #region Private/Internal Methods +#region Private/Internal Methods /// /// Let the session clear its resources. @@ -866,7 +867,11 @@ namespace System.Management.Automation.Remoting _runspacePoolDriver.InstanceId); } +#if !UNIX bool isAdministrator = _senderInfo.UserInfo.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator); +#else + bool isAdministrator = false; +#endif ServerRunspacePoolDriver tmpDriver = new ServerRunspacePoolDriver( clientRunspacePoolId, @@ -1158,6 +1163,6 @@ namespace System.Management.Automation.Remoting cmdTransportManager.ReceivedDataCollection.MaximumReceivedObjectSize = _maxRecvdObjectSize; } - #endregion +#endregion } } diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index d8148083f..ee890e003 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -1609,4 +1609,7 @@ All WinRM sessions connected to Windows PowerShell session configurations, such Other Possible Cause: -The domain or computer name was not included with the specified credential, for example: DOMAIN\UserName or COMPUTER\UserName. + + An error occurred when starting the SSH.exe client needed for the remoting connection. + \ No newline at end of file diff --git a/src/System.Management.Automation/security/wldpNativeMethods.cs b/src/System.Management.Automation/security/wldpNativeMethods.cs index d269c7ef3..17d423d55 100644 --- a/src/System.Management.Automation/security/wldpNativeMethods.cs +++ b/src/System.Management.Automation/security/wldpNativeMethods.cs @@ -258,6 +258,13 @@ namespace System.Management.Automation.Security (System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ? SaferPolicy.Allowed : SaferPolicy.Disallowed; } + catch (ArgumentException) + { + // This is for IO.Path.GetTempPath() call when temp paths are not accessible. + result = + (System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ? + SaferPolicy.Allowed : SaferPolicy.Disallowed; + } finally { if (IO.File.Exists(testPathScript)) { IO.File.Delete(testPathScript); } diff --git a/src/System.Management.Automation/utils/PlatformInvokes.cs b/src/System.Management.Automation/utils/PlatformInvokes.cs index 44fd3dd69..95abb623d 100644 --- a/src/System.Management.Automation/utils/PlatformInvokes.cs +++ b/src/System.Management.Automation/utils/PlatformInvokes.cs @@ -4,6 +4,7 @@ Copyright (c) Microsoft Corporation. All rights reserved. using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; +using Microsoft.Win32.SafeHandles; #if CORECLR // Use stubs for SafeHandleZeroOrMinusOneIsInvalid, SecurityPermissionAttribute and ReliabilityContractAttribute @@ -11,7 +12,6 @@ using Microsoft.PowerShell.CoreClr.Stubs; #else using System.Security.Permissions; using System.Runtime.ConstrainedExecution; -using Microsoft.Win32.SafeHandles; #endif namespace System.Management.Automation @@ -552,5 +552,192 @@ namespace System.Management.Automation internal const uint SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000; internal const int ERROR_SUCCESS = 0x0; + + #region CreateProcess for SSH Remoting + +#if !UNIX + + // Fields + internal static readonly IntPtr INVALID_HANDLE_VALUE = IntPtr.Zero; + internal static UInt32 GENERIC_READ = 0x80000000; + internal static UInt32 GENERIC_WRITE = 0x40000000; + internal static UInt32 FILE_ATTRIBUTE_NORMAL = 0x80000000; + internal static UInt32 CREATE_ALWAYS = 2; + internal static UInt32 FILE_SHARE_WRITE = 0x00000002; + internal static UInt32 FILE_SHARE_READ = 0x00000001; + internal static UInt32 OF_READWRITE = 0x00000002; + internal static UInt32 OPEN_EXISTING = 3; + + [StructLayout(LayoutKind.Sequential)] + internal class PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + + public PROCESS_INFORMATION() + { + this.hProcess = IntPtr.Zero; + this.hThread = IntPtr.Zero; + } + + /// + /// Dispose + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Dispose + /// + /// + private void Dispose(bool disposing) + { + if (disposing) + { + if (this.hProcess != IntPtr.Zero) + { + CloseHandle(this.hProcess); + this.hProcess = IntPtr.Zero; + } + + if (this.hThread != IntPtr.Zero) + { + CloseHandle(this.hThread); + this.hThread = IntPtr.Zero; + } + } + } + } + + [StructLayout(LayoutKind.Sequential)] + internal class STARTUPINFO + { + public int cb; + public IntPtr lpReserved; + public IntPtr lpDesktop; + public IntPtr lpTitle; + public int dwX; + public int dwY; + public int dwXSize; + public int dwYSize; + public int dwXCountChars; + public int dwYCountChars; + public int dwFillAttribute; + public int dwFlags; + public short wShowWindow; + public short cbReserved2; + public IntPtr lpReserved2; + public SafeFileHandle hStdInput; + public SafeFileHandle hStdOutput; + public SafeFileHandle hStdError; + public STARTUPINFO() + { + this.lpReserved = IntPtr.Zero; + this.lpDesktop = IntPtr.Zero; + this.lpTitle = IntPtr.Zero; + this.lpReserved2 = IntPtr.Zero; + this.hStdInput = new SafeFileHandle(IntPtr.Zero, false); + this.hStdOutput = new SafeFileHandle(IntPtr.Zero, false); + this.hStdError = new SafeFileHandle(IntPtr.Zero, false); + this.cb = Marshal.SizeOf(this); + + } + + public void Dispose(bool disposing) + { + if (disposing) + { + if ((this.hStdInput != null) && !this.hStdInput.IsInvalid) + { + this.hStdInput.Dispose(); + this.hStdInput = null; + } + if ((this.hStdOutput != null) && !this.hStdOutput.IsInvalid) + { + this.hStdOutput.Dispose(); + this.hStdOutput = null; + } + if ((this.hStdError != null) && !this.hStdError.IsInvalid) + { + this.hStdError.Dispose(); + this.hStdError = null; + } + } + } + + public void Dispose() + { + Dispose(true); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal class SECURITY_ATTRIBUTES + { + public int nLength; + public SafeLocalMemHandle lpSecurityDescriptor; + public bool bInheritHandle; + public SECURITY_ATTRIBUTES() + { + this.nLength = 12; + this.bInheritHandle = true; + this.lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, true); + } + } + + // Methods + // + + [DllImport(PinvokeDllNames.CreateProcessDllName, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern bool CreateProcess( + [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, + [MarshalAs(UnmanagedType.LPWStr)] string lpCommandLine, + SECURITY_ATTRIBUTES lpProcessAttributes, + SECURITY_ATTRIBUTES lpThreadAttributes, + bool bInheritHandles, + int dwCreationFlags, + IntPtr lpEnvironment, + [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, + STARTUPINFO lpStartupInfo, + PROCESS_INFORMATION lpProcessInformation); + + [DllImport(PinvokeDllNames.ResumeThreadDllName, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern uint ResumeThread(IntPtr threadHandle); + + [DllImport(PinvokeDllNames.CreateFileDllName, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern System.IntPtr CreateFileW( + [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName, + UInt32 dwDesiredAccess, + UInt32 dwShareMode, + SECURITY_ATTRIBUTES lpSecurityAttributes, + UInt32 dwCreationDisposition, + UInt32 dwFlagsAndAttributes, + System.IntPtr hTemplateFile); + +#endif + + #endregion + + #region GetStdHandle + +#if !UNIX + + internal enum StandardHandleId : uint + { + Error = unchecked((uint)-12), + Output = unchecked((uint)-11), + Input = unchecked((uint)-10), + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr GetStdHandle(uint handleId); + +#endif + + #endregion } } \ No newline at end of file