diff options
Diffstat (limited to 'src/org/theb/ssh/SecureShell.java')
-rw-r--r-- | src/org/theb/ssh/SecureShell.java | 370 |
1 files changed, 92 insertions, 278 deletions
diff --git a/src/org/theb/ssh/SecureShell.java b/src/org/theb/ssh/SecureShell.java index 31625f9..8d4dda6 100644 --- a/src/org/theb/ssh/SecureShell.java +++ b/src/org/theb/ssh/SecureShell.java @@ -1,12 +1,30 @@ +/* + * Copyright (C) 2007 Kenny Root (kenny at the-b.org) + * + * This file is part of Connectbot. + * + * Connectbot is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Connectbot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Connectbot. If not, see <http://www.gnu.org/licenses/>. + */ 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.ConnectionMonitor; + import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -17,22 +35,16 @@ import android.os.Handler; import android.text.method.KeyCharacterMap; import android.util.Log; import android.view.KeyEvent; +import android.view.View; import android.view.Window; -import android.widget.TextView; - -import com.trilead.ssh2.ChannelCondition; -import com.trilead.ssh2.Connection; -import com.trilead.ssh2.ConnectionMonitor; -import com.trilead.ssh2.Session; -public class SecureShell extends Activity { - private TextView mOutput; +public class SecureShell extends Activity implements FeedbackUI, ConnectionMonitor { private ConnectionThread mConn; - private String mBuffer; - private KeyCharacterMap mKMap; + // Activities we support. static final int PASSWORD_REQUEST = 0; + // Database projection indices. private static final int HOSTNAME_INDEX = 1; private static final int USERNAME_INDEX = 2; private static final int PORT_INDEX = 3; @@ -46,6 +58,15 @@ public class SecureShell extends Activity { private Cursor mCursor; + // Map to convert from keyboard presses to actual characters. + private KeyCharacterMap mKMap; + + // Terminal window + private Terminal mTerminal; + + // We change the meta state when the user presses the center button. + private boolean mMetaState = false; + // Store the username, hostname, and port from the database. private String mHostname; private String mUsername; @@ -56,241 +77,21 @@ public class SecureShell extends Activity { private boolean mIsWaiting; private String mWaitingTitle; private String mWaitingMessage; - - // Connection lost reason. - private String mDisconnectReason; - - // This is for the password dialog. - Semaphore sPass; - String mPassword = null; - - Connection conn; - Session sess; - InputStream stdin; - InputStream stderr; - OutputStream stdout; - 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); - - setWaiting(true, "Connection", - "Connecting to " + hostname + "..."); - - Log.d("SSH", "Starting connection attempt..."); - - try { - conn.connect(new InteractiveHostKeyVerifier()); - - setWaiting(true, "Authenticating", - "Trying to authenticate..."); - - Log.d("SSH", "Starting authentication..."); - -// 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..."); - setWaiting(true, "Authenticating", - "Trying to authenticate using password..."); - - // 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..."); - setWaiting(true, "Session", "Requesting shell..."); - - 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(); - - stdout = sess.getStdin(); - stderr = sess.getStderr(); - stdin = sess.getStdout(); - - setWaiting(false, null, null); - } catch (IOException e) { - Log.e("SSH", e.getMessage()); - setWaiting(false, null, null); - 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) { - if ((stdin.available() == 0) && (stderr.available() == 0)) { - int conditions = sess.waitForCondition( - ChannelCondition.STDOUT_DATA - | ChannelCondition.STDERR_DATA - | ChannelCondition.EOF, 2000); - if ((conditions & ChannelCondition.TIMEOUT) != 0) - continue; - if ((conditions & ChannelCondition.EOF) != 0) - if ((conditions & - (ChannelCondition.STDERR_DATA - | ChannelCondition.STDOUT_DATA)) == 0) - break; - } - - if (stdin.available() > 0) { - int len = stdin.read(buff); - addText(buff, len); - } - if (stderr.available() > 0) { - int len = stderr.read(buff); - 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] = ' '; - } + // Tell the user why we disconnected. + private String mDisconnectReason; - 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); requestWindowFeature(Window.FEATURE_PROGRESS); - setContentView(R.layout.secure_shell); - mOutput = (TextView) findViewById(R.id.output); + mTerminal = new JTATerminalView(this); + + // TODO: implement scroll bar on right. + setContentView((View)mTerminal); Log.d("SSH", "using URI " + getIntent().getData().toString()); @@ -307,7 +108,7 @@ public class SecureShell extends Activity { this.setTitle(title); - mConn = new ConnectionThread(mHostname, mUsername, mPort); + mConn = new TrileadConnectionThread(this, mTerminal, mHostname, mUsername, mPort); mKMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); @@ -340,10 +141,9 @@ public class SecureShell extends Activity { } }; - public String askPassword() { + public void askPassword() { Intent intent = new Intent(this, PasswordDialog.class); this.startSubActivity(intent, PASSWORD_REQUEST); - return null; } @Override @@ -351,55 +151,71 @@ public class SecureShell extends Activity { String data, Bundle extras) { if (requestCode == PASSWORD_REQUEST) { - - // If the request was cancelled, then we didn't get anything. - if (resultCode == RESULT_CANCELED) - mPassword = ""; - else - mPassword = data; - - sPass.release(); + mConn.setPassword(data); } } @Override public void onDestroy() { super.onDestroy(); - - if (sess != null) { - sess.close(); - sess = null; - } - - if (conn != null) { - conn.close(); - conn = null; - } + + mConn.finish(); + mConn = null; finish(); } @Override public boolean onKeyDown(int keyCode, KeyEvent msg) { - if (stdout != null) { - int c = mKMap.get(keyCode, msg.getMetaState()); + final OutputStream out = mConn.getWriter(); + if (out != null) { try { - stdout.write(c); + if (mKMap.isPrintingKey(keyCode) + || keyCode == KeyEvent.KEYCODE_SPACE) { + int c = mKMap.get(keyCode, msg.getMetaState()); + if (mMetaState) { + // Support CTRL-A through CTRL-Z + if (c >= 0x61 && c <= 0x79) + c -= 0x60; + else if (c >= 0x40 && c <= 0x59) + c -= 0x39; + mMetaState = false; + } + out.write(c); + } else if (keyCode == KeyEvent.KEYCODE_DEL) { + out.write(0x08); // CTRL-H + } else if (keyCode == KeyEvent.KEYCODE_NEWLINE + || keyCode == KeyEvent.KEYCODE_DPAD_LEFT + || keyCode == KeyEvent.KEYCODE_DPAD_UP + || keyCode == KeyEvent.KEYCODE_DPAD_DOWN + || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + byte[] output = mTerminal.getKeyCode(keyCode, msg.getMetaState()); + if (output != null) + out.write(output); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + if (mMetaState) { + out.write(0x1B); // ESCAPE + mMetaState = false; + } else { + mMetaState = true; + } + } else { + // This is not something we handle. + return super.onKeyDown(keyCode, msg); + } + return true; } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Log.e("SSH", "Can't write to stdout: "+ e.getMessage()); } } - - return super.onKeyDown(keyCode, msg); - } - - public void updateViewInUI() { - mOutput.setText(mBuffer); + return super.onKeyDown(keyCode, msg); } final Runnable mDisconnectAlert = new Runnable() { public void run() { + if (SecureShell.this.isFinishing()) + return; + AlertDialog d = AlertDialog.show(SecureShell.this, "Connection Lost", mDisconnectReason, "Ok", false); d.show(); @@ -407,11 +223,9 @@ public class SecureShell extends Activity { } }; - final ConnectionMonitor mConnectionMonitor = new ConnectionMonitor() { - public void connectionLost(Throwable reason) { - Log.d("SSH", "Connection ended."); - mDisconnectReason = reason.getMessage(); - mHandler.post(mDisconnectAlert); - } - }; + public void connectionLost(Throwable reason) { + Log.d("SSH", "Connection ended."); + mDisconnectReason = reason.getMessage(); + mHandler.post(mDisconnectAlert); + } } |