Apache Mina SSHD SshServer Example

I’ve had an absolutely miserable time finding a fully working example of writing a Java ssh daemon. I finally just went ahead and wrote a working demo ssh daemon echo server, because I’m sure I can’t be the only one not able to get any of the current Apache Mina SSHD example ssh echo daemons to work.

One of the first things not to mix up is the difference between the Apache Mina and the Apache Mina SSHD projects. If you down load the Apache Mina project instead of the Apache Mina SSHD project binaries or source, you’re out of luck. You’ll spend many hours confused and have to implement everything you need practically from scratch. Remember to verify you are downloading the Mina SSHD project!

The SshDaemon …

package com.genedavis.ssh;

import java.io.File;
import java.io.IOException;

import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.server.ServerFactoryManager;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;

/**
 * From the commandline connect with ...
 * ssh -p 8000 [email protected]
 */
public class SshDaemon implements Runnable 
{
  private static final int PORT = 8000;
  private static final String BANNER =
      "\n\nWelcome to SshDaemon!\n\n";

  private static boolean shutdownRequested = false;

  public static void main(String[] args) 
  {
    new Thread( new SshDaemon() )
      .start();
  }

  @Override
  public void run() 
  {
    SshServer sshd = SshServer.setUpDefaultServer();
    
    //creating the welcome banner for after login
    PropertyResolverUtils.updateProperty(
        sshd, 
        ServerFactoryManager.WELCOME_BANNER, 
        BANNER);
    
    //adding the password authenticator
    sshd.setPasswordAuthenticator(
        new SshPasswordAuthenticator());
    
    //setting the port the ssh daemon is listening on
    sshd.setPort( PORT );
    
    //setting the class that hooks up users 
    //with their session
    sshd.setShellFactory( new SshSessionFactory() );
    
    sshd.setKeyPairProvider(
        new SimpleGeneratorHostKeyProvider(
            new File("hostkey.ser")));
    
    try {
      sshd.start();
    } catch (IOException e) {
      e.printStackTrace();
    }
    
    //TODO replace with proper interrupt
    while(! shutdownRequested) {
      try{Thread.sleep(500);} catch (Exception e) {}
    }
  }
  
  public static void setShutdownRequested(
      boolean shutdownRequested) {
    SshDaemon.shutdownRequested = shutdownRequested;
  }
}

SshPasswordAuthenticator …

package com.genedavis.ssh;

import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
import org.apache.sshd.server.session.ServerSession;

public class SshPasswordAuthenticator implements PasswordAuthenticator {

  /**
   * Handle username/password authentication here.
   */
  @Override
  public boolean authenticate(
      String username, 
      String password, 
      ServerSession session) 
          throws PasswordChangeRequiredException 
  {
    return true; //lax security 😉
  }

}

SshSessionFactory …

package com.genedavis.ssh;

import org.apache.sshd.common.Factory;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;

/**
 * Handles incoming session requests from clients. 
 */
public class SshSessionFactory 
        implements CommandFactory, Factory<Command> {

  @Override
  public Command createCommand(String command) 
  {
    return new SshSessionInstance();
  }

  @Override
  public Command create() 
  {
    return createCommand("none");
  }

}

SshSessionInstance …

package com.genedavis.ssh;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;

/**
 * Each connection from an SSH client is
 * treated as one command with it's own
 * unique IO streams.
 * 
 * You probably want to turn off local
 * echo and local line editing if you
 * are doing any sort of interactive
 * ssh-based utility like word processing.
 * 
 * In that case, echo on a character-by-
 * character basis, rather than whole
 * lines.
 */
public class SshSessionInstance 
        implements Command, Runnable {
  
  
  //ANSI escape sequences for formatting purposes 
  public static final String ANSI_LOCAL_ECHO = "\u001B[12l";
  public static final String ANSI_NEWLINE_CRLF = "\u001B[20h";
  
  public static final String ANSI_RESET = "\u001B[0m";
  
  public static final String ANSI_BLACK = "\u001B[0;30m";
  public static final String ANSI_RED = "\u001B[0;31m";
  public static final String ANSI_GREEN = "\u001B[0;32m";
  public static final String ANSI_YELLOW = "\u001B[0;33m";
  public static final String ANSI_BLUE = "\u001B[0;34m";
  public static final String ANSI_PURPLE = "\u001B[0;35m";
  public static final String ANSI_CYAN = "\u001B[0;36m";
  public static final String ANSI_WHITE = "\u001B[0;37m";

  
  //IO streams for communication with the client
  private InputStream is;
  private OutputStream os;
  
  //Environment stuff
  @SuppressWarnings("unused")
  private Environment environment;
  private ExitCallback callback;
  
  private Thread sshThread;
  
  @Override
  public void start(Environment env) throws IOException 
  {    
    //must start new thread to free up the input stream
    environment = env;
    sshThread = new Thread(this, "EchoShell");
    sshThread.start();
  }

  @Override
  public void run() 
  {
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    
    //Make sure local echo is on (because password turned it off
    try {
      os.write( 
          (ANSI_LOCAL_ECHO + ANSI_NEWLINE_CRLF)
          .getBytes() );
      os.flush();
    } catch (IOException e1) {
      e1.printStackTrace();
    }
    
    try {
      
      boolean exit = false;
      String text;
      while ( ! exit ) 
      {
        text = br.readLine();
        if (text == null) {
            exit = true;
        }
        else
        {
          os.write((ANSI_GREEN+text+ANSI_RESET + "\r\n").getBytes());
          os.flush();
          if ("exit".equals(text)) 
          {
              exit = true;
          }
        }
      }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        callback.onExit(0);
    }
  }

  @Override
  public void destroy() throws Exception 
  {
    sshThread.interrupt();
  }

  @Override
  public void setErrorStream(OutputStream errOS) 
  {}

  @Override
  public void setExitCallback(ExitCallback ec) 
  {
    callback = ec;
  }

  @Override
  public void setInputStream(InputStream is) 
  {
    this.is = is;
  }

  @Override
  public void setOutputStream(OutputStream os) 
  {
    this.os = os;
  }

}

In conclusion, it isn’t really that hard to get a working ssh daemon echo server up and running in Java when you use Apache Mina SSHD.