using System; using System.Globalization; using System.Text; using System.Threading; using Renci.SshNet.Channels; using Renci.SshNet.Common; namespace Renci.SshNet { /// /// Base class for SSH subsystem implementations /// internal abstract class SubsystemSession : ISubsystemSession { private ISession _session; private readonly string _subsystemName; private IChannelSession _channel; private Exception _exception; private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false); private EventWaitHandle _sessionDisconnectedWaitHandle = new ManualResetEvent(false); private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false); /// /// Specifies a timeout to wait for operation to complete /// protected TimeSpan OperationTimeout { get; private set; } /// /// Occurs when an error occurred. /// public event EventHandler ErrorOccurred; /// /// Occurs when the server has disconnected from the session. /// public event EventHandler Disconnected; /// /// Gets the channel associated with this session. /// /// /// The channel associated with this session. /// internal IChannelSession Channel { get { EnsureNotDisposed(); return _channel; } } /// /// Gets a value indicating whether this session is open. /// /// /// true if this session is open; otherwise, false. /// public bool IsOpen { get { return _channel != null && _channel.IsOpen; } } /// /// Gets the character encoding to use. /// protected Encoding Encoding { get; private set; } /// /// Initializes a new instance of the SubsystemSession class. /// /// The session. /// Name of the subsystem. /// The operation timeout. /// The character encoding to use. /// or or is null. protected SubsystemSession(ISession session, string subsystemName, TimeSpan operationTimeout, Encoding encoding) { if (session == null) throw new ArgumentNullException("session"); if (subsystemName == null) throw new ArgumentNullException("subsystemName"); if (encoding == null) throw new ArgumentNullException("encoding"); _session = session; _subsystemName = subsystemName; OperationTimeout = operationTimeout; Encoding = encoding; } /// /// Connects the subsystem using a new SSH channel session. /// /// The session is already connected. /// The method was called after the session was disposed. public void Connect() { EnsureNotDisposed(); if (IsOpen) throw new InvalidOperationException("The session is already connected."); // reset waithandles in case we're reconnecting _errorOccuredWaitHandle.Reset(); _sessionDisconnectedWaitHandle.Reset(); _sessionDisconnectedWaitHandle.Reset(); _channelClosedWaitHandle.Reset(); _session.ErrorOccured += Session_ErrorOccured; _session.Disconnected += Session_Disconnected; _channel = _session.CreateChannelSession(); _channel.DataReceived += Channel_DataReceived; _channel.Exception += Channel_Exception; _channel.Closed += Channel_Closed; _channel.Open(); _channel.SendSubsystemRequest(_subsystemName); OnChannelOpen(); } /// /// Disconnects the subsystem channel. /// public void Disconnect() { UnsubscribeFromSessionEvents(_session); var channel = _channel; if (channel != null) { channel.DataReceived -= Channel_DataReceived; channel.Exception -= Channel_Exception; channel.Closed -= Channel_Closed; channel.Close(); channel.Dispose(); _channel = null; } } /// /// Sends data to the subsystem. /// /// The data to be sent. public void SendData(byte[] data) { EnsureNotDisposed(); EnsureSessionIsOpen(); _channel.SendData(data); } /// /// Called when channel is open. /// protected abstract void OnChannelOpen(); /// /// Called when data is received. /// /// The data. protected abstract void OnDataReceived(byte[] data); /// /// Raises the error. /// /// The error. protected void RaiseError(Exception error) { _exception = error; var errorOccuredWaitHandle = _errorOccuredWaitHandle; if (errorOccuredWaitHandle != null) errorOccuredWaitHandle.Set(); SignalErrorOccurred(error); } private void Channel_DataReceived(object sender, ChannelDataEventArgs e) { try { OnDataReceived(e.Data); } catch (Exception ex) { RaiseError(ex); } } private void Channel_Exception(object sender, ExceptionEventArgs e) { RaiseError(e.Exception); } private void Channel_Closed(object sender, ChannelEventArgs e) { var channelClosedWaitHandle = _channelClosedWaitHandle; if (channelClosedWaitHandle != null) channelClosedWaitHandle.Set(); } /// /// Waits a specified time for a given to get signaled. /// /// The handle to wait for. /// The time to wait for to get signaled. /// The connection was closed by the server. /// The channel was closed. /// The handle did not get signaled within the specified . public void WaitOnHandle(WaitHandle waitHandle, TimeSpan operationTimeout) { var waitHandles = new[] { _errorOccuredWaitHandle, _sessionDisconnectedWaitHandle, _channelClosedWaitHandle, waitHandle }; switch (WaitHandle.WaitAny(waitHandles, operationTimeout)) { case 0: throw _exception; case 1: throw new SshException("Connection was closed by the server."); case 2: throw new SshException("Channel was closed."); case WaitHandle.WaitTimeout: throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Operation has timed out.")); } } private void Session_Disconnected(object sender, EventArgs e) { var sessionDisconnectedWaitHandle = _sessionDisconnectedWaitHandle; if (sessionDisconnectedWaitHandle != null) sessionDisconnectedWaitHandle.Set(); SignalDisconnected(); } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { RaiseError(e.Exception); } private void SignalErrorOccurred(Exception error) { var errorOccurred = ErrorOccurred; if (errorOccurred != null) { errorOccurred(this, new ExceptionEventArgs(error)); } } private void SignalDisconnected() { var disconnected = Disconnected; if (disconnected != null) { disconnected(this, new EventArgs()); } } private void EnsureSessionIsOpen() { if (!IsOpen) throw new InvalidOperationException("The session is not open."); } /// /// Unsubscribes the current from session events. /// /// The session. /// /// Does nothing when is null. /// private void UnsubscribeFromSessionEvents(ISession session) { if (session == null) return; session.Disconnected -= Session_Disconnected; session.ErrorOccured -= Session_ErrorOccured; } #region IDisposable Members private bool _isDisposed; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { 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) { if (_isDisposed) return; if (disposing) { Disconnect(); _session = null; var errorOccuredWaitHandle = _errorOccuredWaitHandle; if (errorOccuredWaitHandle != null) { errorOccuredWaitHandle.Dispose(); _errorOccuredWaitHandle = null; } var sessionDisconnectedWaitHandle = _sessionDisconnectedWaitHandle; if (sessionDisconnectedWaitHandle != null) { sessionDisconnectedWaitHandle.Dispose(); _sessionDisconnectedWaitHandle = null; } var channelClosedWaitHandle = _channelClosedWaitHandle; if (channelClosedWaitHandle != null) { channelClosedWaitHandle.Dispose(); _channelClosedWaitHandle = null; } _isDisposed = true; } } /// /// Finalizes an instance of the class. /// ~SubsystemSession() { Dispose(false); } private void EnsureNotDisposed() { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); } #endregion } }