SSH Shell Tutorial
Back to tutorial list...
Table of content
# About Rebex SSH Shell
Rebex SSH Shell for .NET is a versatile SSH component for .NET languages (such as C# or VB.NET).
It makes it possible to access remote servers from your application using SSH (Secure Shell) protocol.
SSH is nearly ubiquitous on Unix and Unix-like systems, with OpenSSH being the most common implementation.
There is also a growing number of SSH servers for Windows as well.
Most SSH servers also implement SFTP - a powerful secure file transfer protocol. To take advantage of it,
use our SFTP component. An SFTP + SSH bundle is also available.
back to top...
# Namespaces and assemblies
To use the features of Rebex SSH Shell for .NET described here, you have to reference
the Rebex.Net.SshShell.dll, Rebex.Net.Ssh.dll
and Rebex.TerminalEmulation.dll assemblies in your project.
These contain Ssh, Shell, TerminalControl/SshTerminalControl, VirtualTerminal and other classes
in Rebex.Net and Rebex.TerminalEmulation namespaces.
In your source files, import the following namespace:
C#
using Rebex.Net;
using Rebex.TerminalEmulation;
VB.NET
Imports Rebex.Net
Imports Rebex.TerminalEmulation;
back to top...
# SSH basics - connecting, logging in and disconnecting
Typical SSH session goes like this:
- Connect to the SSH server
- Verify the server's fingerprint
- Login - authenticate with user name and password
- Execute commands and read responses
- Disconnect
And now let's look at some sample code.
C#
// create client and connect
Ssh client = new Ssh();
// to verify the server's fingerprint:
// a) check client.Fingerprint property after the
// 'Connect' method call
// - or -
// b) use client.FingerprintCheck event handler
// to implement a fingerprint checker
client.Connect(hostname);
// verify the server's fingerprint here unless using the event handler
// authenticate
client.Login(username, password);
// execute commands, start shells, etc...
// ...
// disconnect
client.Disconnect();
VB.NET
' create client and connect
Dim client As New Ssh
' to verify the server's fingerprint:
' a) check client.Fingerprint property after the
' 'Connect' method call
' - or -
' b) use client.FingerprintCheck event handler
' to implement a fingerprint checker
client.Connect(hostname)
' verify the server's fingerprint here unless using the event handler
' authenticate
client.Login(username, password)
' execute commands, start shells, etc...
' ...
' disconnect
client.Disconnect()
During a single SSH session, multiple commands may be executed and multiple shells can be activated. Actually, multiple commands
and shells may be active at the same time!
You might have noticed that the fingerprint verification is missing here. This is because implementing this is your responsibility.
The fingerprint ensures the identity of the SSH server and is the same every time you connect to it. In fact, it is a hash of the
server's public key. The Ssh class makes it available as a Fingerprint property (string), which makes it easy to display or check it.
If you don't check the fingerprint each time before(!) logging in, you are making yourself vulnerable to the man-in-the-middle attacks!
In practice, the following approach is recommended:
1) When connecting to an unknown server (for the first time), let the user check and accept the fingerprint. Then save it along with the hostname of the server.
2) When connecting to a known server (whose hostname and fingerprint is already known), check whether the fingerprint presented by the server corresponds to the fingerprint saved before.
It is also possible to authenticate using your private key instead of password. Check out Public key authentication for more information.
back to top...
# Executing commands
Executing a single command at the server is simple - just use the RunCommand
command of the Ssh class:
C#
// create client, connect and log in
Ssh client = new Ssh();
client.Connect(hostname);
client.Login(username, password);
// run the 'uname' command to retrieve OS info
string systemName = client.RunCommand("uname -a");
// display the output
Console.WriteLine("OS info: {0}", systemName);
// run the 'df' command to retrieve disk usage info
string diskFree = client.RunCommand("df");
// display the output
Console.WriteLine("Disk usage info:");
Console.WriteLine(diskFree);
VB.NET
' create client, connect and log in
Dim client As New Ssh
client.Connect(hostname)
client.Login(username, password)
' run the 'uname' command to retrieve OS info
Dim systemName As String = client.RunCommand("uname -a")
' display the output
Console.WriteLine("OS info: {0}", systemName)
' run the 'df' command to retrieve disk usage info
Dim diskFree As String = client.RunCommand("df")
' display the output
Console.WriteLine("Disk usage info:")
Console.WriteLine(diskFree)
Obviously, this only works for commands that don't require user input (check out
the interactive commands tutorial for those that do). Another drawback is
that you only get the response when the command is over, which is inconvenient for commands
that produce lots of output while they run (use StartCommand method if you need
to read the response while the command is running). However, in many cases, the RunCommand
method is perfectly suitable.
back to top...
# Reading command response
If the Ssh's RunCommand method is not suitable for you because you would
like to read the response before the command is finished, just use the StartCommand
method instead. This gives you an instance of Shell that can be used to read the response
in many different ways. You can use the ReadAll method to read the complete response:
C#
// create client, connect and log in
// ...
// start the 'df' command to retrieve disk usage info
Shell shell = client.StartCommand("df");
// read all response, effectively waiting for the command to end
string response = shell.ReadAll();
// display the output
Console.WriteLine("Disk usage info:");
Console.WriteLine(response );
VB.NET
' create client, connect and log in
' ...
' start the 'df' command to retrieve disk usage info
Dim shell As Shell = client.StartCommand("df")
' read all response, effectively waiting for the command to end
Dim response As String = shell.ReadAll()
' display the output
Console.WriteLine("Disk usage info:")
Console.WriteLine(response)
Alternatively, there is ReadLine method that makes it possible to read the response line-by-line:
C#
// create client, connect and log in
// ...
// start the 'df' command to retrieve disk usage info
Shell shell = client.StartCommand("df");
// read all response line-by-line
while (true)
{
// read a single response line
string line = shell.ReadLine();
// a value of null indicates end of response
if (line == null)
break;
// display the line
Console.WriteLine(line);
}
VB.NET
' create client, connect and log in
' ...
' start the 'df' command to retrieve disk usage info
Dim shell As Shell = client.StartCommand("df")
' read all response line-by-line
While True
' read a single response line
Dim line As String = shell.ReadLine()
' a value of null indicates end of response
If line Is Nothing Then Exit While
' display the line
Console.WriteLine(line)
End While
And finally, char-by-char response retrieval is also available through the ReadChar method:
C#
// create client, connect and log in
// ...
// start the 'df' command to retrieve disk usage info
Shell shell = client.StartCommand("df");
// read all response char-by-char
while (true)
{
// read a single response character
char c = shell.ReadChar();
// a value of Shell.EndOfResponse indicates end of response
if (c == Shell.EndOfResponse)
break;
// display the character
Console.Write(c);
}
VB.NET
' create client, connect and log in
' ...
' start the 'df' command to retrieve disk usage info
Dim shell As Shell = client.StartCommand("df")
' read all response char-by-char
While True
' read a single response character
Dim c As Char = shell.ReadChar()
' a value of Shell.EndOfResponse indicates end of response
If c = shell.EndOfResponse Then Exit While
' display the character
Console.Write(c)
End While
At this point, you might be wondering why the class used to read the response is called Shell.
Well, that's because the same class is also used as a shell API, making it easy
to write code that works both with stand-alone remotely executed commands and command executed from a shell.
back to top...
# Using shell
In addition to running separate commands using RunCommand and StartCommand methods,
it is also possible to request a shell session that can be used to run multiple commands. However, detecting
where the response of one command ends is the tricky part - Unix shells were obviously not designed to be used
programmatically and providing a shell API is quite a challenge. For this reason, we offer two different shell modes:
- Well-know shell mode
- Prompt-based command response ending detection mode
Well-known shell mode is the preferred one - it is compatible with most bash-like, tcsh-like and DOS-like shells out-of-the-box.
C#
// create client, connect and log in
// ...
// start the shell in the 'well-known' mode
Shell shell = client.StartShell(ShellMode.WellKnownShell);
// start the 'df' command
shell.SendCommand("df");
// read all response, effectively waiting for the command to end
string response = shell.ReadAll();
// display the output
Console.WriteLine("Disk usage info:");
Console.WriteLine(response);
// start other shell command if needed
VB.NET
' create client, connect and log in
' ...
' start the shell in the 'well-known' mode
Dim shell As Shell = client.StartShell(ShellMode.WellKnownShell)
' start the 'df' command
shell.SendCommand("df")
' read all response, effectively waiting for the command to end
Dim response As String = shell.ReadAll()
' display the output
Console.WriteLine("Disk usage info:")
Console.WriteLine(response)
' start other shell command if needed
Prompt-based command response ending detection mode can be used with other shells as well, but its drawback is that a prompt used to detect
command response ending has to be set manually to match the prompt at the remote server. For this reason, the well-known shell mode should be used whenever
possible. For sample prompts strings of common shells, check out the Telnet tutorial.
C#
// create client, connect and log in
// ...
// start the shell in the 'prompt' mode
Shell shell = client.StartShell(ShellMode.Prompt);
// set the Prompt property to indicate the server's prompt
// (strongly depends on the server)
shell.Prompt = string.Format("regex:{0}@{1}:.+[$#] $", hostname, username);
// start the 'df' command
shell.SendCommand("df");
// read all response, effectively waiting for the command to end
string response = shell.ReadAll();
// display the output
Console.WriteLine("Disk usage info:");
Console.WriteLine(response);
// start other shell command if needed
VB.NET
' create client, connect and log in
' ...
' start the shell in the 'prompt' mode
Dim shell As Shell = client.StartShell(ShellMode.Prompt)
' set the Prompt property to indicate the server's prompt
' (strongly depends on the server)
shell.Prompt = String.Format("regex:{0}@{1}:.+[$#] $", hostname, username)
' start the 'df' command
shell.SendCommand("df")
' read all response, effectively waiting for the command to end
Dim response As String = shell.ReadAll()
' display the output
Console.WriteLine("Disk usage info:")
Console.WriteLine(response)
' start other shell command if needed
To read the command response, any of the three methods described in the reading command response tutorial
can be used as well.
back to top...
# Interactive commands
Some shell commands require user input and this makes them hard to use programmatically.
Whenever possible, a non-interactive mode or another command should be used.
However, if this is not an option, you can still use interactive commands with a bit
of additional work - all you need to know is the string that indicates the command
is waiting for user input. For example, this is how to handle the interactive version
of the rm command:
C#
// see above for how to get an instance of the Shell class
// start the 'rm' command
shell.SendCommand("rm -i /tmp/*");
// repeat while the command is running
while (shell.IsRunning)
{
// read response until end or until '? ' is encountered
string result = shell.ReadAll("? ");
// if '? ' was encountered, send Y for yes
if (result.EndsWith("? "))
{
shell.SendCommand("y");
}
}
VB.NET
' see above for how to get an instance of the Shell class
' start the 'rm' command
shell.SendCommand("rm -i /tmp/*")
' repeat while the command is running
While shell.IsRunning
' read response until end or until '? ' is encountered
Dim result As String = shell.ReadAll("? ")
' if '? ' was encountered, send Y for yes
If result.EndsWith("? ") Then
shell.SendCommand("y")
End If
End While
The loop simply detects all questions ending with "? " and answers 'y' to them.
back to top...
# Terminal emulation with Windows Forms control
The TerminalControl and SshTerminalControl class make it very simple
to add terminal emulation capabilities to your applications. Even writing a full-fledged SSH client
in only a few lines of code is trivial:
C#
// get the terminal control object that was added
// to the Form using the Visual Studio designer
SshTerminalControl terminal = this.Terminal;
// set SSH server address and credentials
terminal.ServerName = serverName;
terminal.UserName = username;
terminal.UserPassword = password;
// connect to the server
terminal.Connect();
// ...
VB.NET
' get the terminal control object that was added
' to the Form using the Visual Studio designer
Dim terminal As SshTerminalControl = Me.Terminal
' set SSH server address and credentials
terminal.ServerName = serverName
terminal.UserName = username
terminal.UserPassword = password
' connect to the server
terminal.Connect()
' ...
In this example code snippet, the SshTerminalControl class was used. This is an extension
of the TerminalControl class that is independent of SSH protocol or component and might be
used for other purposes as well, as demonstrated by the ANSI Player
sample that uses it for a file-based ANSI communication replay. For basic usage, however,
check the Simple Winform SSH Client
and Winform SSH Client samples instead.
back to top...
# Virtual terminal emulation
In some scenarios where no user interaction is needed or desired and/or it is not necessary or desirable to
display the terminal screen content in real time, a Windows Forms based
terminal emulator might not be the right choice. The VirtualTerminal class may be more suitable.
For example, there is no need to use WinFormClient if all you need is to save the content of the terminal screen
into a file:
C#
// create client, connect and log in
Ssh client = new Ssh();
client.Connect(serverName);
client.Login(username, password);
// create a virtual terminal
VirtualTerminal terminal = new VirtualTerminal(80, 25);
// bind the virtual terminal to the SSH client object
terminal.Bind(client);
// send the 'df' command to the server
// please note that you also have to send the 'enter' - '\r' character
terminal.SendToServer("df\r");
// receive and process any server response
TerminalState state;
do
{
// wait 5 seconds for the response
state = terminal.Process(5000);
// if there was a response, try processing more
} while (state == TerminalState.DataReceived);
// save the content of the virtual terminal screen to a file
terminal.Save("terminal.png", TerminalCaptureFormat.Png);
VB.NET
' create client, connect and log in
Dim client As New Ssh()
client.Connect(serverName)
client.Login(username, password)
' create a virtual terminal
Dim terminal As New VirtualTerminal(80, 25)
' bind the virtual terminal to the SSH client object
terminal.Bind(client)
' send the 'df' command to the server
' please note that you also have to send the 'enter' - the vbCr character
terminal.SendToServer("df" & vbCr)
' receive and process any server response
Dim state As TerminalState
Do
' wait 5 seconds for the response
state = terminal.Process(5000)
' if there was a response, try processing more
Loop While state = TerminalState.DataReceived
' save the content of the virtual terminal screen to a file
terminal.Save("terminal.png", TerminalCaptureFormat.Png)
back to top...
# Terminal emulation options
The SshTerminalControl/TerminalControl and VirtualTerminal classes
have lots of options to make them compatible with lots of different terminals. For more information
about these, check out the documentation for the TerminalOptions class or the
Winform SSH Client sample.
C#
// create a virtual terminal
VirtualTerminal terminal = new VirtualTerminal(80, 25);
// initialize the options class and set some options
TerminalOptions options = new TerminalOptions();
options.TerminalName = "xterm";
options.FunctionKeysMode = FunctionKeysMode.Linux;
options.Encoding = System.Text.Encoding.GetEncoding("iso-8859-1");
// assign the options
terminal.Options = options;
VB.NET
' create a virtual terminal
Dim terminal As New VirtualTerminal(80, 25)
' initialize the options class and set some options
Dim options As New TerminalOptions()
options.TerminalName = "xterm"
options.FunctionKeysMode = FunctionKeysMode.Linux
options.Encoding = System.Text.Encoding.GetEncoding("iso-8859-1")
' assign the options
terminal.Options = options
back to top...
# Recording communication
The TerminalControl and VirtualTerminal classes have a
useful and unique feature - communication recording. This makes it possible to save
a complete SSH shell session into a file and replay it later using our
ANSI Player application.
C#
// get the terminal control object
TerminalControl terminal = this.Terminal;
// set the recorder to a file-based stream writer
terminal.Recorder = System.IO.File.AppendText("recording.ans");
// ...
VB.NET
' get the terminal control object
Dim terminal As TerminalControl = Me.Terminal
' set the recorder to a file-based stream writer
terminal.Recorder = System.IO.File.AppendText("recording.ans")
' ...
back to top...
# Getting information about SSH connection
You can easily get information about the SSH connection using properties of
Ssh.Session that makes the underlying Rebex.Net.SshSession class available.
Please note that you also need to reference Rebex.Net.Ssh.dll assembly from your project
to be able to use the SshSession class.
C#
Ssh client = new Ssh();
client.Connect(hostname);
// The server's fingerprint
Console.WriteLine("Fingerprint: {0}", client.Fingerprint);
// The Cipher property contains a lot of
// information about the current cipher
SshCipher cipher = client.Session.Cipher;
Console.WriteLine("Host key algorithm: {0}", cipher.HostKeyAlgorithm);
Console.WriteLine("Key exchange algorithm: {0}", cipher.KeyExchangeAlgorithm);
Console.WriteLine("Incoming MAC algorithm: {0}", cipher.IncomingMacAlgorithm);
Console.WriteLine("Incoming cipher: {0}", cipher.IncomingEncryptionAlgorithm);
Console.WriteLine("Outgoing MAC algorithm: {0}", cipher.OutgoingMacAlgorithm);
Console.WriteLine("Outgoing cipher: {0}", cipher.OutgoingEncryptionAlgorithm);
Console.WriteLine("Summary: {0}", cipher);
VB.NET
Dim client As New Ssh
client.Connect(hostname)
' The server's fingerprint
Console.WriteLine("Fingerprint: {0}", client.Session.Fingerprint)
' The Cipher property contains a lot of
' information about the current cipher
Dim cipher As SshCipher = client.Session.Cipher
Console.WriteLine("Host key algorithm: {0}", cipher.HostKeyAlgorithm)
Console.WriteLine("Key exchange algorithm: {0}", cipher.KeyExchangeAlgorithm)
Console.WriteLine("Incoming MAC algorithm: {0}", cipher.IncomingMacAlgorithm)
Console.WriteLine("Incoming cipher: {0}", cipher.IncomingEncryptionAlgorithm)
Console.WriteLine("Outgoing MAC algorithm: {0}", cipher.OutgoingMacAlgorithm)
Console.WriteLine("Outgoing cipher: {0}", cipher.OutgoingEncryptionAlgorithm)
Console.WriteLine("Summary: {0}", cipher)
back to top...
# Specifying SSH parameters
It is possible to affect many aspects of SSH such us specifying key exchange algorithm
to use or which cipher suites to allow.
Depending on your scenario, it might be a good idea to disable weak cipher suites and only allowing the strong
ones.
Please note that you also need to reference Rebex.Net.Ssh.dll assembly from your project
to be able to use the SshParameters class.
C#
// Create an instance of SshParameters class
// to specify desired arguments.
SshParameters par = new SshParameters();
// Any key exchange method is acceptable.
par.KeyExchangeAlgorithms = SshKeyExchangeAlgorithm.Any;
// Only allow AES and 3DES encryption methods.
par.EncryptionAlgorithms = SshEncryptionAlgorithm.AES | SshEncryptionAlgorithm.TripleDES;
// Connect to the server.
// The third argument refers to the parameters class.
client.Connect(hostname, Ssh.DefaultPort, par);
VB.NET
' Create an instance of SshParameters class
' to specify desired arguments.
Dim par As New SshParameters
' Any key exchange method is acceptable.
par.KeyExchangeAlgorithms = SshKeyExchangeAlgorithm.Any
' Only allow AES and 3DES encryption methods.
par.EncryptionAlgorithms = SshEncryptionAlgorithm.AES Or SshEncryptionAlgorithm.TripleDES
' Connect to the server.
' The third argument refers to the parameters class.
client.Connect(hostname, Ssh.DefaultPort, par)
back to top...
# Public key authentication
Instead of using a password to authenticate, it is often desirable to use public key cryptography.
The SSH server keeps a database of public keys for each account - exact details of this depends on the server.
For example, OpenSSH keeps the public keys in ~/.ssh/authorized_keys file in each account's home directory.
Each public key has a corresponding private key that is kept secret by the client, usually in a file encrypted using a password.
To authenticate to the server using public key cryptography, you need this private key.
To generate the public/private key pair, either use the Key Generator
sample application or another utility such as OpenSSH's ssh-keygen.
Or write your own key generator using Rebex SSH Shell classes.
To authenticate using your private key, you have to load it into an SshPrivateKey instance. SshPrivateKey
supports all the common private key file formats: PKCS#8 v1 and v2 or the traditional SSLeay compatible format. Support for other formats
may be added on request.
Please note that you also need to reference Rebex.Net.Ssh.dll assembly from your project
to be able to use the SshPrivateKey class.
C#
// create client and connect
Ssh client = new Ssh();
client.Connect(hostname);
// verify the server's fingerprint (client.Fingerprint)
SshPrivateKey privateKey = new SshPrivateKey("key_rsa.pem", "password");
// authenticate
client.Login(username, privateKey);
// ...
VB.NET
' create client and connect
Dim client As New Ssh
client.Connect(hostname)
' verify the server's fingerprint (client.Fingerprint)
Dim privateKey As New SshPrivateKey("key_rsa.pem", "password")
' authenticate
client.Login(username, privateKey)
' ...
Fingerprint verification was already discussed in SSH basics.
back to top...
# Private and public key generation
Rebex SSH Shell can generate the private and public keys to use within public key authentication.
To generate a key, use SshPrivateKey class's Generate method. Once the RSA or DSA key is generated, save
it using Save and SavePublicKey methods, or use GetPublicKey to get the raw data of the public key.
To be able to authenticate using your newly generated key, the public key has to be added to SSH server's database for your account.
For example, in OpenSSH, the public keys are kept in ~/.ssh/authorized_keys each account's home directory.
The following code snippet shows how to generate a private key, save it (also the associated public key) and constructs the string
to be added to OpenSSH ~/.ssh/authorized_keys file.
Please note that you also need to reference Rebex.Net.Ssh.dll assembly from your project
to be able to use the SshPrivateKey class.
C#
// select the key algorithm (RSA or DSA) and key size
SshHostKeyAlgorithm algorithm = SshHostKeyAlgorithm.RSA;
int keySize = 1024;
// generate private key
SshPrivateKey privateKey = SshPrivateKey.Generate(algorithm, keySize);
// save the private key
privateKey.Save("key_rsa.pem", "password", null);
// save the public key
privateKey.SavePublicKey("key_rsa.pub");
// the only purpose of the rest of this sample
// is to construct the OpenSSH-style public key string
// get the raw form of SSH public key
byte[] rawPublicKey = privateKey.GetPublicKey();
// select the appropriate algorithm id
string publicKeyAlgorithm;
switch (algorithm)
{
case SshHostKeyAlgorithm.RSA:
publicKeyAlgorithm = "ssh-rsa";
break;
case SshHostKeyAlgorithm.DSS:
publicKeyAlgorithm = "ssh-dss";
break;
default:
throw new ApplicationException("Unsupported algorithm.");
}
// the string to be added to OpenSSH's ~/.ssh/authorized_keys file
string sshPublicKey = string.Format("{0} {1} username@hostname",
publicKeyAlgorithm,
Convert.ToBase64String(rawPublicKey));
// and display it
Console.WriteLine("Add the following line to your ~/.ssh/authorized_keys file:");
Console.WriteLine(sshPublicKey);
Console.WriteLine("(Modify 'username@hostname' to match your username and hostname.)");
VB.NET
' select the key algorithm (RSA or DSA) and key size
Dim algorithm As SshHostKeyAlgorithm = SshHostKeyAlgorithm.RSA
Dim keySize As Integer = 1024
' generate private key
Dim privateKey As SshPrivateKey = SshPrivateKey.Generate(algorithm, keySize)
' save the private key
privateKey.Save("key_rsa.pem", "password", Nothing)
' save the public key
privateKey.SavePublicKey("key_rsa.pub")
' the only purpose of the rest of this sample
' is to construct the OpenSSH-style public key string
' get the raw form of SSH public key
Dim rawPublicKey As Byte() = privateKey.GetPublicKey()
' select the appropriate algorithm id
Dim publicKeyAlgorithm As String
Select Case algorithm
Case SshHostKeyAlgorithm.RSA
publicKeyAlgorithm = "ssh-rsa"
Case SshHostKeyAlgorithm.DSS
publicKeyAlgorithm = "ssh-dss"
Case Else
Throw New ApplicationException("Unsupported algorithm.")
End Select
' the string to be added to OpenSSH's ~/.ssh/authorized_keys file
Dim sshPublicKey As String = String.Format("{0} {1} username@hostname", _
publicKeyAlgorithm, _
Convert.ToBase64String(rawPublicKey))
' and display it
Console.WriteLine("Add the following line to your ~/.ssh/authorized_keys file:")
Console.WriteLine(sshPublicKey)
Console.WriteLine("(Modify 'username@hostname' to match your username and hostname.)")
back to top...
Back to tutorial list...