line source
/*
* ConnectBot: simple, powerful, open-source SSH client for Android
* Copyright 2007 Kenny Root, Jeffrey Sharkey
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.five_ten_sg.connectbot.transport;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URL;
import java.net.MalformedURLException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.five_ten_sg.connectbot.R;
import com.five_ten_sg.connectbot.bean.HostBean;
import com.five_ten_sg.connectbot.bean.PortForwardBean;
import com.five_ten_sg.connectbot.bean.PubkeyBean;
import com.five_ten_sg.connectbot.service.TerminalBridge;
import com.five_ten_sg.connectbot.service.TerminalManager;
import com.five_ten_sg.connectbot.service.TerminalManager.KeyHolder;
import com.five_ten_sg.connectbot.util.HostDatabase;
import com.five_ten_sg.connectbot.util.PubkeyDatabase;
import com.five_ten_sg.connectbot.util.PubkeyUtils;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import com.trilead.ssh2.AuthAgentCallback;
import com.trilead.ssh2.ChannelCondition;
import com.trilead.ssh2.Connection;
import com.trilead.ssh2.ConnectionInfo;
import com.trilead.ssh2.ConnectionMonitor;
import com.trilead.ssh2.DynamicPortForwarder;
import com.trilead.ssh2.InteractiveCallback;
import com.trilead.ssh2.KnownHosts;
import com.trilead.ssh2.LocalPortForwarder;
import com.trilead.ssh2.SCPClient;
import com.trilead.ssh2.ServerHostKeyVerifier;
import com.trilead.ssh2.Session;
import com.trilead.ssh2.HTTPProxyData;
import com.trilead.ssh2.HTTPProxyException;
import com.trilead.ssh2.crypto.PEMDecoder;
import com.trilead.ssh2.signature.DSASHA1Verify;
import com.trilead.ssh2.signature.RSASHA1Verify;
/**
* @author Kenny Root
*
*/
public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveCallback, AuthAgentCallback {
private static final String PROTOCOL = "ssh";
private static final String TAG = "ConnectBot.SSH";
private static final int DEFAULT_PORT = 22;
private static final String AUTH_PUBLICKEY = "publickey",
AUTH_PASSWORD = "password",
AUTH_KEYBOARDINTERACTIVE = "keyboard-interactive";
private final static int AUTH_TRIES = 20;
static final Pattern hostmask;
static {
hostmask = Pattern.compile("^(.+)@([0-9a-z.-]+)(:(\\d+))?$", Pattern.CASE_INSENSITIVE);
}
private boolean compression = false;
private String httpproxy = null;
private volatile boolean authenticated = false;
private volatile boolean connected = false;
private volatile boolean sessionOpen = false;
private boolean pubkeysExhausted = false;
private boolean interactiveCanContinue = true;
private Connection connection;
private Session session;
private ConnectionInfo connectionInfo;
private OutputStream stdin;
private InputStream stdout;
private InputStream stderr;
private static final int conditions = ChannelCondition.STDOUT_DATA
| ChannelCondition.STDERR_DATA
| ChannelCondition.CLOSED
| ChannelCondition.EOF;
private List<PortForwardBean> portForwards = new LinkedList<PortForwardBean>();
private int columns;
private int rows;
private int width;
private int height;
private String useAuthAgent = HostDatabase.AUTHAGENT_NO;
private String agentLockPassphrase;
public class HostKeyVerifier implements ServerHostKeyVerifier {
public boolean verifyServerHostKey(String hostname, int port,
String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException {
// read in all known hosts from hostdb
KnownHosts hosts = manager.hostdb.getKnownHosts();
Boolean result;
String matchName = String.format(Locale.US, "%s:%d", hostname, port);
String fingerprint = KnownHosts.createHexFingerprint(serverHostKeyAlgorithm, serverHostKey);
String algorithmName;
if ("ssh-rsa".equals(serverHostKeyAlgorithm))
algorithmName = "RSA";
else if ("ssh-dss".equals(serverHostKeyAlgorithm))
algorithmName = "DSA";
else if (serverHostKeyAlgorithm.startsWith("ecdsa-"))
algorithmName = "EC";
else
algorithmName = serverHostKeyAlgorithm;
switch (hosts.verifyHostkey(matchName, serverHostKeyAlgorithm, serverHostKey)) {
case KnownHosts.HOSTKEY_IS_OK:
bridge.outputLine(manager.res.getString(R.string.terminal_sucess, algorithmName, fingerprint));
return true;
case KnownHosts.HOSTKEY_IS_NEW:
// prompt user
bridge.outputLine(manager.res.getString(R.string.host_authenticity_warning, hostname));
bridge.outputLine(manager.res.getString(R.string.host_fingerprint, algorithmName, fingerprint));
result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_continue_connecting));
if (result == null) return false;
if (result.booleanValue()) {
// save this key in known database
manager.hostdb.saveKnownHost(hostname, port, serverHostKeyAlgorithm, serverHostKey);
}
return result.booleanValue();
case KnownHosts.HOSTKEY_HAS_CHANGED:
String header = String.format("@ %s @",
manager.res.getString(R.string.host_verification_failure_warning_header));
char[] atsigns = new char[header.length()];
Arrays.fill(atsigns, '@');
String border = new String(atsigns);
bridge.outputLine(border);
bridge.outputLine(manager.res.getString(R.string.host_verification_failure_warning));
bridge.outputLine(border);
bridge.outputLine(String.format(manager.res.getString(R.string.host_fingerprint),
algorithmName, fingerprint));
// Users have no way to delete keys, so we'll prompt them for now.
result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_continue_connecting));
if (result == null) return false;
if (result.booleanValue()) {
// save this key in known database
manager.hostdb.saveKnownHost(hostname, port, serverHostKeyAlgorithm, serverHostKey);
}
return result.booleanValue();
default:
return false;
}
}
}
public SSH() {
super();
}
/**
* @return protocol part of the URI
*/
public static String getProtocolName() {
return PROTOCOL;
}
public Uri getUri(String input) {
Matcher matcher = hostmask.matcher(input);
if (!matcher.matches())
return null;
StringBuilder sb = new StringBuilder();
sb.append(PROTOCOL)
.append("://")
.append(Uri.encode(matcher.group(1)))
.append('@')
.append(matcher.group(2));
String portString = matcher.group(4);
int port = DEFAULT_PORT;
if (portString != null) {
try {
port = Integer.parseInt(portString);
if (port < 1 || port > 65535) {
port = DEFAULT_PORT;
}
}
catch (NumberFormatException nfe) {
// Keep the default port
}
}
if (port != DEFAULT_PORT) {
sb.append(':')
.append(port);
}
sb.append("/#")
.append(Uri.encode(input));
Uri uri = Uri.parse(sb.toString());
return uri;
}
private void authenticate() {
try {
if (connection.authenticateWithNone(host.getUsername())) {
finishConnection();
return;
}
}
catch (Exception e) {
Log.d(TAG, "Host does not support 'none' authentication.");
}
bridge.outputLine(manager.res.getString(R.string.terminal_auth));
try {
long pubkeyId = host.getPubkeyId();
if (!pubkeysExhausted &&
pubkeyId != HostDatabase.PUBKEYID_NEVER &&
connection.isAuthMethodAvailable(host.getUsername(), AUTH_PUBLICKEY)) {
// if explicit pubkey defined for this host, then prompt for password as needed
// otherwise just try all in-memory keys held in terminalmanager
if (pubkeyId == HostDatabase.PUBKEYID_ANY) {
// try each of the in-memory keys
bridge.outputLine(manager.res
.getString(R.string.terminal_auth_pubkey_any));
for (Entry<String, KeyHolder> entry : manager.loadedKeypairs.entrySet()) {
if (entry.getValue().bean.isConfirmUse()
&& !promptForPubkeyUse(entry.getKey()))
continue;
if (this.tryPublicKey(host.getUsername(), entry.getKey(),
entry.getValue().pair)) {
finishConnection();
break;
}
}
}
else {
bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_specific));
// use a specific key for this host, as requested
PubkeyBean pubkey = manager.pubkeydb.findPubkeyById(pubkeyId);
if (pubkey == null)
bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_invalid));
else if (tryPublicKey(pubkey))
finishConnection();
}
pubkeysExhausted = true;
}
else if (interactiveCanContinue &&
connection.isAuthMethodAvailable(host.getUsername(), AUTH_KEYBOARDINTERACTIVE)) {
// this auth method will talk with us using InteractiveCallback interface
// it blocks until authentication finishes
bridge.outputLine(manager.res.getString(R.string.terminal_auth_ki));
interactiveCanContinue = false;
if (connection.authenticateWithKeyboardInteractive(host.getUsername(), this)) {
finishConnection();
}
else {
bridge.outputLine(manager.res.getString(R.string.terminal_auth_ki_fail));
}
}
else if (connection.isAuthMethodAvailable(host.getUsername(), AUTH_PASSWORD)) {
bridge.outputLine(manager.res.getString(R.string.terminal_auth_pass));
String password = bridge.getPromptHelper().requestPasswordPrompt(null,
manager.res.getString(R.string.prompt_password));
if (password != null
&& connection.authenticateWithPassword(host.getUsername(), password)) {
finishConnection();
}
else {
bridge.outputLine(manager.res.getString(R.string.terminal_auth_pass_fail));
}
}
else {
bridge.outputLine(manager.res.getString(R.string.terminal_auth_fail));
}
}
catch (IllegalStateException e) {
Log.e(TAG, "Connection went away while we were trying to authenticate", e);
return;
}
catch (Exception e) {
Log.e(TAG, "Problem during handleAuthentication()", e);
}
}
/**
* Attempt connection with database row pointed to by cursor.
* @param cursor
* @return true for successful authentication
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws IOException
*/
private boolean tryPublicKey(PubkeyBean pubkey) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
KeyPair pair = null;
if (manager.isKeyLoaded(pubkey.getNickname())) {
// load this key from memory if its already there
Log.d(TAG, String.format("Found unlocked key '%s' already in-memory", pubkey.getNickname()));
if (pubkey.isConfirmUse()) {
if (!promptForPubkeyUse(pubkey.getNickname()))
return false;
}
pair = manager.getKey(pubkey.getNickname());
}
else {
// otherwise load key from database and prompt for password as needed
String password = null;
if (pubkey.isEncrypted()) {
password = bridge.getPromptHelper().requestPasswordPrompt(null,
manager.res.getString(R.string.prompt_pubkey_password, pubkey.getNickname()));
// Something must have interrupted the prompt.
if (password == null)
return false;
}
if (PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType())) {
// load specific key using pem format
pair = PEMDecoder.decode(new String(pubkey.getPrivateKey()).toCharArray(), password);
}
else {
// load using internal generated format
PrivateKey privKey;
try {
privKey = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(),
pubkey.getType(), password);
}
catch (Exception e) {
String message = String.format("Bad password for key '%s'. Authentication failed.", pubkey.getNickname());
Log.e(TAG, message, e);
bridge.outputLine(message);
return false;
}
PublicKey pubKey = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType());
// convert key to trilead format
pair = new KeyPair(pubKey, privKey);
Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey));
}
Log.d(TAG, String.format("Unlocked key '%s'", pubkey.getNickname()));
// save this key in memory
manager.addKey(pubkey, pair);
}
return tryPublicKey(host.getUsername(), pubkey.getNickname(), pair);
}
private boolean tryPublicKey(String username, String keyNickname, KeyPair pair) throws IOException {
//bridge.outputLine(String.format("Attempting 'publickey' with key '%s' [%s]...", keyNickname, trileadKey.toString()));
boolean success = connection.authenticateWithPublicKey(username, pair);
if (!success)
bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_fail, keyNickname));
return success;
}
/**
* Internal method to request actual PTY terminal once we've finished
* authentication. If called before authenticated, it will just fail.
*/
private void finishConnection() {
authenticated = true;
for (PortForwardBean portForward : portForwards) {
try {
enablePortForward(portForward);
bridge.outputLine(manager.res.getString(R.string.terminal_enable_portfoward, portForward.getDescription()));
}
catch (Exception e) {
Log.e(TAG, "Error setting up port forward during connect", e);
}
}
if (!host.getWantSession()) {
bridge.outputLine(manager.res.getString(R.string.terminal_no_session));
bridge.onConnected();
return;
}
try {
session = connection.openSession();
if (!useAuthAgent.equals(HostDatabase.AUTHAGENT_NO))
session.requestAuthAgentForwarding(this);
if (host.getWantX11Forward()) {
try {
session.requestX11Forwarding(host.getX11Host(), host.getX11Port(), null, false);
}
catch (IOException e2) {
Log.e(TAG, "Problem while trying to setup X11 forwarding in finishConnection()", e2);
}
}
session.requestPTY(getEmulation(), columns, rows, width, height, null);
session.startShell();
stdin = session.getStdin();
stdout = session.getStdout();
stderr = session.getStderr();
sessionOpen = true;
bridge.onConnected();
}
catch (IOException e1) {
Log.e(TAG, "Problem while trying to create PTY in finishConnection()", e1);
}
}
@Override
public void connect() {
connection = new Connection(host.getHostname(), host.getPort());
connection.addConnectionMonitor(this);
try {
connection.setCompression(compression);
}
catch (IOException e) {
Log.e(TAG, "Could not enable compression!", e);
}
if (httpproxy != null && httpproxy.length() > 0) {
Log.d(TAG, "Want HTTP Proxy: " + httpproxy, null);
try {
URL u;
if (httpproxy.startsWith("http://")) {
u = new URL(httpproxy);
}
else {
u = new URL("http://" + httpproxy);
}
connection.setProxyData(new HTTPProxyData(
u.getHost(),
u.getPort(),
u.getUserInfo(),
u.getAuthority()));
bridge.outputLine("Connecting via proxy: " + httpproxy);
}
catch (MalformedURLException e) {
Log.e(TAG, "Could not parse proxy " + httpproxy, e);
// Display the reason in the text.
bridge.outputLine("Bad proxy URL: " + httpproxy);
onDisconnect();
return;
}
}
try {
/* Uncomment when debugging SSH protocol:
DebugLogger logger = new DebugLogger() {
public void log(int level, String className, String message) {
Log.d("SSH", message);
}
};
Logger.enabled = true;
Logger.logger = logger;
*/
connectionInfo = connection.connect(new HostKeyVerifier());
connected = true;
if (connectionInfo.clientToServerCryptoAlgorithm
.equals(connectionInfo.serverToClientCryptoAlgorithm)
&& connectionInfo.clientToServerMACAlgorithm
.equals(connectionInfo.serverToClientMACAlgorithm)) {
bridge.outputLine(manager.res.getString(R.string.terminal_using_algorithm,
connectionInfo.clientToServerCryptoAlgorithm,
connectionInfo.clientToServerMACAlgorithm));
}
else {
bridge.outputLine(manager.res.getString(
R.string.terminal_using_c2s_algorithm,
connectionInfo.clientToServerCryptoAlgorithm,
connectionInfo.clientToServerMACAlgorithm));
bridge.outputLine(manager.res.getString(
R.string.terminal_using_s2c_algorithm,
connectionInfo.serverToClientCryptoAlgorithm,
connectionInfo.serverToClientMACAlgorithm));
}
}
catch (HTTPProxyException e) {
Log.e(TAG, "Failed to connect to HTTP Proxy", e);
// Display the reason in the text.
bridge.outputLine("Failed to connect to HTTP Proxy.");
onDisconnect();
return;
}
catch (IOException e) {
Log.e(TAG, "Problem in SSH connection thread during authentication", e);
// Display the reason in the text.
bridge.outputLine(e.getCause().getMessage());
onDisconnect();
return;
}
try {
// enter a loop to keep trying until authentication
int tries = 0;
while (connected && !connection.isAuthenticationComplete() && tries++ < AUTH_TRIES) {
authenticate();
// sleep to make sure we dont kill system
Thread.sleep(1000);
}
}
catch (Exception e) {
Log.e(TAG, "Problem in SSH connection thread during authentication", e);
}
}
@Override
public boolean willBlock() {
if (stdout == null) return true;
try {
return stdout.available() == 0;
}
catch (Exception e) {
return true;
}
}
@Override
public int read(byte[] buffer, int start, int len) throws IOException {
int bytesRead = 0;
if (session == null)
return 0;
int newConditions = session.waitForCondition(conditions, 0);
if ((newConditions & ChannelCondition.STDOUT_DATA) != 0) {
bytesRead = stdout.read(buffer, start, len);
}
if ((newConditions & ChannelCondition.STDERR_DATA) != 0) {
byte discard[] = new byte[256];
while (stderr.available() > 0) {
stderr.read(discard);
}
}
if ((newConditions & ChannelCondition.EOF) != 0) {
onDisconnect();
throw new IOException("Remote end closed connection");
}
return bytesRead;
}
@Override
public void write(byte[] buffer) throws IOException {
if (stdin != null)
stdin.write(buffer);
}
@Override
public void write(int c) throws IOException {
if (stdin != null)
stdin.write(c);
}
@Override
public void flush() throws IOException {
if (stdin != null)
stdin.flush();
}
public void connectionLost(Throwable reason) {
onDisconnect();
}
private void onDisconnect() {
close();
bridge.dispatchDisconnect(false);
}
@Override
public void close() {
connected = false;
if (session != null) {
session.close();
session = null;
}
if (connection != null) {
connection.close();
connection = null;
}
}
@Override
public void setDimensions(int columns, int rows, int width, int height) {
this.columns = columns;
this.rows = rows;
if (sessionOpen) {
try {
session.resizePTY(columns, rows, width, height);
}
catch (IOException e) {
Log.e(TAG, "Couldn't send resize PTY packet", e);
}
}
}
@Override
public void setOptions(Map<String, String> options) {
if (options.containsKey("compression"))
compression = Boolean.parseBoolean(options.get("compression"));
if (options.containsKey("httpproxy"))
httpproxy = options.get("httpproxy");
}
@Override
public Map<String, String> getOptions() {
Map<String, String> options = new HashMap<String, String>();
options.put("compression", Boolean.toString(compression));
if (httpproxy != null)
options.put("httpproxy", httpproxy);
return options;
}
@Override
public void setCompression(boolean compression) {
this.compression = compression;
}
@Override
public void setHttpproxy(String httpproxy) {
this.httpproxy = httpproxy;
}
@Override
public void setUseAuthAgent(String useAuthAgent) {
this.useAuthAgent = useAuthAgent;
}
@Override
public boolean canForwardPorts() {
return true;
}
@Override
public List<PortForwardBean> getPortForwards() {
return portForwards;
}
@Override
public boolean addPortForward(PortForwardBean portForward) {
return portForwards.add(portForward);
}
@Override
public boolean removePortForward(PortForwardBean portForward) {
// Make sure we don't have a phantom forwarder.
disablePortForward(portForward);
return portForwards.remove(portForward);
}
@Override
public boolean enablePortForward(PortForwardBean portForward) {
if (!portForwards.contains(portForward)) {
Log.e(TAG, "Attempt to enable port forward not in list");
return false;
}
if (!authenticated)
return false;
if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) {
LocalPortForwarder lpf = null;
try {
lpf = connection.createLocalPortForwarder(
new InetSocketAddress(InetAddress.getLocalHost(), portForward.getSourcePort()),
portForward.getDestAddr(), portForward.getDestPort());
}
catch (Exception 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 {
connection.requestRemotePortForwarding("", portForward.getSourcePort(), portForward.getDestAddr(), portForward.getDestPort());
}
catch (Exception e) {
Log.e(TAG, "Could not create remote port forward", e);
return false;
}
portForward.setEnabled(true);
return true;
}
else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) {
DynamicPortForwarder dpf = null;
try {
dpf = connection.createDynamicPortForwarder(
new InetSocketAddress(InetAddress.getLocalHost(), portForward.getSourcePort()));
}
catch (Exception e) {
Log.e(TAG, "Could not create dynamic port forward", e);
return false;
}
portForward.setIdentifier(dpf);
portForward.setEnabled(true);
return true;
}
else {
// Unsupported type
Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType()));
return false;
}
}
@Override
public boolean disablePortForward(PortForwardBean portForward) {
if (!portForwards.contains(portForward)) {
Log.e(TAG, "Attempt to disable port forward not in list");
return false;
}
if (!authenticated)
return false;
if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) {
LocalPortForwarder lpf = null;
lpf = (LocalPortForwarder)portForward.getIdentifier();
if (!portForward.isEnabled() || lpf == null) {
Log.d(TAG, String.format("Could not disable %s; it appears to be not enabled or have no handler", portForward.getNickname()));
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 (HostDatabase.PORTFORWARD_REMOTE.equals(portForward.getType())) {
portForward.setEnabled(false);
try {
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 if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) {
DynamicPortForwarder dpf = null;
dpf = (DynamicPortForwarder)portForward.getIdentifier();
if (!portForward.isEnabled() || dpf == null) {
Log.d(TAG, String.format("Could not disable %s; it appears to be not enabled or have no handler", portForward.getNickname()));
return false;
}
portForward.setEnabled(false);
try {
dpf.close();
}
catch (IOException e) {
Log.e(TAG, "Could not stop dynamic port forwarder, setting enabled to false", e);
return false;
}
return true;
}
else {
// Unsupported type
Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType()));
return false;
}
}
@Override
public boolean canTransferFiles() {
return true;
}
@Override
public boolean downloadFile(String remoteFile, String localFolder) {
try {
SCPClient client = new SCPClient(connection);
if (localFolder == null || localFolder == "")
localFolder = Environment.getExternalStorageDirectory().getAbsolutePath();
File dir = new File(localFolder);
dir.mkdirs();
client.get(remoteFile, localFolder);
return true;
}
catch (IOException e) {
Log.e(TAG, "Could not download remote file", e);
return false;
}
}
@Override
public boolean uploadFile(String localFile, String remoteFile,
String remoteFolder, String mode) {
try {
SCPClient client = new SCPClient(connection);
if (remoteFolder == null)
remoteFolder = "";
if (remoteFile == null || remoteFile == "")
client.put(localFile, remoteFolder, mode);
else
client.put(localFile, remoteFile, remoteFolder, mode);
return true;
}
catch (IOException e) {
Log.e(TAG, "Could not upload local file", e);
return false;
}
}
@Override
public int getDefaultPort() {
return DEFAULT_PORT;
}
@Override
public boolean isConnected() {
return connected;
}
@Override
public boolean isSessionOpen() {
return sessionOpen;
}
@Override
public boolean isAuthenticated() {
return authenticated;
}
@Override
public String getDefaultNickname(String username, String hostname, int port) {
if (port == DEFAULT_PORT) {
return String.format(Locale.US, "%s@%s", username, hostname);
}
else {
return String.format(Locale.US, "%s@%s:%d", username, hostname, port);
}
}
@Override
public void getSelectionArgs(Uri uri, Map<String, String> selection) {
selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL);
selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment());
selection.put(HostDatabase.FIELD_HOST_HOSTNAME, uri.getHost());
int port = uri.getPort();
if (port < 0)
port = DEFAULT_PORT;
selection.put(HostDatabase.FIELD_HOST_PORT, Integer.toString(port));
selection.put(HostDatabase.FIELD_HOST_USERNAME, uri.getUserInfo());
}
@Override
public HostBean createHost(Uri uri) {
HostBean host = new HostBean();
host.setProtocol(PROTOCOL);
host.setHostname(uri.getHost());
int port = uri.getPort();
if (port < 0)
port = DEFAULT_PORT;
host.setPort(port);
host.setUsername(uri.getUserInfo());
String nickname = uri.getFragment();
if (nickname == null || nickname.length() == 0) {
host.setNickname(getDefaultNickname(host.getUsername(),
host.getHostname(), host.getPort()));
}
else {
host.setNickname(uri.getFragment());
}
return host;
}
public String getFormatHint(Context context) {
return String.format("%s@%s:%s",
context.getString(R.string.format_username),
context.getString(R.string.format_hostname),
context.getString(R.string.format_port));
}
/**
* @return do we use the network
*/
@Override
public boolean usesNetwork() {
return true;
}
/**
* Handle challenges from keyboard-interactive authentication mode.
*/
public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) {
interactiveCanContinue = true;
String[] responses = new String[numPrompts];
for (int i = 0; i < numPrompts; i++) {
// request response from user for each prompt
responses[i] = bridge.promptHelper.requestPasswordPrompt(instruction, prompt[i]);
}
return responses;
}
public Map<String, byte[]> retrieveIdentities() {
Map<String, byte[]> pubKeys = new HashMap<String, byte[]> (manager.loadedKeypairs.size());
for (Entry<String, KeyHolder> entry : manager.loadedKeypairs.entrySet()) {
KeyPair pair = entry.getValue().pair;
try {
PrivateKey privKey = pair.getPrivate();
if (privKey instanceof RSAPrivateKey) {
RSAPublicKey pubkey = (RSAPublicKey) pair.getPublic();
pubKeys.put(entry.getKey(), RSASHA1Verify.encodeSSHRSAPublicKey(pubkey));
}
else if (privKey instanceof DSAPrivateKey) {
DSAPublicKey pubkey = (DSAPublicKey) pair.getPublic();
pubKeys.put(entry.getKey(), DSASHA1Verify.encodeSSHDSAPublicKey(pubkey));
}
else
continue;
}
catch (IOException e) {
continue;
}
}
return pubKeys;
}
public KeyPair getKeyPair(byte[] publicKey) {
String nickname = manager.getKeyNickname(publicKey);
if (nickname == null)
return null;
if (useAuthAgent.equals(HostDatabase.AUTHAGENT_NO)) {
Log.e(TAG, "");
return null;
}
else if (useAuthAgent.equals(HostDatabase.AUTHAGENT_CONFIRM) ||
manager.loadedKeypairs.get(nickname).bean.isConfirmUse()) {
if (!promptForPubkeyUse(nickname))
return null;
}
return manager.getKey(nickname);
}
private boolean promptForPubkeyUse(String nickname) {
Boolean result = bridge.promptHelper.requestBooleanPrompt(null,
manager.res.getString(R.string.prompt_allow_agent_to_use_key,
nickname));
return result;
}
public boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime) {
PubkeyBean pubkey = new PubkeyBean();
// pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED);
pubkey.setNickname(comment);
pubkey.setConfirmUse(confirmUse);
pubkey.setLifetime(lifetime);
manager.addKey(pubkey, pair);
return true;
}
public boolean removeAllIdentities() {
manager.loadedKeypairs.clear();
return true;
}
public boolean removeIdentity(byte[] publicKey) {
return manager.removeKey(publicKey);
}
public boolean isAgentLocked() {
return agentLockPassphrase != null;
}
public boolean requestAgentUnlock(String unlockPassphrase) {
if (agentLockPassphrase == null)
return false;
if (agentLockPassphrase.equals(unlockPassphrase))
agentLockPassphrase = null;
return agentLockPassphrase == null;
}
public boolean setAgentLock(String lockPassphrase) {
if (agentLockPassphrase != null)
return false;
agentLockPassphrase = lockPassphrase;
return true;
}
}