using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; using System.Text; using Renci.SshNet.Common; namespace Renci.SshNet { /// /// Provides client connection to SSH server. /// public class SshClient : BaseClient { /// /// Holds the list of forwarded ports. /// private readonly List _forwardedPorts; /// /// Holds a value indicating whether the current instance is disposed. /// /// /// true if the current instance is disposed; otherwise, false. /// private bool _isDisposed; private Stream _inputStream; /// /// Gets the list of forwarded ports. /// public IEnumerable ForwardedPorts { get { return _forwardedPorts.AsReadOnly(); } } /// /// Initializes a new instance of the class. /// /// The connection info. /// /// /// /// /// /// /// is null. public SshClient(ConnectionInfo connectionInfo) : this(connectionInfo, ownsConnectionInfo: false) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Connection port. /// Authentication username. /// Authentication password. /// is null. /// is invalid, or is null or contains only whitespace characters. /// is not within and . [SuppressMessage("Microsoft.Reliability", "C2A000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SshClient(string host, int port, string username, string password) : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Authentication username. /// Authentication password. /// /// /// /// is null. /// is invalid, or is null or contains only whitespace characters. public SshClient(string host, string username, string password) : this(host, ConnectionInfo.DefaultPort, username, password) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Connection port. /// Authentication username. /// Authentication private key file(s) . /// /// /// /// /// is null. /// is invalid, -or- is null or contains only whitespace characters. /// is not within and . [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SshClient(string host, int port, string username, params IPrivateKeySource[] keyFiles) : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Authentication username. /// Authentication private key file(s) . /// /// /// /// /// is null. /// is invalid, -or- is null or contains only whitespace characters. public SshClient(string host, string username, params IPrivateKeySource[] keyFiles) : this(host, ConnectionInfo.DefaultPort, username, keyFiles) { } /// /// Initializes a new instance of the class. /// /// The connection info. /// Specified whether this instance owns the connection info. /// is null. /// /// If is true, then the /// connection info will be disposed when this instance is disposed. /// private SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo) : this(connectionInfo, ownsConnectionInfo, new ServiceFactory()) { } /// /// Initializes a new instance of the class. /// /// The connection info. /// Specified whether this instance owns the connection info. /// The factory to use for creating new services. /// is null. /// is null. /// /// If is true, then the /// connection info will be disposed when this instance is disposed. /// internal SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) : base(connectionInfo, ownsConnectionInfo, serviceFactory) { _forwardedPorts = new List(); } /// /// Called when client is disconnecting from the server. /// protected override void OnDisconnecting() { base.OnDisconnecting(); foreach (var port in _forwardedPorts) { port.Stop(); } } /// /// Adds the forwarded port. /// /// The port. /// /// /// /// /// Forwarded port is already added to a different client. /// is null. /// Client is not connected. public void AddForwardedPort(ForwardedPort port) { if (port is null) { throw new ArgumentNullException(nameof(port)); } EnsureSessionIsOpen(); AttachForwardedPort(port); _forwardedPorts.Add(port); } /// /// Stops and removes the forwarded port from the list. /// /// Forwarded port. /// is null. public void RemoveForwardedPort(ForwardedPort port) { if (port is null) { throw new ArgumentNullException(nameof(port)); } // Stop port forwarding before removing it port.Stop(); DetachForwardedPort(port); _ = _forwardedPorts.Remove(port); } private void AttachForwardedPort(ForwardedPort port) { if (port.Session != null && port.Session != Session) { throw new InvalidOperationException("Forwarded port is already added to a different client."); } port.Session = Session; } private static void DetachForwardedPort(ForwardedPort port) { port.Session = null; } /// /// Creates the command to be executed. /// /// The command text. /// object. /// Client is not connected. public SshCommand CreateCommand(string commandText) { return CreateCommand(commandText, ConnectionInfo.Encoding); } /// /// Creates the command to be executed with specified encoding. /// /// The command text. /// The encoding to use for results. /// object which uses specified encoding. /// This method will change current default encoding. /// Client is not connected. /// or is null. public SshCommand CreateCommand(string commandText, Encoding encoding) { EnsureSessionIsOpen(); ConnectionInfo.Encoding = encoding; return new SshCommand(Session, commandText, encoding); } /// /// Creates and executes the command. /// /// The command text. /// Returns an instance of with execution results. /// This method internally uses asynchronous calls. /// /// /// /// /// CommandText property is empty. /// Invalid Operation - An existing channel was used to execute this command. /// Asynchronous operation is already in progress. /// Client is not connected. /// is null. public SshCommand RunCommand(string commandText) { var cmd = CreateCommand(commandText); _ = cmd.Execute(); return cmd; } /// /// Creates the shell. /// /// The input. /// The output. /// The extended output. /// Name of the terminal. /// The columns. /// The rows. /// The width. /// The height. /// The terminal mode. /// Size of the internal read buffer. /// /// Returns a representation of a object. /// /// Client is not connected. public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes, int bufferSize) { EnsureSessionIsOpen(); return new Shell(Session, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, bufferSize); } /// /// Creates the shell. /// /// The input. /// The output. /// The extended output. /// Name of the terminal. /// The columns. /// The rows. /// The width. /// The height. /// The terminal mode. /// /// Returns a representation of a object. /// /// Client is not connected. public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes) { return CreateShell(input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, 1024); } /// /// Creates the shell. /// /// The input. /// The output. /// The extended output. /// /// Returns a representation of a object. /// /// Client is not connected. public Shell CreateShell(Stream input, Stream output, Stream extendedOutput) { return CreateShell(input, output, extendedOutput, string.Empty, 0, 0, 0, 0, terminalModes: null, 1024); } /// /// Creates the shell. /// /// The encoding to use to send the input. /// The input. /// The output. /// The extended output. /// Name of the terminal. /// The columns. /// The rows. /// The width. /// The height. /// The terminal mode. /// Size of the internal read buffer. /// /// Returns a representation of a object. /// /// Client is not connected. public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes, int bufferSize) { // TODO let shell dispose of input stream when we own the stream! _inputStream = new MemoryStream(); var writer = new StreamWriter(_inputStream, encoding); writer.Write(input); writer.Flush(); _ = _inputStream.Seek(0, SeekOrigin.Begin); return CreateShell(_inputStream, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, bufferSize); } /// /// Creates the shell. /// /// The encoding. /// The input. /// The output. /// The extended output. /// Name of the terminal. /// The columns. /// The rows. /// The width. /// The height. /// The terminal modes. /// /// Returns a representation of a object. /// /// Client is not connected. public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes) { return CreateShell(encoding, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, 1024); } /// /// Creates the shell. /// /// The encoding. /// The input. /// The output. /// The extended output. /// /// Returns a representation of a object. /// /// Client is not connected. public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput) { return CreateShell(encoding, input, output, extendedOutput, string.Empty, 0, 0, 0, 0, terminalModes: null, 1024); } /// /// Creates the shell stream. /// /// The TERM environment variable. /// The terminal width in columns. /// The terminal width in rows. /// The terminal width in pixels. /// The terminal height in pixels. /// The size of the buffer. /// /// The created instance. /// /// Client is not connected. /// /// /// The TERM environment variable contains an identifier for the text window's capabilities. /// You can get a detailed list of these cababilities by using the ‘infocmp’ command. /// /// /// The column/row dimensions override the pixel dimensions(when nonzero). Pixel dimensions refer /// to the drawable area of the window. /// /// public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize) { return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, terminalModeValues: null); } /// /// Creates the shell stream. /// /// The TERM environment variable. /// The terminal width in columns. /// The terminal width in rows. /// The terminal width in pixels. /// The terminal height in pixels. /// The size of the buffer. /// The terminal mode values. /// /// The created instance. /// /// Client is not connected. /// /// /// The TERM environment variable contains an identifier for the text window's capabilities. /// You can get a detailed list of these cababilities by using the ‘infocmp’ command. /// /// /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer /// to the drawable area of the window. /// /// public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary terminalModeValues) { EnsureSessionIsOpen(); return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize); } /// /// Stops forwarded ports. /// protected override void OnDisconnected() { base.OnDisconnected(); for (var i = _forwardedPorts.Count - 1; i >= 0; i--) { var port = _forwardedPorts[i]; DetachForwardedPort(port); _forwardedPorts.RemoveAt(i); } } /// /// 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) { base.Dispose(disposing); if (_isDisposed) { return; } if (disposing) { if (_inputStream != null) { _inputStream.Dispose(); _inputStream = null; } _isDisposed = true; } } private void EnsureSessionIsOpen() { if (Session is null) { throw new SshConnectionException("Client not connected."); } } } }