001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider.sftp; 018 019import java.io.File; 020import java.util.Properties; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024import org.apache.commons.vfs2.FileSystemException; 025import org.apache.commons.vfs2.FileSystemOptions; 026import org.apache.commons.vfs2.util.Os; 027 028import com.jcraft.jsch.JSch; 029import com.jcraft.jsch.JSchException; 030import com.jcraft.jsch.Logger; 031import com.jcraft.jsch.Proxy; 032import com.jcraft.jsch.ProxyHTTP; 033import com.jcraft.jsch.ProxySOCKS5; 034import com.jcraft.jsch.Session; 035import com.jcraft.jsch.UserInfo; 036 037/** 038 * Create a JSch Session instance. 039 */ 040public final class SftpClientFactory { 041 private static final String SSH_DIR_NAME = ".ssh"; 042 043 private static final Log LOG = LogFactory.getLog(SftpClientFactory.class); 044 045 static { 046 JSch.setLogger(new JSchLogger()); 047 } 048 049 private SftpClientFactory() { 050 } 051 052 /** 053 * Creates a new connection to the server. 054 * 055 * @param hostname The name of the host to connect to. 056 * @param port The port to use. 057 * @param username The user's id. 058 * @param password The user's password. 059 * @param fileSystemOptions The FileSystem options. 060 * @return A Session. 061 * @throws FileSystemException if an error occurs. 062 */ 063 public static Session createConnection(final String hostname, final int port, final char[] username, 064 final char[] password, final FileSystemOptions fileSystemOptions) throws FileSystemException { 065 final JSch jsch = new JSch(); 066 067 File sshDir = null; 068 069 // new style - user passed 070 final SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance(); 071 final File knownHostsFile = builder.getKnownHosts(fileSystemOptions); 072 final IdentityInfo[] identities = builder.getIdentityInfo(fileSystemOptions); 073 final IdentityRepositoryFactory repositoryFactory = builder.getIdentityRepositoryFactory(fileSystemOptions); 074 075 sshDir = findSshDir(); 076 077 setKnownHosts(jsch, sshDir, knownHostsFile); 078 079 if (repositoryFactory != null) { 080 jsch.setIdentityRepository(repositoryFactory.create(jsch)); 081 } 082 083 addIdentities(jsch, sshDir, identities); 084 085 Session session; 086 try { 087 session = jsch.getSession(new String(username), hostname, port); 088 if (password != null) { 089 session.setPassword(new String(password)); 090 } 091 092 final Integer timeout = builder.getTimeout(fileSystemOptions); 093 if (timeout != null) { 094 session.setTimeout(timeout.intValue()); 095 } 096 097 final UserInfo userInfo = builder.getUserInfo(fileSystemOptions); 098 if (userInfo != null) { 099 session.setUserInfo(userInfo); 100 } 101 102 final Properties config = new Properties(); 103 104 // set StrictHostKeyChecking property 105 final String strictHostKeyChecking = builder.getStrictHostKeyChecking(fileSystemOptions); 106 if (strictHostKeyChecking != null) { 107 config.setProperty("StrictHostKeyChecking", strictHostKeyChecking); 108 } 109 // set PreferredAuthentications property 110 final String preferredAuthentications = builder.getPreferredAuthentications(fileSystemOptions); 111 if (preferredAuthentications != null) { 112 config.setProperty("PreferredAuthentications", preferredAuthentications); 113 } 114 115 // set compression property 116 final String compression = builder.getCompression(fileSystemOptions); 117 if (compression != null) { 118 config.setProperty("compression.s2c", compression); 119 config.setProperty("compression.c2s", compression); 120 } 121 122 final String proxyHost = builder.getProxyHost(fileSystemOptions); 123 if (proxyHost != null) { 124 final int proxyPort = builder.getProxyPort(fileSystemOptions); 125 final SftpFileSystemConfigBuilder.ProxyType proxyType = builder.getProxyType(fileSystemOptions); 126 Proxy proxy = null; 127 if (SftpFileSystemConfigBuilder.PROXY_HTTP.equals(proxyType)) { 128 proxy = createProxyHTTP(proxyHost, proxyPort); 129 } else if (SftpFileSystemConfigBuilder.PROXY_SOCKS5.equals(proxyType)) { 130 proxy = createProxySOCKS5(proxyHost, proxyPort); 131 } else if (SftpFileSystemConfigBuilder.PROXY_STREAM.equals(proxyType)) { 132 proxy = createStreamProxy(proxyHost, proxyPort, fileSystemOptions, builder); 133 } 134 135 if (proxy != null) { 136 session.setProxy(proxy); 137 } 138 } 139 140 // set properties for the session 141 if (config.size() > 0) { 142 session.setConfig(config); 143 } 144 session.setDaemonThread(true); 145 session.connect(); 146 } catch (final Exception exc) { 147 throw new FileSystemException("vfs.provider.sftp/connect.error", exc, hostname); 148 } 149 150 return session; 151 } 152 153 private static void addIdentities(final JSch jsch, final File sshDir, final IdentityInfo[] identities) 154 throws FileSystemException { 155 if (identities != null) { 156 for (final IdentityInfo info : identities) { 157 addIndentity(jsch, info); 158 } 159 } else { 160 // Load the private key (rsa-key only) 161 final File privateKeyFile = new File(sshDir, "id_rsa"); 162 if (privateKeyFile.isFile() && privateKeyFile.canRead()) { 163 addIndentity(jsch, new IdentityInfo(privateKeyFile)); 164 } 165 } 166 } 167 168 private static void addIndentity(final JSch jsch, final IdentityInfo info) throws FileSystemException { 169 try { 170 final String privateKeyFile = info.getPrivateKey() != null ? info.getPrivateKey().getAbsolutePath() : null; 171 final String publicKeyFile = info.getPublicKey() != null ? info.getPublicKey().getAbsolutePath() : null; 172 jsch.addIdentity(privateKeyFile, publicKeyFile, info.getPassPhrase()); 173 } catch (final JSchException e) { 174 throw new FileSystemException("vfs.provider.sftp/load-private-key.error", info, e); 175 } 176 } 177 178 private static void setKnownHosts(final JSch jsch, final File sshDir, File knownHostsFile) 179 throws FileSystemException { 180 try { 181 if (knownHostsFile != null) { 182 jsch.setKnownHosts(knownHostsFile.getAbsolutePath()); 183 } else { 184 // Load the known hosts file 185 knownHostsFile = new File(sshDir, "known_hosts"); 186 if (knownHostsFile.isFile() && knownHostsFile.canRead()) { 187 jsch.setKnownHosts(knownHostsFile.getAbsolutePath()); 188 } 189 } 190 } catch (final JSchException e) { 191 throw new FileSystemException("vfs.provider.sftp/known-hosts.error", knownHostsFile.getAbsolutePath(), e); 192 } 193 194 } 195 196 private static Proxy createStreamProxy(final String proxyHost, final int proxyPort, 197 final FileSystemOptions fileSystemOptions, final SftpFileSystemConfigBuilder builder) { 198 Proxy proxy; 199 // Use a stream proxy, i.e. it will use a remote host as a proxy 200 // and run a command (e.g. netcat) that forwards input/output 201 // to the target host. 202 203 // Here we get the settings for connecting to the proxy: 204 // user, password, options and a command 205 final String proxyUser = builder.getProxyUser(fileSystemOptions); 206 final String proxyPassword = builder.getProxyPassword(fileSystemOptions); 207 final FileSystemOptions proxyOptions = builder.getProxyOptions(fileSystemOptions); 208 209 final String proxyCommand = builder.getProxyCommand(fileSystemOptions); 210 211 // Create the stream proxy 212 proxy = new SftpStreamProxy(proxyCommand, proxyUser, proxyHost, proxyPort, proxyPassword, proxyOptions); 213 return proxy; 214 } 215 216 private static ProxySOCKS5 createProxySOCKS5(final String proxyHost, final int proxyPort) { 217 return proxyPort == 0 ? new ProxySOCKS5(proxyHost) : new ProxySOCKS5(proxyHost, proxyPort); 218 } 219 220 private static ProxyHTTP createProxyHTTP(final String proxyHost, final int proxyPort) { 221 return proxyPort == 0 ? new ProxyHTTP(proxyHost) : new ProxyHTTP(proxyHost, proxyPort); 222 } 223 224 /** 225 * Finds the .ssh directory. 226 * <p> 227 * The lookup order is: 228 * <ol> 229 * <li>The system property {@code vfs.sftp.sshdir} (the override mechanism)</li> 230 * <li>{user.home}/.ssh</li> 231 * <li>On Windows only: C:\cygwin\home\{user.name}\.ssh</li> 232 * <li>The current directory, as a last resort.</li> 233 * <ol> 234 * Windows Notes:<br> 235 * The default installation directory for Cygwin is {@code C:\cygwin}. On my set up (Gary here), I have Cygwin in 236 * C:\bin\cygwin, not the default. Also, my .ssh directory was created in the {user.home} directory. 237 * 238 * @return The .ssh directory 239 */ 240 private static File findSshDir() { 241 String sshDirPath; 242 sshDirPath = System.getProperty("vfs.sftp.sshdir"); 243 if (sshDirPath != null) { 244 final File sshDir = new File(sshDirPath); 245 if (sshDir.exists()) { 246 return sshDir; 247 } 248 } 249 250 File sshDir = new File(System.getProperty("user.home"), SSH_DIR_NAME); 251 if (sshDir.exists()) { 252 return sshDir; 253 } 254 255 if (Os.isFamily(Os.OS_FAMILY_WINDOWS)) { 256 // TODO - this may not be true 257 final String userName = System.getProperty("user.name"); 258 sshDir = new File("C:\\cygwin\\home\\" + userName + "\\" + SSH_DIR_NAME); 259 if (sshDir.exists()) { 260 return sshDir; 261 } 262 } 263 return new File(""); 264 } 265 266 /** Interface JSchLogger with JCL. */ 267 private static class JSchLogger implements Logger { 268 @Override 269 public boolean isEnabled(final int level) { 270 switch (level) { 271 case FATAL: 272 return LOG.isFatalEnabled(); 273 case ERROR: 274 return LOG.isErrorEnabled(); 275 case WARN: 276 return LOG.isDebugEnabled(); 277 case DEBUG: 278 return LOG.isDebugEnabled(); 279 case INFO: 280 return LOG.isInfoEnabled(); 281 default: 282 return LOG.isDebugEnabled(); 283 284 } 285 } 286 287 @Override 288 public void log(final int level, final String msg) { 289 switch (level) { 290 case FATAL: 291 LOG.fatal(msg); 292 break; 293 case ERROR: 294 LOG.error(msg); 295 break; 296 case WARN: 297 LOG.warn(msg); 298 break; 299 case DEBUG: 300 LOG.debug(msg); 301 break; 302 case INFO: 303 LOG.info(msg); 304 break; 305 default: 306 LOG.debug(msg); 307 } 308 } 309 } 310}