using System; using System.Collections.Generic; using System.Globalization; using System.Threading; using Renci.SshNet.Common; using Renci.SshNet.Messages.Connection; namespace Renci.SshNet.Channels { /// /// Implements Session SSH channel. /// internal sealed class ChannelSession : ClientChannel, IChannelSession { /// /// Counts failed channel open attempts. /// private int _failedOpenAttempts; /// /// Holds a value indicating whether the session semaphore has been obtained by the current /// channel. /// /// /// 0 when the session semaphore has not been obtained or has already been released, /// and 1 when the session has been obtained and still needs to be released. /// private int _sessionSemaphoreObtained; /// /// Wait handle to signal when response was received to open the channel. /// private EventWaitHandle _channelOpenResponseWaitHandle = new AutoResetEvent(initialState: false); private EventWaitHandle _channelRequestResponse = new ManualResetEvent(initialState: false); private bool _channelRequestSucces; /// /// Initializes a new instance of the class. /// /// The session. /// The local channel number. /// Size of the window. /// Size of the packet. public ChannelSession(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize) : base(session, localChannelNumber, localWindowSize, localPacketSize) { } /// /// Gets the type of the channel. /// /// /// The type of the channel. /// public override ChannelTypes ChannelType { get { return ChannelTypes.Session; } } /// /// Opens the channel. /// public void Open() { // Try to open channel several times while (!IsOpen && _failedOpenAttempts < ConnectionInfo.RetryAttempts) { SendChannelOpenMessage(); try { WaitOnHandle(_channelOpenResponseWaitHandle); } catch (Exception) { // avoid leaking session semaphore ReleaseSemaphore(); throw; } } if (!IsOpen) { throw new SshException(string.Format(CultureInfo.CurrentCulture, "Failed to open a channel after {0} attempts.", _failedOpenAttempts)); } } /// /// Called when channel is opened by the server. /// /// The remote channel number. /// Initial size of the window. /// Maximum size of the packet. protected override void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize) { base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize); _ = _channelOpenResponseWaitHandle.Set(); } /// /// Called when channel failed to open. /// /// The reason code. /// The description. /// The language. protected override void OnOpenFailure(uint reasonCode, string description, string language) { _failedOpenAttempts++; ReleaseSemaphore(); _ = _channelOpenResponseWaitHandle.Set(); } protected override void Close() { base.Close(); ReleaseSemaphore(); } /// /// Sends the pseudo terminal request. /// /// The environment variable. /// The columns. /// The rows. /// The width. /// The height. /// The terminal mode values. /// /// true if request was successful; otherwise false. /// public bool SendPseudoTerminalRequest(string environmentVariable, uint columns, uint rows, uint width, uint height, IDictionary terminalModeValues) { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new PseudoTerminalRequestInfo(environmentVariable, columns, rows, width, height, terminalModeValues))); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends the X11 forwarding request. /// /// if set to true the it is single connection. /// The protocol. /// The cookie. /// The screen number. /// /// true if request was successful; otherwise false. /// public bool SendX11ForwardingRequest(bool isSingleConnection, string protocol, byte[] cookie, uint screenNumber) { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new X11ForwardingRequestInfo(isSingleConnection, protocol, cookie, screenNumber))); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends the environment variable request. /// /// Name of the variable. /// The variable value. /// /// true if request was successful; otherwise false. /// public bool SendEnvironmentVariableRequest(string variableName, string variableValue) { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new EnvironmentVariableRequestInfo(variableName, variableValue))); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends the shell request. /// /// /// true if request was successful; otherwise false. /// public bool SendShellRequest() { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ShellRequestInfo())); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends the exec request. /// /// The command. /// /// true if request was successful; otherwise false. /// public bool SendExecRequest(string command) { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ExecRequestInfo(command, ConnectionInfo.Encoding))); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends the exec request. /// /// Length of the break. /// /// true if request was successful; otherwise false. /// public bool SendBreakRequest(uint breakLength) { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new BreakRequestInfo(breakLength))); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends the subsystem request. /// /// The subsystem. /// /// true if request was successful; otherwise false. /// public bool SendSubsystemRequest(string subsystem) { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new SubsystemRequestInfo(subsystem))); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends the window change request. /// /// The columns. /// The rows. /// The width. /// The height. /// /// true if request was successful; otherwise false. /// public bool SendWindowChangeRequest(uint columns, uint rows, uint width, uint height) { SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new WindowChangeRequestInfo(columns, rows, width, height))); return true; } /// /// Sends the local flow request. /// /// if set to true [client can do]. /// /// true if request was successful; otherwise false. /// public bool SendLocalFlowRequest(bool clientCanDo) { SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new XonXoffRequestInfo(clientCanDo))); return true; } /// /// Sends the signal request. /// /// Name of the signal. /// /// true if request was successful; otherwise false. /// public bool SendSignalRequest(string signalName) { SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new SignalRequestInfo(signalName))); return true; } /// /// Sends the exit status request. /// /// The exit status. /// /// true if request was successful; otherwise false. /// public bool SendExitStatusRequest(uint exitStatus) { SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ExitStatusRequestInfo(exitStatus))); return true; } /// /// Sends the exit signal request. /// /// Name of the signal. /// if set to true [core dumped]. /// The error message. /// The language. /// /// true if request was successful; otherwise false. /// public bool SendExitSignalRequest(string signalName, bool coreDumped, string errorMessage, string language) { SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ExitSignalRequestInfo(signalName, coreDumped, errorMessage, language))); return true; } /// /// Sends eow@openssh.com request. /// /// /// true if request was successful; otherwise false. /// public bool SendEndOfWriteRequest() { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new EndOfWriteRequestInfo())); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Sends keepalive@openssh.com request. /// /// /// true if request was successful; otherwise false. /// public bool SendKeepAliveRequest() { _ = _channelRequestResponse.Reset(); SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new KeepAliveRequestInfo())); WaitOnHandle(_channelRequestResponse); return _channelRequestSucces; } /// /// Called when channel request was successful. /// protected override void OnSuccess() { base.OnSuccess(); _channelRequestSucces = true; _ = _channelRequestResponse?.Set(); } /// /// Called when channel request failed. /// protected override void OnFailure() { base.OnFailure(); _channelRequestSucces = false; _ = _channelRequestResponse?.Set(); } /// /// Sends the channel open message. /// /// The client is not connected. /// The operation timed out. /// The size of the packet exceeds the maximum size defined by the protocol. /// /// /// When a session semaphore for this instance has not yet been obtained by this or any other thread, /// the thread will block until such a semaphore is available and send a /// to the remote host. /// /// /// Note that the session semaphore is released in any of the following cases: /// /// /// A is received for the channel being opened. /// /// /// The remote host does not respond to the within the configured . /// /// /// The remote host closes the channel. /// /// /// The is disposed. /// /// /// A socket error occurs sending a message to the remote host. /// /// /// /// /// If the session semaphore was already obtained for this instance (and not released), then this method /// immediately returns control to the caller. This should only happen when another thread has obtain the /// session semaphore and already sent the , but the remote host did not /// confirmed or rejected attempt to open the channel. /// /// private void SendChannelOpenMessage() { // do not allow open to be ChannelOpenMessage to be sent again until we've // had a response on the previous attempt for the current channel if (Interlocked.CompareExchange(ref _sessionSemaphoreObtained, 1, 0) == 0) { SessionSemaphore.Wait(); SendMessage(new ChannelOpenMessage(LocalChannelNumber, LocalWindowSize, LocalPacketSize, new SessionChannelOpenInfo())); } } /// /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { var channelOpenResponseWaitHandle = _channelOpenResponseWaitHandle; if (channelOpenResponseWaitHandle != null) { _channelOpenResponseWaitHandle = null; channelOpenResponseWaitHandle.Dispose(); } var channelRequestResponse = _channelRequestResponse; if (channelRequestResponse != null) { _channelRequestResponse = null; channelRequestResponse.Dispose(); } } } /// /// Releases the session semaphore. /// /// /// When the session semaphore has already been released, or was never obtained by /// this instance, then this method does nothing. /// private void ReleaseSemaphore() { if (Interlocked.CompareExchange(ref _sessionSemaphoreObtained, 0, 1) == 1) { _ = SessionSemaphore.Release(); } } } }