aboutsummaryrefslogtreecommitdiffstats
path: root/src/org/theb/ssh/SecureShell.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/theb/ssh/SecureShell.java')
-rw-r--r--src/org/theb/ssh/SecureShell.java363
1 files changed, 363 insertions, 0 deletions
diff --git a/src/org/theb/ssh/SecureShell.java b/src/org/theb/ssh/SecureShell.java
new file mode 100644
index 0000000..e1c5d80
--- /dev/null
+++ b/src/org/theb/ssh/SecureShell.java
@@ -0,0 +1,363 @@
+package org.theb.ssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Semaphore;
+
+import org.theb.provider.HostDb;
+
+import com.trilead.ssh2.Connection;
+import com.trilead.ssh2.ConnectionMonitor;
+import com.trilead.ssh2.Session;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.ContentURI;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.method.KeyCharacterMap;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class SecureShell extends Activity {
+ private Context mContext;
+ private TextView mOutput;
+ private ConnectionThread mConn;
+ private String mBuffer;
+ private KeyCharacterMap mKMap;
+
+ static final int PASSWORD_REQUEST = 0;
+
+ private static final int HOSTNAME_INDEX = 1;
+ private static final int USERNAME_INDEX = 2;
+ private static final int PORT_INDEX = 3;
+
+ private static final String[] PROJECTION = new String[] {
+ HostDb.Hosts._ID, // 0
+ HostDb.Hosts.HOSTNAME, // 1
+ HostDb.Hosts.USERNAME, // 2
+ HostDb.Hosts.PORT, // 3
+ };
+
+ private Cursor mCursor;
+
+ // This is for the password dialog.
+ Semaphore sPass;
+ String mPassword = null;
+
+ Connection conn;
+ Session sess;
+ InputStream in;
+ OutputStream out;
+ int x;
+ int y;
+
+ final Handler mHandler = new Handler();
+
+ final Runnable mUpdateView = new Runnable() {
+ public void run() {
+ updateViewInUI();
+ }
+ };
+
+ class ConnectionThread extends Thread {
+ String hostname;
+ String username;
+ int port;
+
+ char[][] lines;
+ int posy = 0;
+ int posx = 0;
+
+ public ConnectionThread(String hostname, String username, int port) {
+ this.hostname = hostname;
+ this.username = username;
+ this.port = port;
+ }
+
+ public void run() {
+ conn = new Connection(hostname, port);
+
+ conn.addConnectionMonitor(mConnectionMonitor);
+
+ Log.d("SSH", "Starting connection attempt...");
+ mBuffer = "Attemping to connect...";
+ mHandler.post(mUpdateView);
+
+ try {
+ conn.connect(new InteractiveHostKeyVerifier());
+
+ Log.d("SSH", "Starting authentication...");
+ mBuffer = "Attemping to authenticate...";
+ mHandler.post(mUpdateView);
+
+ boolean enableKeyboardInteractive = true;
+ boolean enableDSA = true;
+ boolean enableRSA = true;
+
+ while (true) {
+ /*
+ if ((enableDSA || enableRSA ) &&
+ mConn.isAuthMethodAvailable(username, "publickey");
+ */
+
+ if (conn.isAuthMethodAvailable(username, "password")) {
+ Log.d("SSH", "Trying password authentication...");
+
+ // Set a semaphore that is unset by the returning dialog.
+ sPass = new Semaphore(0);
+ askPassword();
+
+ // Wait for the user to answer.
+ sPass.acquire();
+ sPass = null;
+ if (mPassword == null)
+ continue;
+
+ boolean res = conn.authenticateWithPassword(username, mPassword);
+ if (res == true)
+ break;
+
+ continue;
+ }
+
+ throw new IOException("No supported authentication methods available.");
+ }
+
+ Log.d("SSH", "Opening session...");
+ mBuffer = "Opening session...";
+ mHandler.post(mUpdateView);
+
+ sess = conn.openSession();
+
+ y = (int)(mOutput.getHeight() / mOutput.getLineHeight());
+ // TODO: figure out how to get the width of monospace font characters.
+ x = y * 3;
+ Log.d("SSH", "Requesting PTY of size " + x + "x" + y);
+
+ sess.requestPTY("dumb", x, y, 0, 0, null);
+
+ Log.d("SSH", "Requesting shell...");
+ sess.startShell();
+
+ out = sess.getStdin();
+ in = sess.getStdout();
+
+ mBuffer = "Welcome...";
+ mHandler.post(mUpdateView);
+
+ } catch (IOException e) {
+ Log.e("SSH", e.getMessage());
+ mConnectionMonitor.connectionLost(e);
+ return;
+ } catch (InterruptedException e) {
+ // This thread is coming to an end. Let us exit.
+ Log.e("SSH", "Connection thread interrupted.");
+ return;
+ }
+
+ byte[] buff = new byte[8192];
+ lines = new char[y][];
+
+ try {
+ while (true) {
+ int len = in.read(buff);
+ if (len == -1)
+ return;
+ addText(buff, len);
+ }
+ } catch (Exception e) {
+ Log.e("SSH", "Got exception reading: " + e.getMessage());
+ }
+ }
+
+ public void addText(byte[] data, int len) {
+ for (int i = 0; i < len; i++) {
+ char c = (char) (data[i] & 0xff);
+
+ if (c == 8) { // Backspace, VERASE
+ if (posx < 0)
+ continue;
+ posx--;
+ continue;
+ }
+ if (c == '\r') {
+ posx = 0;
+ continue;
+ }
+
+ if (c == '\n') {
+ posy++;
+ if (posy >= y) {
+ for (int k = 1; k < y; k++)
+ lines[k - 1] = lines[k];
+
+ posy--;
+ lines[y - 1] = new char[x];
+
+ for (int k = 0; k < x; k++)
+ lines[y - 1][k] = ' ';
+ }
+ continue;
+ }
+
+ if (c < 32) {
+ continue;
+ }
+
+ if (posx >= x) {
+ posx = 0;
+ posy++;
+ if (posy >= y) {
+ posy--;
+
+ for (int k = 1; k < y; k++)
+ lines[k - 1] = lines[k];
+ lines[y - 1] = new char[x];
+ for (int k = 0; k < x; k++)
+ lines[y - 1][k] = ' ';
+ }
+ }
+
+ if (lines[posy] == null) {
+ lines[posy] = new char[x];
+ for (int k = 0; k < x; k++)
+ lines[posy][k] = ' ';
+ }
+
+ lines[posy][posx] = c;
+ posx++;
+ }
+
+ StringBuffer sb = new StringBuffer(x * y);
+
+ for (int i = 0; i < lines.length; i++) {
+ if (i != 0)
+ sb.append('\n');
+
+ if (lines[i] != null)
+ sb.append(lines[i]);
+ }
+
+ mBuffer = sb.toString();
+ mHandler.post(mUpdateView);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedValues) {
+ super.onCreate(savedValues);
+
+ mContext = this;
+
+ setContentView(R.layout.secure_shell);
+
+ Log.d("SSH", "using URI " + getIntent().getData().toString());
+
+ mCursor = managedQuery(getIntent().getData(), PROJECTION, null, null);
+ mCursor.first();
+
+ mOutput = (TextView) findViewById(R.id.output);
+
+ mKMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
+
+ mConn = new ConnectionThread(
+ mCursor.getString(HOSTNAME_INDEX),
+ mCursor.getString(USERNAME_INDEX),
+ mCursor.getInt(PORT_INDEX));
+
+ Log.d("SSH", "Starting new ConnectionThread");
+ mConn.start();
+ }
+
+ public String askPassword() {
+ Intent intent = new Intent(this, PasswordDialog.class);
+ this.startSubActivity(intent, PASSWORD_REQUEST);
+ return null;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ String data, Bundle extras)
+ {
+ if (requestCode == PASSWORD_REQUEST) {
+
+ // If the request was cancelled, then we didn't get anything.
+ if (resultCode == RESULT_CANCELED) {
+ return;
+
+ // Otherwise, there now should be a password ready for us.
+ } else {
+ mPassword = data;
+ sPass.release();
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (sess != null) {
+ sess.close();
+ sess = null;
+ }
+
+ if (conn != null) {
+ conn.close();
+ conn = null;
+ }
+
+ finish();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent msg) {
+ if (out != null) {
+ int c = mKMap.get(keyCode, msg.getMetaState());
+ try {
+ out.write(c);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ return super.onKeyDown(keyCode, msg);
+ }
+
+ public void updateViewInUI() {
+ mOutput.setText(mBuffer);
+ }
+
+ final ConnectionMonitor mConnectionMonitor = new ConnectionMonitor() {
+ public void connectionLost(Throwable reason) {
+ Log.d("SSH", "Connection ended.");
+ Dialog d = new Dialog(mContext);
+ d.setTitle("Connection Lost");
+ d.setContentView(R.layout.message_dialog);
+
+ TextView msg = (TextView) d.findViewById(R.id.message);
+ msg.setText(reason.getMessage());
+
+ Button b = (Button) d.findViewById(R.id.dismiss);
+ b.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ finish();
+ }
+ });
+ d.show();
+ finish();
+ }
+ };
+}