From a8adef465ce39af203d0dc83164f049c57b1f7da Mon Sep 17 00:00:00 2001 From: olegkap_cp Date: Wed, 17 Nov 2010 18:29:39 +0000 Subject: [PATCH] Refactor and complete ChannelRequestMessage for different channel request; Add support for version 1.99; Add shell/terminal like capabilities; --- .../Renci.SshClient/Channels/Channel.cs | 4 +- .../Channels/ChannelSession.cs | 195 +++++++++++++++++- .../Channels/ChannelSessionExec.cs | 33 +-- .../Channels/ChannelSessionSftp.cs | 14 +- .../Channels/ChannelSessionShell.cs | 99 +++++++++ .../Renci.SshClient/Common/SshData.cs | 22 ++ ...hannelRequestEnvironmentVariableMessage.cs | 30 +++ .../ChannelRequestExecMessage.cs | 26 +++ .../ChannelRequestExitSignalMessage.cs | 38 ++++ .../ChannelRequestExitStatusMessage.cs | 23 +++ .../ChannelRequest/ChannelRequestMessage.cs | 31 +++ .../ChannelRequestPseudoTerminalMessage.cs | 44 ++++ .../ChannelRequestShellMessage.cs | 19 ++ .../ChannelRequestSignalMessage.cs | 26 +++ .../ChannelRequestSubsystemMessage.cs | 26 +++ .../ChannelRequestWindowChangeMessage.cs | 38 ++++ .../ChannelRequestX11ForwardingMessage.cs | 38 ++++ .../ChannelRequestXonXoffMessage.cs | 26 +++ .../Connection/ChannelRequestMessage.cs | 106 ---------- .../Connection/ChannelRequestNames.cs | 50 ----- .../Renci.SshClient/Renci.SshClient.csproj | 16 +- Renci.SshClient/Renci.SshClient/Session.cs | 11 +- Renci.SshClient/Renci.SshClient/Shell.cs | 41 +--- Renci.SshClient/Renci.SshClient/SshClient.cs | 5 + 24 files changed, 717 insertions(+), 244 deletions(-) create mode 100644 Renci.SshClient/Renci.SshClient/Channels/ChannelSessionShell.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestEnvironmentVariableMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExecMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitSignalMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitStatusMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestPseudoTerminalMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestShellMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSignalMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSubsystemMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestWindowChangeMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestX11ForwardingMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestXonXoffMessage.cs delete mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs delete mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestNames.cs diff --git a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs index 811f6f5d..006c04c2 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs @@ -167,7 +167,7 @@ namespace Renci.SshClient.Channels this._channelClosedWaitHandle.Set(); } - protected virtual void OnRequest(ChannelRequestNames requestName, bool wantReply, string command, string subsystemName, uint exitStatus) + protected virtual void OnRequest(ChannelRequestMessage message) { } @@ -294,7 +294,7 @@ namespace Renci.SshClient.Channels { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { - this.OnRequest(e.Message.RequestName, e.Message.WantReply, e.Message.Command, e.Message.SubsystemName, e.Message.ExitStatus); + this.OnRequest(e.Message); } } diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs index c882a4db..0eb18841 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs @@ -17,6 +17,15 @@ namespace Renci.SshClient.Channels /// private EventWaitHandle _channelOpenResponseWaitHandle = new AutoResetEvent(false); + private EventWaitHandle _channelRequestResponse = new ManualResetEvent(false); + + private bool _channelRequestSucces; + + public override ChannelTypes ChannelType + { + get { return ChannelTypes.Session; } + } + /// /// Opens the channel /// @@ -53,6 +62,23 @@ namespace Renci.SshClient.Channels 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++; + + Debug.WriteLine(string.Format("Local channel: {0} attempts: {1}.", this.LocalChannelNumber, this._failedOpenAttempts)); + + this.SessionSemaphore.Release(); + + this._channelOpenResponseWaitHandle.Set(); + } + /// /// Called when channel is closed /// @@ -70,21 +96,168 @@ namespace Renci.SshClient.Channels this.SessionSemaphore.Release(); } - /// - /// Called when channel failed to open - /// - /// The reason code. - /// The description. - /// The language. - protected override void OnOpenFailure(uint reasonCode, string description, string language) + public bool SendPseudoTerminalRequest(string environmentVariable, uint columns, uint rows, uint width, uint height, string terminalMode) { - this._failedOpenAttempts++; + this._channelRequestResponse.Reset(); - Debug.WriteLine(string.Format("Local channel: {0} attempts: {1}.", this.LocalChannelNumber, this._failedOpenAttempts)); + this.SendMessage(new ChannelRequestPseudoTerminalMessage + { + LocalChannelNumber = this.RemoteChannelNumber, + //RequestName = ChannelRequestNames.PseudoTerminal, + WantReply = true, + EnvironmentVariable = environmentVariable, + Columns = columns, + Rows = rows, + PixelWidth = width, + PixelHeight = height, + TerminalMode = terminalMode, + }); - this.SessionSemaphore.Release(); + this._channelRequestResponse.WaitOne(); - this._channelOpenResponseWaitHandle.Set(); + return this._channelRequestSucces; + } + + public bool SendX11ForwardingRequest() + { + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient channel + //string "x11-req" + //boolean want reply + //boolean single connection + //string x11 authentication protocol + //string x11 authentication cookie + //uint32 x11 screen number + return false; + } + + public bool SendEnvironmentVariableRequest() + { + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient channel + //string "env" + //boolean want reply + //string variable name + //string variable value + return false; + } + + public bool SendShellRequest() + { + this._channelRequestResponse.Reset(); + + this.SendMessage(new ChannelRequestShellMessage + { + LocalChannelNumber = this.RemoteChannelNumber, + //RequestName = ChannelRequestNames.Shell, + WantReply = true, + }); + + this._channelRequestResponse.WaitOne(); + + return this._channelRequestSucces; + } + + public bool SendExecRequest(string command) + { + this._channelRequestResponse.Reset(); + + this.SendMessage(new ChannelRequestExecMessage + { + LocalChannelNumber = this.RemoteChannelNumber, + WantReply = true, + Command = command, + }); + + this._channelRequestResponse.WaitOne(); + + return this._channelRequestSucces; + } + + public bool SendSubsystemRequest(string subsystem) + { + this._channelRequestResponse.Reset(); + + this.SendMessage(new ChannelRequestSubsystemMessage + { + LocalChannelNumber = this.RemoteChannelNumber, + WantReply = true, + SubsystemName = subsystem, + }); + + this._channelRequestResponse.WaitOne(); + + return this._channelRequestSucces; + } + + public bool SendWindowChangeRequest() + { + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient channel + //string "window-change" + //boolean FALSE + //uint32 terminal width, columns + //uint32 terminal height, rows + //uint32 terminal width, pixels + //uint32 terminal height, pixels + return false; + } + + public bool SendLocalFlowRequest() + { + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient channel + //string "xon-xoff" + //boolean FALSE + //boolean client can do + return false; + } + + public bool SendSignalRequest() + { + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient channel + //string "signal" + //boolean FALSE + //string signal name (without the "SIG" prefix) + return false; + } + + public bool SendExitStatusRequest() + { + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient channel + //string "exit-status" + //boolean FALSE + //uint32 exit_status + return false; + } + + public bool SendExitSignalRequest() + { + //byte SSH_MSG_CHANNEL_REQUEST + //uint32 recipient channel + //string "exit-signal" + //boolean FALSE + //string signal name (without the "SIG" prefix) + //boolean core dumped + //string error message in ISO-10646 UTF-8 encoding + //string language tag [RFC3066] + return false; + } + + protected override void OnSuccess() + { + base.OnSuccess(); + this._channelRequestSucces = true; + this._channelRequestResponse.Set(); + } + + protected override void OnFailure() + { + base.OnFailure(); + this._channelRequestSucces = false; + this._channelRequestResponse.Set(); } /// diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs index 49018dc3..e0d468c2 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs @@ -23,11 +23,6 @@ namespace Renci.SshClient.Channels private AsyncCallback _callback; - public override ChannelTypes ChannelType - { - get { return ChannelTypes.Session; } - } - /// /// Gets or sets a value indicating whether this channel has error. /// @@ -81,13 +76,7 @@ namespace Renci.SshClient.Channels this.Open(); // Send channel command request - this.SendMessage(new ChannelRequestMessage - { - LocalChannelNumber = this.RemoteChannelNumber, - RequestName = ChannelRequestNames.Exec, - WantReply = false, - Command = command, - }); + this.SendExecRequest(command); return _asyncResult; } @@ -193,34 +182,28 @@ namespace Renci.SshClient.Channels /// The command. /// Name of the subsystem. /// The exit status. - protected override void OnRequest(ChannelRequestNames requestName, bool wantReply, string command, string subsystemName, uint exitStatus) + protected override void OnRequest(ChannelRequestMessage message) { - base.OnRequest(requestName, wantReply, command, subsystemName, exitStatus); + base.OnRequest(message); Message replyMessage = new ChannelFailureMessage() { LocalChannelNumber = this.LocalChannelNumber, }; - if (requestName == ChannelRequestNames.ExitStatus) + if (message.RequestName == ChannelRequestExitStatusMessage.REQUEST_NAME) { - this.ExitStatus = exitStatus; + ChannelRequestExitStatusMessage exitStatusMessage = message.OfType(); + + this.ExitStatus = exitStatusMessage.ExitStatus; replyMessage = new ChannelSuccessMessage() { LocalChannelNumber = this.LocalChannelNumber, }; } - else if (requestName == ChannelRequestNames.PseudoTerminal) - { - // TODO: Check if when this request is received what to do, I suspect we receive this request when no more channel sessions are available - } - else - { - throw new NotImplementedException(string.Format("Request name {0} is not implemented.", requestName)); - } - if (wantReply) + if (message.WantReply) { this.SendMessage(replyMessage); } diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionSftp.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionSftp.cs index 2dcbe925..f9252e4e 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionSftp.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionSftp.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; using System.Threading; using Renci.SshClient.Common; -using Renci.SshClient.Messages.Connection; using Renci.SshClient.Messages.Sftp; namespace Renci.SshClient.Channels @@ -30,11 +29,6 @@ namespace Renci.SshClient.Channels private ChannelAsyncResult _asyncResult; - public override ChannelTypes ChannelType - { - get { return ChannelTypes.Session; } - } - public ChannelSessionSftp() : base() { @@ -46,13 +40,7 @@ namespace Renci.SshClient.Channels base.Open(); // Send channel command request - this.SendMessage(new ChannelRequestMessage - { - LocalChannelNumber = this.RemoteChannelNumber, - RequestName = ChannelRequestNames.Subsystem, - WantReply = true, - SubsystemName = "sftp", - }); + this.SendSubsystemRequest("sftp"); this.WaitHandle(this._channelRequestSuccessWaitHandle); diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionShell.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionShell.cs new file mode 100644 index 00000000..4bcb9037 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionShell.cs @@ -0,0 +1,99 @@ +using System.IO; +using System.Linq; +using System.Threading; +using Renci.SshClient.Messages.Connection; + +namespace Renci.SshClient.Channels +{ + internal class ChannelSessionShell : ChannelSession + { + + private EventWaitHandle _success = new AutoResetEvent(false); + + /// + /// Holds channel data stream + /// + private Stream _channelData; + + /// + /// Holds channel extended data stream + /// + private Stream _channelExtendedData; + + public void Start(Stream output, Stream extendedOutput) + { + this.Open(); + + this._channelData = output; + this._channelExtendedData = extendedOutput; + + this.SendPseudoTerminalRequest("xterm", 80, 24, 640, 240, ""); + + _success.WaitOne(); + + this.SendShellRequest(); + } + + public void Stop() + { + // Close channel + this.Close(); + } + + protected override void OnSuccess() + { + base.OnSuccess(); + + _success.Set(); + } + + protected override void OnFailure() + { + base.OnFailure(); + } + + protected override void OnData(string data) + { + base.OnData(data); + + this._channelData.Write(data.GetSshBytes().ToArray(), 0, data.Length); + this._channelData.Flush(); + } + + protected override void OnExtendedData(string data, uint dataTypeCode) + { + base.OnExtendedData(data, dataTypeCode); + + // TODO: dataTypeCode is not handled + this._channelExtendedData.Write(data.GetSshBytes().ToArray(), 0, data.Length); + this._channelExtendedData.Flush(); + } + + public void Send(string data) + { + this.SendMessage(new ChannelDataMessage + { + LocalChannelNumber = this.RemoteChannelNumber, + Data = data, + }); + } + + public class ShellStream : MemoryStream + { + public ShellStream() + { + + } + + public override void Write(byte[] buffer, int offset, int count) + { + base.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + base.WriteByte(value); + } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Common/SshData.cs b/Renci.SshClient/Renci.SshClient/Common/SshData.cs index 10cc0e49..bf46e142 100644 --- a/Renci.SshClient/Renci.SshClient/Common/SshData.cs +++ b/Renci.SshClient/Renci.SshClient/Common/SshData.cs @@ -23,6 +23,8 @@ namespace Renci.SshClient.Common } } + private IEnumerable _loadedData; + public virtual IEnumerable GetBytes() { this._data = new List(); @@ -32,6 +34,14 @@ namespace Renci.SshClient.Common return this._data; } + internal T OfType() where T : SshData, new() + { + var result = new T(); + result.LoadBytes(this._loadedData); + result.LoadData(); + return result; + } + protected abstract void LoadData(); protected abstract void SaveData(); @@ -39,6 +49,7 @@ namespace Renci.SshClient.Common protected void LoadBytes(IEnumerable bytes) { this.ResetReader(); + this._loadedData = bytes.ToArray(); this._data = new List(bytes); } @@ -47,6 +58,17 @@ namespace Renci.SshClient.Common this._readerIndex = 1; // Set to 1 to skip first byte which specifies message type } + /// + /// Read all data + /// + /// + protected IEnumerable ReadBytes() + { + var result = this._data.Skip(this._readerIndex); + this._readerIndex = this._data.Count; + return result; + } + protected IEnumerable ReadBytes(int length) { var result = this._data.Skip(this._readerIndex).Take(length); diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestEnvironmentVariableMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestEnvironmentVariableMessage.cs new file mode 100644 index 00000000..ceb10fc6 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestEnvironmentVariableMessage.cs @@ -0,0 +1,30 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestEnvironmentVariableMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "env"; + + public string VariableName { get; set; } + + public string VariableValue { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.VariableName = this.ReadString(); + this.VariableValue = this.ReadString(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.VariableName); + this.Write(this.VariableValue); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExecMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExecMessage.cs new file mode 100644 index 00000000..51fe229b --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExecMessage.cs @@ -0,0 +1,26 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestExecMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "exec"; + + public string Command { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.Command = this.ReadString(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.Command); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitSignalMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitSignalMessage.cs new file mode 100644 index 00000000..af1ce2e9 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitSignalMessage.cs @@ -0,0 +1,38 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestExitSignalMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "exit-signal"; + + public string SignalName { get; set; } + + public bool CoreDumped { get; set; } + + public string ErrorMessage { get; set; } + + public string Language { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.SignalName = this.ReadString(); + this.CoreDumped = this.ReadBoolean(); + this.ErrorMessage = this.ReadString(); + this.Language = this.ReadString(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.SignalName); + this.Write(this.CoreDumped); + this.Write(this.ErrorMessage); + this.Write(this.Language); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitStatusMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitStatusMessage.cs new file mode 100644 index 00000000..a84f840e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestExitStatusMessage.cs @@ -0,0 +1,23 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestExitStatusMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "exit-status"; + + public uint ExitStatus { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.ExitStatus = this.ReadUInt32(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + base.SaveData(); + this.Write(this.ExitStatus); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs new file mode 100644 index 00000000..118923e7 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestMessage.cs @@ -0,0 +1,31 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelRequest; } + } + + public string RequestName { get; protected set; } + + public bool WantReply { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.RequestName = this.ReadString(); + this.WantReply = this.ReadBoolean(); + } + + protected override void SaveData() + { + base.SaveData(); + + this.Write(this.RequestName); + this.Write(this.WantReply); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestPseudoTerminalMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestPseudoTerminalMessage.cs new file mode 100644 index 00000000..587d1bc5 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestPseudoTerminalMessage.cs @@ -0,0 +1,44 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestPseudoTerminalMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "pty-req"; + + public string EnvironmentVariable { get; set; } + + public uint Columns { get; set; } + + public uint Rows { get; set; } + + public uint PixelWidth { get; set; } + + public uint PixelHeight { get; set; } + + public string TerminalMode { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.EnvironmentVariable = this.ReadString(); + this.Columns = this.ReadUInt32(); + this.Rows = this.ReadUInt32(); + this.PixelWidth = this.ReadUInt32(); + this.PixelHeight = this.ReadUInt32(); + this.TerminalMode = this.ReadString(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + base.SaveData(); + this.Write(this.EnvironmentVariable); + this.Write(this.Columns); + this.Write(this.Rows); + this.Write(this.Rows); + this.Write(this.PixelHeight); + this.Write(this.TerminalMode); + + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestShellMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestShellMessage.cs new file mode 100644 index 00000000..2eeb4a72 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestShellMessage.cs @@ -0,0 +1,19 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestShellMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "shell"; + + protected override void LoadData() + { + base.LoadData(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + base.SaveData(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSignalMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSignalMessage.cs new file mode 100644 index 00000000..dcee7d7e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSignalMessage.cs @@ -0,0 +1,26 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestSignalMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "signal"; + + public string SignalName { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.SignalName = this.ReadString(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.SignalName); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSubsystemMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSubsystemMessage.cs new file mode 100644 index 00000000..34ddabe4 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestSubsystemMessage.cs @@ -0,0 +1,26 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestSubsystemMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "subsystem"; + + public string SubsystemName { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.SubsystemName = this.ReadString(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.SubsystemName); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestWindowChangeMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestWindowChangeMessage.cs new file mode 100644 index 00000000..06aac41b --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestWindowChangeMessage.cs @@ -0,0 +1,38 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestWindowChangeMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "window-change"; + + public uint Columns { get; set; } + + public uint Rows { get; set; } + + public uint Width { get; set; } + + public uint Height { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.Columns = this.ReadUInt32(); + this.Rows = this.ReadUInt32(); + this.Width = this.ReadUInt32(); + this.Height = this.ReadUInt32(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.Columns); + this.Write(this.Rows); + this.Write(this.Width); + this.Write(this.Height); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestX11ForwardingMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestX11ForwardingMessage.cs new file mode 100644 index 00000000..7e34813e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestX11ForwardingMessage.cs @@ -0,0 +1,38 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestX11ForwardingMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "x11-req"; + + public bool IsSingleConnection { get; set; } + + public string AuthenticationProtocol { get; set; } + + public string AuthenticationCookie { get; set; } + + public uint ScreenNumber { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.IsSingleConnection = this.ReadBoolean(); + this.AuthenticationProtocol = this.ReadString(); + this.AuthenticationCookie = this.ReadString(); + this.ScreenNumber = this.ReadUInt32(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.IsSingleConnection); + this.Write(this.AuthenticationProtocol); + this.Write(this.AuthenticationCookie); + this.Write(this.ScreenNumber); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestXonXoffMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestXonXoffMessage.cs new file mode 100644 index 00000000..ad1a1c04 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequest/ChannelRequestXonXoffMessage.cs @@ -0,0 +1,26 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestXonXoffMessage : ChannelRequestMessage + { + public const string REQUEST_NAME = "xon-xoff"; + + public bool ClientCanDo { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + this.ClientCanDo = this.ReadBoolean(); + } + + protected override void SaveData() + { + this.RequestName = REQUEST_NAME; + + base.SaveData(); + + this.Write(this.ClientCanDo); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs deleted file mode 100644 index eeac5587..00000000 --- a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs +++ /dev/null @@ -1,106 +0,0 @@ - -namespace Renci.SshClient.Messages.Connection -{ - internal class ChannelRequestMessage : ChannelMessage - { - public override MessageTypes MessageType - { - get { return MessageTypes.ChannelRequest; } - } - - public ChannelRequestNames RequestName { get; set; } - - public bool WantReply { get; set; } - - public string Command { get; set; } - - public string SubsystemName { get; set; } - - public uint ExitStatus { get; set; } - - protected override void LoadData() - { - base.LoadData(); - - var requestName = this.ReadString(); - switch (requestName) - { - case "pty-req": - break; - case "x11-req": - break; - case "env": - break; - case "shell": - break; - case "exec": - this.RequestName = ChannelRequestNames.Exec; - this.WantReply = this.ReadBoolean(); - this.Command = this.ReadString(); - break; - case "subsystem": - this.RequestName = ChannelRequestNames.Subsystem; - this.WantReply = this.ReadBoolean(); - this.SubsystemName = this.ReadString(); - break; - case "window-change": - break; - case "xon-xoff": - break; - case "signal": - break; - case "exit-status": - this.RequestName = ChannelRequestNames.ExitStatus; - this.WantReply = this.ReadBoolean(); - this.ExitStatus = this.ReadUInt32(); - break; - case "exit-signal": - break; - default: - break; - } - } - - protected override void SaveData() - { - base.SaveData(); - - switch (this.RequestName) - { - case ChannelRequestNames.PseudoTerminal: - break; - case ChannelRequestNames.X11Forwarding: - break; - case ChannelRequestNames.EnvironmentVariable: - break; - case ChannelRequestNames.Shell: - break; - case ChannelRequestNames.Exec: - this.Write("exec"); - this.Write(this.WantReply); - this.Write(this.Command); - break; - case ChannelRequestNames.Subsystem: - this.Write("subsystem"); - this.Write(this.WantReply); - this.Write(this.SubsystemName); - break; - case ChannelRequestNames.WindowChange: - break; - case ChannelRequestNames.XonXoff: - break; - case ChannelRequestNames.Signal: - break; - case ChannelRequestNames.ExitStatus: - this.Write("exit-status"); - this.Write(this.WantReply); - this.Write(this.ExitStatus); - break; - case ChannelRequestNames.ExitSignal: - break; - default: - break; - } - } - } -} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestNames.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestNames.cs deleted file mode 100644 index 8ad83d0d..00000000 --- a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestNames.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Renci.SshClient.Messages.Connection -{ - internal enum ChannelRequestNames - { - /// - /// pty-req - /// - PseudoTerminal, - /// - /// x11-req - /// - X11Forwarding, - /// - /// env - /// - EnvironmentVariable, - /// - /// shell - /// - Shell, - /// - /// exec - /// - Exec, - /// - /// subsystem - /// - Subsystem, - /// - /// window-change - /// - WindowChange, - /// - /// xon-xoff - /// - XonXoff, - /// - /// signal - /// - Signal, - /// - /// exit-status - /// - ExitStatus, - /// - /// exit-signal - /// - ExitSignal - } -} diff --git a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj index 87119f09..b89bc333 100644 --- a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj +++ b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj @@ -60,10 +60,21 @@ + + + + + + + + + + + + - @@ -136,7 +147,6 @@ - @@ -157,7 +167,7 @@ - + diff --git a/Renci.SshClient/Renci.SshClient/Session.cs b/Renci.SshClient/Renci.SshClient/Session.cs index 8c610ab8..f06442e0 100644 --- a/Renci.SshClient/Renci.SshClient/Session.cs +++ b/Renci.SshClient/Renci.SshClient/Session.cs @@ -442,12 +442,19 @@ namespace Renci.SshClient // Get server SSH version var version = versionMatch.Result("${protoversion}"); - if (!version.Equals("2.0")) + if (!(version.Equals("2.0") || version.Equals("1.99"))) { throw new SshException(string.Format("Server version '{0}' is not supported.", version), DisconnectReasons.ProtocolVersionNotSupported); } - this.Write(Encoding.ASCII.GetBytes(string.Format("{0}\n", this.ClientVersion))); + if (version.Equals("1.99")) + { + this.Write(Encoding.ASCII.GetBytes(string.Format("{0}", this.ClientVersion))); + } + else + { + this.Write(Encoding.ASCII.GetBytes(string.Format("{0}\n", this.ClientVersion))); + } // Register Transport response messages this.RegisterMessageType(MessageTypes.Disconnect); diff --git a/Renci.SshClient/Renci.SshClient/Shell.cs b/Renci.SshClient/Renci.SshClient/Shell.cs index 268f98dd..d562402d 100644 --- a/Renci.SshClient/Renci.SshClient/Shell.cs +++ b/Renci.SshClient/Renci.SshClient/Shell.cs @@ -1,7 +1,5 @@  -using System; using System.IO; -using System.Text; using Renci.SshClient.Channels; namespace Renci.SshClient { @@ -9,48 +7,27 @@ namespace Renci.SshClient { private readonly Session _session; + private ChannelSessionShell _channel; + internal Shell(Session session) { this._session = session; } - public string Execute(string command) + public void Connect(Stream output, Stream extendedOutput) { - return this.Execute(command, null); + this._channel = this._session.CreateChannel(); + this._channel.Start(output, extendedOutput); } - public string Execute(string command, Stream extended) + public void Send(string data) { - using (MemoryStream resultStream = new MemoryStream()) - { - this.Execute(command, resultStream, extended); - - return Encoding.ASCII.GetString(resultStream.ToArray()); - } + this._channel.Send(data); } - public void Execute(string command, Stream output, Stream extended) + public void Disconnect() { - this.EndExecute(this.BeginExecute(command, output, extended, null, null)); - } - - public IAsyncResult BeginExecute(string command, Stream output, AsyncCallback callback, object state) - { - return this.BeginExecute(command, output, null, callback, state); - } - - public IAsyncResult BeginExecute(string command, Stream output, Stream extendedOutput, AsyncCallback callback, object state) - { - var channel = this._session.CreateChannel(); - - return channel.BeginExecute(command, output, extendedOutput, callback, state); - } - - public void EndExecute(IAsyncResult asynchResult) - { - ChannelAsyncResult channelAsyncResult = asynchResult as ChannelAsyncResult; - - channelAsyncResult.Channel.EndExecute(asynchResult); + this._channel.Close(); } } } diff --git a/Renci.SshClient/Renci.SshClient/SshClient.cs b/Renci.SshClient/Renci.SshClient/SshClient.cs index 4a0aabe7..58bd77b8 100644 --- a/Renci.SshClient/Renci.SshClient/SshClient.cs +++ b/Renci.SshClient/Renci.SshClient/SshClient.cs @@ -139,6 +139,11 @@ namespace Renci.SshClient return cmd; } + public Shell CreateShell() + { + return new Shell(this._session); + } + public void RemoveForwardedPort(ForwardedPort port) { this._forwardedPorts.Remove(port);