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();
}
}
}
}