You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2012/10/29 04:48:25 UTC
svn commit: r1403155 - in /commons/proper/vfs/trunk/core/src:
main/java/org/apache/commons/vfs2/provider/sftp/
test/java/org/apache/commons/vfs2/provider/sftp/test/
Author: ggregory
Date: Mon Oct 29 03:48:25 2012
New Revision: 1403155
URL: http://svn.apache.org/viewvc?rev=1403155&view=rev
Log:
[VFS-440] [SFTP] Stream (e.g. netcat) proxy for Sftp file system (aka ProxyCommand).
Added:
commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpStreamProxy.java (with props)
Modified:
commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpClientFactory.java
commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileSystemConfigBuilder.java
commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/sftp/test/SftpProviderTestCase.java
Modified: commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpClientFactory.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpClientFactory.java?rev=1403155&r1=1403154&r2=1403155&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpClientFactory.java (original)
+++ commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpClientFactory.java Mon Oct 29 03:48:25 2012
@@ -225,6 +225,23 @@ public final class SftpClientFactory
proxy = new ProxySOCKS5(proxyHost);
}
}
+ else if (SftpFileSystemConfigBuilder.PROXY_STREAM.equals(proxyType))
+ {
+ // 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
+ String proxyUser = builder.getProxyUser(fileSystemOptions);
+ String proxyPassword = builder.getProxyPassword(fileSystemOptions);
+ FileSystemOptions proxyOptions = builder.getProxyOptions(fileSystemOptions);
+
+ String proxyCommand = builder.getProxyCommand(fileSystemOptions);
+
+ // Create the stream proxy
+ proxy = new SftpStreamProxy(proxyCommand, proxyUser, proxyHost, proxyPort, proxyPassword, proxyOptions);
+ }
if (proxy != null)
{
Modified: commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileSystemConfigBuilder.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileSystemConfigBuilder.java?rev=1403155&r1=1403154&r2=1403155&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileSystemConfigBuilder.java (original)
+++ commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileSystemConfigBuilder.java Mon Oct 29 03:48:25 2012
@@ -101,15 +101,39 @@ public final class SftpFileSystemConfigB
private static final String IDENTITY_REPOSITORY_FACTORY = _PREFIX + "IDENTITY_REPOSITORY_FACTORY";
private static final String KNOWN_HOSTS = _PREFIX + ".KNOWN_HOSTS";
private static final String PREFERRED_AUTHENTICATIONS = _PREFIX + ".PREFERRED_AUTHENTICATIONS";
- private static final String PROXY_HOST = _PREFIX + ".PROXY_HOST";
/** HTTP Proxy. */
public static final ProxyType PROXY_HTTP = new ProxyType("http");
- private static final String PROXY_PORT = _PREFIX + ".PROXY_PORT";
/** SOCKS Proxy. */
public static final ProxyType PROXY_SOCKS5 = new ProxyType("socks");
+
+ /**
+ * @brief Stream Proxy.
+ *
+ * <p>Connects to the SFTP server through a remote host reached by SSH.
+ * On this proxy host, a command
+ * (e.g. {@linkplain SftpStreamProxy#NETCAT_COMMAND}
+ * or {@linkplain SftpStreamProxy#NETCAT_COMMAND}) is run to forward
+ * input/output streams between the target host and the VFS host.</p>
+ * <p>
+ * When used, the proxy username ({@linkplain #setProxyUser}) and
+ * hostname ({@linkplain #setProxyHost}) <b>must</b> be set.
+ * Optionnaly, the command ({@linkplain #setProxyCommand}),
+ * password ({@linkplain #setProxyPassword}) and connection options
+ * ({@linkplain #setProxyOptions}) can be set.
+ * </p>
+ */
+ public static final ProxyType PROXY_STREAM = new ProxyType("stream");
+
+ private static final String PROXY_HOST = _PREFIX + ".PROXY_HOST";
+ private static final String PROXY_USER = _PREFIX + ".PROXY_USER";
+ private static final String PROXY_OPTIONS = _PREFIX + ".PROXY_OPTIONS";
private static final String PROXY_TYPE = _PREFIX + ".PROXY_TYPE";
+ private static final String PROXY_PORT = _PREFIX + ".PROXY_PORT";
+ private static final String PROXY_PASSWORD = _PREFIX + ".PROXY_PASSWORD";
+ private static final String PROXY_COMMAND = _PREFIX + ".PROXY_COMMAND";
+
private static final String STRICT_HOST_KEY_CHECKING = _PREFIX + ".STRICT_HOST_KEY_CHECKING";
private static final String TIMEOUT = _PREFIX + ".TIMEOUT";
private static final String USER_DIR_IS_ROOT = _PREFIX + ".USER_DIR_IS_ROOT";
@@ -198,6 +222,21 @@ public final class SftpFileSystemConfigB
return (String) this.getParam(opts, PREFERRED_AUTHENTICATIONS);
}
+
+ /**
+ * Gets the user name for the proxy used for the SFTP connection.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @return proxyUser
+ * @see #setProxyUser
+ * @since 2.1
+ */
+ public String getProxyUser(final FileSystemOptions opts)
+ {
+ return this.getString(opts, PROXY_USER);
+ }
+
/**
* Gets the proxy to use for the SFTP connection. You have to set the ProxyPort too if you would like to have the proxy
* really used.
@@ -227,6 +266,55 @@ public final class SftpFileSystemConfigB
}
/**
+ * Gets the proxy options that are used to connect
+ * to the proxy host.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @return proxyOptions
+ * @see SftpStreamProxy
+ * @see #setProxyOptions
+ * @since 2.1
+ */
+ public FileSystemOptions getProxyOptions(final FileSystemOptions opts)
+ {
+ return (FileSystemOptions)this.getParam(opts, PROXY_OPTIONS);
+ }
+
+ /**
+ * Gets the proxy password that are used to connect
+ * to the proxy host.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @return proxyOptions
+ * @see SftpStreamProxy
+ * @see #setProxyPassword
+ * @since 2.1
+ */
+ public String getProxyPassword(final FileSystemOptions opts)
+ {
+ return this.getString(opts, PROXY_PASSWORD);
+ }
+
+ /**
+ * Gets the command that will be run on the proxy
+ * host when using a {@linkplain SftpStreamProxy}. The
+ * command defaults to {@linkplain SftpStreamProxy#NETCAT_COMMAND}.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @return proxyOptions
+ * @see SftpStreamProxy
+ * @see #setProxyOptions
+ * @since 2.1
+ */
+ public String getProxyCommand(final FileSystemOptions opts)
+ {
+ return this.getString(opts, PROXY_COMMAND, SftpStreamProxy.NETCAT_COMMAND);
+ }
+
+ /**
* Gets the proxy type to use for the SFTP connection.
*
* @param opts
@@ -405,6 +493,13 @@ public final class SftpFileSystemConfigB
/**
* Sets the proxy type to use for the SFTP connection.
*
+ * The possibles values are:
+ * <ul>
+ * <li>{@linkplain #PROXY_HTTP} connects using an HTTP proxy</li>
+ * <li>{@linkplain #PROXY_SOCKS5} connects using an Socket5 proxy</li>
+ * <li>{@linkplain #PROXY_STREAM} connects through a remote host stream command</li>
+ * </ul>
+ *
* @param opts
* The FileSystem options.
* @param proxyType
@@ -416,6 +511,69 @@ public final class SftpFileSystemConfigB
}
/**
+ * Sets the proxy username to use for the SFTP connection.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @param proxyUser
+ * the username used to connect to the proxy
+ * @see #getProxyUser
+ * @since 2.1
+ */
+ public void setProxyUser(final FileSystemOptions opts, final String proxyUser)
+ {
+ this.setParam(opts, PROXY_USER, proxyUser);
+ }
+
+
+ /**
+ * Sets the proxy password to use for the SFTP connection.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @param proxyPassword
+ * the username used to connect to the proxy
+ * @see #getProxyPassword
+ * @since 2.1
+ */
+ public void setProxyPassword(final FileSystemOptions opts, final String proxyPassword)
+ {
+ this.setParam(opts, PROXY_PASSWORD, proxyPassword);
+ }
+
+
+
+ /**
+ * Sets the proxy username to use for the SFTP connection.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @param proxyOptions
+ * the options
+ * @see #getProxyOptions
+ * @since 2.1
+ */
+ public void setProxyOptions(final FileSystemOptions opts, final FileSystemOptions proxyOptions)
+ {
+ this.setParam(opts, PROXY_OPTIONS, proxyOptions);
+ }
+
+ /**
+ * Sets the proxy username to use for the SFTP connection.
+ *
+ * @param opts
+ * The FileSystem options.
+ * @param proxyCommand
+ * the port
+ * @see #getProxyOptions
+ * @since 2.1
+ */
+ public void setProxyCommand(final FileSystemOptions opts, final String proxyCommand)
+ {
+ this.setParam(opts, PROXY_COMMAND, proxyCommand);
+ }
+
+ /**
* Configures the host key checking to use.
* <p>
* Valid arguments are: {@code "yes"}, {@code "no"} and {@code "ask"}.
Added: commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpStreamProxy.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpStreamProxy.java?rev=1403155&view=auto
==============================================================================
--- commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpStreamProxy.java (added)
+++ commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpStreamProxy.java Mon Oct 29 03:48:25 2012
@@ -0,0 +1,189 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import org.apache.commons.vfs2.FileSystemOptions;
+
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.Proxy;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SocketFactory;
+
+/**
+ * Stream based proxy for JSch.
+ *
+ * <p>
+ * Use a command on the proxy that will forward the SSH stream to the target host and port.
+ * </p>
+ *
+ * @since 2.1
+ */
+public class SftpStreamProxy implements Proxy
+{
+ /**
+ * Command format using bash built-in TCP stream.
+ */
+ public final static String BASH_TCP_COMMAND = "/bin/bash -c 'exec 3<>/dev/tcp/%s/%d; cat <&3 & cat >&3; kill $!";
+
+ /**
+ * Command format using netcat command.
+ */
+ public final static String NETCAT_COMMAND = "nc -q 0 %s %d";
+
+ private ChannelExec channel;
+
+ /**
+ * Command pattern to execute on the proxy host.
+ *
+ * <p>
+ * When run, the command output should be forwarded to the target host and port, and its input should be forwarded
+ * from the target host and port.
+ * </p>
+ *
+ * <p>
+ * The command will be created for each host/port pair by using {@linkplain String#format(String, Object...)} with
+ * two objects: the target host name ({@linkplain String}) and the target port ({@linkplain Integer}).
+ * </p>
+ * <p/>
+ * <p>
+ * Here are two examples (that can be easily used by using the static members of this class):
+ * <ul>
+ * <li><code>nc -q 0 %s %d</code> to use the netcat command ({@linkplain #NETCAT_COMMAND})</li>
+ * <li><code>/bin/bash -c 'exec 3<>/dev/tcp/%s/%d; cat <&3 & cat >&3; kill $!</code> will use bash built-in TCP
+ * stream, which can be useful when there is no netcat available. ({@linkplain #BASH_TCP_COMMAND})</li>
+ * </ul>
+ * </p>
+ */
+ private final String commandFormat;
+
+ /**
+ * Hostname used to connect to the proxy host.
+ */
+ private final String proxyHost;
+
+ /**
+ * The options for connection.
+ */
+ private FileSystemOptions proxyOptions;
+
+ /**
+ * The password to be used for connection.
+ */
+ private final String proxyPassword;
+
+ /**
+ * Port used to connect to the proxy host.
+ */
+ private final int proxyPort;
+
+ /**
+ * Username used to connect to the proxy host.
+ */
+ private final String proxyUser;
+
+ private Session session;
+
+ /**
+ * Creates a stream proxy.
+ *
+ * @param commandFormat
+ * A format string that will be used to create the command to execute on the proxy host using
+ * {@linkplain String#format(String, Object...)}. Two parameters are given to the format command, the
+ * target host name (String) and port (Integer).
+ * @param proxyUser
+ * The proxy user
+ * @param proxyPassword
+ * The proxy password
+ * @param proxyHost
+ * The proxy host
+ * @param proxyPort
+ * The port to connect to on the proxy
+ * @param proxyOptions
+ * Options used when connecting to the proxy
+ */
+ public SftpStreamProxy(String commandFormat, String proxyUser, String proxyHost, int proxyPort,
+ String proxyPassword, FileSystemOptions proxyOptions)
+ {
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
+ this.proxyUser = proxyUser;
+ this.proxyPassword = proxyPassword;
+ this.commandFormat = commandFormat;
+ this.proxyOptions = proxyOptions;
+ }
+
+ @Override
+ public void close()
+ {
+ if (channel != null)
+ {
+ channel.disconnect();
+ }
+ if (session != null)
+ {
+ session.disconnect();
+ }
+ }
+
+ @Override
+ public void connect(SocketFactory socketFactory, String targetHost, int targetPort, int timeout) throws Exception
+ {
+ session = SftpClientFactory.createConnection(proxyHost, proxyPort, proxyUser.toCharArray(),
+ proxyPassword.toCharArray(), proxyOptions);
+ channel = (ChannelExec) session.openChannel("exec");
+ channel.setCommand(String.format(commandFormat, targetHost, targetPort));
+ channel.connect(timeout);
+ }
+
+ @Override
+ public InputStream getInputStream()
+ {
+ try
+ {
+ return channel.getInputStream();
+ }
+ catch (IOException e)
+ {
+ throw new IllegalStateException("IOException getting the SSH proxy input stream", e);
+ }
+ }
+
+ @Override
+ public OutputStream getOutputStream()
+ {
+ try
+ {
+ return channel.getOutputStream();
+ }
+ catch (IOException e)
+ {
+ throw new IllegalStateException("IOException getting the SSH proxy output stream", e);
+ }
+ }
+
+ @Override
+ public Socket getSocket()
+ {
+ return null;
+ }
+}
Propchange: commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpStreamProxy.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpStreamProxy.java
------------------------------------------------------------------------------
svn:keywords = Id
Modified: commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/sftp/test/SftpProviderTestCase.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/sftp/test/SftpProviderTestCase.java?rev=1403155&r1=1403154&r2=1403155&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/sftp/test/SftpProviderTestCase.java (original)
+++ commons/proper/vfs/trunk/core/src/test/java/org/apache/commons/vfs2/provider/sftp/test/SftpProviderTestCase.java Mon Oct 29 03:48:25 2012
@@ -16,35 +16,44 @@
*/
package org.apache.commons.vfs2.provider.sftp.test;
-import java.io.*;
-import java.net.InetSocketAddress;
-import java.security.PublicKey;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.TreeMap;
-
import com.jcraft.jsch.SftpATTRS;
+import com.jcraft.jsch.TestIdentityRepositoryFactory;
+import junit.extensions.TestSetup;
import junit.framework.Test;
-
+import junit.framework.TestSuite;
import org.apache.commons.AbstractVfsTestCase;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
-import org.apache.commons.vfs2.test.PermissionsTests;
import org.apache.commons.vfs2.provider.sftp.SftpFileProvider;
+import org.apache.commons.vfs2.provider.sftp.SftpFileSystem;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;
+import org.apache.commons.vfs2.provider.sftp.SftpStreamProxy;
import org.apache.commons.vfs2.provider.sftp.TrustEveryoneUserInfo;
import org.apache.commons.vfs2.test.AbstractProviderTestConfig;
+import org.apache.commons.vfs2.test.PermissionsTests;
+import org.apache.commons.vfs2.test.ProviderReadTests;
+import org.apache.commons.vfs2.test.ProviderTestConfig;
import org.apache.commons.vfs2.test.ProviderTestSuite;
import org.apache.commons.vfs2.util.FreeSocketPortUtil;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Session;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.common.util.SecurityUtils;
-import org.apache.sshd.server.*;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.FileSystemFactory;
+import org.apache.sshd.server.FileSystemView;
+import org.apache.sshd.server.ForwardingFilter;
+import org.apache.sshd.server.PasswordAuthenticator;
+import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.auth.UserAuthNone;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.server.filesystem.NativeSshFile;
@@ -53,7 +62,21 @@ import org.apache.sshd.server.keyprovide
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.sftp.SftpSubsystem;
-import com.jcraft.jsch.TestIdentityRepositoryFactory;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Tests cases for the SFTP provider.
@@ -64,6 +87,11 @@ import com.jcraft.jsch.TestIdentityRepos
public class SftpProviderTestCase extends AbstractProviderTestConfig
{
/**
+ * The underlying filesystem
+ */
+ private SftpFileSystem filesystem;
+
+ /**
* Implements FileSystemFactory because SSHd does not know about users and home directories.
*/
static final class TestFileSystemFactory implements FileSystemFactory
@@ -156,6 +184,9 @@ public class SftpProviderTestCase extend
private static final String TEST_URI = "test.sftp.uri";
+ /** True if we are testing the SFTP stream proxy */
+ private boolean streamProxyMode;
+
private static String getSystemTestUriOverride()
{
return System.getProperty(TEST_URI);
@@ -167,8 +198,12 @@ public class SftpProviderTestCase extend
* @throws FtpException
* @throws IOException
*/
- private static void setUpClass() throws FtpException, IOException
+ private static void setUpClass() throws FtpException, IOException, InterruptedException
{
+ SocketPort = FreeSocketPortUtil.findFreeLocalPort();
+ // Use %40 for @ in a URL
+ ConnectionUri = String.format("sftp://%s@localhost:%d", DEFAULT_USER, SocketPort);
+
if (Server != null)
{
return;
@@ -179,7 +214,14 @@ public class SftpProviderTestCase extend
Server.setPort(SocketPort);
if (SecurityUtils.isBouncyCastleRegistered())
{
- Server.setKeyPairProvider(new PEMGeneratorHostKeyProvider(tmpDir + "/key.pem"));
+ // A temporary file will hold the key
+ final File keyFile = File.createTempFile("key", ".pem", new File(tmpDir));
+ keyFile.deleteOnExit();
+ // It has to be deleted in order to be generated
+ keyFile.delete();
+
+ final PEMGeneratorHostKeyProvider keyProvider = new PEMGeneratorHostKeyProvider(keyFile.getAbsolutePath());
+ Server.setKeyPairProvider(keyProvider);
} else
{
Server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(tmpDir + "/key.ser"));
@@ -256,6 +298,27 @@ public class SftpProviderTestCase extend
// Do this after we start the server to simplify this set up code.
Server.getUserAuthFactories().add(new UserAuthNone.Factory());
// HACK End
+ }
+
+
+ static private class BaseTest extends ProviderTestSuite {
+
+ public BaseTest(ProviderTestConfig providerConfig) throws Exception
+ {
+ super(providerConfig);
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ // Close all active sessions
+ // Note that it should be done by super.tearDown()
+ // while closing
+ for (AbstractSession session : Server.getActiveSessions()) {
+ session.close(true);
+ }
+ super.tearDown();
+ }
}
@@ -264,7 +327,38 @@ public class SftpProviderTestCase extend
*/
public static Test suite() throws Exception
{
- final ProviderTestSuite suite = new ProviderTestSuite(new SftpProviderTestCase())
+ // The test suite to be returned
+ TestSuite suite = new TestSuite();
+
+ // --- Standard VFS test suite
+ final SftpProviderTestCase standardTestCase = new SftpProviderTestCase(false);
+ final ProviderTestSuite sftpSuite = new BaseTest(standardTestCase);
+
+ // VFS-405: set/get permissions
+ sftpSuite.addTests(PermissionsTests.class);
+
+ suite.addTest(sftpSuite);
+
+
+ // --- VFS-440: stream proxy test suite
+ // We override the addBaseTests method so that only
+ // one test is run (we just test that the input/output are correctly forwarded, and
+ // hence if the reading test succeeds/fails the other will also succeed/fail)
+ final SftpProviderTestCase streamProxyTestCase = new SftpProviderTestCase(true);
+ final ProviderTestSuite sftpStreamSuite = new BaseTest(streamProxyTestCase)
+ {
+ @Override
+ protected void addBaseTests() throws Exception
+ {
+ // Just tries to read
+ addTests(ProviderReadTests.class);
+ }
+ };
+ suite.addTest(sftpStreamSuite);
+
+
+ // Decorate the test suite to set up the Sftp server
+ final TestSetup setup = new TestSetup(suite)
{
@Override
protected void setUp() throws Exception
@@ -279,17 +373,14 @@ public class SftpProviderTestCase extend
@Override
protected void tearDown() throws Exception
{
+ // Close SFTP server if needed
tearDownClass();
super.tearDown();
}
};
- // VFS-405: set/get permissions
- suite.addTests(PermissionsTests.class);
-
- return suite;
-
+ return setup;
}
/**
@@ -305,11 +396,9 @@ public class SftpProviderTestCase extend
}
}
- public SftpProviderTestCase() throws IOException
+ public SftpProviderTestCase(boolean streamProxyMode) throws IOException
{
- SocketPort = FreeSocketPortUtil.findFreeLocalPort();
- // Use %40 for @ in a URL
- ConnectionUri = String.format("sftp://%s@localhost:%d", DEFAULT_USER, SocketPort);
+ this.streamProxyMode = streamProxyMode;
}
/**
@@ -330,7 +419,33 @@ public class SftpProviderTestCase extend
builder.setUserInfo(fileSystemOptions, new TrustEveryoneUserInfo());
builder.setIdentityRepositoryFactory(fileSystemOptions, new TestIdentityRepositoryFactory());
- return manager.resolveFile(uri, fileSystemOptions);
+ if (streamProxyMode)
+ {
+ final FileSystemOptions proxyOptions = (FileSystemOptions) fileSystemOptions.clone();
+
+ URI parsedURI = new URI(uri);
+ final String userInfo = parsedURI.getUserInfo();
+ final String[] userFields = userInfo.split(":", 2);
+
+ builder.setProxyType(fileSystemOptions, SftpFileSystemConfigBuilder.PROXY_STREAM);
+ builder.setProxyUser(fileSystemOptions, userFields[0]);
+ if (userFields.length > 1)
+ {
+ builder.setProxyPassword(fileSystemOptions, userFields[1]);
+ }
+ builder.setProxyHost(fileSystemOptions, parsedURI.getHost());
+ builder.setProxyPort(fileSystemOptions, parsedURI.getPort());
+ builder.setProxyCommand(fileSystemOptions, SftpStreamProxy.NETCAT_COMMAND);
+ builder.setProxyOptions(fileSystemOptions, proxyOptions);
+ builder.setProxyPassword(fileSystemOptions, parsedURI.getAuthority());
+
+ // Set up the new URI
+ uri = String.format("sftp://%s@localhost:%d", userInfo, parsedURI.getPort());
+ }
+
+ final FileObject fileObject = manager.resolveFile(uri, fileSystemOptions);
+ this.filesystem = (SftpFileSystem) fileObject.getFileSystem();
+ return fileObject;
}
/**
@@ -345,34 +460,44 @@ public class SftpProviderTestCase extend
/**
* The command factory for the SSH server:
- * Handles two commands: id -u and id -G
+ * Handles these commands
+ * <p>
+ * <li><code>id -u</code> (permissions test)</li>
+ * <li><code>id -G</code> (permission tests)</li>
+ * <li><code>nc -q 0 localhost port</code> (Stream proxy tests)</li>
+ * </p>
*/
private static class TestCommandFactory extends ScpCommandFactory
{
+
+ public static final Pattern NETCAT_COMMAND = Pattern.compile("nc -q 0 localhost (\\d+)");
+
@Override
public Command createCommand(final String command)
{
return new Command()
{
public ExitCallback callback = null;
- public PrintStream out = null;
- public PrintStream err = null;
+ public OutputStream out = null;
+ public OutputStream err = null;
+ public InputStream in = null;
@Override
public void setInputStream(InputStream in)
{
+ this.in = in;
}
@Override
public void setOutputStream(OutputStream out)
{
- this.out = new PrintStream(out);
+ this.out = out;
}
@Override
public void setErrorStream(OutputStream err)
{
- this.err = new PrintStream(err);
+ this.err = err;
}
@Override
@@ -388,15 +513,36 @@ public class SftpProviderTestCase extend
int code = 0;
if (command.equals("id -G") || command.equals("id -u"))
{
- out.println(0);
+ new PrintStream(out).println(0);
+ } else if (NETCAT_COMMAND.matcher(command).matches())
+ {
+ final Matcher matcher = NETCAT_COMMAND.matcher(command);
+ matcher.matches();
+ final int port = Integer.parseInt(matcher.group(1));
+
+ Socket socket = new Socket((String) null, port);
+
+ if (out != null)
+ {
+ connect("from nc", socket.getInputStream(), out, null);
+ }
+
+ if (in != null)
+ {
+ connect("to nc", in, socket.getOutputStream(), callback);
+ }
+
+ return;
+
} else
{
if (err != null)
{
- err.format("Unknown command %s%n", command);
+ new PrintStream(err).format("Unknown command %s%n", command);
}
code = -1;
}
+
if (out != null)
{
out.flush();
@@ -416,6 +562,54 @@ public class SftpProviderTestCase extend
}
}
+ /**
+ * Creates a pipe thread that connects an input to an output
+ * @param name The name of the thread (for debugging purposes)
+ * @param in The input stream
+ * @param out The output stream
+ * @param callback An object whose method {@linkplain ExitCallback#onExit(int)} will be
+ * called when the pipe is broken. The integer argument is 0 if everything
+ * went well.
+ */
+ private static void connect(final String name, final InputStream in, final OutputStream out, final ExitCallback callback)
+ {
+ Thread thread = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ int code = 0;
+ try
+ {
+ byte buffer[] = new byte[1024];
+ int len;
+ while ((len = in.read(buffer, 0, buffer.length)) != -1)
+ {
+ out.write(buffer, 0, len);
+ out.flush();
+ }
+ }
+ catch (SshException ex)
+ {
+ // Nothing to do, this occurs when the connection
+ // is closed on the remote side
+ }
+ catch (IOException ex)
+ {
+ if (!ex.getMessage().equals("Pipe closed"))
+ {
+ code = -1;
+ }
+ }
+ if (callback != null)
+ {
+ callback.onExit(code);
+ }
+ }
+ }, name);
+ thread.setDaemon(true);
+ thread.start();
+ }
+
private static class SftpAttrs
{