View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.sftp;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.util.Properties;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.commons.vfs2.FileSystemException;
26  import org.apache.commons.vfs2.FileSystemOptions;
27  import org.apache.commons.vfs2.util.Os;
28  
29  import com.jcraft.jsch.ConfigRepository;
30  import com.jcraft.jsch.JSch;
31  import com.jcraft.jsch.JSchException;
32  import com.jcraft.jsch.Logger;
33  import com.jcraft.jsch.OpenSSHConfig;
34  import com.jcraft.jsch.Proxy;
35  import com.jcraft.jsch.ProxyHTTP;
36  import com.jcraft.jsch.ProxySOCKS5;
37  import com.jcraft.jsch.Session;
38  import com.jcraft.jsch.UserInfo;
39  
40  /**
41   * Create a JSch Session instance.
42   */
43  public final class SftpClientFactory {
44  
45      private static final String SSH_DIR_NAME = ".ssh";
46      private static final String OPENSSH_CONFIG_NAME = "config";
47      private static final Log LOG = LogFactory.getLog(SftpClientFactory.class);
48  
49      static {
50          JSch.setLogger(new JSchLogger());
51      }
52  
53      private SftpClientFactory() {
54      }
55  
56      /**
57       * Creates a new connection to the server.
58       *
59       * @param hostname The name of the host to connect to.
60       * @param port The port to use.
61       * @param username The user's id.
62       * @param password The user's password.
63       * @param fileSystemOptions The FileSystem options.
64       * @return A Session, never null.
65       * @throws FileSystemException if an error occurs.
66       */
67      public static Session createConnection(final String hostname, final int port, final char[] username,
68              final char[] password, final FileSystemOptions fileSystemOptions) throws FileSystemException {
69          final JSch jsch = new JSch();
70  
71          File sshDir = null;
72  
73          // new style - user passed
74          final SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();
75          final File knownHostsFile = builder.getKnownHosts(fileSystemOptions);
76          final IdentityProvider[] identities = builder.getIdentityProvider(fileSystemOptions);
77          final IdentityRepositoryFactory repositoryFactory = builder.getIdentityRepositoryFactory(fileSystemOptions);
78          final ConfigRepository configRepository = builder.getConfigRepository(fileSystemOptions);
79          final boolean loadOpenSSHConfig = builder.isLoadOpenSSHConfig(fileSystemOptions);
80  
81          sshDir = findSshDir();
82  
83          setKnownHosts(jsch, sshDir, knownHostsFile);
84  
85          if (repositoryFactory != null) {
86              jsch.setIdentityRepository(repositoryFactory.create(jsch));
87          }
88  
89          addIdentities(jsch, sshDir, identities);
90          setConfigRepository(jsch, sshDir, configRepository, loadOpenSSHConfig);
91  
92          Session session;
93          try {
94              session = jsch.getSession(new String(username), hostname, port);
95              if (password != null) {
96                  session.setPassword(new String(password));
97              }
98  
99              final Integer sessionTimeout = builder.getSessionTimeoutMillis(fileSystemOptions);
100             if (sessionTimeout != null) {
101                 session.setTimeout(sessionTimeout.intValue());
102             }
103 
104             final UserInfo userInfo = builder.getUserInfo(fileSystemOptions);
105             if (userInfo != null) {
106                 session.setUserInfo(userInfo);
107             }
108 
109             final Properties config = new Properties();
110 
111             // set StrictHostKeyChecking property
112             final String strictHostKeyChecking = builder.getStrictHostKeyChecking(fileSystemOptions);
113             if (strictHostKeyChecking != null) {
114                 config.setProperty("StrictHostKeyChecking", strictHostKeyChecking);
115             }
116             // set PreferredAuthentications property
117             final String preferredAuthentications = builder.getPreferredAuthentications(fileSystemOptions);
118             if (preferredAuthentications != null) {
119                 config.setProperty("PreferredAuthentications", preferredAuthentications);
120             }
121 
122             // set compression property
123             final String compression = builder.getCompression(fileSystemOptions);
124             if (compression != null) {
125                 config.setProperty("compression.s2c", compression);
126                 config.setProperty("compression.c2s", compression);
127             }
128 
129             final String keyExchangeAlgorithm = builder.getKeyExchangeAlgorithm(fileSystemOptions);
130             if (keyExchangeAlgorithm != null) {
131                 config.setProperty("kex", keyExchangeAlgorithm);
132             }
133 
134             final String proxyHost = builder.getProxyHost(fileSystemOptions);
135             if (proxyHost != null) {
136                 final int proxyPort = builder.getProxyPort(fileSystemOptions);
137                 final SftpFileSystemConfigBuilder.ProxyType proxyType = builder.getProxyType(fileSystemOptions);
138                 final String proxyUser =  builder.getProxyUser(fileSystemOptions);
139                 final String proxyPassword = builder.getProxyPassword(fileSystemOptions);
140                 Proxy proxy = null;
141                 if (SftpFileSystemConfigBuilder.PROXY_HTTP.equals(proxyType)) {
142                     proxy = createProxyHTTP(proxyHost, proxyPort);
143                     ((ProxyHTTP)proxy).setUserPasswd(proxyUser, proxyPassword);
144                 } else if (SftpFileSystemConfigBuilder.PROXY_SOCKS5.equals(proxyType)) {
145                     proxy = createProxySOCKS5(proxyHost, proxyPort);
146                     ((ProxySOCKS5)proxy).setUserPasswd(proxyUser, proxyPassword);
147                 } else if (SftpFileSystemConfigBuilder.PROXY_STREAM.equals(proxyType)) {
148                     proxy = createStreamProxy(proxyHost, proxyPort, fileSystemOptions, builder);
149                 }
150 
151                 if (proxy != null) {
152                     session.setProxy(proxy);
153                 }
154             }
155 
156             // set properties for the session
157             if (config.size() > 0) {
158                 session.setConfig(config);
159             }
160             session.setDaemonThread(true);
161             session.connect();
162         } catch (final Exception exc) {
163             throw new FileSystemException("vfs.provider.sftp/connect.error", exc, hostname);
164         }
165 
166         return session;
167     }
168 
169     private static void addIdentities(final JSch jsch, final File sshDir, final IdentityProvider[] identities)
170             throws FileSystemException {
171         if (identities != null) {
172             for (final IdentityProvider info : identities) {
173                 addIdentity(jsch, info);
174             }
175         } else {
176             // Load the private key (rsa-key only)
177             final File privateKeyFile = new File(sshDir, "id_rsa");
178             if (privateKeyFile.isFile() && privateKeyFile.canRead()) {
179                 addIdentity(jsch, new IdentityInfo(privateKeyFile));
180             }
181         }
182     }
183 
184     private static void setConfigRepository(final JSch jsch, final File sshDir, final ConfigRepository configRepository, final boolean loadOpenSSHConfig) throws FileSystemException {
185         if (configRepository != null) {
186             jsch.setConfigRepository(configRepository);
187         } else if (loadOpenSSHConfig) {
188             try {
189                 // loading openssh config (~/.ssh/config)
190                 final ConfigRepository openSSHConfig = OpenSSHConfig.parseFile(new File(sshDir, OPENSSH_CONFIG_NAME).getAbsolutePath());
191                 jsch.setConfigRepository(openSSHConfig);
192             } catch (final IOException e) {
193                 throw new FileSystemException("vfs.provider.sftp/load-openssh-config.error", e);
194             }
195         }
196     }
197 
198     private static void addIdentity(final JSch jsch, final IdentityProvider identity) throws FileSystemException {
199         try {
200             identity.addIdentity(jsch);
201         } catch (final JSchException e) {
202             throw new FileSystemException("vfs.provider.sftp/load-private-key.error", identity, e);
203         }
204     }
205 
206     private static void setKnownHosts(final JSch jsch, final File sshDir, File knownHostsFile)
207             throws FileSystemException {
208         try {
209             if (knownHostsFile != null) {
210                 jsch.setKnownHosts(knownHostsFile.getAbsolutePath());
211             } else {
212                 // Load the known hosts file
213                 knownHostsFile = new File(sshDir, "known_hosts");
214                 if (knownHostsFile.isFile() && knownHostsFile.canRead()) {
215                     jsch.setKnownHosts(knownHostsFile.getAbsolutePath());
216                 }
217             }
218         } catch (final JSchException e) {
219             throw new FileSystemException("vfs.provider.sftp/known-hosts.error", knownHostsFile.getAbsolutePath(), e);
220         }
221 
222     }
223 
224     private static Proxy createStreamProxy(final String proxyHost, final int proxyPort,
225             final FileSystemOptions fileSystemOptions, final SftpFileSystemConfigBuilder builder) {
226         Proxy proxy;
227         // Use a stream proxy, i.e. it will use a remote host as a proxy
228         // and run a command (e.g. netcat) that forwards input/output
229         // to the target host.
230 
231         // Here we get the settings for connecting to the proxy:
232         // user, password, options and a command
233         final String proxyUser = builder.getProxyUser(fileSystemOptions);
234         final String proxyPassword = builder.getProxyPassword(fileSystemOptions);
235         final FileSystemOptions proxyOptions = builder.getProxyOptions(fileSystemOptions);
236 
237         final String proxyCommand = builder.getProxyCommand(fileSystemOptions);
238 
239         // Create the stream proxy
240         proxy = new SftpStreamProxy(proxyCommand, proxyUser, proxyHost, proxyPort, proxyPassword, proxyOptions);
241         return proxy;
242     }
243 
244     private static ProxySOCKS5 createProxySOCKS5(final String proxyHost, final int proxyPort) {
245         return proxyPort == 0 ? new ProxySOCKS5(proxyHost) : new ProxySOCKS5(proxyHost, proxyPort);
246     }
247 
248     private static ProxyHTTP createProxyHTTP(final String proxyHost, final int proxyPort) {
249         return proxyPort == 0 ? new ProxyHTTP(proxyHost) : new ProxyHTTP(proxyHost, proxyPort);
250     }
251 
252     /**
253      * Finds the {@code .ssh} directory.
254      * <p>
255      * The lookup order is:
256      * </p>
257      * <ol>
258      * <li>The system property {@code vfs.sftp.sshdir} (the override mechanism)</li>
259      * <li>{@code user.home}/.ssh</li>
260      * <li>On Windows only: {@code C:\cygwin\home[user.name]\.ssh}</li>
261      * <li>The current directory, as a last resort.</li>
262      * </ol>
263      *
264      * <h2>Windows Notes</h2>
265      * <p>
266      * The default installation directory for Cygwin is {@code C:\cygwin}. On my set up (Gary here), I have Cygwin in
267      * {@code C:\bin\cygwin}, not the default. Also, my .ssh directory was created in the {@code user.home} directory.
268      * </p>
269      *
270      * @return The {@code .ssh} directory
271      */
272     private static File findSshDir() {
273         String sshDirPath;
274         sshDirPath = System.getProperty("vfs.sftp.sshdir");
275         if (sshDirPath != null) {
276             final File sshDir = new File(sshDirPath);
277             if (sshDir.exists()) {
278                 return sshDir;
279             }
280         }
281 
282         File sshDir = new File(System.getProperty("user.home"), SSH_DIR_NAME);
283         if (sshDir.exists()) {
284             return sshDir;
285         }
286 
287         if (Os.isFamily(Os.OS_FAMILY_WINDOWS)) {
288             // TODO - this may not be true
289             final String userName = System.getProperty("user.name");
290             sshDir = new File("C:\\cygwin\\home\\" + userName + "\\" + SSH_DIR_NAME);
291             if (sshDir.exists()) {
292                 return sshDir;
293             }
294         }
295         return new File("");
296     }
297 
298     /** Interface JSchLogger with JCL. */
299     private static class JSchLogger implements Logger {
300         @Override
301         public boolean isEnabled(final int level) {
302             switch (level) {
303             case FATAL:
304                 return LOG.isFatalEnabled();
305             case ERROR:
306                 return LOG.isErrorEnabled();
307             case WARN:
308                 return LOG.isDebugEnabled();
309             case DEBUG:
310                 return LOG.isDebugEnabled();
311             case INFO:
312                 return LOG.isInfoEnabled();
313             default:
314                 return LOG.isDebugEnabled();
315 
316             }
317         }
318 
319         @Override
320         public void log(final int level, final String msg) {
321             switch (level) {
322             case FATAL:
323                 LOG.fatal(msg);
324                 break;
325             case ERROR:
326                 LOG.error(msg);
327                 break;
328             case WARN:
329                 LOG.warn(msg);
330                 break;
331             case DEBUG:
332                 LOG.debug(msg);
333                 break;
334             case INFO:
335                 LOG.info(msg);
336                 break;
337             default:
338                 LOG.debug(msg);
339             }
340         }
341     }
342 }