diff options
author | Kenny Root <kenny@the-b.org> | 2008-11-07 11:30:23 +0000 |
---|---|---|
committer | Kenny Root <kenny@the-b.org> | 2008-11-07 11:30:23 +0000 |
commit | 706a57a55373b68604acc9a54ddcf5fd8f47d9d2 (patch) | |
tree | 3ab7e2c99284bb8a9f3d1348ae136739ea4e0034 | |
parent | 1a6ca4d897fe789a589e165e49281f1fb27785ca (diff) | |
download | connectbot-706a57a55373b68604acc9a54ddcf5fd8f47d9d2.tar.gz connectbot-706a57a55373b68604acc9a54ddcf5fd8f47d9d2.tar.bz2 connectbot-706a57a55373b68604acc9a54ddcf5fd8f47d9d2.zip |
* Add port forward list activity so port forwards can be saved in database
* Start saved port forwards when host is connected
* Add titles to activities to distinguish where user is
-rw-r--r-- | AndroidManifest.xml | 1 | ||||
-rw-r--r-- | res/layout/act_portforwardlist.xml | 39 | ||||
-rw-r--r-- | res/layout/dia_portforward.xml | 86 | ||||
-rw-r--r-- | res/layout/dia_tunnel.xml | 68 | ||||
-rw-r--r-- | res/layout/item_portforward.xml | 40 | ||||
-rw-r--r-- | res/values/strings.xml | 34 | ||||
-rw-r--r-- | src/org/connectbot/ConsoleActivity.java | 73 | ||||
-rw-r--r-- | src/org/connectbot/HostEditorActivity.java | 10 | ||||
-rw-r--r-- | src/org/connectbot/HostListActivity.java | 15 | ||||
-rw-r--r-- | src/org/connectbot/PortForwardListActivity.java | 258 | ||||
-rw-r--r-- | src/org/connectbot/PubkeyListActivity.java | 74 | ||||
-rw-r--r-- | src/org/connectbot/bean/PortForwardBean.java | 235 | ||||
-rw-r--r-- | src/org/connectbot/service/TerminalBridge.java | 144 | ||||
-rw-r--r-- | src/org/connectbot/util/HostDatabase.java | 166 |
14 files changed, 1065 insertions, 178 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3517574..1d5f06d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -23,6 +23,7 @@ <activity android:name=".PubkeyListActivity" android:configChanges="keyboardHidden|orientation" /> <activity android:name=".GeneratePubkeyActivity" android:configChanges="keyboardHidden|orientation" /> <activity android:name=".HostEditorActivity" android:configChanges="keyboardHidden|orientation" /> + <activity android:name=".PortForwardListActivity" android:configChanges="keyboardHidden|orientation" /> <activity android:name=".SettingsActivity" android:configChanges="keyboardHidden|orientation" /> <activity android:name=".WizardActivity" android:configChanges="keyboardHidden|orientation" /> diff --git a/res/layout/act_portforwardlist.xml b/res/layout/act_portforwardlist.xml new file mode 100644 index 0000000..f2fea38 --- /dev/null +++ b/res/layout/act_portforwardlist.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + + <ListView android:id="@android:id/list" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1" + /> + + <TextView android:id="@android:id/empty" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:text="@string/portforward_list_empty" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center" + /> +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/dia_portforward.xml b/res/layout/dia_portforward.xml new file mode 100644 index 0000000..e8743f9 --- /dev/null +++ b/res/layout/dia_portforward.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +--> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:scrollbars="vertical" + android:layout_width="fill_parent" + > + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="10dip" + android:paddingRight="10dip" + > + + <TableLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + > + + <TableRow> + <TextView android:text="@string/prompt_nickname" android:paddingRight="10dip" android:gravity="right|center_vertical" android:textAppearance="?android:attr/textAppearanceMedium" /> + <EditText android:id="@+id/nickname" android:hint="Internal Webserver" android:layout_width="200dip" android:layout_height="wrap_content" /> + </TableRow> + </TableLayout> + + <RadioGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + + <RadioButton + android:id="@+id/portforward_local" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/portforward_local" + android:paddingRight="30dip" + /> + + <RadioButton + android:id="@+id/portforward_remote" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/portforward_remote" + /> + + </RadioGroup> + + <TableLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + > + + <TableRow> + <TextView android:text="@string/prompt_source_port" android:paddingRight="10dip" android:gravity="right|center_vertical" android:textAppearance="?android:attr/textAppearanceMedium" /> + <EditText android:id="@+id/portforward_source" android:hint="143" android:layout_width="200dip" android:layout_height="wrap_content" /> + </TableRow> + + <TableRow> + <TextView android:text="@string/prompt_destination" android:paddingRight="10dip" android:gravity="right|center_vertical" android:textAppearance="?android:attr/textAppearanceMedium" /> + <EditText android:id="@+id/portforward_destination" android:hint="localhost:143" android:layout_width="200dip" android:layout_height="wrap_content" /> + </TableRow> + + </TableLayout> + + </LinearLayout> +</ScrollView>
\ No newline at end of file diff --git a/res/layout/dia_tunnel.xml b/res/layout/dia_tunnel.xml deleted file mode 100644 index dd1db3d..0000000 --- a/res/layout/dia_tunnel.xml +++ /dev/null @@ -1,68 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ConnectBot: simple, powerful, open-source SSH client for Android - Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey - - This program 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. - - This program 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 this program. If not, see <http://www.gnu.org/licenses/>. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingLeft="10dip" - android:paddingRight="10dip" - > - - <RadioGroup - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - > - - <RadioButton - android:id="@+id/tunnel_local" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Local" - android:paddingRight="30dip" - /> - - <RadioButton - android:id="@+id/tunnel_remote" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Remote" - /> - - </RadioGroup> - - <TableLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - > - - <TableRow> - <TextView android:text="Source port:" android:paddingRight="10dip" android:gravity="right|center_vertical" android:textAppearance="?android:attr/textAppearanceMedium" /> - <EditText android:id="@+id/tunnel_source" android:hint="143" android:layout_width="200dip" android:layout_height="wrap_content" /> - </TableRow> - - <TableRow> - <TextView android:text="Destination:" android:paddingRight="10dip" android:gravity="right|center_vertical" android:textAppearance="?android:attr/textAppearanceMedium" /> - <EditText android:id="@+id/tunnel_destination" android:hint="localhost:143" android:layout_width="200dip" android:layout_height="wrap_content" /> - </TableRow> - - </TableLayout> - -</LinearLayout>
\ No newline at end of file diff --git a/res/layout/item_portforward.xml b/res/layout/item_portforward.xml new file mode 100644 index 0000000..fd53d98 --- /dev/null +++ b/res/layout/item_portforward.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="left|center_vertical" android:layout_width="wrap_content"> + + <TextView + android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="Tunnel Nickname" + /> + + <TextView + android:id="@android:id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="Local port 8080 to 192.168.1.1:80" + /> + +</LinearLayout>
\ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 5dfc9aa..261b7d5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3,9 +3,11 @@ <string name="app_name">ConnectBot</string> <string name="app_desc">Simple, powerful, open-source SSH client.</string> - <string name="title_hosts_list">ConnectBot</string> - <string name="title_host">Host</string> - <string name="title_shell">Secure Shell</string> + <string name="title_hosts_list">Hosts</string> + <string name="title_pubkey_list">Public keys</string> + <string name="title_port_forwards_list">Port forwards</string> + <string name="title_host_editor">Edit Host</string> + <string name="title_password">Input Password</string> <string name="title_pubkey">Public Keys</string> <string name="title_entropy">Gathering Entropy</string> @@ -31,6 +33,12 @@ <string name="pubkey_unknown_format">Unknown format</string> <string name="pubkey_change_password">Change Password</string> + <string name="portforward_list_empty">Tap "Menu" to create\nport forwards.</string> + <string name="portforward_edit">Edit Tunnel</string> + + <string name="prompt_nickname">Nickname:</string> + <string name="prompt_source_port">Source port:</string> + <string name="prompt_destination">Destination:</string> <string name="prompt_old_password">Old password:</string> <string name="prompt_password">Password:</string> <string name="prompt_again">(again)</string> @@ -39,9 +47,7 @@ <string name="alert_wrong_password_msg">Wrong password!</string> <string name="alert_key_corrupted_msg">Private key appears corrupt!</string> - <string name="button_ok">Ok</string> <string name="button_add">Add</string> - <string name="button_cancel">Cancel</string> <string name="button_change">Change</string> <string name="button_generate">Generate Key</string> <string name="button_resize">Resize</string> @@ -89,15 +95,18 @@ <string name="console_menu_disconnect">Disconnect</string> <string name="console_menu_copy">Copy</string> <string name="console_menu_paste">Paste</string> - <string name="console_menu_tunnel">Tunnel</string> + <string name="console_menu_portforwards">Port Forwards</string> <string name="console_menu_resize">Force Size</string> - <string name="tunnel_pos">Create tunnel</string> - <string name="tunnel_neg">Cancel</string> + <string name="portforward_local">Local</string> + <string name="portforward_remote">Remote</string> + <string name="portforward_pos">Create port forward</string> + <string name="portforward_neg">Cancel</string> - <string name="tunnel_done_local">Successfully created tunnel -L%d:%s:%d</string> - <string name="tunnel_done_remote">Successfully created tunnel -R%d:%s:%d</string> - <string name="tunnel_problem">Problem creating tunnel, maybe you're using ports under 1024?</string> + <string name="portforward_done">Successfully created port forward</string> + <string name="portforward_problem">Problem creating port forward, maybe you're using ports under 1024?</string> + + <string name="portforward_menu_add">Add port forward</string> <string name="list_format_error">Use the format 'username@hostname:port'</string> <string name="list_menu_sortcolor">Sort by color</string> @@ -107,6 +116,7 @@ <string name="list_host_disconnect">Disconnect</string> <string name="list_host_edit">Edit host</string> + <string name="list_host_portforwards">Edit port forwards</string> <string name="list_host_delete">Delete host</string> <string name="list_rotation_land">Force landscape</string> @@ -122,13 +132,11 @@ <string name="delete_pos">Yes, delete</string> <string name="delete_neg">Cancel</string> - <string name="wizard_agree">Agree</string> <string name="wizard_cancel">Cancel</string> <string name="wizard_next">Next</string> <string name="wizard_back">Back</string> - <string name="terminal_hostkey_warn">The authenticity of host '%s' can't be established.</string> <string name="terminal_hostkey">RSA key fingerprint is %s</string> <string name="terminal_hostkey_prompt">Are you sure you want\nto continue connecting?</string> diff --git a/src/org/connectbot/ConsoleActivity.java b/src/org/connectbot/ConsoleActivity.java index a1523cc..ee24993 100644 --- a/src/org/connectbot/ConsoleActivity.java +++ b/src/org/connectbot/ConsoleActivity.java @@ -18,9 +18,11 @@ package org.connectbot; +import org.connectbot.bean.PortForwardBean; import org.connectbot.service.PromptHelper; import org.connectbot.service.TerminalBridge; import org.connectbot.service.TerminalManager; +import org.connectbot.util.HostDatabase; import android.app.Activity; import android.app.AlertDialog; @@ -485,7 +487,6 @@ public class ConsoleActivity extends Activity { if(copySource == null) return false; float row = event.getY() / copySource.bridge.charHeight; float col = event.getX() / copySource.bridge.charWidth; - Log.d(TAG, String.format("X = %f, Y = %f, row = %f, col = %f", event.getX(), event.getY(), row, col)); switch(event.getAction()) { case MotionEvent.ACTION_DOWN: @@ -595,7 +596,7 @@ public class ConsoleActivity extends Activity { } - protected MenuItem disconnect, copy, paste, tunnel, resize; + protected MenuItem disconnect, copy, paste, portForward, resize; protected boolean copying = false; protected TerminalView copySource = null; @@ -658,29 +659,31 @@ public class ConsoleActivity extends Activity { }); - tunnel = menu.add(R.string.console_menu_tunnel); - tunnel.setIcon(android.R.drawable.ic_menu_manage); - tunnel.setEnabled(activeTerminal && authenticated); - tunnel.setOnMenuItemClickListener(new OnMenuItemClickListener() { + portForward = menu.add(R.string.console_menu_portforwards); + portForward.setIcon(android.R.drawable.ic_menu_manage); + portForward.setEnabled(activeTerminal && authenticated); + portForward.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // show dialog to create tunnel for this host + // show dialog to create portForward for this host final TerminalView terminal = (TerminalView)view; // build dialog to prompt user about updating - final View tunnelView = inflater.inflate(R.layout.dia_tunnel, null, false); - ((RadioButton)tunnelView.findViewById(R.id.tunnel_local)).setChecked(true); + final View portForwardView = inflater.inflate(R.layout.dia_portforward, null, false); + ((RadioButton)portForwardView.findViewById(R.id.portforward_local)).setChecked(true); new AlertDialog.Builder(ConsoleActivity.this) - .setView(tunnelView) - .setPositiveButton("Create tunnel", new DialogInterface.OnClickListener() { + .setView(portForwardView) + .setPositiveButton(R.string.portforward_pos, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - String type = ((RadioButton)tunnelView.findViewById(R.id.tunnel_local)).isChecked() ? TUNNEL_LOCAL : TUNNEL_REMOTE; - String source = ((TextView)tunnelView.findViewById(R.id.tunnel_source)).getText().toString(); - String dest = ((TextView)tunnelView.findViewById(R.id.tunnel_destination)).getText().toString(); + String nickname = ((TextView)portForwardView.findViewById(R.id.nickname)).getText().toString(); + String type = ((RadioButton)portForwardView.findViewById(R.id.portforward_local)).isChecked() + ? HostDatabase.PORTFORWARD_LOCAL : HostDatabase.PORTFORWARD_REMOTE; + String source = ((TextView)portForwardView.findViewById(R.id.portforward_source)).getText().toString(); + String dest = ((TextView)portForwardView.findViewById(R.id.portforward_destination)).getText().toString(); - createTunnel(terminal, type, source, dest); + createPortForward(terminal, nickname, type, source, dest); } }) - .setNegativeButton("Cancel", null).create().show(); + .setNegativeButton(R.string.portforward_neg, null).create().show(); return true; } @@ -703,7 +706,7 @@ public class ConsoleActivity extends Activity { terminal.forceSize(width, height); } - }).setNegativeButton(R.string.button_cancel, null).create().show(); + }).setNegativeButton(android.R.string.cancel, null).create().show(); return true; } @@ -713,34 +716,24 @@ public class ConsoleActivity extends Activity { } - public final static String EXTRA_TYPE = "type", EXTRA_SOURCE = "source", EXTRA_DEST = "dest", EXTRA_SILENT = "silent"; - public final static String TUNNEL_LOCAL = "local", TUNNEL_REMOTE = "remote"; - - protected void createTunnel(TerminalView target, String type, String source, String dest) { - String summary = null; + protected void createPortForward(TerminalView target, String nickname, String type, String source, String dest) { + String summary = getString(R.string.portforward_problem); try { - boolean local = TUNNEL_LOCAL.equals(type); - int sourcePort = Integer.parseInt(source); - String[] destSplit = dest.split(":"); - String destHost = destSplit[0]; - int destPort = Integer.parseInt(destSplit[1]); - - if(local) { - target.bridge.connection.createLocalPortForwarder(sourcePort, destHost, destPort); - summary = getString(R.string.tunnel_done_local, sourcePort, destHost, destPort); - } else { - target.bridge.connection.requestRemotePortForwarding("", sourcePort, destHost, destPort); - summary = getString(R.string.tunnel_done_remote, sourcePort, destHost, destPort); + HostDatabase hostdb = new HostDatabase(this); + long hostId = hostdb.findHostByNickname(target.bridge.nickname); + + PortForwardBean pfb = new PortForwardBean(hostId, nickname, type, source, dest); + + target.bridge.addPortForward(pfb); + if (target.bridge.enablePortForward(pfb)) { + summary = getString(R.string.portforward_done); } } catch(Exception e) { - Log.e(TAG, "Problem trying to create tunnel", e); - summary = getString(R.string.tunnel_problem); + Log.e(TAG, "Problem trying to create portForward", e); } - Toast.makeText(ConsoleActivity.this, summary, Toast.LENGTH_LONG).show(); - + Toast.makeText(ConsoleActivity.this, summary, Toast.LENGTH_LONG).show(); } - @Override public boolean onPrepareOptionsMenu(Menu menu) { @@ -755,7 +748,7 @@ public class ConsoleActivity extends Activity { disconnect.setEnabled(activeTerminal); copy.setEnabled(activeTerminal && authenticated); paste.setEnabled(clipboard.hasText() && activeTerminal && authenticated); - tunnel.setEnabled(activeTerminal && authenticated); + portForward.setEnabled(activeTerminal && authenticated); resize.setEnabled(activeTerminal && authenticated); return true; diff --git a/src/org/connectbot/HostEditorActivity.java b/src/org/connectbot/HostEditorActivity.java index daa81a0..3c5367d 100644 --- a/src/org/connectbot/HostEditorActivity.java +++ b/src/org/connectbot/HostEditorActivity.java @@ -46,12 +46,12 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr public class CursorPreferenceHack implements SharedPreferences { protected final String table; - protected final int id; + protected final long id; protected Map<String, String> values = new HashMap<String, String>(); // protected Map<String, String> pubkeys = new HashMap<String, String>(); - public CursorPreferenceHack(String table, int id) { + public CursorPreferenceHack(String table, long id) { this.table = table; this.id = id; @@ -65,7 +65,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr SQLiteDatabase db = hostdb.getReadableDatabase(); Cursor cursor = db.query(table, null, "_id = ?", - new String[] { Integer.toString(id) }, null, null, null); + new String[] { String.valueOf(id) }, null, null, null); cursor.moveToFirst(); for(int i = 0; i < cursor.getColumnCount(); i++) { @@ -112,7 +112,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr public boolean commit() { //Log.d(this.getClass().toString(), "commit() changes back to database"); SQLiteDatabase db = hostdb.getWritableDatabase(); - db.update(table, update, "_id = ?", new String[] { Integer.toString(id) }); + db.update(table, update, "_id = ?", new String[] { String.valueOf(id) }); db.close(); // make sure we refresh the parent cached values @@ -217,7 +217,7 @@ public class HostEditorActivity extends PreferenceActivity implements OnSharedPr public void onCreate(Bundle icicle) { super.onCreate(icicle); - int id = this.getIntent().getIntExtra(Intent.EXTRA_TITLE, -1); + long id = this.getIntent().getLongExtra(Intent.EXTRA_TITLE, -1); // TODO: we could pass through a specific ContentProvider uri here //this.getPreferenceManager().setSharedPreferencesName(uri); diff --git a/src/org/connectbot/HostListActivity.java b/src/org/connectbot/HostListActivity.java index 429d4e0..f931111 100644 --- a/src/org/connectbot/HostListActivity.java +++ b/src/org/connectbot/HostListActivity.java @@ -160,6 +160,9 @@ public class HostListActivity extends ListActivity { super.onCreate(icicle); setContentView(R.layout.act_hostlist); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_hosts_list))); // check for eula agreement this.prefs = PreferenceManager.getDefaultSharedPreferences(this); @@ -380,7 +383,7 @@ public class HostListActivity extends ListActivity { final String nickname = cursor.getString(COL_NICKNAME); menu.setHeaderTitle(nickname); - final int id = cursor.getInt(COL_ID); + final long id = cursor.getLong(COL_ID); // edit, disconnect, delete MenuItem connect = menu.add(R.string.list_host_disconnect); @@ -403,6 +406,16 @@ public class HostListActivity extends ListActivity { return true; } }); + + MenuItem portForwards = menu.add(R.string.list_host_portforwards); + portForwards.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(HostListActivity.this, PortForwardListActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, id); + HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); MenuItem delete = menu.add(R.string.list_host_delete); delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { diff --git a/src/org/connectbot/PortForwardListActivity.java b/src/org/connectbot/PortForwardListActivity.java new file mode 100644 index 0000000..3bee861 --- /dev/null +++ b/src/org/connectbot/PortForwardListActivity.java @@ -0,0 +1,258 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package org.connectbot; + +import java.util.List; + +import org.connectbot.bean.PortForwardBean; +import org.connectbot.util.HostDatabase; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.MenuItem.OnMenuItemClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.TextView; + +/** + * List all portForwards for a particular host and provide a way for users to add more portForwards, + * edit existing portForwards, and delete portForwards. + * + * @author Kenny Root + */ +public class PortForwardListActivity extends ListActivity { + public final static String TAG = PortForwardListActivity.class.toString(); + + protected HostDatabase hostdb; + + protected Cursor hosts; + protected List<PortForwardBean> portForwards; + + protected LayoutInflater inflater = null; + + private long hostId; + + @Override + public void onStart() { + super.onStart(); + + if(this.hostdb == null) + this.hostdb = new HostDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + + if(this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + this.hostId = this.getIntent().getLongExtra(Intent.EXTRA_TITLE, -1); + + setContentView(R.layout.act_portforwardlist); + + // connect with hosts database and populate list + this.hostdb = new HostDatabase(this); + + this.setTitle(String.format("%s: %s (%s)", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_port_forwards_list), + hostdb.findNicknameById(hostId))); + + this.updateList(); + + this.registerForContextMenu(this.getListView()); + + this.inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + MenuItem add = menu.add(R.string.portforward_menu_add); + add.setIcon(android.R.drawable.ic_menu_add); + add.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // build dialog to prompt user about updating + final View portForwardView = inflater.inflate(R.layout.dia_portforward, null, false); + ((RadioButton)portForwardView.findViewById(R.id.portforward_local)).setChecked(true); + new AlertDialog.Builder(PortForwardListActivity.this) + .setView(portForwardView) + .setPositiveButton(R.string.portforward_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + final EditText nicknameEdit = (EditText) portForwardView.findViewById(R.id.nickname); + final EditText sourcePortEdit = (EditText) portForwardView.findViewById(R.id.portforward_source); + final EditText destEdit = (EditText) portForwardView.findViewById(R.id.portforward_destination); + + String type = ((RadioButton)portForwardView.findViewById(R.id.portforward_local)).isChecked() + ? HostDatabase.PORTFORWARD_LOCAL : HostDatabase.PORTFORWARD_REMOTE; + + PortForwardBean pfb = new PortForwardBean(hostId, + nicknameEdit.getText().toString(), type, + sourcePortEdit.getText().toString(), + destEdit.getText().toString()); + + if (!hostdb.savePortForward(pfb)) + throw new Exception("Could not save port forward"); + + updateHandler.sendEmptyMessage(-1); + } catch (Exception e) { + Log.e(TAG, "Could not update port forward", e); + // TODO Show failure dialog. + } + } + }) + .setNegativeButton(R.string.portforward_neg, null).create().show(); + + return true; + } + }); + + return true; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // Create menu to handle deleting and editing port forward + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final PortForwardBean pfb = (PortForwardBean) this.getListView().getItemAtPosition(info.position); + + menu.setHeaderTitle(pfb.getNickname()); + + MenuItem edit = menu.add(R.string.portforward_edit); + edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final View editTunnelView = inflater.inflate(R.layout.dia_portforward, null, false); + + final RadioButton portForwardLocal = (RadioButton) editTunnelView.findViewById(R.id.portforward_local); + if (HostDatabase.PORTFORWARD_LOCAL.equals(pfb.getType())) { + portForwardLocal.setChecked(true); + } else { // if (HostDatabase.PORTFORWARD_REMOTE.equals(type)) { + ((RadioButton) editTunnelView.findViewById(R.id.portforward_remote)).setChecked(true); + } + + final EditText nicknameEdit = (EditText) editTunnelView.findViewById(R.id.nickname); + nicknameEdit.setText(pfb.getNickname()); + + final EditText sourcePortEdit = (EditText) editTunnelView.findViewById(R.id.portforward_source); + sourcePortEdit.setText(String.valueOf(pfb.getSourcePort())); + + final EditText destEdit = (EditText) editTunnelView.findViewById(R.id.portforward_destination); + destEdit.setText(String.format("%s:%d", pfb.getDestAddr(), pfb.getDestPort())); + + new AlertDialog.Builder(PortForwardListActivity.this) + .setView(editTunnelView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + pfb.setNickname(nicknameEdit.getText().toString()); + + if (portForwardLocal.isChecked()) + pfb.setType(HostDatabase.PORTFORWARD_LOCAL); + else + pfb.setType(HostDatabase.PORTFORWARD_REMOTE); + + pfb.setSourcePort(Integer.parseInt(sourcePortEdit.getText().toString())); + pfb.setDest(destEdit.getText().toString()); + + if (!hostdb.savePortForward(pfb)) + throw new Exception("Could not save port forward"); + + updateHandler.sendEmptyMessage(-1); + } catch (Exception e) { + Log.e(TAG, "Could not update port forward", e); + // TODO Show failure dialog. + } + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + + return true; + } + }); + } + + public Handler updateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + PortForwardListActivity.this.updateList(); + } + }; + + protected void updateList() { + if (this.hostdb == null) return; + + this.portForwards = this.hostdb.getPortForwardsForHost(hostId); + + PortForwardAdapter adapter = new PortForwardAdapter(this, this.portForwards); + this.setListAdapter(adapter); + + //this.startManagingCursor(portForwards); + } + + class PortForwardAdapter extends ArrayAdapter<PortForwardBean> { + private List<PortForwardBean> portForwards; + + public PortForwardAdapter(Context context, List<PortForwardBean> portForwards) { + super(context, R.layout.item_portforward, portForwards); + + this.portForwards = portForwards; + } + + public View getView(int position, View view, ViewGroup parent) { + if (view == null) + view = inflater.inflate(R.layout.item_portforward, null, false); + + TextView nickname = (TextView)view.findViewById(android.R.id.text1); + TextView caption = (TextView)view.findViewById(android.R.id.text2); + + PortForwardBean pfb = portForwards.get(position); + nickname.setText(pfb.getNickname()); + caption.setText(pfb.getDescription()); + + return view; + } + } + +} diff --git a/src/org/connectbot/PubkeyListActivity.java b/src/org/connectbot/PubkeyListActivity.java index 24dbdcf..7d0d590 100644 --- a/src/org/connectbot/PubkeyListActivity.java +++ b/src/org/connectbot/PubkeyListActivity.java @@ -68,6 +68,12 @@ import android.widget.SimpleCursorAdapter.ViewBinder; import com.trilead.ssh2.crypto.PEMDecoder; import com.trilead.ssh2.crypto.PEMStructure; +/** + * List public keys in database by nickname and describe their properties. Allow users to import, + * generate, rename, and delete key pairs. + * + * @author Kenny Root + */ public class PubkeyListActivity extends ListActivity implements EventListener { public final static String TAG = PubkeyListActivity.class.toString(); @@ -123,6 +129,10 @@ public class PubkeyListActivity extends ListActivity implements EventListener { super.onCreate(icicle); setContentView(R.layout.act_pubkeylist); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_pubkey_list))); + // connect with hosts database and populate list this.pubkeydb = new PubkeyDatabase(this); @@ -327,7 +337,7 @@ public class PubkeyListActivity extends ListActivity implements EventListener { // TODO: option load/unload key from in-memory list // prompt for password as needed for passworded keys - final int id = cursor.getInt(COL_ID); + final long id = cursor.getLong(COL_ID); final byte[] pubkeyEncoded = cursor.getBlob(COL_PUBLIC); final String keyType = cursor.getString(COL_TYPE); final int encrypted = cursor.getInt(COL_ENCRYPTED); @@ -388,39 +398,39 @@ public class PubkeyListActivity extends ListActivity implements EventListener { ((TableRow)changePasswordView.findViewById(R.id.old_password_prompt)) .setVisibility(encrypted != 0 ? View.VISIBLE : View.GONE); new AlertDialog.Builder(PubkeyListActivity.this) - .setView(changePasswordView) - .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String oldPassword = ((EditText)changePasswordView.findViewById(R.id.old_password)).getText().toString(); - String password1 = ((EditText)changePasswordView.findViewById(R.id.password1)).getText().toString(); - String password2 = ((EditText)changePasswordView.findViewById(R.id.password2)).getText().toString(); - - if (!password1.equals(password2)) { - new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(R.string.alert_passwords_do_not_match_msg) - .setPositiveButton(android.R.string.ok, null) - .create().show(); - return; - } - - try { - if (!pubkeydb.changePassword(id, oldPassword, password1)) + .setView(changePasswordView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String oldPassword = ((EditText)changePasswordView.findViewById(R.id.old_password)).getText().toString(); + String password1 = ((EditText)changePasswordView.findViewById(R.id.password1)).getText().toString(); + String password2 = ((EditText)changePasswordView.findViewById(R.id.password2)).getText().toString(); + + if (!password1.equals(password2)) { new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(R.string.alert_wrong_password_msg) - .setPositiveButton(android.R.string.ok, null) - .create().show(); - else - updateHandler.sendEmptyMessage(-1); - } catch (Exception e) { - Log.e(TAG, "Could not change private key password", e); - new AlertDialog.Builder(PubkeyListActivity.this) - .setMessage(R.string.alert_key_corrupted_msg) - .setPositiveButton(android.R.string.ok, null) - .create().show(); + .setMessage(R.string.alert_passwords_do_not_match_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + return; + } + + try { + if (!pubkeydb.changePassword(id, oldPassword, password1)) + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_wrong_password_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + else + updateHandler.sendEmptyMessage(-1); + } catch (Exception e) { + Log.e(TAG, "Could not change private key password", e); + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_key_corrupted_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + } } - } - }) - .setNegativeButton(android.R.string.cancel, null).create().show(); + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); return true; } diff --git a/src/org/connectbot/bean/PortForwardBean.java b/src/org/connectbot/bean/PortForwardBean.java new file mode 100644 index 0000000..f5fd7ac --- /dev/null +++ b/src/org/connectbot/bean/PortForwardBean.java @@ -0,0 +1,235 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +*/ +package org.connectbot.bean; + +import org.connectbot.util.HostDatabase; + +import android.content.ContentValues; + + +/** + * @author Kenny Root + * + */ +public class PortForwardBean { + private long id = -1; + private long hostId = -1; + private String nickname = null; + private String type = null; + private int sourcePort = -1; + private String destAddr = null; + private int destPort = -1; + + private boolean enabled = false; + private Object identifier = null; + + /** + * @param id database ID of port forward + * @param nickname Nickname to use to identify port forward + * @param type One of the port forward types from {@link HostDatabase} + * @param sourcePort Source port number + * @param destAddr Destination hostname or IP address + * @param destPort Destination port number + */ + public PortForwardBean(long id, long hostId, String nickname, String type, int sourcePort, String destAddr, int destPort) { + this.id = id; + this.hostId = hostId; + this.nickname = nickname; + this.type = type; + this.sourcePort = sourcePort; + this.destAddr = destAddr; + this.destPort = destPort; + } + + /** + * + */ + public PortForwardBean(long hostId) { + this.hostId = hostId; + } + + /** + * @param type One of the port forward types from {@link HostDatabase} + * @param source Source port number + * @param dest Destination is "host:port" format + */ + public PortForwardBean(long hostId, String nickname, String type, String source, String dest) { + this.hostId = hostId; + this.nickname = nickname; + this.type = type; + this.sourcePort = Integer.parseInt(source); + + this.setDest(dest); + } + + /** + * @param id the id to set + */ + public void setId(long id) { + this.id = id; + } + + /** + * @return the id + */ + public long getId() { + return id; + } + + /** + * @param nickname the nickname to set + */ + public void setNickname(String nickname) { + this.nickname = nickname; + } + + /** + * @return the nickname + */ + public String getNickname() { + return nickname; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * @return the type + */ + public String getType() { + return type; + } + + /** + * @param sourcePort the sourcePort to set + */ + public void setSourcePort(int sourcePort) { + this.sourcePort = sourcePort; + } + + /** + * @return the sourcePort + */ + public int getSourcePort() { + return sourcePort; + } + + /** + * @param dest The destination in "host:port" format + */ + public void setDest(String dest) { + String[] destSplit = dest.split(":"); + this.destAddr = destSplit[0]; + this.destPort = Integer.parseInt(destSplit[1]); + } + + /** + * @param destAddr the destAddr to set + */ + public void setDestAddr(String destAddr) { + this.destAddr = destAddr; + } + + /** + * @return the destAddr + */ + public String getDestAddr() { + return destAddr; + } + + /** + * @param destPort the destPort to set + */ + public void setDestPort(int destPort) { + this.destPort = destPort; + } + + /** + * @return the destPort + */ + public int getDestPort() { + return destPort; + } + + /** + * @param enabled the enabled to set + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * @return the enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * @param identifier the identifier of this particular type to set + */ + public void setIdentifier(Object identifier) { + this.identifier = identifier; + } + + /** + * @return the identifier used by this particular type + */ + public Object getIdentifier() { + return identifier; + } + + /** + * @return human readable description of the port forward + */ + public CharSequence getDescription() { + String description = "Unknown type"; + + if (HostDatabase.PORTFORWARD_LOCAL.equals(type)) { + description = String.format("Local port %d to %s:%d", sourcePort, destAddr, destPort); + } else if (HostDatabase.PORTFORWARD_REMOTE.equals(type)) { + description = String.format("Remote port %d to %s:%d", sourcePort, destAddr, destPort); + } else if (HostDatabase.PORTFORWARD_DYNAMIC4.equals(type)) { + description = String.format("Dynamic port %d (SOCKS4)", sourcePort); + } else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(type)) { + description = String.format("Dynamic port %d (SOCKS5)", sourcePort); + } + + return description; + } + + /** + * @return + */ + public ContentValues getValues() { + ContentValues values = new ContentValues(); + + values.put(HostDatabase.FIELD_PORTFORWARD_HOSTID, hostId); + values.put(HostDatabase.FIELD_PORTFORWARD_NICKNAME, nickname); + values.put(HostDatabase.FIELD_PORTFORWARD_TYPE, type); + values.put(HostDatabase.FIELD_PORTFORWARD_SOURCEPORT, sourcePort); + values.put(HostDatabase.FIELD_PORTFORWARD_DESTADDR, destAddr); + values.put(HostDatabase.FIELD_PORTFORWARD_DESTPORT, destPort); + + return values; + } +} diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index bb1ae20..9bf4790 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -25,9 +25,14 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; import org.connectbot.R; import org.connectbot.TerminalView; +import org.connectbot.bean.PortForwardBean; import org.connectbot.util.HostDatabase; import org.connectbot.util.PubkeyDatabase; import org.connectbot.util.PubkeyUtils; @@ -50,6 +55,7 @@ import com.trilead.ssh2.Connection; import com.trilead.ssh2.ConnectionMonitor; import com.trilead.ssh2.InteractiveCallback; import com.trilead.ssh2.KnownHosts; +import com.trilead.ssh2.LocalPortForwarder; import com.trilead.ssh2.ServerHostKeyVerifier; import com.trilead.ssh2.Session; import com.trilead.ssh2.crypto.PEMDecoder; @@ -89,6 +95,8 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal ); } + private List<PortForwardBean> portForwards = new LinkedList<PortForwardBean>(); + public int color[] = { Color.BLACK, Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.WHITE, }; @@ -125,10 +133,7 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal private boolean ctrlPressed = false; private boolean altPressed = false; private boolean shiftPressed = false; - - //protected PubkeyDatabase pubkeydb = null; - //protected Cursor pubkeys = null; - //final int COL_NICKNAME, COL_TYPE, COL_PRIVATE, COL_PUBLIC, COL_ENCRYPTED; + private boolean pubkeysExhausted = false; private boolean forcedSize = false; @@ -236,20 +241,14 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal this.buffer.setDisplay(this); this.buffer.setCursorPosition(0, 0); + // TODO Change this when hosts are beans as well + this.portForwards = manager.hostdb.getPortForwardsForHost(manager.hostdb.findHostByNickname(nickname)); + // prepare the ssh connection for opening // we perform the actual connection later in startConnection() this.outputLine(String.format("Connecting to %s:%d", hostname, port)); this.connection = new Connection(hostname, port); this.connection.addConnectionMonitor(this); - -// this.pubkeydb = new PubkeyDatabase(manager); -// this.pubkeys = this.pubkeydb.allPubkeys(); -// -// this.COL_NICKNAME = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_NICKNAME); -// this.COL_TYPE = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_TYPE); -// this.COL_PRIVATE = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PRIVATE); -// this.COL_PUBLIC = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_PUBLIC); -// this.COL_ENCRYPTED = pubkeys.getColumnIndexOrThrow(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED); } public final static int AUTH_TRIES = 20; @@ -517,6 +516,14 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal this.fullyConnected = true; + // Start up predefined port forwards + ListIterator<PortForwardBean> itr = portForwards.listIterator(); + while (itr.hasNext()) { + PortForwardBean pfb = itr.next(); + Log.d(TAG, String.format("Enabling port forward %s", pfb.getDescription())); + enablePortForward(pfb); + } + // finally send any post-login string, if requested this.injectString(postlogin); @@ -987,6 +994,117 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal return -1; } + /** + * Adds the {@link PortForwardBean} to the list. + * @param portForward the port forward bean to add + * @return true on successful addition + */ + public boolean addPortForward(PortForwardBean portForward) { + return this.portForwards.add(portForward); + } + + /** + * Removes the {@link PortForwardBean} from the list. + * @param portForward the port forward bean to remove + * @return true on successful removal + */ + public boolean removePortForward(PortForwardBean portForward) { + // Make sure we don't have a phantom forwarder. + disablePortForward(portForward); + + return this.portForwards.remove(portForward); + } + + /** + * @return the list of port forwards + */ + public List<PortForwardBean> getPortForwards() { + return portForwards; + } + + /** + * Enables a port forward member. After calling this method, the port forward should + * be operational. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward setup + */ + public boolean enablePortForward(PortForwardBean portForward) { + if (!this.portForwards.contains(portForward)) { + Log.e(TAG, "Attempt to enable port forward not in list"); + return false; + } + + if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) { + LocalPortForwarder lpf = null; + try { + lpf = this.connection.createLocalPortForwarder(portForward.getSourcePort(), portForward.getDestAddr(), portForward.getDestPort()); + } catch (IOException e) { + Log.e(TAG, "Could not create local port forward", e); + return false; + } + + if (lpf == null) { + Log.e(TAG, "returned LocalPortForwarder object is null"); + return false; + } + + portForward.setIdentifier(lpf); + portForward.setEnabled(true); + return true; + } else if (HostDatabase.PORTFORWARD_REMOTE.equals(portForward.getType())) { + try { + this.connection.requestRemotePortForwarding("", portForward.getSourcePort(), portForward.getDestAddr(), portForward.getDestPort()); + } catch (IOException e) { + Log.e(TAG, "Could not create remote port forward", e); + return false; + } + + portForward.setEnabled(false); + return true; + } else { + // Unsupported type + Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType())); + return false; + } + } + + /** + * Disables a port forward member. After calling this method, the port forward should + * be non-functioning. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward tear-down + */ + public boolean disablePortForward(PortForwardBean portForward) { + if (portForward.getType() == HostDatabase.PORTFORWARD_LOCAL) { + LocalPortForwarder lpf = null; + lpf = (LocalPortForwarder)portForward.getIdentifier(); + + if (!portForward.isEnabled() || lpf == null) + return false; + + portForward.setEnabled(false); + try { + lpf.close(); + } catch (IOException e) { + Log.e(TAG, "Could not stop local port forwarder, setting enabled to false", e); + return false; + } + + return true; + } else if (portForward.getType() == HostDatabase.PORTFORWARD_REMOTE) { + portForward.setEnabled(false); + try { + this.connection.cancelRemotePortForwarding(portForward.getSourcePort()); + } catch (IOException e) { + Log.e(TAG, "Could not stop remote port forwarding, setting enabled to false", e); + return false; + } + + return true; + } else { + return false; + } + } } diff --git a/src/org/connectbot/util/HostDatabase.java b/src/org/connectbot/util/HostDatabase.java index fe4c8d4..59d6326 100644 --- a/src/org/connectbot/util/HostDatabase.java +++ b/src/org/connectbot/util/HostDatabase.java @@ -18,6 +18,11 @@ package org.connectbot.util; +import java.util.LinkedList; +import java.util.List; + +import org.connectbot.bean.PortForwardBean; + import com.trilead.ssh2.KnownHosts; import android.content.ContentValues; @@ -38,7 +43,7 @@ public class HostDatabase extends SQLiteOpenHelper { public final static String TAG = HostDatabase.class.toString(); public final static String DB_NAME = "hosts"; - public final static int DB_VERSION = 11; + public final static int DB_VERSION = 12; public final static String TABLE_HOSTS = "hosts"; public final static String FIELD_HOST_NICKNAME = "nickname"; @@ -52,11 +57,24 @@ public class HostDatabase extends SQLiteOpenHelper { public final static String FIELD_HOST_USEKEYS = "usekeys"; public final static String FIELD_HOST_POSTLOGIN = "postlogin"; public final static String FIELD_HOST_PUBKEYID = "pubkeyid"; + + public final static String TABLE_PORTFORWARDS = "portforwards"; + public final static String FIELD_PORTFORWARD_HOSTID = "hostid"; + public final static String FIELD_PORTFORWARD_NICKNAME = "nickname"; + public final static String FIELD_PORTFORWARD_TYPE = "type"; + public final static String FIELD_PORTFORWARD_SOURCEPORT = "sourceport"; + public final static String FIELD_PORTFORWARD_DESTADDR = "destaddr"; + public final static String FIELD_PORTFORWARD_DESTPORT = "destport"; public final static String COLOR_RED = "red"; public final static String COLOR_GREEN = "green"; public final static String COLOR_BLUE = "blue"; public final static String COLOR_GRAY = "gray"; + + public final static String PORTFORWARD_LOCAL = "local"; + public final static String PORTFORWARD_REMOTE = "remote"; + public final static String PORTFORWARD_DYNAMIC4 = "dynamic4"; + public final static String PORTFORWARD_DYNAMIC5 = "dynamic5"; public final static long PUBKEYID_NEVER = -2; public final static long PUBKEYID_ANY = -1; @@ -86,6 +104,14 @@ public class HostDatabase extends SQLiteOpenHelper { this.createHost(db, "cron@server.example.com", "cron", "server.example.com", 22, COLOR_GRAY, PUBKEYID_ANY); this.createHost(db, "backup@example.net", "backup", "example.net", 22, COLOR_BLUE, PUBKEYID_ANY); + db.execSQL("CREATE TABLE " + TABLE_PORTFORWARDS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_PORTFORWARD_HOSTID + " INTEGER, " + + FIELD_PORTFORWARD_NICKNAME + " TEXT, " + + FIELD_PORTFORWARD_TYPE + " TEXT NOT NULL DEFAULT " + PORTFORWARD_LOCAL + ", " + + FIELD_PORTFORWARD_SOURCEPORT + " INTEGER NOT NULL DEFAULT 8080, " + + FIELD_PORTFORWARD_DESTADDR + " TEXT, " + + FIELD_PORTFORWARD_DESTPORT + " TEXT)"); } @Override @@ -94,12 +120,23 @@ public class HostDatabase extends SQLiteOpenHelper { // shot without warning. if (oldVersion <= 9) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_HOSTS); - onCreate(db); + onCreate(db); + return; } - if (oldVersion == 10) { + switch (oldVersion) { + case 10: db.execSQL("ALTER TABLE " + TABLE_HOSTS + " ADD COLUMN " + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY); + case 11: + db.execSQL("CREATE TABLE " + TABLE_PORTFORWARDS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_PORTFORWARD_HOSTID + " INTEGER, " + + FIELD_PORTFORWARD_NICKNAME + " TEXT, " + + FIELD_PORTFORWARD_TYPE + " TEXT NOT NULL DEFAULT " + PORTFORWARD_LOCAL + ", " + + FIELD_PORTFORWARD_SOURCEPORT + " INTEGER NOT NULL DEFAULT 8080, " + + FIELD_PORTFORWARD_DESTADDR + " TEXT, " + + FIELD_PORTFORWARD_DESTPORT + " INTEGER)"); } } @@ -121,6 +158,49 @@ public class HostDatabase extends SQLiteOpenHelper { } /** + * Find a specific host ID + * @param nickname Nickname field of host to find + */ + public long findHostByNickname(String nickname) { + SQLiteDatabase db = this.getReadableDatabase(); + long id = -1; + + Cursor c = db.query(TABLE_HOSTS, new String[] { "_id" }, + FIELD_HOST_NICKNAME + " = ?", new String[] { nickname }, + null, null, null); + + if (c != null && c.moveToFirst()) + id = c.getLong(0); + + c.close(); + db.close(); + + return id; + } + + /** + * Find a host's nickname in the database by its ID. + * @param hostId + * @return the host's nickname + */ + public String findNicknameById(long hostId) { + SQLiteDatabase db = this.getReadableDatabase(); + String nickname = "unsaved host"; + + Cursor c = db.query(TABLE_HOSTS, new String[] { FIELD_HOST_NICKNAME }, + "_id = ?", new String[] { String.valueOf(hostId) }, + null, null, null); + + if (c != null && c.moveToFirst()) + nickname = c.getString(0); + + c.close(); + db.close(); + + return nickname; + } + + /** * Create a new host using the given parameters, and return its new * <code>_id</code> value. */ @@ -188,9 +268,12 @@ public class HostDatabase extends SQLiteOpenHelper { return result; } - + /** * Record the given hostkey into database under this nickname. + * @param hostname + * @param hostkeyalgo + * @param hostkey */ public void saveKnownHost(String hostname, String hostkeyalgo, byte[] hostkey) { @@ -207,6 +290,7 @@ public class HostDatabase extends SQLiteOpenHelper { /** * Build list of known hosts for Trilead library. + * @return */ public KnownHosts getKnownHosts() { KnownHosts known = new KnownHosts(); @@ -242,6 +326,8 @@ public class HostDatabase extends SQLiteOpenHelper { /** * Find the pubkey to use for a given nickname. + * @param nickname of host + * @return pubkey ID in database */ public long getPubkeyId(String nickname) { long result = PUBKEYID_ANY; @@ -256,6 +342,11 @@ public class HostDatabase extends SQLiteOpenHelper { return result; } + /** + * Set which public key to use for a particular host. + * @param hostId host ID in database + * @param pubkeyId public key ID in databae + */ public void setPubkeyId(long hostId, long pubkeyId) { SQLiteDatabase db = this.getWritableDatabase(); @@ -267,9 +358,10 @@ public class HostDatabase extends SQLiteOpenHelper { Log.d(TAG, String.format("Updated host id %d to use pubkey id %d", hostId, pubkeyId)); } - + /** - * Unset any hosts using a pubkey that has been deleted. + * Unset any hosts using a pubkey ID that has been deleted. + * @param pubkeyId */ public void stopUsingPubkey(long pubkeyId) { if (pubkeyId < 0) return; @@ -284,4 +376,66 @@ public class HostDatabase extends SQLiteOpenHelper { Log.d(TAG, String.format("Set all hosts using pubkey id %d to -1", pubkeyId)); } + + /* + * Methods for dealing with port forwards attached to hosts + */ + + /** + * Returns a list of all the port forwards associated with a particular host ID. + * @param hostId ID of host + * @return port forwards associated with host ID + */ + public List<PortForwardBean> getPortForwardsForHost(long hostId) { + SQLiteDatabase db = this.getReadableDatabase(); + List<PortForwardBean> portForwards = new LinkedList<PortForwardBean>(); + + Cursor c = db.query(TABLE_PORTFORWARDS, new String[] { + "_id", FIELD_PORTFORWARD_NICKNAME, FIELD_PORTFORWARD_TYPE, FIELD_PORTFORWARD_SOURCEPORT, + FIELD_PORTFORWARD_DESTADDR, FIELD_PORTFORWARD_DESTPORT }, + FIELD_PORTFORWARD_HOSTID + " = ?", new String[] { String.valueOf(hostId) }, + null, null, null); + + while (c.moveToNext()) { + PortForwardBean pfb = new PortForwardBean( + c.getInt(0), + hostId, + c.getString(1), + c.getString(2), + c.getInt(3), + c.getString(4), + c.getInt(5)); + portForwards.add(pfb); + } + + c.close(); + db.close(); + + return portForwards; + } + + + /** + * Update the parameters of a port forward in the database. + * @param port forwardId ID of port forward in database + * @param values + * @return true on success + */ + public boolean savePortForward(PortForwardBean pfb) { + boolean success = false; + SQLiteDatabase db = this.getWritableDatabase(); + + if (pfb.getId() < 0) { + long id = db.insert(TABLE_PORTFORWARDS, null, pfb.getValues()); + pfb.setId(id); + success = true; + } else { + if (db.update(TABLE_PORTFORWARDS, pfb.getValues(), "_id = ?", new String[] { String.valueOf(pfb.getId()) }) > 0) + success = true; + } + + db.close(); + + return success; + } } |