/*******************************************************************************
* Copyright (c) 2012, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.internal.server.sftpfile;
import com.jcraft.jsch.*;
import java.net.URI;
import org.eclipse.core.runtime.*;
import org.eclipse.orion.internal.server.servlets.Activator;
import org.eclipse.orion.server.core.LogHelper;
import org.eclipse.osgi.util.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that maintains a small cache of open SFTP channels.
*/
public class ChannelCache {
/**
* Duration to keep the cache alive.
*/
private static final long CACHE_TIMEOUT = 60000;
/**
* Only cache one channel at a time for now.
*/
private static SynchronizedChannel cache;
/**
* The host that we are currently caching for
*/
private static URI cacheHost;
private static long cacheExpiry;
/**
* Cleans up a channel at the end of an operation. This method MUST NOT
* throw exceptions because the caller is trying to open a different unrelated channel.
*/
private static void closeChannel() {
//clear fields first in case of failure
URI hostToClose = cacheHost;
SynchronizedChannel channelToClose = cache;
cache = null;
cacheHost = null;
if (channelToClose == null)
return;
try {
channelToClose.disconnect();
} catch (Exception e) {
String msg = NLS.bind("Failure closing connection to {0}", hostToClose, e.getMessage()); //$NON-NLS-1$
LogHelper.log(new Status(IStatus.ERROR, Activator.PI_SERVER_SERVLETS, msg, e));
}
}
public static synchronized SynchronizedChannel getChannel(URI host) throws CoreException {
Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.sftp"); //$NON-NLS-1$
if (isCacheAlive(host, logger))
return cache;
//discard the current channel because we will cache a new one below
closeChannel();
cache = openChannel(host, logger);
cacheHost = host;
return cache;
}
private static boolean isCacheAlive(URI host, Logger logger) {
if (cacheHost == null)
return false;
if (!cacheHost.equals(host))
return false;
if (!cache.isConnected())
return false;
if (System.currentTimeMillis() > cacheExpiry) {
if (logger.isInfoEnabled())
logger.info("Cache expired: " + host); //$NON-NLS-1$
return false;
}
return true;
}
/**
* Something went wrong, so flush the cached channel for this host.
*/
public static synchronized void flush(URI host) {
if (host != null && host.equals(cacheHost)) {
closeChannel();
}
}
private static SynchronizedChannel openChannel(URI host, Logger logger) throws CoreException {
if (logger.isInfoEnabled())
logger.info("Opening channel to: " + host); //$NON-NLS-1$
JSch jsch = new JSch();
int port = host.getPort();
if (port < 0)
port = 22;//default SFTP port
String user = host.getUserInfo();
//standard URI format of user:password
String[] userParts = user != null ? user.split(":") : null; //$NON-NLS-1$
if (userParts == null || userParts.length != 2)
throw authFail(host);
try {
Session session = jsch.getSession(userParts[0], host.getHost(), port);
String password = userParts[1];
session.setPassword(password);
//don't require host key to be in orion server's known hosts file
session.setConfig("StrictHostKeyChecking", "no"); //$NON-NLS-1$ //$NON-NLS-2$
session.connect();
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); //$NON-NLS-1$
channel.connect();
cacheExpiry = System.currentTimeMillis() + CACHE_TIMEOUT;
return new SynchronizedChannel(channel);
} catch (Exception e) {
//Message is hard-coded in jsch implementation and is the only way to distinguish from other errors
if ("Auth fail".equals(e.getMessage())) //$NON-NLS-1$
throw authFail(host);
String msg = NLS.bind("Failure connecting to {0}", host, e.getMessage());
throw new CoreException(new Status(IStatus.ERROR, Activator.PI_SERVER_SERVLETS, msg, e));
}
}
/**
* Returns a core exception indicating failure to authenticate with the given host.
*/
private static CoreException authFail(URI host) {
return new AuthCoreException(host.getHost());
}
}