/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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 org.jclouds.ssh.jsch;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.inject.Inject;
import org.apache.commons.io.input.ProxyInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jclouds.logging.Logger;
import org.jclouds.ssh.ExecResponse;
import org.jclouds.ssh.SshClient;
import org.jclouds.ssh.SshException;
import org.jclouds.util.Utils;
import com.google.common.io.Closeables;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
/**
*
* @author Adrian Cole
*/
public class JschSshClient implements SshClient {
private final class CloseFtpChannelOnCloseInputStream extends ProxyInputStream {
private final ChannelSftp sftp;
private CloseFtpChannelOnCloseInputStream(InputStream proxy, ChannelSftp sftp) {
super(proxy);
this.sftp = sftp;
}
@Override
public void close() throws IOException {
super.close();
if (sftp != null)
sftp.disconnect();
}
}
private final InetAddress host;
private final int port;
private final String username;
private final String password;
@Resource
protected Logger logger = Logger.NULL;
private Session session;
private final byte[] privateKey;
final byte[] emptyPassPhrase = new byte[0];
private final int timeout;
@Inject
public JschSshClient(InetSocketAddress socket, int timeout, String username, String password) {
this.host = checkNotNull(socket, "socket").getAddress();
checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
this.port = socket.getPort();
this.username = checkNotNull(username, "username");
this.password = checkNotNull(password, "password");
this.timeout = timeout;
this.privateKey = null;
}
@Inject
public JschSshClient(InetSocketAddress socket, int timeout, String username, byte[] privateKey) {
this.host = checkNotNull(socket, "socket").getAddress();
checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
this.port = socket.getPort();
this.username = checkNotNull(username, "username");
this.timeout = timeout;
this.password = null;
this.privateKey = checkNotNull(privateKey, "privateKey");
}
public InputStream get(String path) {
checkNotNull(path, "path");
checkConnected();
logger.debug("%s@%s:%d: Opening sftp Channel.", username, host.getHostAddress(), port);
ChannelSftp sftp = null;
try {
sftp = (ChannelSftp) session.openChannel("sftp");
sftp.connect();
} catch (JSchException e) {
throw new SshException(String.format("%s@%s:%d: Error connecting to sftp.", username, host
.getHostAddress(), port), e);
}
try {
return new CloseFtpChannelOnCloseInputStream(sftp.get(path), sftp);
} catch (SftpException e) {
throw new SshException(String.format("%s@%s:%d: Error getting path: %s", username, host
.getHostAddress(), port, path), e);
}
}
public void put(String path, InputStream contents) {
checkNotNull(path, "path");
checkNotNull(contents, "contents");
checkConnected();
logger.debug("%s@%s:%d: Opening sftp Channel.", username, host.getHostAddress(), port);
ChannelSftp sftp = null;
try {
sftp = (ChannelSftp) session.openChannel("sftp");
sftp.connect();
} catch (JSchException e) {
throw new SshException(String.format("%s@%s:%d: Error connecting to sftp.", username, host
.getHostAddress(), port), e);
}
try {
sftp.put(contents, path);
} catch (SftpException e) {
throw new SshException(String.format("%s@%s:%d: Error putting path: %s", username, host
.getHostAddress(), port, path), e);
} finally {
Closeables.closeQuietly(contents);
}
}
private void checkConnected() {
checkState(session != null && session.isConnected(), String.format(
"%s@%s:%d: SFTP not connected!", username, host.getHostAddress(), port));
}
@PostConstruct
public void connect() {
disconnect();
JSch jsch = new JSch();
session = null;
try {
session = jsch.getSession(username, host.getHostAddress(), port);
if (timeout != 0)
session.setTimeout(timeout);
logger.debug("%s@%s:%d: Session created.", username, host.getHostAddress(), port);
if (password != null) {
session.setPassword(password);
} else {
jsch.addIdentity(username, privateKey, null, emptyPassPhrase);
}
} catch (JSchException e) {
throw new SshException(String.format("%s@%s:%d: Error creating session.", username, host
.getHostAddress(), port), e);
}
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
try {
session.connect();
} catch (JSchException e) {
throw new SshException(String.format("%s@%s:%d: Error connecting to session.", username,
host.getHostAddress(), port), e);
}
logger.debug("%s@%s:%d: Session connected.", username, host.getHostAddress(), port);
}
@PreDestroy
public void disconnect() {
if (session != null && session.isConnected())
session.disconnect();
}
public ExecResponse exec(String command) {
checkConnected();
ChannelExec executor = null;
try {
try {
executor = (ChannelExec) session.openChannel("exec");
} catch (JSchException e) {
throw new SshException(String.format("%s@%s:%d: Error connecting to exec.", username,
host.getHostAddress(), port), e);
}
executor.setCommand(command);
ByteArrayOutputStream error = new ByteArrayOutputStream();
executor.setErrStream(error);
try {
executor.connect();
return new ExecResponse(Utils.toStringAndClose(executor.getInputStream()), error
.toString(), executor.getExitStatus());
} catch (Exception e) {
throw new SshException(String.format("%s@%s:%d: Error executing command: ", username,
host.getHostAddress(), port, command), e);
}
} finally {
if (executor != null)
executor.disconnect();
}
}
@Override
public String getHostAddress() {
return this.host.getHostAddress();
}
@Override
public String getUsername() {
return this.username;
}
}