using System; using System.Threading; using Renci.SshNet.Common; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Connection; using System.Globalization; namespace Renci.SshNet.Channels { /// /// Represents base class for SSH channel implementations. /// internal abstract class Channel : IDisposable { private EventWaitHandle _channelClosedWaitHandle = new AutoResetEvent(false); private EventWaitHandle _channelWindowAdjustWaitHandle = new AutoResetEvent(false); private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false); private EventWaitHandle _disconnectedWaitHandle = new ManualResetEvent(false); private uint _initialWindowSize = 0x100000; private uint _maximumPacketSize = 0x8000; private Session _session; /// /// Gets the type of the channel. /// /// /// The type of the channel. /// public abstract ChannelTypes ChannelType { get; } /// /// Gets the local channel number. /// public uint LocalChannelNumber { get; private set; } /// /// Gets the remote channel number assigned by the server. /// public uint RemoteChannelNumber { get; private set; } /// /// Gets the size of the local window. /// /// /// The size of the local window. /// public uint LocalWindowSize { get; private set; } /// /// Gets or sets the size of the server window. /// /// /// The size of the server window. /// public uint ServerWindowSize { get; protected set; } /// /// Gets the size of the packet. /// /// /// The size of the packet. /// public uint PacketSize { get; private set; } /// /// Gets a value indicating whether this channel is open. /// /// /// true if this channel is open; otherwise, false. /// public bool IsOpen { get; private set; } #region Message events /// /// Occurs when message received /// public event EventHandler OpenFailed; /// /// Occurs when message received /// public event EventHandler DataReceived; /// /// Occurs when message received /// public event EventHandler ExtendedDataReceived; /// /// Occurs when message received /// public event EventHandler EndOfData; /// /// Occurs when message received /// public event EventHandler Closed; /// /// Occurs when message received /// public event EventHandler RequestReceived; /// /// Occurs when message received /// public event EventHandler RequestSuccessed; /// /// Occurs when message received /// public event EventHandler RequestFailed; #endregion /// /// Gets a value indicating whether the session is connected. /// /// /// true if the session is connected; otherwise, false. /// protected bool IsConnected { get { return this._session.IsConnected; } } /// /// Gets the connection info. /// /// The connection info. protected ConnectionInfo ConnectionInfo { get { return this._session.ConnectionInfo; } } /// /// Gets the session semaphore to control number of session channels /// /// The session semaphore. protected SemaphoreLight SessionSemaphore { get { return this._session.SessionSemaphore; } } /// /// Initializes a new instance of the class. /// internal Channel() { } /// /// Initializes the channel. /// /// The session. /// The server channel number. /// Size of the window. /// Size of the packet. internal virtual void Initialize(Session session, uint serverChannelNumber, uint windowSize, uint packetSize) { this._initialWindowSize = windowSize; this._maximumPacketSize = Math.Max(packetSize, 0x8000); // Ensure minimum maximum packet size of 0x8000 bytes this._session = session; this.LocalWindowSize = this._initialWindowSize; // Initial window size this.PacketSize = this._maximumPacketSize; // Maximum packet size this.LocalChannelNumber = session.NextChannelNumber; this.RemoteChannelNumber = serverChannelNumber; this._session.ChannelOpenReceived += OnChannelOpen; this._session.ChannelOpenConfirmationReceived += OnChannelOpenConfirmation; this._session.ChannelOpenFailureReceived += OnChannelOpenFailure; this._session.ChannelWindowAdjustReceived += OnChannelWindowAdjust; this._session.ChannelDataReceived += OnChannelData; this._session.ChannelExtendedDataReceived += OnChannelExtendedData; this._session.ChannelEofReceived += OnChannelEof; this._session.ChannelCloseReceived += OnChannelClose; this._session.ChannelRequestReceived += OnChannelRequest; this._session.ChannelSuccessReceived += OnChannelSuccess; this._session.ChannelFailureReceived += OnChannelFailure; this._session.ErrorOccured += Session_ErrorOccured; this._session.Disconnected += Session_Disconnected; } /// /// Sends the SSH_MSG_CHANNEL_EOF message. /// internal void SendEof() { // Send EOF message first when channel need to be closed this.SendMessage(new ChannelEofMessage(this.RemoteChannelNumber)); } internal void SendData(byte[] buffer) { this.SendMessage(new ChannelDataMessage(this.RemoteChannelNumber, buffer)); } /// /// Closes the channel. /// public virtual void Close() { // Send message to close the channel on the server this.SendMessage(new ChannelCloseMessage(this.RemoteChannelNumber)); // Wait for channel to be closed this._session.WaitHandle(this._channelClosedWaitHandle); } #region Channel virtual methods /// /// Called when channel need to be open on the client. /// /// Channel open information. protected virtual void OnOpen(ChannelOpenInfo info) { } /// /// Called when channel is opened by the server. /// /// The remote channel number. /// Initial size of the window. /// Maximum size of the packet. protected virtual void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize) { this.RemoteChannelNumber = remoteChannelNumber; this.ServerWindowSize = initialWindowSize; this.PacketSize = maximumPacketSize; // Chanel consider to be open when confirmation message was received this.IsOpen = true; } /// /// Called when channel failed to open. /// /// The reason code. /// The description. /// The language. protected virtual void OnOpenFailure(uint reasonCode, string description, string language) { if (this.OpenFailed != null) { this.OpenFailed(this, new ChannelOpenFailedEventArgs(this.LocalChannelNumber, reasonCode, description, language)); } } /// /// Called when channel window need to be adjust. /// /// The bytes to add. protected virtual void OnWindowAdjust(uint bytesToAdd) { this.ServerWindowSize += bytesToAdd; } /// /// Called when channel data is received. /// /// The data. protected virtual void OnData(byte[] data) { this.AdjustDataWindow(data); if (this.DataReceived != null) { this.DataReceived(this, new ChannelDataEventArgs(this.LocalChannelNumber, data)); } } /// /// Called when channel extended data is received. /// /// The data. /// The data type code. protected virtual void OnExtendedData(byte[] data, uint dataTypeCode) { this.AdjustDataWindow(data); if (this.ExtendedDataReceived != null) { this.ExtendedDataReceived(this, new ChannelDataEventArgs(this.LocalChannelNumber, data, dataTypeCode)); } } /// /// Called when channel has no more data to receive. /// protected virtual void OnEof() { if (this.EndOfData != null) { this.EndOfData(this, new ChannelEventArgs(this.LocalChannelNumber)); } } /// /// Called when channel is closed by the server. /// protected virtual void OnClose() { // No more channel messages are allowed after Close message received this._session.ChannelOpenReceived -= OnChannelOpen; this._session.ChannelOpenConfirmationReceived -= OnChannelOpenConfirmation; this._session.ChannelOpenFailureReceived -= OnChannelOpenFailure; this._session.ChannelWindowAdjustReceived -= OnChannelWindowAdjust; this._session.ChannelDataReceived -= OnChannelData; this._session.ChannelExtendedDataReceived -= OnChannelExtendedData; this._session.ChannelEofReceived -= OnChannelEof; this._session.ChannelCloseReceived -= OnChannelClose; this._session.ChannelRequestReceived -= OnChannelRequest; this._session.ChannelSuccessReceived -= OnChannelSuccess; this._session.ChannelFailureReceived -= OnChannelFailure; this._session.ErrorOccured -= Session_ErrorOccured; this._session.Disconnected -= Session_Disconnected; // Send close message to channel to confirm channel closing this.SendMessage(new ChannelCloseMessage(this.RemoteChannelNumber)); if (this.Closed != null) { this.Closed(this, new ChannelEventArgs(this.LocalChannelNumber)); } } /// /// Called when channel request received. /// /// Channel request information. protected virtual void OnRequest(RequestInfo info) { if (this.RequestReceived != null) { this.RequestReceived(this, new ChannelRequestEventArgs(info)); } } /// /// Called when channel request was successful /// protected virtual void OnSuccess() { if (this.RequestSuccessed != null) { this.RequestSuccessed(this, new ChannelEventArgs(this.LocalChannelNumber)); } } /// /// Called when channel request failed. /// protected virtual void OnFailure() { if (this.RequestFailed != null) { this.RequestFailed(this, new ChannelEventArgs(this.LocalChannelNumber)); } } #endregion /// /// Sends SSH message to the server. /// /// The message. protected void SendMessage(Message message) { // Send channel messages only while channel is open if (!this.IsOpen) return; this._session.SendMessage(message); } protected void SendMessage(ChannelOpenConfirmationMessage message) { // No need to check whether channel is open when trying to open a channel this._session.SendMessage(message); // Chanel consider to be open when confirmation message is sent this.IsOpen = true; } /// /// Send message to open a channel. /// /// Message to send protected void SendMessage(ChannelOpenMessage message) { // No need to check whether channel is open when trying to open a channel this._session.SendMessage(message); } /// /// Sends close channel message to the server /// /// Message to send. protected void SendMessage(ChannelCloseMessage message) { // Send channel messages only while channel is open if (!this.IsOpen) return; this._session.SendMessage(message); // When channel close message is sent channel considred to be closed this.IsOpen = false; } /// /// Sends channel data message to the servers. /// /// This method takes care of managing the window size. /// Channel data message. protected void SendMessage(ChannelDataMessage message) { // Send channel messages only while channel is open if (!this.IsOpen) return; if (this.ServerWindowSize < 1) { // Wait for window to be adjust this._session.WaitHandle(this._channelWindowAdjustWaitHandle); } this.ServerWindowSize -= (uint)message.Data.Length; this._session.SendMessage(message); } /// /// Sends channel extended data message to the servers. /// /// This method takes care of managing the window size. /// Channel data message. protected void SendMessage(ChannelExtendedDataMessage message) { // Send channel messages only while channel is open if (!this.IsOpen) return; if (this.ServerWindowSize < 1) { // Wait for window to be adjust this._session.WaitHandle(this._channelWindowAdjustWaitHandle); } this.ServerWindowSize -= (uint)message.Data.Length; this._session.SendMessage(message); } /// /// Waits for the handle to be signaled or for an error to occurs. /// /// The wait handle. protected void WaitHandle(WaitHandle waitHandle) { this._session.WaitHandle(waitHandle); } private void Session_Disconnected(object sender, EventArgs e) { this._disconnectedWaitHandle.Set(); } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { this._errorOccuredWaitHandle.Set(); } #region Channel message event handlers private void OnChannelOpen(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnOpen(e.Message.Info); } } private void OnChannelOpenConfirmation(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnOpenConfirmation(e.Message.RemoteChannelNumber, e.Message.InitialWindowSize, e.Message.MaximumPacketSize); } } private void OnChannelOpenFailure(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnOpenFailure(e.Message.ReasonCode, e.Message.Description, e.Message.Language); } } private void OnChannelWindowAdjust(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnWindowAdjust(e.Message.BytesToAdd); this._channelWindowAdjustWaitHandle.Set(); } } private void OnChannelData(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnData(e.Message.Data); } } private void OnChannelExtendedData(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnExtendedData(e.Message.Data, e.Message.DataTypeCode); } } private void OnChannelEof(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnEof(); } } private void OnChannelClose(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnClose(); this._channelClosedWaitHandle.Set(); } } private void OnChannelRequest(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { if (this._session.ConnectionInfo.ChannelRequests.ContainsKey(e.Message.RequestName)) { // Get request specific class RequestInfo requestInfo = this._session.ConnectionInfo.ChannelRequests[e.Message.RequestName]; // Load request specific data requestInfo.Load(e.Message.RequestData); // Raise request specific event this.OnRequest(requestInfo); } else { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Request '{0}' is not supported.", e.Message.RequestName)); } } } private void OnChannelSuccess(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnSuccess(); } } private void OnChannelFailure(object sender, MessageEventArgs e) { if (e.Message.LocalChannelNumber == this.LocalChannelNumber) { this.OnFailure(); } } #endregion private void AdjustDataWindow(byte[] messageData) { this.LocalWindowSize -= (uint)messageData.Length; // Adjust window if window size is too low if (this.LocalWindowSize < this.PacketSize) { this.SendMessage(new ChannelWindowAdjustMessage(this.RemoteChannelNumber, this._initialWindowSize - this.LocalWindowSize)); this.LocalWindowSize = this._initialWindowSize; } } #region IDisposable Members private bool _isDisposed = false; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!this._isDisposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { // Dispose managed resources. if (this._channelClosedWaitHandle != null) { this._channelClosedWaitHandle.Dispose(); this._channelClosedWaitHandle = null; } if (this._channelWindowAdjustWaitHandle != null) { this._channelWindowAdjustWaitHandle.Dispose(); this._channelWindowAdjustWaitHandle = null; } if (this._errorOccuredWaitHandle != null) { this._errorOccuredWaitHandle.Dispose(); this._errorOccuredWaitHandle = null; } if (this._disconnectedWaitHandle != null) { this._disconnectedWaitHandle.Dispose(); this._disconnectedWaitHandle = null; } } // Ensure that all events are detached from current instance this._session.ChannelOpenReceived -= OnChannelOpen; this._session.ChannelOpenConfirmationReceived -= OnChannelOpenConfirmation; this._session.ChannelOpenFailureReceived -= OnChannelOpenFailure; this._session.ChannelWindowAdjustReceived -= OnChannelWindowAdjust; this._session.ChannelDataReceived -= OnChannelData; this._session.ChannelExtendedDataReceived -= OnChannelExtendedData; this._session.ChannelEofReceived -= OnChannelEof; this._session.ChannelCloseReceived -= OnChannelClose; this._session.ChannelRequestReceived -= OnChannelRequest; this._session.ChannelSuccessReceived -= OnChannelSuccess; this._session.ChannelFailureReceived -= OnChannelFailure; this._session.ErrorOccured -= Session_ErrorOccured; this._session.Disconnected -= Session_Disconnected; // Note disposing has been done. _isDisposed = true; } } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// ~Channel() { // Do not re-create Dispose clean-up code here. // Calling Dispose(false) is optimal in terms of // readability and maintainability. this.Dispose(false); } #endregion } }