/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo 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.
*
* OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.netbeans.lib.cvsclient.connection;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Properties;
import javax.net.SocketFactory;
import org.netbeans.lib.cvsclient.command.CommandAbortedException;
import org.netbeans.lib.cvsclient.util.LoggedDataInputStream;
import org.netbeans.lib.cvsclient.util.LoggedDataOutputStream;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
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;
/**
* Provides SSH tunnel for :ext: connection method.
*
* @author Maros Sandor
*/
public class SSHConnection extends AbstractConnection {
private static final String CVS_SERVER_COMMAND = System.getenv("CVS_SERVER") != null ? System.getenv("CVS_SERVER") + " server"
: "cvs server"; // NOI18N
private final SocketFactory socketFactory;
private final String host;
private final int port;
private final String username;
private final String password;
private Session session;
private ChannelExec channel;
/**
* Creates new SSH connection object.
*
* @param socketFactory
* socket factory to use when connecting to SSH server
* @param host
* host names of the SSH server
* @param port
* port number of SSH server
* @param username
* SSH username
* @param password
* SSH password
*/
public SSHConnection(SocketFactory socketFactory, String host, int port, String username, String password) {
this.socketFactory = socketFactory;
this.host = host;
this.port = port;
this.username = username != null ? username : System.getProperty("user.name"); // NOI18N
this.password = password;
}
@Override
public void open() throws AuthenticationException, CommandAbortedException {
if (password == null) {
System.out.println("NULL passwd");
throw new AuthenticationException("null password", "null password");
}
Properties props = new Properties();
props.put("StrictHostKeyChecking", "no"); // NOI18N
JSch jsch = new JSch();
try {
session = jsch.getSession(username, host, port);
session.setSocketFactory(new SocketFactoryBridge());
session.setConfig(props);
session.setUserInfo(new SSHUserInfo());
session.connect();
} catch (JSchException e) {
throw new AuthenticationException(e, "NbBundle.getMessage(SSHConnection.class, 'BK3001'");
}
try {
channel = (ChannelExec) session.openChannel("exec"); // NOI18N
channel.setCommand(CVS_SERVER_COMMAND);
setInputStream(new LoggedDataInputStream(new SshChannelInputStream(channel)));
setOutputStream(new LoggedDataOutputStream(channel.getOutputStream()));
channel.connect();
} catch (JSchException e) {
IOException ioe = new IOException("NbBundle.getMessage(SSHConnection.class, 'BK3002'");
ioe.initCause(e);
throw new AuthenticationException(ioe, "NbBundle.getMessage(SSHConnection.class, 'BK3003'");
} catch (IOException e) {
throw new AuthenticationException(e, "NbBundle.getMessage(SSHConnection.class, 'BK3003'");
}
}
/**
* Verifies that we can successfuly connect to the SSH server and run 'cvs server' command on it.
*
* @throws AuthenticationException
* if connection to the SSH server cannot be established (network problem)
*/
@Override
public void verify() throws AuthenticationException {
try {
open();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
}
if (channel.getExitStatus() != -1) {
throw new AuthenticationException(CVS_SERVER_COMMAND, "NbBundle.getMessage(SSHConnection.class, 'BK3004'");
}
close();
} catch (CommandAbortedException e) {
throw new AuthenticationException(e, "NbBundle.getMessage(SSHConnection.class, 'BK3005'");
} catch (IOException e) {
throw new AuthenticationException(e, "NbBundle.getMessage(SSHConnection.class, 'Bk3006'");
} finally {
reset();
}
}
private void reset() {
session = null;
channel = null;
setInputStream(null);
setOutputStream(null);
}
@Override
public void close() throws IOException {
if (channel != null) {
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
reset();
}
@Override
public boolean isOpen() {
return channel != null && channel.isConnected();
}
@Override
public int getPort() {
return port;
}
@Override
public void modifyInputStream(ConnectionModifier modifier) throws IOException {
modifier.modifyInputStream(getInputStream());
}
@Override
public void modifyOutputStream(ConnectionModifier modifier) throws IOException {
modifier.modifyOutputStream(getOutputStream());
}
/**
* Provides JSch with SSH password.
*/
private class SSHUserInfo implements UserInfo, UIKeyboardInteractive {
@Override
public String getPassphrase() {
return null;
}
@Override
public String getPassword() {
return password;
}
@Override
public boolean promptPassword(String message) {
return true;
}
@Override
public boolean promptPassphrase(String message) {
return true;
}
@Override
public boolean promptYesNo(String message) {
return false;
}
@Override
public void showMessage(String message) {
}
@Override
public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
String[] response = new String[prompt.length];
if (prompt.length == 1) {
response[0] = password;
}
return response;
}
}
/**
* Bridges com.jcraft.jsch.SocketFactory and javax.net.SocketFactory.
*/
private class SocketFactoryBridge implements com.jcraft.jsch.SocketFactory {
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return socketFactory.createSocket(host, port);
}
@Override
public InputStream getInputStream(Socket socket) throws IOException {
return socket.getInputStream();
}
@Override
public OutputStream getOutputStream(Socket socket) throws IOException {
return socket.getOutputStream();
}
}
private static class SshChannelInputStream extends FilterInputStream {
private final Channel channel;
public SshChannelInputStream(Channel channel) throws IOException {
super(channel.getInputStream());
this.channel = channel;
}
@Override
public int available() throws IOException {
checkChannelState();
return super.available();
}
private void checkChannelState() throws IOException {
int exitStatus = channel.getExitStatus();
if (exitStatus > 0 || exitStatus < -1) {
throw new IOException("NbBundle.getMessage(SSHConnection.class, 'BK3004'");
}
if (exitStatus == 0 || channel.isEOF()) {
throw new EOFException("NbBundle.getMessage(SSHConnection.class, 'BK3007'");
}
}
}
}