using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Connection;
namespace Renci.SshNet.Channels
{
///
/// Implements Session SSH channel.
///
internal class ChannelSession : ClientChannel, IChannelSession
{
///
/// Counts faile channel open attempts
///
private int _failedOpenAttempts;
///
/// Wait handle to signal when response was received to open the channel
///
private EventWaitHandle _channelOpenResponseWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _channelRequestResponse = new ManualResetEvent(false);
private bool _channelRequestSucces;
///
/// Gets the type of the channel.
///
///
/// The type of the channel.
///
public override ChannelTypes ChannelType
{
get { return ChannelTypes.Session; }
}
///
/// Opens the channel.
///
public virtual void Open()
{
if (!this.IsOpen)
{
// Try to open channel several times
while (this._failedOpenAttempts < this.ConnectionInfo.RetryAttempts && !this.IsOpen)
{
this.SendChannelOpenMessage();
this.WaitOnHandle(this._channelOpenResponseWaitHandle);
}
if (!this.IsOpen)
throw new SshException(string.Format(CultureInfo.CurrentCulture, "Failed to open a channel after {0} attempts.", this._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);
this._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)
{
this._failedOpenAttempts++;
this.SessionSemaphore.Release();
this._channelOpenResponseWaitHandle.Set();
}
///
/// Called when channel is closed by the server.
///
protected override void OnClose()
{
base.OnClose();
// This timeout needed since when channel is closed it does not immediately becomes available
// but it takes time for the server to clean up resource and allow new channels to be created.
Thread.Sleep(100);
this.SessionSemaphore.Release();
}
protected override void Close(bool wait)
{
base.Close(wait);
if (!wait)
this.SessionSemaphore.Release();
}
///
/// 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)
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new PseudoTerminalRequestInfo(environmentVariable, columns, rows, width, height, terminalModeValues)));
this.WaitOnHandle(this._channelRequestResponse);
return this._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)
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new X11ForwardingRequestInfo(isSingleConnection, protocol, cookie, screenNumber)));
this.WaitOnHandle(this._channelRequestResponse);
return this._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)
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new EnvironmentVariableRequestInfo(variableName, variableValue)));
this.WaitOnHandle(this._channelRequestResponse);
return this._channelRequestSucces;
}
///
/// Sends the shell request.
///
///
/// true if request was successful; otherwise false.
///
public bool SendShellRequest()
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new ShellRequestInfo()));
this.WaitOnHandle(this._channelRequestResponse);
return this._channelRequestSucces;
}
///
/// Sends the exec request.
///
/// The command.
///
/// true if request was successful; otherwise false.
///
public bool SendExecRequest(string command)
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new ExecRequestInfo(command, this.ConnectionInfo.Encoding)));
this.WaitOnHandle(this._channelRequestResponse);
return this._channelRequestSucces;
}
///
/// Sends the exec request.
///
/// Length of the break.
///
/// true if request was successful; otherwise false.
///
public bool SendBreakRequest(uint breakLength)
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new BreakRequestInfo(breakLength)));
this.WaitOnHandle(this._channelRequestResponse);
return this._channelRequestSucces;
}
///
/// Sends the subsystem request.
///
/// The subsystem.
///
/// true if request was successful; otherwise false.
///
public bool SendSubsystemRequest(string subsystem)
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new SubsystemRequestInfo(subsystem)));
this.WaitOnHandle(this._channelRequestResponse);
return this._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)
{
this.SendMessage(new ChannelRequestMessage(this.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)
{
this.SendMessage(new ChannelRequestMessage(this.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)
{
this.SendMessage(new ChannelRequestMessage(this.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)
{
this.SendMessage(new ChannelRequestMessage(this.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)
{
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new ExitSignalRequestInfo(signalName, coreDumped, errorMessage, language)));
return true;
}
///
/// Sends eow@openssh.com request.
///
///
/// true if request was successful; otherwise false.
///
public bool SendEndOfWriteRequest()
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new EndOfWriteRequestInfo()));
this.WaitOnHandle(this._channelRequestResponse);
return this._channelRequestSucces;
}
///
/// Sends keepalive@openssh.com request.
///
///
/// true if request was successful; otherwise false.
///
public bool SendKeepAliveRequest()
{
this._channelRequestResponse.Reset();
this.SendMessage(new ChannelRequestMessage(this.RemoteChannelNumber, new KeepAliveRequestInfo()));
this.WaitOnHandle(this._channelRequestResponse);
return this._channelRequestSucces;
}
///
/// Called when channel request was successful
///
protected override void OnSuccess()
{
base.OnSuccess();
this._channelRequestSucces = true;
var channelRequestResponse = _channelRequestResponse;
if (channelRequestResponse != null)
channelRequestResponse.Set();
}
///
/// Called when channel request failed.
///
protected override void OnFailure()
{
base.OnFailure();
_channelRequestSucces = false;
var channelRequestResponse = _channelRequestResponse;
if (channelRequestResponse != null)
channelRequestResponse.Set();
}
///
/// Sends the channel open message.
///
protected void SendChannelOpenMessage()
{
lock (this.SessionSemaphore)
{
// Ensure that channels are available
this.SessionSemaphore.Wait();
this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.LocalPacketSize, new SessionChannelOpenInfo()));
}
}
///
/// Releases unmanaged and - optionally - managed resources
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (this._channelOpenResponseWaitHandle != null)
{
this._channelOpenResponseWaitHandle.Dispose();
this._channelOpenResponseWaitHandle = null;
}
if (this._channelRequestResponse != null)
{
this._channelRequestResponse.Dispose();
this._channelRequestResponse = null;
}
base.Dispose(disposing);
}
}
}