Understanding Chilkat’s SSH Tunnel Class

Before Chilkat’s SSH Tunnel, your app would need to connect to a tunnel process/service running either on the local machine or on some computer on the LAN, and then tunnel out to the remote server.  It would look like this:

App ——(1)——>  TunnelService —-(2)——> SshServer —-(3)—–> SomeDestServerSuchAsSQL

You can wrap a non-encrypted TCP connection, or a TLS connection within the SSH Tunnel.
For example, if you connect with TLS, then (1) and (3) are TLS, and (2) is TLS wrapped inside SSH.
The (1) connection is within your LAN, the (3) connection is within the LAN on the server-side, and the (2) connection is over the Internet.

With Chilkat, the TunnelService is no longer a separate service running somewhere on your LAN.
Rather, it’s a background thread of your app.  Thus.. you can run your app anywhere and tunnel without needing to pre-install some SSH tunnel service on the LAN.  (In effect, you’re app is carrying the TunnelService in it’s back pocket..)

Multi-Hop SSH Tunneling for Chilkat v9.5.0.55

The ability to multi-hop SSH tunnels is available for testing in the Chilkat v9.5.0.55 pre-release.

The typical schemes for multiple hop SSH look like this:

(for running a remote shell or commands on SSH_server_B)
application => SSH_server_A => SSH_server_B

(for connecting to a remote host:port, via Socket with TCP or TLS)
application(TCP_or_TLS) => SSH_server_A => SSH_server_B => destHost:destPort

(IMAP)
application => SSH_server_A => SSH_server_B => IMAP_server

(SMTP)
application => SSH_server_A => SSH_server_B => SMTP_server

(POP3)
application => SSH_server_A => SSH_server_B => POP3_server

(SFTP)
application => SSH_server_A => SFTP_server_B

(SCP)
application => SSH_server_A => SSH_server_B

(HTTP -- dynamic port forwarding)
application => SshTunnel_bg_thread(SOCKS) => SSH_server_A => SSH_server_B => destHost:destPort

It is technically possible to chain any number of hops together, although performance would get worse for each additional hop.

application => SSH_server_A => SSH_server_B => ... => SSH_server_N => destHost:destPort

The new v9.5.0.55 methods to achieve multi-hop SSH are:

  • Ssh.ConnectThroughSsh
  • SFtp.ConnectThroughSsh
  • SshTunnel.ConnectThroughSsh
  • Imap.UseSsh
  • Mailman.UseSsh
  • Socket.UseSsh

See the online reference documentation at Chilkat reference documentation.

See these examples:

 

SFTP vs. FTPS

Clarification on the acronyms “SFTP” and “FTPS”

“SFTP” is the Secure File Transfer Protocol over SSH.  It is a protocol unrelated to the FTP protocol. (It is actually a subsystem of SSH.)  The Chilkat SSH / SFTP component is used for “SFTP”.  SFTP is achieved by connecting to an SSH server at port 22.

On the other hand, the Chilkat FTP2 component is for FTP.   FTP servers listen at port 21 (non-SSL/TLS) and port 990 (SSL).  FTP over SSL (i.e. port 990) is called “FTPS”.

SSH SendReqExec — Interactive Commands such as “more”

Commands that assume an interactive user at a shell prompt should not be passed to SendRequestExec.  For example, the “more” command assumes there is a shell and that the user will press RETURN when another screenful of text is wanted.  It would be more appropriate to use the Unix/Linux “cat” command w/ SendRequestExec.

To run interactive commands, one should instead start a remote shell by (1) starting a pseudo-terminal by calling SendReqPty and then (2) starting a shell by calling SendReqShell. See this example:

ASP: SSH — Running Commands that Prompt for Additional Input, such as “su”
SQL Server: SSH — Running Commands that Prompt for Additional Input, such as “su”
C#: SSH — Running Commands that Prompt for Additional Input, such as “su”
Delphi: SSH — Running Commands that Prompt for Additional Input, such as “su”
Visual FoxPro: SSH — Running Commands that Prompt for Additional Input, such as “su”
PHP: SSH — Running Commands that Prompt for Additional Input, such as “su”
VB.NET: SSH — Running Commands that Prompt for Additional Input, such as “su”
Visual Basic: SSH — Running Commands that Prompt for Additional Input, such as “su”
VBScript: SSH — Running Commands that Prompt for Additional Input, such as “su”

SSH SendReqExec — Commands with No Output

