Working with the Client API

Introduction

Maverick NG provides a unified framework for creating both client and server SSH solutions. Both sides of the connection utilise the SshEngine to provide the protocol services required but setting up is quite a different process. 

Those of you that have used our Maverick Legacy Server API in the past may be familiar with some of the concepts as the Maverick NG API is branched from that product. Where the old API used static SshContext instances to configure the server, the Maverick NG API now uses similar SshClientContext and SshServerContext objects, but now creation of the context has been delegated to the ProtocolContextFactory that you install. The advantages of this is that you can now configure a unique context for each connection.

Create the Engine

The first thing you must do is create the SshEngine. This provides the I/O services to the SSH protocol. By starting the engine you have created the framework necessary to make connections (and accept them too if you want to run a server in the same instance). However in the example below no sockets are actually opened; the framework is just initialised ready to do some work.

     SshEngine ssh = new SshEngine();
ssh.startup();

 

Create the Context

How you create the SshClientContext for each connection depends on your implementation. The context holds all the information that will enable the engine to authenticate the connection with the remote server. So in theory you should have at least one context for each different user account you will be using. The following demonstrates creating a static context that will be fed into the connection we make

First create the context instance

     SshClientContext sshContext = new SshClientContext(ssh);

Next, configure the context to authenticate against the remote server. In this instance we will configure the context to support both public key and password authentication. When we specify multiple authenticators, the client will authenticate using the order in which they are added. So in this case it will try public key authentication, and if required or that fails it will then attempt password authentication

     sshContext.setUsername("lee");
sshContext.addAuthenticator(new PublicKeyAuthenticator(
SshPrivateKeyFileFactory.parse(
new FileInputStream("/Users/lee/.ssh/id_rsa")).toKeyPair(null)));
sshContext.addAuthenticator(new PasswordAuthenticator("xxxxxxxx"));

We now have configured our context to authenticate. And before we can connect the client we just need to configure the the tasks we want to perform when the client connects. This is where the API differs greatly from our previous products. Rather than return you a client object that you then invoke methods on to perform SSH operations, you instead add a series of tasks to the connection. We add tasks once we have a Connection object and this is obtained by adding a ConnectionStateListener to the context.

    sshContext.addStateListener(
new ConnectionStateListener<SshClientContext>() {
@Override
public void connected(final Connection<SshClientContext> con) {
// The connection is established and authenticated.
// Add tasks to the Connection object.
}
@Override
public void disconnected(Connection<SshClientContext> con) {
// The connection has been terminated.
}
});

 

Adding Tasks

Once you have a connected Connection you can add tasks to it. The following example uses the AbstractCommandTask to execute the ls -l command on the remote server. As you can see you receive two events, one when the session is opened and another when the session is closed. Place this code within the connected event of the ConnectionStateListener.

    con.addTask(new AbstractCommandTask(con, "ls -l") {
@Override
protected void onOpenSession(SessionChannel session) { 
try {
int b;
byte[] buf = new byte[1024];
while((b = getInputStream().read(buf)) > -1) {
System.out.write(buf, 0, b);
}
} catch(IOException e) { }


@Override
protected void onCloseSession(SessionChannel session) {
con.disconnect("Session closed");
}
});

With this particular task, AbstractCommandTask you receive an InputStream to read data from the command output which means within the onOpenSession method you can read the command output until the InputStream returns EOF. The framework will call onCloseSession once the command session has closed. Here I call disconnect to terminate the connection. If you do not terminate the connection here it will remain on the SshEngine as a connected client. Its possible that you may want to do this to park the client in a pool for later use. You can add tasks to the Connection object at anytime whilst the client is connected.

 

Making the Connection

We have now created enough code and configured the context to create a working client. The only thing left to do is to create our connection to the remote server. This is done using the SshEngine's connect method.

    ssh.connect("localhost", 22, 
new StaticContextFactory<SshClientContext>(sshContext));

Here we call connect passing the hostname and port we want to connect to. We pass our context wrapped in a StaticContextFactory instance which will return our context when needed.

 

Summary

In this article I have walked you through the basic concepts of the Maverick NG Client API. From creating the SshEngine which provides the I/O workhorse, creating the client context which provides the connection configuration and behaviour and then finally calling connect on the SshEngine to initiate the connection. We have briefly touched on how to authenticate and execute a command and these topics will be extended in later articles.

Public Key Authentication

Keyboard Interactive Authentication

SFTP Task

Command Task

Session Task

Subsystem Task

 

Have more questions? Submit a request

0 Comments

Please sign in to leave a comment.