package plugins.cluster.ssh;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Properties;
import com.mindbright.gui.AWTConvenience;
import com.mindbright.jca.security.SecureRandom;
import com.mindbright.ssh2.SSH2ConsoleRemote;
import com.mindbright.ssh2.SSH2FTPProxyFilter;
import com.mindbright.ssh2.SSH2HostKeyVerifier;
import com.mindbright.ssh2.SSH2Interactor;
import com.mindbright.ssh2.SSH2Preferences;
import com.mindbright.ssh2.SSH2SimpleClient;
import com.mindbright.ssh2.SSH2StreamFilterFactory;
import com.mindbright.ssh2.SSH2StreamSniffer;
import com.mindbright.ssh2.SSH2TerminalAdapterImpl;
import com.mindbright.ssh2.SSH2Transport;
import com.mindbright.ssh2.SSH2UserCancelException;
import com.mindbright.terminal.GlobalClipboard;
import com.mindbright.terminal.LineReaderTerminal;
import com.mindbright.terminal.TerminalFrameTitle;
import com.mindbright.terminal.TerminalMenuHandler;
import com.mindbright.terminal.TerminalMenuHandlerFull;
import com.mindbright.terminal.TerminalMenuListener;
import com.mindbright.terminal.TerminalWin;
import com.mindbright.util.RandomSeed;
import com.mindbright.util.SecureRandomAndPad;
import com.mindbright.util.Util;
/**
* Ssh2 client which opens a terminal window and asks the user where to connect
* to.
* <p>
* Usage: <code> java -cp examples.jar examples.BasicClient
* [<em>props_file_name</em>]</code>
* <p>
* Username and password as well as server can be stored in the properties file.
* <p>
* It can also read portforwards from properties. Create properties named
* 'localN' or 'remoteN' where N is an integer 0-31. The contents of the
* properties is in the following format:
*
* <pre>
* [/plugin/][<em>local_host</em>:]<em>local_port</em>:<em>remote_host</em>:<em>remote_port</em>
* </pre>
*
* This client understands the <code>ftp</code> and <code>sniff</code> plugins.
*
* @see com.mindbright.ssh2.SSH2FTPProxyFilter
* @see com.mindbright.ssh2.SSH2StreamSniffer
*/
public final class BasicClient extends WindowAdapter implements SSH2Interactor, TerminalMenuListener, Runnable
{
private Frame frame;
private TerminalWin terminal;
private SSH2Transport transport;
private SSH2SimpleClient client;
private SSH2ConsoleRemote console;
private Properties props;
private LineReaderTerminal lineReader;
private int exitStatus;
private String host;
private String passwd;
/**
* Simple constructor where all required properties have good default values
* so no properties have to be provided. However the properties is the only
* way to change the encryption algorithms etc in this client.
*
* @param props
* SSH2 protocol properties.
*/
public BasicClient(Properties props)
{
this.props = props;
this.exitStatus = 1;
}
/**
* Actually runs the client. This gets called from the <code>main</code>
* function.
*/
public void run()
{
try
{
int port;
String user;
/*
* Create and show terminal window
*/
boolean haveMenus = Boolean.valueOf(props.getProperty("havemenus")).booleanValue();
frame = (haveMenus ? AWTConvenience.newFrameWithMenuBar() : new Frame());
terminal = new TerminalWin(frame, props);
RandomSeed seed = new RandomSeed();
terminal.addAsEntropyGenerator(seed);
frame.setLayout(new BorderLayout());
frame.add(terminal.getPanelWithScrollbar(), BorderLayout.CENTER);
TerminalFrameTitle frameTitle = new TerminalFrameTitle(frame, getTitle());
frameTitle.attach(terminal);
if (haveMenus)
{
try
{
TerminalMenuHandler tmenus = TerminalMenuHandlerFull.getInstance(frame);
tmenus.setTitleName(getTitle());
tmenus.addBasicMenus(terminal, frame);
tmenus.setTerminalMenuListener(this);
}
catch (Throwable t)
{
}
}
else
{
terminal.setClipboard(GlobalClipboard.getClipboardHandler());
}
frame.addWindowListener(this);
frame.pack();
frame.setVisible(true);
/*
* Prompt for where to connect to
*/
lineReader = new LineReaderTerminal(terminal);
host = props.getProperty("server");
port = getPort(props.getProperty("port"));
user = props.getProperty("username");
terminal.write("Basic SSH2 client demo\n\r");
while (host == null)
{
host = lineReader.promptLine("\r\nssh2 server[:port]: ", null, false);
}
port = Util.getPort(host, port);
host = Util.getHost(host);
while (user == null)
{
user = lineReader.promptLine(host + " login: ", null, false);
}
// Create the preferences object
SSH2Preferences prefs = new SSH2Preferences(props);
// This shows how to force certain properties
// prefs.setPreference(SSH2Preferences.CIPHERS_C2S, "blowfish-cbc");
// prefs.setPreference(SSH2Preferences.CIPHERS_S2C, "blowfish-cbc");
// prefs.setPreference(SSH2Preferences.LOG_LEVEL, "9");
// prefs.setPreference(SSH2Preferences.LOG_FILE, "ssh2out.log");
// It is important that the random number generator here is good!
SecureRandomAndPad secureRandom = new SecureRandomAndPad(new SecureRandom(seed.getBytesBlocking(20, false)));
/*
* Open the TCP connection to the server and create the
* SSH2Transport object. No traffic will be sent yet.
*/
transport = new SSH2Transport(new Socket(host, port), prefs, secureRandom);
/*
* Prepare for host fingerprint verification. This implementation
* check fingerprints agains properties of the form:
* fingerprint.HOSTNAME.PORT
*/
String fingerprint = props.getProperty("fingerprint." + host + "." + port);
/*
* If we found a fingerprint property for this host:port then create
* a key verifier which checks that the fingerprint of the actual
* host matches.
*/
if (fingerprint != null)
{
transport.setEventHandler(new SSH2HostKeyVerifier(fingerprint));
}
client = null;
/*
* This simple client can only authenticate using either publickey
* or passwords. Depending on which it uses different constructors
* of SSH2SimpleClient.
*
* The actual password to use can be stored in the properties. This
* has severe security implications though.
*
* The construction of SSH2SimpleClient will start up the session
* and cause the encrypted connection to be establiushed and the
* user to be authenticated.
*/
String auth = props.getProperty("auth-method");
if ("publickey".equals(auth))
{
String keyFile = props.getProperty("private-key");
String keyPasswd = props.getProperty("passphrase");
client = new SSH2SimpleClient(transport, user, keyFile, keyPasswd);
}
else
{
passwd = props.getProperty("password");
while (passwd == null)
{
passwd = lineReader.promptLine(user + "@" + host + "'s password: ", null, true);
}
client = new SSH2SimpleClient(transport, user, passwd);
}
// This class will not interact with the user anymore.
lineReader.detach();
// Start any portforwards defined in the preferences.
startForwards();
/*
* Create the remote console to use for command execution.
*/
console = new SSH2ConsoleRemote(client.getConnection());
SSH2TerminalAdapterImpl termAdapter = new SSH2TerminalAdapterImpl(terminal);
if (!console.terminal(termAdapter))
{
throw new Exception("Couldn't start terminal!");
}
// Wait for user to close remote shell
exitStatus = console.waitForExitStatus();
}
catch (LineReaderTerminal.ExternalMessageException e)
{
// ignore
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if (frame != null)
{
frame.dispose();
}
}
}
/**
* Get the exit status from the SSH2ConsoleRemote instance
*
* @return the exit status
*/
public int getExitStatus()
{
return exitStatus;
}
/**
* Starts any portforwards specified in the properties.
*/
private void startForwards()
{
int i;
for (i = 0; i < 32; i++)
{
String spec = props.getProperty("local" + i);
if (spec == null) break;
Object[] components = Util.parseForwardSpec(spec, "127.0.0.1");
try
{
SSH2StreamFilterFactory filter = null;
if ("ftp".equals(components[0]))
{
filter = new SSH2FTPProxyFilter((String) components[1], host);
}
else if ("sniff".equals(components[0]))
{
filter = SSH2StreamSniffer.getFilterFactory();
}
client.getConnection().newLocalForward((String) components[1], ((Integer) components[2]).intValue(),
(String) components[3], ((Integer) components[4]).intValue(), filter);
terminal.write("started local forward: " + spec + "\n\r");
}
catch (IOException e)
{
terminal.write("failed local forward: " + spec + e.getMessage() + "\n\r");
}
}
for (i = 0; i < 32; i++)
{
String spec = props.getProperty("remote" + i);
if (spec == null) break;
Object[] components = Util.parseForwardSpec(spec, "127.0.0.1");
client.getConnection().newRemoteForward((String) components[1], ((Integer) components[2]).intValue(),
(String) components[3], ((Integer) components[4]).intValue());
terminal.write("started remote forward: " + spec + "\n\r");
}
}
/**
* Get the port number of the ssh server stored in the string. Defaults to
* 22, the ssh standard port, if none is specified.
*/
private static int getPort(String port)
{
int p;
try
{
p = Integer.parseInt(port);
}
catch (Exception e)
{
p = 22;
}
return p;
}
private String getTitle()
{
return "Basic SSH2 Client";
}
/**
* Close the connection to the server (if any) in a controlled way.
*/
public void doClose()
{
if (lineReader != null)
{
lineReader.breakPromptLine("");
}
if (console != null)
{
console.close();
}
if (transport != null)
{
transport.normalDisconnect("User disconnects");
}
}
/**
* Overide corresponding function in java.awt.event.WindowAdapter
*/
public void windowClosing(WindowEvent e)
{
doClose();
}
/*
* TerminalMenuListener interface implementation
*/
public void close(TerminalMenuHandler origMenu)
{
doClose();
}
public void update()
{
// Ignore
}
/**
* Run the application
*/
public static void main(String[] argv)
{
Properties props = new Properties();
if (argv.length > 0)
{
String propsFile = argv[0];
try
{
props.load(new FileInputStream(propsFile));
}
catch (Exception e)
{
e.printStackTrace();
}
}
BasicClient ssh2 = new BasicClient(props);
ssh2.run();
System.exit(ssh2.getExitStatus());
}
/*
* SSH2Interactor interface
*/
public String promptLine(String prompt, boolean echo) throws SSH2UserCancelException
{
return passwd;
}
public String[] promptMulti(String[] prompts, boolean[] echos) throws SSH2UserCancelException
{
return promptMultiFull("", "", prompts, echos);
}
public String[] promptMultiFull(String name, String instruction, String[] prompts, boolean[] echos)
throws SSH2UserCancelException
{
if (prompts.length == 1)
{
return new String[]
{ passwd };
}
else if (prompts.length == 0)
{
return new String[0];
}
throw new SSH2UserCancelException();
}
public int promptList(String name, String instruction, String[] choices) throws SSH2UserCancelException
{
throw new SSH2UserCancelException();
}
}