/**
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.commons.file.protocol.sftp;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
import com.mucommander.commons.file.Credentials;
import com.mucommander.commons.file.FileURL;
import com.mucommander.commons.file.connection.ConnectionHandler;
/**
* Handles connections to SFTP servers.
*
* @author Arik Hadas, Maxence Bernard, Vassil Dichev
*/
class SFTPConnectionHandler extends ConnectionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(SFTPConnectionHandler.class);
Session session;
ChannelSftp channelSftp;
/** 'Public key' SSH authentication method, not supported at the moment */
private final static String PUBLIC_KEY_AUTH_METHOD = "publickey";
SFTPConnectionHandler(FileURL location) {
super(location);
}
//////////////////////////////////////
// ConnectionHandler implementation //
//////////////////////////////////////
@Override
public void startConnection() throws IOException {
LOGGER.info("starting connection to {}", realm);
try {
FileURL realm = getRealm();
// Retrieve credentials to be used to authenticate
final Credentials credentials = getCredentials();
// Throw an AuthException if no auth information, required for SSH
if(credentials ==null)
throwAuthException("Login and password required"); // Todo: localize this entry
LOGGER.trace("creating SshClient");
JSch jsch = new JSch();
// Override default port (22) if a custom port was specified in the URL
int port = realm.getPort();
if(port==-1)
port = 22;
String privateKeyPath = realm.getProperty(SFTPFile.PRIVATE_KEY_PATH_PROPERTY_NAME);
if (privateKeyPath != null) {
LOGGER.info("Using {} authentication method", PUBLIC_KEY_AUTH_METHOD);
jsch.addIdentity(privateKeyPath);
}
session = jsch.getSession(credentials.getLogin(), realm.getHost(), port);
session.setUserInfo(new PasswordAuthentication());
session.connect(5*1000);
// Init SFTP connections
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect(5*1000);
LOGGER.info("authentication complete");
}
catch(IOException e) {
LOGGER.info("IOException thrown while starting connection", e);
// Disconnect if something went wrong
if(session!=null && session.isConnected())
session.disconnect();;
session = null;
channelSftp = null;
// Re-throw exception
throw e;
} catch (JSchException e) {
LOGGER.info("Caught exception while authenticating: {}", e.getMessage());
LOGGER.debug("Exception:", e);
throwAuthException(e.getMessage());
}
}
@Override
public synchronized boolean isConnected() {
return session!=null && session.isConnected()
&& channelSftp!=null && !channelSftp.isClosed();
}
@Override
public synchronized void closeConnection() {
if(channelSftp!=null) {
channelSftp.quit();
}
if(session!=null)
session.disconnect();
}
@Override
public void keepAlive() {
// No-op, keep alive is not available and shouldn't really be necessary, SSH servers such as OpenSSH usually
// maintain connections open without limit.
}
private class PasswordAuthentication implements UserInfo, UIKeyboardInteractive {
@Override
public void showMessage(String message) {
}
@Override
public boolean promptYesNo(String message) {
return true;
}
@Override
public boolean promptPassword(String message) {
return true;
}
@Override
public boolean promptPassphrase(String message) {
return true;
}
@Override
public String getPassword() {
return credentials.getPassword();
}
@Override
public String getPassphrase() {
return credentials.getPassword();
}
@Override
public String[] promptKeyboardInteractive(String destination,
String name,
String instruction,
String[] prompt,
boolean[] echo) {
String[] result = new String[prompt.length];
for (int i=0; i<echo.length; i++)
result[i] = echo[i] ? credentials.getLogin() : credentials.getPassword();
return result;
}
}
}