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.java370
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);
+ }
}