When a command is passed to SendReqExec that produces no output, such as “echo 1 > test.txt”, then do not try to read the channel (such as by calling ChannelReadAndPoll) because no data will be forthcoming and the channel read will timeout (as expected). The correct sequence of method calls would be to:

  1. Call SendReqExec to execute the command on the remote server.
  2. Call ChannelSendClose to initiate the closing of the channel.
  3. Call ChannelReceiveToClose to wait until the server closes the channel. In this case, no actual output data is returned because the command passed to SendReqExec produces no output.  This method is called to cleanly wait for the server’s “close” message.

    SSH / SFTP – Too much time between connect and authentication

    The Solution:

    Issue solved.   The problem was, that we stepped through the code and because of that too much time elapsed between connect and authentication.  As we ran the program without breakpoints it worked.

    The Problem:

    The AuthenticatePw method failed and the LastErrorText contained this information:

    ChilkatLog:
       AuthenticatePw:
         DllDate: Jan 31 2010
         UnlockPrefix: ***
         Username: ***
         Component: .NET 2.0
         SshVersion: SSH-2.0-XFB.Gateway Windows
         SftpVersion: 0
         login: ***
         sendMessage:
           msgName: SERVICE_REQUEST
           unpaddedLength: 22
           remainder: 6
           paddingLen: 10
           totalSize: 32
         SentServiceReq: ssh-userauth
         numBytesRequested: 16
         Connection closed by server.
         Failed to read data on SSH connection.
         Failed to read packet from SSH server.
         Error reading service accept.
         Socket connection lost.
         Failed.
    

    Very simple C# SSH Shell Console Terminal

    Here’s an example that demonstrates a rough start to creating a C# console SSH shell terminal (where the user can type commands and output from the remote command echos to the console:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    
    namespace SshTerminalConsole
    {
        class Program
        {
            static void Main(string[] args)
            {
                Chilkat.Ssh ssh = new Chilkat.Ssh();
                ssh.UnlockComponent("Test");
    
                //  Hostname may be an IP address or hostname:
                string hostname = "192.168.1.117";
                int port = 22;
    
                Console.WriteLine("Connecting...");
    
                //ssh.KeepSessionLog = true;
                bool success = ssh.Connect(hostname, port);
                if (success != true)
                {
                    Console.WriteLine(ssh.LastErrorText + "\r\n");
                    // Read so we can see the error before the console closes.
                    string x = Console.ReadLine();      
                    return;
                }
    
                //  When reading, if no additional data arrives for more than
                //  5 seconds, then abort:
                ssh.IdleTimeoutMs = 5000;
    
                Console.WriteLine("Authenticating...");
    
                //  SSH Server Authentication
                //  If there is no login/password required, you must still call
                //  AuthenticatePw and use any values for login/password.
                success = ssh.AuthenticatePw("chilkat", "***");
                if (success != true)
                {
                    Console.WriteLine(ssh.LastErrorText + "\r\n");
                    // Read so we can see the error before the console closes.
                    string x = Console.ReadLine();
                    return;
                }
    
                Console.WriteLine("Opening Channel...");
    
                //  Open a session channel.
                int channelNum = ssh.OpenSessionChannel();
                if (channelNum < 0)
                {
                    Console.WriteLine(ssh.LastErrorText + "\r\n");
                    // Read so we can see the error before the console closes.
                    string x = Console.ReadLine();
                    return;
                }
    
                //  Request a pseudo-terminal
                string termType;
                termType = "dumb";
                int widthInChars;
                widthInChars = 120;
                int heightInChars;
                heightInChars = 40;
                int pixWidth;
                pixWidth = 0;
                int pixHeight;
                pixHeight = 0;
                success = ssh.SendReqPty(channelNum, termType, widthInChars, heightInChars, pixWidth, pixHeight);
                if (success != true)
                {
                    Console.WriteLine(ssh.LastErrorText + "\r\n");
                    // Read so we can see the error before the console closes.
                    string x = Console.ReadLine();
                    return;
                }
    
                Console.WriteLine("Starting a shell...");
    
                //  Start a shell on the channel:
                success = ssh.SendReqShell(channelNum);
                if (success != true)
                {
                    Console.WriteLine(ssh.LastErrorText + "\r\n");
                    // Read so we can see the error before the console closes.
                    string x = Console.ReadLine();
                    return;
                }
    
                // Loop to read from the SSH channel, output to the console, and read keyboard input from the console.
                StringBuilder sb = new StringBuilder();
                while (true)
                {
                    if (Console.KeyAvailable)
                    {
                        ConsoleKeyInfo key = Console.ReadKey(true);
                        Console.Write(key.KeyChar);
    
                        switch (key.Key)
                        {
                            case ConsoleKey.Enter:
                                Console.WriteLine("");
    
                                sb.Append("\n");
                                success = ssh.ChannelSendString(channelNum, sb.ToString(), "ansi");
                                if (success != true)
                                {
                                    Console.WriteLine(ssh.LastErrorText + "\r\n");
                                    // Read so we can see the error before the console closes.
                                    string x = Console.ReadLine();
                                    return;
                                }
    
                                sb.Length = 0;
                                break;
                            default:
                                //Console.Write(key.KeyChar);
                                sb.Append(key.KeyChar);
                                break;
                        }
    
                    }
    
                    // Now check for incoming data from the SSH channel.
                    int retval = ssh.ChannelPoll(channelNum, 10);
                    if (retval == -1)
                    {
                        Console.Write(ssh.LastErrorText);
                        Console.WriteLine("");
                        // Read so we can see the error before the console closes.
                        string x = Console.ReadLine();
                        return;
                    }
                    if (retval > 0)
                    {
                        Console.Write(ssh.GetReceivedText(channelNum, "ansi"));
                    }
                    else
                    {
                        // If data arrived, loop around and get more immediately.
                        // Otherwise wait 20ms.
                        System.Threading.Thread.Sleep(20);
                    }
    
                }  
    
            }
        }
    }