You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
16 KiB
C#

9 months ago
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
{
/// <summary>
/// Implements Session SSH channel.
/// </summary>
internal class ChannelSession : ClientChannel, IChannelSession
{
/// <summary>
/// Counts failed channel open attempts
/// </summary>
private int _failedOpenAttempts;
/// <summary>
/// Holds a value indicating whether the session semaphore has been obtained by the current
/// channel.
/// </summary>
/// <value>
/// <c>0</c> when the session semaphore has not been obtained or has already been released,
/// and <c>1</c> when the session has been obtained and still needs to be released.
/// </value>
private int _sessionSemaphoreObtained;
/// <summary>
/// Wait handle to signal when response was received to open the channel
/// </summary>
private EventWaitHandle _channelOpenResponseWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _channelRequestResponse = new ManualResetEvent(false);
private bool _channelRequestSucces;
/// <summary>
/// Initializes a new <see cref="ChannelSession"/> instance.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="localChannelNumber">The local channel number.</param>
/// <param name="localWindowSize">Size of the window.</param>
/// <param name="localPacketSize">Size of the packet.</param>
public ChannelSession(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize)
: base(session, localChannelNumber, localWindowSize, localPacketSize)
{
}
/// <summary>
/// Gets the type of the channel.
/// </summary>
/// <value>
/// The type of the channel.
/// </value>
public override ChannelTypes ChannelType
{
get { return ChannelTypes.Session; }
}
/// <summary>
/// Opens the channel.
/// </summary>
public virtual 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));
}
/// <summary>
/// Called when channel is opened by the server.
/// </summary>
/// <param name="remoteChannelNumber">The remote channel number.</param>
/// <param name="initialWindowSize">Initial size of the window.</param>
/// <param name="maximumPacketSize">Maximum size of the packet.</param>
protected override void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize)
{
base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize);
_channelOpenResponseWaitHandle.Set();
}
/// <summary>
/// Called when channel failed to open.
/// </summary>
/// <param name="reasonCode">The reason code.</param>
/// <param name="description">The description.</param>
/// <param name="language">The language.</param>
protected override void OnOpenFailure(uint reasonCode, string description, string language)
{
_failedOpenAttempts++;
ReleaseSemaphore();
_channelOpenResponseWaitHandle.Set();
}
protected override void Close()
{
base.Close();
ReleaseSemaphore();
}
/// <summary>
/// Sends the pseudo terminal request.
/// </summary>
/// <param name="environmentVariable">The environment variable.</param>
/// <param name="columns">The columns.</param>
/// <param name="rows">The rows.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="terminalModeValues">The terminal mode values.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendPseudoTerminalRequest(string environmentVariable, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues)
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new PseudoTerminalRequestInfo(environmentVariable, columns, rows, width, height, terminalModeValues)));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Sends the X11 forwarding request.
/// </summary>
/// <param name="isSingleConnection">if set to <c>true</c> the it is single connection.</param>
/// <param name="protocol">The protocol.</param>
/// <param name="cookie">The cookie.</param>
/// <param name="screenNumber">The screen number.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
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;
}
/// <summary>
/// Sends the environment variable request.
/// </summary>
/// <param name="variableName">Name of the variable.</param>
/// <param name="variableValue">The variable value.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendEnvironmentVariableRequest(string variableName, string variableValue)
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new EnvironmentVariableRequestInfo(variableName, variableValue)));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Sends the shell request.
/// </summary>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendShellRequest()
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ShellRequestInfo()));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Sends the exec request.
/// </summary>
/// <param name="command">The command.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendExecRequest(string command)
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ExecRequestInfo(command, ConnectionInfo.Encoding)));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Sends the exec request.
/// </summary>
/// <param name="breakLength">Length of the break.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendBreakRequest(uint breakLength)
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new BreakRequestInfo(breakLength)));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Sends the subsystem request.
/// </summary>
/// <param name="subsystem">The subsystem.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendSubsystemRequest(string subsystem)
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new SubsystemRequestInfo(subsystem)));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Sends the window change request.
/// </summary>
/// <param name="columns">The columns.</param>
/// <param name="rows">The rows.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendWindowChangeRequest(uint columns, uint rows, uint width, uint height)
{
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new WindowChangeRequestInfo(columns, rows, width, height)));
return true;
}
/// <summary>
/// Sends the local flow request.
/// </summary>
/// <param name="clientCanDo">if set to <c>true</c> [client can do].</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendLocalFlowRequest(bool clientCanDo)
{
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new XonXoffRequestInfo(clientCanDo)));
return true;
}
/// <summary>
/// Sends the signal request.
/// </summary>
/// <param name="signalName">Name of the signal.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendSignalRequest(string signalName)
{
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new SignalRequestInfo(signalName)));
return true;
}
/// <summary>
/// Sends the exit status request.
/// </summary>
/// <param name="exitStatus">The exit status.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendExitStatusRequest(uint exitStatus)
{
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ExitStatusRequestInfo(exitStatus)));
return true;
}
/// <summary>
/// Sends the exit signal request.
/// </summary>
/// <param name="signalName">Name of the signal.</param>
/// <param name="coreDumped">if set to <c>true</c> [core dumped].</param>
/// <param name="errorMessage">The error message.</param>
/// <param name="language">The language.</param>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendExitSignalRequest(string signalName, bool coreDumped, string errorMessage, string language)
{
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new ExitSignalRequestInfo(signalName, coreDumped, errorMessage, language)));
return true;
}
/// <summary>
/// Sends eow@openssh.com request.
/// </summary>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendEndOfWriteRequest()
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new EndOfWriteRequestInfo()));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Sends keepalive@openssh.com request.
/// </summary>
/// <returns>
/// <c>true</c> if request was successful; otherwise <c>false</c>.
/// </returns>
public bool SendKeepAliveRequest()
{
_channelRequestResponse.Reset();
SendMessage(new ChannelRequestMessage(RemoteChannelNumber, new KeepAliveRequestInfo()));
WaitOnHandle(_channelRequestResponse);
return _channelRequestSucces;
}
/// <summary>
/// Called when channel request was successful
/// </summary>
protected override void OnSuccess()
{
base.OnSuccess();
_channelRequestSucces = true;
var channelRequestResponse = _channelRequestResponse;
if (channelRequestResponse != null)
channelRequestResponse.Set();
}
/// <summary>
/// Called when channel request failed.
/// </summary>
protected override void OnFailure()
{
base.OnFailure();
_channelRequestSucces = false;
var channelRequestResponse = _channelRequestResponse;
if (channelRequestResponse != null)
channelRequestResponse.Set();
}
/// <summary>
/// Sends the channel open message.
/// </summary>
protected 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()));
}
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
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();
}
}
}
/// <summary>
/// Releases the session semaphore.
/// </summary>
/// <remarks>
/// When the session semaphore has already been released, or was never obtained by
/// this instance, then this method does nothing.
/// </remarks>
private void ReleaseSemaphore()
{
if (Interlocked.CompareExchange(ref _sessionSemaphoreObtained, 0, 1) == 1)
SessionSemaphore.Release();
}
}
}