| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.commons.vfs2.provider.sftp; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.time.Duration; |
| import java.util.Properties; |
| |
| import org.apache.commons.lang3.SystemUtils; |
| import org.apache.commons.lang3.time.DurationUtils; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.commons.vfs2.FileSystemException; |
| import org.apache.commons.vfs2.FileSystemOptions; |
| |
| import com.jcraft.jsch.ConfigRepository; |
| import com.jcraft.jsch.JSch; |
| import com.jcraft.jsch.JSchException; |
| import com.jcraft.jsch.Logger; |
| import com.jcraft.jsch.OpenSSHConfig; |
| import com.jcraft.jsch.Proxy; |
| import com.jcraft.jsch.ProxyHTTP; |
| import com.jcraft.jsch.ProxySOCKS5; |
| import com.jcraft.jsch.Session; |
| import com.jcraft.jsch.UserInfo; |
| |
| /** |
| * Create a JSch Session instance. |
| */ |
| public final class SftpClientFactory { |
| |
| private static final String SSH_DIR_NAME = ".ssh"; |
| private static final String OPENSSH_CONFIG_NAME = "config"; |
| private static final Log LOG = LogFactory.getLog(SftpClientFactory.class); |
| |
| static { |
| JSch.setLogger(new JSchLogger()); |
| } |
| |
| private SftpClientFactory() { |
| } |
| |
| /** |
| * Creates a new connection to the server. |
| * |
| * @param hostname The name of the host to connect to. |
| * @param port The port to use. |
| * @param username The user's id. |
| * @param password The user's password. |
| * @param fileSystemOptions The FileSystem options. |
| * @return A Session, never null. |
| * @throws FileSystemException if an error occurs. |
| */ |
| public static Session createConnection(final String hostname, final int port, final char[] username, |
| final char[] password, final FileSystemOptions fileSystemOptions) throws FileSystemException { |
| final JSch jsch = new JSch(); |
| |
| // new style - user passed |
| final SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance(); |
| final File knownHostsFile = builder.getKnownHosts(fileSystemOptions); |
| final IdentityProvider[] identities = builder.getIdentityProvider(fileSystemOptions); |
| final IdentityRepositoryFactory repositoryFactory = builder.getIdentityRepositoryFactory(fileSystemOptions); |
| final ConfigRepository configRepository = builder.getConfigRepository(fileSystemOptions); |
| final boolean loadOpenSSHConfig = builder.isLoadOpenSSHConfig(fileSystemOptions); |
| |
| final File sshDir = findSshDir(); |
| |
| setKnownHosts(jsch, sshDir, knownHostsFile); |
| |
| if (repositoryFactory != null) { |
| jsch.setIdentityRepository(repositoryFactory.create(jsch)); |
| } |
| |
| addIdentities(jsch, sshDir, identities); |
| setConfigRepository(jsch, sshDir, configRepository, loadOpenSSHConfig); |
| |
| final Session session; |
| try { |
| session = jsch.getSession(new String(username), hostname, port); |
| if (password != null) { |
| session.setPassword(new String(password)); |
| } |
| |
| final Duration sessionTimeout = builder.getSessionTimeout(fileSystemOptions); |
| if (sessionTimeout != null) { |
| session.setTimeout(DurationUtils.toMillisInt(sessionTimeout)); |
| } |
| |
| final UserInfo userInfo = builder.getUserInfo(fileSystemOptions); |
| if (userInfo != null) { |
| session.setUserInfo(userInfo); |
| } |
| |
| final Properties config = new Properties(); |
| |
| // set StrictHostKeyChecking property |
| final String strictHostKeyChecking = builder.getStrictHostKeyChecking(fileSystemOptions); |
| if (strictHostKeyChecking != null) { |
| config.setProperty("StrictHostKeyChecking", strictHostKeyChecking); |
| } |
| // set PreferredAuthentications property |
| final String preferredAuthentications = builder.getPreferredAuthentications(fileSystemOptions); |
| if (preferredAuthentications != null) { |
| config.setProperty("PreferredAuthentications", preferredAuthentications); |
| } |
| |
| // set compression property |
| final String compression = builder.getCompression(fileSystemOptions); |
| if (compression != null) { |
| config.setProperty("compression.s2c", compression); |
| config.setProperty("compression.c2s", compression); |
| } |
| |
| final String keyExchangeAlgorithm = builder.getKeyExchangeAlgorithm(fileSystemOptions); |
| if (keyExchangeAlgorithm != null) { |
| config.setProperty("kex", keyExchangeAlgorithm); |
| } |
| |
| final String proxyHost = builder.getProxyHost(fileSystemOptions); |
| if (proxyHost != null) { |
| final int proxyPort = builder.getProxyPort(fileSystemOptions); |
| final SftpFileSystemConfigBuilder.ProxyType proxyType = builder.getProxyType(fileSystemOptions); |
| final String proxyUser = builder.getProxyUser(fileSystemOptions); |
| final String proxyPassword = builder.getProxyPassword(fileSystemOptions); |
| Proxy proxy = null; |
| if (SftpFileSystemConfigBuilder.PROXY_HTTP.equals(proxyType)) { |
| proxy = createProxyHTTP(proxyHost, proxyPort); |
| ((ProxyHTTP)proxy).setUserPasswd(proxyUser, proxyPassword); |
| } else if (SftpFileSystemConfigBuilder.PROXY_SOCKS5.equals(proxyType)) { |
| proxy = createProxySOCKS5(proxyHost, proxyPort); |
| ((ProxySOCKS5)proxy).setUserPasswd(proxyUser, proxyPassword); |
| } else if (SftpFileSystemConfigBuilder.PROXY_STREAM.equals(proxyType)) { |
| proxy = createStreamProxy(proxyHost, proxyPort, fileSystemOptions, builder); |
| } |
| |
| if (proxy != null) { |
| session.setProxy(proxy); |
| } |
| } |
| |
| // set properties for the session |
| if (!config.isEmpty()) { |
| session.setConfig(config); |
| } |
| session.setDaemonThread(true); |
| session.connect(); |
| } catch (final Exception exc) { |
| throw new FileSystemException("vfs.provider.sftp/connect.error", exc, hostname); |
| } |
| |
| return session; |
| } |
| |
| private static void addIdentities(final JSch jsch, final File sshDir, final IdentityProvider[] identities) |
| throws FileSystemException { |
| if (identities != null) { |
| for (final IdentityProvider info : identities) { |
| addIdentity(jsch, info); |
| } |
| } else { |
| // Load the private key (rsa-key only) |
| final File privateKeyFile = new File(sshDir, "id_rsa"); |
| if (privateKeyFile.isFile() && privateKeyFile.canRead()) { |
| addIdentity(jsch, new IdentityInfo(privateKeyFile)); |
| } |
| } |
| } |
| |
| private static void setConfigRepository(final JSch jsch, final File sshDir, final ConfigRepository configRepository, final boolean loadOpenSSHConfig) throws FileSystemException { |
| if (configRepository != null) { |
| jsch.setConfigRepository(configRepository); |
| } else if (loadOpenSSHConfig) { |
| try { |
| // loading openssh config (~/.ssh/config) |
| final ConfigRepository openSSHConfig = OpenSSHConfig.parseFile(new File(sshDir, OPENSSH_CONFIG_NAME).getAbsolutePath()); |
| jsch.setConfigRepository(openSSHConfig); |
| } catch (final IOException e) { |
| throw new FileSystemException("vfs.provider.sftp/load-openssh-config.error", e); |
| } |
| } |
| } |
| |
| private static void addIdentity(final JSch jsch, final IdentityProvider identity) throws FileSystemException { |
| try { |
| identity.addIdentity(jsch); |
| } catch (final JSchException e) { |
| throw new FileSystemException("vfs.provider.sftp/load-private-key.error", identity, e); |
| } |
| } |
| |
| private static void setKnownHosts(final JSch jsch, final File sshDir, File knownHostsFile) |
| throws FileSystemException { |
| try { |
| if (knownHostsFile != null) { |
| jsch.setKnownHosts(knownHostsFile.getAbsolutePath()); |
| } else { |
| // Load the known hosts file |
| knownHostsFile = new File(sshDir, "known_hosts"); |
| if (knownHostsFile.isFile() && knownHostsFile.canRead()) { |
| jsch.setKnownHosts(knownHostsFile.getAbsolutePath()); |
| } |
| } |
| } catch (final JSchException e) { |
| throw new FileSystemException("vfs.provider.sftp/known-hosts.error", knownHostsFile.getAbsolutePath(), e); |
| } |
| |
| } |
| |
| private static Proxy createStreamProxy(final String proxyHost, final int proxyPort, |
| final FileSystemOptions fileSystemOptions, final SftpFileSystemConfigBuilder builder) { |
| // Use a stream proxy, i.e. it will use a remote host as a proxy |
| // and run a command (e.g. netcat) that forwards input/output |
| // to the target host. |
| |
| // Here we get the settings for connecting to the proxy: |
| // user, password, options and a command |
| final String proxyUser = builder.getProxyUser(fileSystemOptions); |
| final String proxyPassword = builder.getProxyPassword(fileSystemOptions); |
| final FileSystemOptions proxyOptions = builder.getProxyOptions(fileSystemOptions); |
| |
| final String proxyCommand = builder.getProxyCommand(fileSystemOptions); |
| |
| // Create the stream proxy |
| return new SftpStreamProxy(proxyCommand, proxyUser, proxyHost, proxyPort, proxyPassword, proxyOptions); |
| } |
| |
| private static ProxySOCKS5 createProxySOCKS5(final String proxyHost, final int proxyPort) { |
| return proxyPort == 0 ? new ProxySOCKS5(proxyHost) : new ProxySOCKS5(proxyHost, proxyPort); |
| } |
| |
| private static ProxyHTTP createProxyHTTP(final String proxyHost, final int proxyPort) { |
| return proxyPort == 0 ? new ProxyHTTP(proxyHost) : new ProxyHTTP(proxyHost, proxyPort); |
| } |
| |
| /** |
| * Finds the {@code .ssh} directory. |
| * <p> |
| * The lookup order is: |
| * </p> |
| * <ol> |
| * <li>The system property {@code vfs.sftp.sshdir} (the override mechanism)</li> |
| * <li>{@code user.home}/.ssh</li> |
| * <li>On Windows only: {@code C:\cygwin\home[user.name]\.ssh}</li> |
| * <li>The current directory, as a last resort.</li> |
| * </ol> |
| * |
| * <h2>Windows Notes</h2> |
| * <p> |
| * The default installation directory for Cygwin is {@code C:\cygwin}. On my set up (Gary here), I have Cygwin in |
| * {@code C:\bin\cygwin}, not the default. Also, my .ssh directory was created in the {@code user.home} directory. |
| * </p> |
| * |
| * @return The {@code .ssh} directory |
| */ |
| private static File findSshDir() { |
| final String sshDirPath; |
| sshDirPath = System.getProperty("vfs.sftp.sshdir"); |
| if (sshDirPath != null) { |
| final File sshDir = new File(sshDirPath); |
| if (sshDir.exists()) { |
| return sshDir; |
| } |
| } |
| |
| File sshDir = new File(System.getProperty("user.home"), SSH_DIR_NAME); |
| if (sshDir.exists()) { |
| return sshDir; |
| } |
| |
| if (SystemUtils.IS_OS_WINDOWS) { |
| // TODO - this may not be true |
| final String userName = System.getProperty("user.name"); |
| sshDir = new File("C:\\cygwin\\home\\" + userName + "\\" + SSH_DIR_NAME); |
| if (sshDir.exists()) { |
| return sshDir; |
| } |
| } |
| return new File(""); |
| } |
| |
| /** Interface JSchLogger with JCL. */ |
| private static class JSchLogger implements Logger { |
| @Override |
| public boolean isEnabled(final int level) { |
| switch (level) { |
| case FATAL: |
| return LOG.isFatalEnabled(); |
| case ERROR: |
| return LOG.isErrorEnabled(); |
| case WARN: |
| return LOG.isDebugEnabled(); |
| case DEBUG: |
| return LOG.isDebugEnabled(); |
| case INFO: |
| return LOG.isInfoEnabled(); |
| default: |
| return LOG.isDebugEnabled(); |
| |
| } |
| } |
| |
| @Override |
| public void log(final int level, final String msg) { |
| switch (level) { |
| case FATAL: |
| LOG.fatal(msg); |
| break; |
| case ERROR: |
| LOG.error(msg); |
| break; |
| case WARN: |
| LOG.warn(msg); |
| break; |
| case DEBUG: |
| LOG.debug(msg); |
| break; |
| case INFO: |
| LOG.info(msg); |
| break; |
| default: |
| LOG.debug(msg); |
| } |
| } |
| } |
| } |