Compare commits

...

26 Commits

Author SHA1 Message Date
Jeroen van Erp
1c749da957 v0.13.0 2015-08-18 14:25:13 +02:00
Jeroen van Erp
742553912c Move to new travis infra 2015-08-13 10:23:08 +02:00
Jeroen van Erp
84d15f4cf5 Removed old pom 2015-08-03 14:06:55 +02:00
Jeroen van Erp
1ebcbb07ba Fixed examples build 2015-08-03 14:06:38 +02:00
Jeroen van Erp
9982e5c30e Added testcase for #194 2015-06-17 21:49:05 +02:00
Jeroen van Erp
3f340d6927 Updated version in default config 2015-06-17 16:17:36 +02:00
Jeroen van Erp
b8eec64a37 Added tests and categories 2015-06-17 16:04:01 +02:00
Jeroen van Erp
314d9d01cf Updated release notes 2015-06-17 13:04:32 +02:00
Jeroen van Erp
c526f8e3de Merge branch 'bkarge-issue-183' 2015-06-17 12:37:55 +02:00
Jeroen van Erp
9529c30105 Reformatted 2015-06-17 12:36:31 +02:00
Jeroen van Erp
6a476858d1 Added RemoteFileTest 2015-06-17 12:28:57 +02:00
Jeroen van Erp
6bfb268c11 Merge branch 'issue-183' of https://github.com/bkarge/sshj into bkarge-issue-183 2015-06-17 12:12:56 +02:00
Jeroen van Erp
e334525da5 Rewritten integration tests 2015-06-17 12:12:37 +02:00
Jeroen van Erp
8776500fa0 Merge pull request #201 from iterate-ch/feature/algorithms-verifier
Add option for client to verify negotiated key exchange algorithms.
2015-06-16 15:50:06 +02:00
David Kocher
a747db88ed Add option for client to verify negotiated key exchange algorithms. 2015-06-16 15:42:01 +02:00
Jeroen van Erp
97065264de Cleared some JavaDoc warnings 2015-06-16 14:12:36 +02:00
Jeroen van Erp
7c26ac669a Started better integration testing setup with Mina 2015-06-16 14:12:24 +02:00
Jeroen van Erp
1c5b462206 Merge pull request #195 from bluekeyes/feature/gss-api
Add support for "gssapi-with-mic" authentication (Kerberos)
2015-06-16 10:33:44 +02:00
Jeroen van Erp
4cb9610cdd Merge pull request #196 from Boris-de/fix_hostname_matching
bugfix: match complete host instead of contains on the hoststring
2015-06-16 10:23:52 +02:00
Billy Keyes
b9d0a03cb3 Add simple test for AuthGssApiWithMic
Mock enough of the JGSS API to avoid needing a real Kerberos
environment. I'm not sure how accurate this is, but it should test that
the client is sending the correct packets in the corect order.
2015-06-11 11:44:44 -07:00
Billy Keyes
4adc83b9df Expose GSSManager in AuthGssApiWithMic
The default implementation only supports Kerberos and encourages
subclassing, so there should be a way to provide subclasses.
2015-06-11 11:38:02 -07:00
Björn Karge
14edb33fa9 fix for issue 183 (sftp.RemoteFile.ReadAheadRemoteFileInputStream) (revised) 2015-06-04 10:50:13 +08:00
Björn Karge
8e74330b0b fix for issue 183 (sftp.RemoteFile.ReadAheadRemoteFileInputStream) 2015-06-03 14:36:23 +08:00
Boris Wachtmeister
5217d34198 bugfix: match complete host instead of contains on the hoststring
The SimpleEntry currently matches the hostname of the connection against
the complete hoststring of the entry. This way substrings also match, so
for example "10.0.0.1" matches on an entry for "10.0.0.10", resulting in
a host-key-changed message if the key differs which is usually does.
2015-05-28 21:57:25 +02:00
Billy Keyes
d3d019c1c2 Remove unused imports in SSHClient 2015-05-19 10:49:20 -07:00
Ben Hamme
49185b044d Added AuthGssApiWithMic for Kerberos auth 2015-05-18 15:00:11 -07:00
38 changed files with 1476 additions and 523 deletions

View File

@@ -1 +1,2 @@
language: java
language: java
sudo: false

View File

@@ -1,7 +1,7 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.11.0
:sshj_version: 0.13.0
:source-highlighter: pygments
image::https://travis-ci.org/hierynomus/sshj.svg?branch=master[]
@@ -92,7 +92,11 @@ Google Group: http://groups.google.com/group/sshj-users
Fork away!
== Release history
SSHJ 0.13.0 (2015-08-18)::
* Merged https://github.com/hierynomus/sshj/issues/199[#199]: Fix for IndexOutOfBoundsException in ReadAheadRemoteFileInputStream, fixes https://github.com/hierynomus/sshj/issues/183[#183]
* Merged https://github.com/hierynomus/sshj/issues/195[#195]: New authentication supported: `gssapi-with-mic`
* Merged https://github.com/hierynomus/sshj/issues/201[#201]: New option to verify negotiated key exchange algorithms
* Merged https://github.com/hierynomus/sshj/issues/196[#196]: Fix for looking up complete hostname in known hosts file
SSHJ 0.12.0 (2015-04-14)::
* Added support for HTTP proxies when running JDK6 or JDK7, fixes: https://github.com/hierynomus/sshj/issues/170[#170]
* Merged https://github.com/hierynomus/sshj/issues/186[#186]: Fix for detecting end-of-stream

View File

@@ -4,7 +4,7 @@ apply plugin: "signing"
apply plugin: "osgi"
group = "com.hierynomus"
version = "0.12.0"
version = "0.13.0"
repositories {
mavenCentral()
@@ -22,6 +22,13 @@ configurations {
test {
include "**/*Test.*"
if (!project.hasProperty("allTests")) {
useJUnit {
excludeCategories 'com.hierynomus.sshj.test.SlowTests'
excludeCategories 'com.hierynomus.sshj.test.KnownFailingTests'
}
}
afterSuite { descriptor, result ->
if (descriptor.className != null) {
def indicator = "\u001B[32m✓\u001b[0m"

View File

@@ -21,10 +21,10 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.schmizz</groupId>
<artifactId>sshj</artifactId>
<groupId>com.hierynomus</groupId>
<artifactId>sshj-examples</artifactId>
<packaging>jar</packaging>
<version>0.10.0</version>
<version>0.13.0</version>
<name>sshj-examples</name>
<description>Examples for SSHv2 library for Java</description>
@@ -53,9 +53,9 @@
<dependencies>
<dependency>
<groupId>net.schmizz</groupId>
<groupId>com.hierynomus</groupId>
<artifactId>sshj</artifactId>
<version>0.10.0</version>
<version>0.13.0</version>
</dependency>
</dependencies>

344
pom.xml
View File

@@ -1,344 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2009 sshj contributors
Licensed 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.schmizz</groupId>
<artifactId>sshj</artifactId>
<packaging>bundle</packaging>
<version>0.10.1-SNAPSHOT</version>
<name>sshj</name>
<description>SSHv2 library for Java</description>
<url>http://github.com/hierynomus/sshj</url>
<inceptionYear>2009</inceptionYear>
<issueManagement>
<system>github</system>
<url>http://github.com/hierynomus/sshj/issues</url>
</issueManagement>
<scm>
<connection>scm:git:git://github.com/hierynomus/sshj.git</connection>
<developerConnection>scm:git:git@github.com:hierynomus/sshj.git</developerConnection>
<url>http://github.com/hierynomus/sshj</url>
</scm>
<licenses>
<license>
<name>Apache 2</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.51</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.51</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>0.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<developers>
<developer>
<id>hierynomus</id>
<name>Jeroen van Erp</name>
<email>jeroen@hierynomus.com</email>
</developer>
<developer>
<id>shikhar</id>
<name>Shikhar Bhushan</name>
<email>shikhar@schmizz.net</email>
<url>http://schmizz.net</url>
</developer>
<developer>
<id>iterate</id>
<name>David Kocher</name>
<email>dkocher@iterate.ch</email>
<organization>iterate GmbH</organization>
<organizationUrl>https://iterate.ch</organizationUrl>
</developer>
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.4.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>
!net.schmizz.*,
javax.crypto*,
com.jcraft.jzlib*;version="[1.1,2)";resolution:=optional,
org.slf4j*;version="[1.7,5)",
org.bouncycastle*,
*
</Import-Package>
<Export-Package>net.schmizz.*</Export-Package>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>clirr-maven-plugin</artifactId>
<version>2.6.1</version>
</plugin>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>2.6</version>
<configuration>
<header>src/etc/license-header</header>
<properties>
<owner>sshj contributors</owner>
<email>sshj-users@googlegroups.com</email>
</properties>
<excludes>
<exclude>**/README</exclude>
<exclude>src/test/resources/**</exclude>
<exclude>src/main/resources/**</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.2</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
--> </plugins>
</build>
<distributionManagement>
<repository>
<id>sonatype-nexus-staging</id>
<name>Nexus Release Repository</name>
<url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<profiles>
<profile>
<id>full-deps</id>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>doclint-java8-disable</id>
<activation>
<jdk>[1.8,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>clirr-maven-plugin</artifactId>
<version>2.6.1</version>
</plugin>
</plugins>
</reporting>
</project>

View File

@@ -1,39 +0,0 @@
package nl.javadude.sshj.connection.channel;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
public class ChannelCloseEofTest {
private SSHClient sshClient;
@Before
public void setUp() throws Exception {
sshClient = new SSHClient();
}
@After
public void tearDown() throws IOException {
sshClient.disconnect();
}
@Test
public void shouldCorrectlyHandleSessionChannelEof() throws IOException, InterruptedException {
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
sshClient.connect("172.16.37.129");
sshClient.authPassword("jeroen", "jeroen");
Session session = sshClient.startSession();
session.allocateDefaultPTY();
session.close();
Thread.sleep(1000);
assertThat("Should still be connected", sshClient.isConnected());
}
}

View File

@@ -9,7 +9,7 @@ public class Sockets {
/**
* Java 7 and up have Socket implemented as Closeable, whereas Java6 did not have this inheritance.
* @param socket The socket to wrap as Closeable
* @return
* @return The (potentially wrapped) Socket as a Closeable.
*/
public static Closeable asCloseable(final Socket socket) {
if (Closeable.class.isAssignableFrom(socket.getClass())) {

View File

@@ -81,7 +81,7 @@ public class DefaultConfig
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String VERSION = "SSHJ_0_9_2";
private static final String VERSION = "SSHJ_0_13_0";
public DefaultConfig() {
setVersion(VERSION);

View File

@@ -39,6 +39,7 @@ import net.schmizz.sshj.transport.TransportImpl;
import net.schmizz.sshj.transport.compression.DelayedZlibCompression;
import net.schmizz.sshj.transport.compression.NoneCompression;
import net.schmizz.sshj.transport.compression.ZlibCompression;
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
import net.schmizz.sshj.userauth.UserAuth;
@@ -49,6 +50,7 @@ import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
import net.schmizz.sshj.userauth.method.AuthMethod;
import net.schmizz.sshj.userauth.method.AuthPassword;
@@ -58,15 +60,19 @@ import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import net.schmizz.sshj.userauth.password.Resource;
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
import org.ietf.jgss.Oid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.security.auth.login.LoginContext;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
@@ -153,10 +159,19 @@ public class SSHClient
* Add a {@link HostKeyVerifier} which will be invoked for verifying host key during connection establishment and
* future key exchanges.
*
* @param hostKeyVerifier {@link HostKeyVerifier} instance
* @param verifier {@link HostKeyVerifier} instance
*/
public void addHostKeyVerifier(HostKeyVerifier hostKeyVerifier) {
trans.addHostKeyVerifier(hostKeyVerifier);
public void addHostKeyVerifier(HostKeyVerifier verifier) {
trans.addHostKeyVerifier(verifier);
}
/**
* Add a {@link AlgorithmsVerifier} which will be invoked for verifying negotiated algorithms.
*
* @param verifier {@link AlgorithmsVerifier} instance
*/
public void addAlgorithmsVerifier(AlgorithmsVerifier verifier) {
trans.addAlgorithmsVerifier(verifier);
}
/**
@@ -365,6 +380,30 @@ public class SSHClient
authPublickey(username, keyProviders);
}
/**
* Authenticate {@code username} using the {@code "gssapi-with-mic"} authentication method, given a login context
* for the peer GSS machine and a list of supported OIDs.
* <p/>
* Supported OIDs should be ordered by preference as the SSH server will choose the first OID that it also
* supports. At least one OID is required
*
* @param username user to authenticate
* @param context {@code LoginContext} for the peer GSS machine
* @param supportedOid first supported OID
* @param supportedOids other supported OIDs
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authGssApiWithMic(String username, LoginContext context, Oid supportedOid, Oid... supportedOids)
throws UserAuthException, TransportException {
// insert supportedOid to the front of the list since ordering matters
List<Oid> oids = new ArrayList<Oid>(Arrays.asList(supportedOids));
oids.add(0, supportedOid);
auth(username, new AuthGssApiWithMic(context, oids));
}
/**
* Disconnects from the connected SSH server. {@code SSHClient} objects are not reusable therefore it is incorrect
* to attempt connection after this method has been called.

View File

@@ -61,7 +61,7 @@ public abstract class SocketClient {
* @param port The port to connect to.
* @param proxy The proxy to connect via.
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(InetAddress host, int port, Proxy proxy) throws IOException {
@@ -86,7 +86,7 @@ public abstract class SocketClient {
* @param port The port to connect to.
* @param proxy The proxy to connect via.
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(String hostname, int port, Proxy proxy) throws IOException {
@@ -120,7 +120,7 @@ public abstract class SocketClient {
* @param host The host address to connect to.
* @param proxy The proxy to connect via.
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(InetAddress host, Proxy proxy) throws IOException {
@@ -132,7 +132,7 @@ public abstract class SocketClient {
* @param hostname The host name to connect to.
* @param proxy The proxy to connect via.
* @deprecated This method will be removed after v0.12.0. If you want to connect via a proxy, you can do this by injecting a {@link javax.net.SocketFactory}
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket(java.net.Proxy)} constructor.
* into the SocketClient. The SocketFactory should create sockets using the {@link java.net.Socket#Socket(java.net.Proxy)} constructor.
*/
@Deprecated
public void connect(String hostname, Proxy proxy) throws IOException {

View File

@@ -46,6 +46,9 @@ public enum Message {
USERAUTH_60(60),
USERAUTH_INFO_RESPONSE(61),
USERAUTH_GSSAPI_EXCHANGE_COMPLETE(63),
USERAUTH_GSSAPI_MIC(66),
GLOBAL_REQUEST(80),
REQUEST_SUCCESS(81),
REQUEST_FAILURE(82),

View File

@@ -66,7 +66,7 @@ public class ConnectionImpl
*
* @param config the ssh config
* @param trans transport layer
* @param keepAlive
* @param keepAlive the keep alive provider
*/
public ConnectionImpl(Transport trans, KeepAliveProvider keepAlive) {
super("ssh-connection", trans);

View File

@@ -1,12 +1,12 @@
/**
* Copyright 2009 sshj contributors
*
* <p/>
* Licensed 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
*
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
@@ -19,6 +19,7 @@ import net.schmizz.concurrent.Promise;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.sftp.Response.StatusCode;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -33,37 +34,31 @@ public class RemoteFile
super(requester, path, handle);
}
public FileAttributes fetchAttributes()
throws IOException {
public FileAttributes fetchAttributes() throws IOException {
return requester.request(newRequest(PacketType.FSTAT))
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS)
.ensurePacketTypeIs(PacketType.ATTRS)
.readFileAttributes();
}
public long length()
throws IOException {
public long length() throws IOException {
return fetchAttributes().getSize();
}
public void setLength(long len)
throws IOException {
public void setLength(long len) throws IOException {
setAttributes(new FileAttributes.Builder().withSize(len).build());
}
public int read(long fileOffset, byte[] to, int offset, int len)
throws IOException {
public int read(long fileOffset, byte[] to, int offset, int len) throws IOException {
final Response res = asyncRead(fileOffset, len).retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
return checkReadResponse(res, to, offset);
}
protected Promise<Response, SFTPException> asyncRead(long fileOffset, int len)
throws IOException {
protected Promise<Response, SFTPException> asyncRead(long fileOffset, int len) throws IOException {
return requester.request(newRequest(PacketType.READ).putUInt64(fileOffset).putUInt32(len));
}
protected int checkReadResponse(Response res, byte[] to, int offset)
throws Buffer.BufferException, SFTPException {
protected int checkReadResponse(Response res, byte[] to, int offset) throws Buffer.BufferException, SFTPException {
switch (res.getType()) {
case DATA:
int recvLen = res.readUInt32AsInt();
@@ -79,28 +74,25 @@ public class RemoteFile
}
}
public void write(long fileOffset, byte[] data, int off, int len)
throws IOException {
public void write(long fileOffset, byte[] data, int off, int len) throws IOException {
checkWriteResponse(asyncWrite(fileOffset, data, off, len));
}
protected Promise<Response, SFTPException> asyncWrite(long fileOffset, byte[] data, int off, int len)
throws IOException {
return requester.request(newRequest(PacketType.WRITE)
.putUInt64(fileOffset)
// TODO The SFTP spec claims this field is unneeded...? See #187
.putUInt32(len)
.putRawBytes(data, off, len)
.putUInt64(fileOffset)
// TODO The SFTP spec claims this field is unneeded...? See #187
.putUInt32(len)
.putRawBytes(data, off, len)
);
}
private void checkWriteResponse(Promise<Response, SFTPException> responsePromise)
throws SFTPException {
private void checkWriteResponse(Promise<Response, SFTPException> responsePromise) throws SFTPException {
responsePromise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
}
public void setAttributes(FileAttributes attrs)
throws IOException {
public void setAttributes(FileAttributes attrs) throws IOException {
requester.request(newRequest(PacketType.FSETSTAT).putFileAttributes(attrs))
.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK();
}
@@ -140,15 +132,13 @@ public class RemoteFile
}
@Override
public void write(int w)
throws IOException {
public void write(int w) throws IOException {
b[0] = (byte) w;
write(b, 0, 1);
}
@Override
public void write(byte[] buf, int off, int len)
throws IOException {
public void write(byte[] buf, int off, int len) throws IOException {
if (unconfirmedWrites.size() > maxUnconfirmedWrites) {
checkWriteResponse(unconfirmedWrites.remove());
}
@@ -157,23 +147,20 @@ public class RemoteFile
}
@Override
public void flush()
throws IOException {
public void flush() throws IOException {
while (!unconfirmedWrites.isEmpty()) {
checkWriteResponse(unconfirmedWrites.remove());
}
}
@Override
public void close()
throws IOException {
public void close() throws IOException {
flush();
}
}
public class RemoteFileInputStream
extends InputStream {
public class RemoteFileInputStream extends InputStream {
private final byte[] b = new byte[1];
@@ -201,31 +188,29 @@ public class RemoteFile
}
@Override
public void reset()
throws IOException {
public void reset() throws IOException {
fileOffset = markPos;
}
@Override
public long skip(long n)
throws IOException {
public long skip(long n) throws IOException {
return (this.fileOffset = Math.min(fileOffset + n, length()));
}
@Override
public int read()
throws IOException {
public int read() throws IOException {
return read(b, 0, 1) == -1 ? -1 : b[0] & 0xff;
}
@Override
public int read(byte[] into, int off, int len)
throws IOException {
public int read(byte[] into, int off, int len) throws IOException {
int read = RemoteFile.this.read(fileOffset, into, off, len);
if (read != -1) {
fileOffset += read;
if (markPos != 0 && read > readLimit) // Invalidate mark position
if (markPos != 0 && read > readLimit) {
// Invalidate mark position
markPos = 0;
}
}
return read;
}
@@ -238,27 +223,56 @@ public class RemoteFile
private final byte[] b = new byte[1];
private final int maxUnconfirmedReads;
private final Queue<Promise<Response, SFTPException>> unconfirmedReads;
private final Queue<Promise<Response, SFTPException>> unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
private final Queue<Long> unconfirmedReadOffsets = new LinkedList<Long>();
private long fileOffset;
private long requestOffset;
private long responseOffset;
private boolean eof;
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads) {
assert 0 <= maxUnconfirmedReads;
this.maxUnconfirmedReads = maxUnconfirmedReads;
this.unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
this.fileOffset = 0;
}
public ReadAheadRemoteFileInputStream(int maxUnconfirmedReads, long fileOffset) {
assert 0 <= maxUnconfirmedReads;
assert 0 <= fileOffset;
this.maxUnconfirmedReads = maxUnconfirmedReads;
this.unconfirmedReads = new LinkedList<Promise<Response, SFTPException>>();
this.fileOffset = fileOffset;
this.requestOffset = this.responseOffset = fileOffset;
}
@Override
public long skip(long n)
throws IOException {
throw new IOException("skip is not supported by ReadAheadFileInputStream, use RemoteFileInputStream instead");
private ByteArrayInputStream pending = new ByteArrayInputStream(new byte[0]);
private boolean retrieveUnconfirmedRead(boolean blocking) throws IOException {
if (unconfirmedReads.size() <= 0) {
return false;
}
if (!blocking && !unconfirmedReads.peek().isDelivered()) {
return false;
}
unconfirmedReadOffsets.remove();
final Response res = unconfirmedReads.remove().retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
switch (res.getType()) {
case DATA:
int recvLen = res.readUInt32AsInt();
responseOffset += recvLen;
pending = new ByteArrayInputStream(res.array(), res.rpos(), recvLen);
break;
case STATUS:
res.ensureStatusIs(Response.StatusCode.EOF);
eof = true;
break;
default:
throw new SFTPException("Unexpected packet: " + res.getType());
}
return true;
}
@Override
@@ -268,26 +282,66 @@ public class RemoteFile
}
@Override
public int read(byte[] into, int off, int len)
throws IOException {
while (!eof && unconfirmedReads.size() <= maxUnconfirmedReads) {
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
unconfirmedReads.add(asyncRead(fileOffset, len));
fileOffset += len;
public int read(byte[] into, int off, int len) throws IOException {
while (!eof && pending.available() <= 0) {
// we also need to go here for len <= 0, because pending may be at
// EOF in which case it would return -1 instead of 0
while (unconfirmedReads.size() <= maxUnconfirmedReads) {
// Send read requests as long as there is no EOF and we have not reached the maximum parallelism
int reqLen = Math.max(1024, len); // don't be shy!
unconfirmedReads.add(RemoteFile.this.asyncRead(requestOffset, reqLen));
unconfirmedReadOffsets.add(requestOffset);
requestOffset += reqLen;
}
long nextOffset = unconfirmedReadOffsets.peek();
if (responseOffset != nextOffset) {
// the server could not give us all the data we needed, so
// we try to fill the gap synchronously
assert responseOffset < nextOffset;
assert 0 < (nextOffset - responseOffset);
assert (nextOffset - responseOffset) <= Integer.MAX_VALUE;
byte[] buf = new byte[(int) (nextOffset - responseOffset)];
int recvLen = RemoteFile.this.read(responseOffset, buf, 0, buf.length);
if (recvLen < 0) {
eof = true;
return -1;
}
if (0 == recvLen) {
// avoid infinite loops
throw new SFTPException("Unexpected response size (0), bailing out");
}
responseOffset += recvLen;
pending = new ByteArrayInputStream(buf, 0, recvLen);
} else if (!retrieveUnconfirmedRead(true /*blocking*/)) {
// this may happen if we change prefetch strategy
// currently, we should never get here...
throw new IllegalStateException("Could not retrieve data for pending read request");
}
}
if (unconfirmedReads.isEmpty()) {
assert eof;
return -1;
}
// Retrieve first in
final Response res = unconfirmedReads.remove().retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS);
final int recvLen = checkReadResponse(res, into, off);
if (recvLen == -1) {
eof = true;
}
return recvLen;
return pending.read(into, off, len);
}
@Override
public int available() throws IOException {
boolean lastRead = true;
while (!eof && (pending.available() <= 0) && lastRead) {
lastRead = retrieveUnconfirmedRead(false /*blocking*/);
}
return pending.available();
}
}
}
}

View File

@@ -33,6 +33,8 @@ import net.schmizz.sshj.transport.digest.Digest;
import net.schmizz.sshj.transport.kex.KeyExchange;
import net.schmizz.sshj.transport.mac.MAC;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,6 +70,8 @@ final class KeyExchanger
*/
private final Queue<HostKeyVerifier> hostVerifiers = new LinkedList<HostKeyVerifier>();
private final Queue<AlgorithmsVerifier> algorithmVerifiers = new LinkedList<AlgorithmsVerifier>();
private final AtomicBoolean kexOngoing = new AtomicBoolean();
/** What we are expecting from the next packet */
@@ -108,6 +112,10 @@ final class KeyExchanger
hostVerifiers.add(hkv);
}
synchronized void addAlgorithmsVerifier(AlgorithmsVerifier verifier) {
algorithmVerifiers.add(verifier);
}
/**
* Returns the session identifier computed during key exchange.
*
@@ -218,6 +226,13 @@ final class KeyExchanger
final Proposal serverProposal = new Proposal(buf);
negotiatedAlgs = clientProposal.negotiate(serverProposal);
log.debug("Negotiated algorithms: {}", negotiatedAlgs);
for(AlgorithmsVerifier v: algorithmVerifiers) {
log.debug("Trying to verify algorithms with {}", v);
if(!v.verify(negotiatedAlgs)) {
throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED,
"Failed to verify negotiated algorithms `" + negotiatedAlgs + "`");
}
}
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(),
negotiatedAlgs.getKeyExchangeAlgorithm());
try {

View File

@@ -48,23 +48,25 @@ public final class Reader
int read;
try {
read = inp.read(recvbuf, 0, needed);
}
catch(SocketTimeoutException e) {
} catch(SocketTimeoutException e) {
if (isInterrupted()) {
throw e;
}
continue;
}
if (read == -1)
if (read == -1) {
throw new TransportException("Broken transport; encountered EOF");
else
} else {
needed = decoder.received(recvbuf, read);
}
}
} catch (Exception e) {
//noinspection StatementWithEmptyBody
if (isInterrupted()) {
// We are meant to shut up and draw to a close if interrupted
} else
} else {
trans.die(e);
}
}
log.debug("Stopping");

View File

@@ -20,6 +20,7 @@ import net.schmizz.sshj.Service;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.common.SSHPacketHandler;
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import java.io.InputStream;
@@ -51,6 +52,13 @@ public interface Transport
*/
void addHostKeyVerifier(HostKeyVerifier hkv);
/**
* Adds the specified verifier.
*
* @param verifier The verifier to call with negotiated algorithms
*/
void addAlgorithmsVerifier(AlgorithmsVerifier verifier);
/**
* Do key exchange and algorithm negotiation. This can be the initial one or for algorithm renegotiation.
*

View File

@@ -27,6 +27,7 @@ import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -235,6 +236,11 @@ public final class TransportImpl
kexer.addHostKeyVerifier(hkv);
}
@Override
public void addAlgorithmsVerifier(AlgorithmsVerifier verifier) {
kexer.addAlgorithmsVerifier(verifier);
}
@Override
public void doKex()
throws TransportException {

View File

@@ -0,0 +1,27 @@
/**
* Copyright 2009 sshj contributors
*
* Licensed 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 net.schmizz.sshj.transport.verification;
import net.schmizz.sshj.transport.NegotiatedAlgorithms;
public interface AlgorithmsVerifier {
/**
* Callback is invoked when algorithms have been negotiated between client and server.
* @return False to interrupt the connection
*/
boolean verify(NegotiatedAlgorithms algorithms);
}

View File

@@ -352,7 +352,7 @@ public class OpenSSHKnownHosts
@Override
public boolean appliesTo(KeyType type, String host)
throws IOException {
return type == this.type && hostnames.contains(host);
return type == this.type && hosts.contains(host);
}
}

View File

@@ -0,0 +1,188 @@
package net.schmizz.sshj.userauth.method;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import net.schmizz.sshj.common.Buffer.BufferException;
import net.schmizz.sshj.common.Buffer.PlainBuffer;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.userauth.UserAuthException;
/** Implements authentication by GSS-API. */
public class AuthGssApiWithMic
extends AbstractAuthMethod {
private final LoginContext loginContext;
private final List<Oid> mechanismOids;
private final GSSManager manager;
private GSSContext secContext;
public AuthGssApiWithMic(LoginContext loginContext, List<Oid> mechanismOids) {
this(loginContext, mechanismOids, GSSManager.getInstance());
}
public AuthGssApiWithMic(LoginContext loginContext, List<Oid> mechanismOids, GSSManager manager) {
super("gssapi-with-mic");
this.loginContext = loginContext;
this.mechanismOids = mechanismOids;
this.manager = manager;
secContext = null;
}
@Override
public SSHPacket buildReq()
throws UserAuthException {
SSHPacket packet = super.buildReq() // the generic stuff
.putUInt32(mechanismOids.size()); // number of OIDs we support
for (Oid oid : mechanismOids) {
try {
packet.putString(oid.getDER());
} catch (GSSException e) {
throw new UserAuthException("Mechanism OID could not be encoded: " + oid.toString(), e);
}
}
return packet;
}
/**
* PrivilegedExceptionAction to be executed within the given LoginContext for
* initializing the GSSContext.
*
* @author Ben Hamme
*/
private class InitializeContextAction implements PrivilegedExceptionAction<GSSContext> {
private final Oid selectedOid;
public InitializeContextAction(Oid selectedOid) {
this.selectedOid = selectedOid;
}
@Override
public GSSContext run() throws GSSException {
GSSName clientName = manager.createName(params.getUsername(), GSSName.NT_USER_NAME);
GSSCredential clientCreds = manager.createCredential(clientName, GSSContext.DEFAULT_LIFETIME, selectedOid, GSSCredential.INITIATE_ONLY);
GSSName peerName = manager.createName("host@" + params.getTransport().getRemoteHost(), GSSName.NT_HOSTBASED_SERVICE);
GSSContext context = manager.createContext(peerName, selectedOid, clientCreds, GSSContext.DEFAULT_LIFETIME);
context.requestMutualAuth(true);
context.requestInteg(true);
return context;
}
}
private void sendToken(byte[] token) throws TransportException {
SSHPacket packet = new SSHPacket(Message.USERAUTH_INFO_RESPONSE).putString(token);
params.getTransport().write(packet);
}
private void handleContextInitialization(SSHPacket buf)
throws UserAuthException, TransportException {
byte[] bytes;
try {
bytes = buf.readBytes();
} catch (BufferException e) {
throw new UserAuthException("Failed to read byte array from message buffer", e);
}
Oid selectedOid;
try {
selectedOid = new Oid(bytes);
} catch (GSSException e) {
throw new UserAuthException("Exception constructing OID from server response", e);
}
log.debug("Server selected OID: {}", selectedOid.toString());
log.debug("Initializing GSSAPI context");
Subject subject = loginContext.getSubject();
try {
secContext = Subject.doAs(subject, new InitializeContextAction(selectedOid));
} catch (PrivilegedActionException e) {
throw new UserAuthException("Exception during context initialization", e);
}
log.debug("Sending initial token");
byte[] inToken = new byte[0];
try {
byte[] outToken = secContext.initSecContext(inToken, 0, inToken.length);
sendToken(outToken);
} catch (GSSException e) {
throw new UserAuthException("Exception sending initial token", e);
}
}
private byte[] handleTokenFromServer(SSHPacket buf) throws UserAuthException {
byte[] token;
try {
token = buf.readStringAsBytes();
} catch (BufferException e) {
throw new UserAuthException("Failed to read string from message buffer", e);
}
try {
return secContext.initSecContext(token, 0, token.length);
} catch (GSSException e) {
throw new UserAuthException("Exception during token exchange", e);
}
}
private byte[] generateMIC() throws UserAuthException {
byte[] msg = new PlainBuffer().putString(params.getTransport().getSessionID())
.putByte(Message.USERAUTH_REQUEST.toByte())
.putString(params.getUsername())
.putString(params.getNextServiceName())
.putString(getName())
.getCompactData();
try {
return secContext.getMIC(msg, 0, msg.length, null);
} catch (GSSException e) {
throw new UserAuthException("Exception getting message integrity code", e);
}
}
@Override
public void handle(Message cmd, SSHPacket buf)
throws UserAuthException, TransportException {
if (cmd == Message.USERAUTH_60) {
handleContextInitialization(buf);
} else if (cmd == Message.USERAUTH_INFO_RESPONSE) {
byte[] token = handleTokenFromServer(buf);
if (!secContext.isEstablished()) {
log.debug("Sending token");
sendToken(token);
} else {
if (secContext.getIntegState()) {
log.debug("Per-message integrity protection available: finalizing authentication with message integrity code");
params.getTransport().write(new SSHPacket(Message.USERAUTH_GSSAPI_MIC).putString(generateMIC()));
} else {
log.debug("Per-message integrity protection unavailable: finalizing authentication");
params.getTransport().write(new SSHPacket(Message.USERAUTH_GSSAPI_EXCHANGE_COMPLETE));
}
}
} else {
super.handle(cmd, buf);
}
}
}

View File

@@ -0,0 +1,27 @@
package com.hierynomus.sshj.connection.channel;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.connection.channel.direct.Session;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
public class ChannelCloseEofTest {
@Rule
public SshFixture fixture = new SshFixture();
@Test
public void shouldCorrectlyHandleSessionChannelEof() throws IOException, InterruptedException {
fixture.setupConnectedDefaultClient().authPassword("jeroen", "jeroen");
Session session = fixture.getClient().startSession();
session.allocateDefaultPTY();
session.close();
Thread.sleep(1000);
assertThat("Should still be connected", fixture.getClient().isConnected());
}
}

View File

@@ -0,0 +1,34 @@
package com.hierynomus.sshj.connection.channel.direct;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
public class CommandTest {
@Rule
public SshFixture fixture = new SshFixture();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Test
public void shouldExecuteBackgroundCommand() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
sshClient.authPassword("jeroen", "jeroen");
File file = new File(temp.getRoot(), "testdir");
assertThat("File should not exist", !file.exists());
Session.Command exec = sshClient.startSession().exec("mkdir " + file.getPath() + " &");
exec.join();
assertThat("File should exist", file.exists());
assertThat("File should be directory", file.isDirectory());
}
}

View File

@@ -0,0 +1,54 @@
package com.hierynomus.sshj.keepalive;
import com.hierynomus.sshj.test.KnownFailingTests;
import com.hierynomus.sshj.test.SlowTests;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.UserAuthException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import static org.junit.Assert.fail;
public class KeepAliveThreadTerminationTest {
@Rule
public SshFixture fixture = new SshFixture();
@Test
@Category({SlowTests.class, KnownFailingTests.class})
public void shouldCorrectlyTerminateThreadOnDisconnect() throws IOException, InterruptedException {
DefaultConfig defaultConfig = new DefaultConfig();
defaultConfig.setKeepAliveProvider(KeepAliveProvider.KEEP_ALIVE);
for (int i = 0; i < 10; i++) {
SSHClient sshClient = fixture.setupClient(defaultConfig);
fixture.connectClient(sshClient);
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(1);
try {
sshClient.authPassword("bad", "credentials");
fail("Should not auth.");
} catch (UserAuthException e) {
// OK
}
fixture.stopClient();
Thread.sleep(2000);
}
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
for (long l : threadMXBean.getAllThreadIds()) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(l);
if (threadInfo.getThreadName().equals("keep-alive") && threadInfo.getThreadState() != Thread.State.TERMINATED) {
System.err.println("Found thread in state " + threadInfo.getThreadState());
throw new RuntimeException("Found alive keep-alive thread");
}
}
}
}

View File

@@ -0,0 +1,74 @@
package com.hierynomus.sshj.sftp;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.OpenMode;
import net.schmizz.sshj.sftp.RemoteFile;
import net.schmizz.sshj.sftp.SFTPEngine;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.EnumSet;
import java.util.Random;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
public class RemoteFileTest {
@Rule
public SshFixture fixture = new SshFixture();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Test
public void shouldNotGoOutOfBoundsInReadAheadInputStream() throws IOException {
SSHClient ssh = fixture.setupConnectedDefaultClient();
ssh.authPassword("test", "test");
SFTPEngine sftp = new SFTPEngine(ssh).init();
RemoteFile rf;
File file = temp.newFile("SftpReadAheadTest.bin");
rf = sftp.open(file.getPath(), EnumSet.of(OpenMode.WRITE, OpenMode.CREAT));
byte[] data = new byte[8192];
new Random(53).nextBytes(data);
data[3072] = 1;
rf.write(0, data, 0, data.length);
rf.close();
assertThat("The file should exist", file.exists());
rf = sftp.open(file.getPath());
InputStream rs = rf.new ReadAheadRemoteFileInputStream(16 /*maxUnconfirmedReads*/);
byte[] test = new byte[4097];
int n = 0;
while (n < 2048) {
n += rs.read(test, n, 2048 - n);
}
while (n < 3072) {
n += rs.read(test, n, 3072 - n);
}
if (test[3072] != 0) {
System.err.println("buffer overrun!");
}
n += rs.read(test, n, test.length - n); // --> ArrayIndexOutOfBoundsException
byte[] test2 = new byte[data.length];
System.arraycopy(test, 0, test2, 0, test.length);
while (n < data.length) {
n += rs.read(test2, n, data.length - n);
}
assertThat("The written and received data should match", data, equalTo(test2));
}
}

View File

@@ -0,0 +1,39 @@
package com.hierynomus.sshj.sftp;
import com.hierynomus.sshj.test.SshFixture;
import com.hierynomus.sshj.test.util.FileUtil;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.SFTPClient;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
public class SFTPClientTest {
@Rule
public SshFixture fixture = new SshFixture();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Test
public void shouldNotThrowExceptionOnCloseBeforeDisconnect() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
sshClient.authPassword("test", "test");
SFTPClient sftpClient = sshClient.newSFTPClient();
File file = temp.newFile("source.txt");
FileUtil.writeToFile(file, "This is the source");
try {
try {
sftpClient.put(file.getPath(), temp.newFile("dest.txt").getPath());
} finally {
sftpClient.close();
}
} finally {
sshClient.disconnect();
}
}
}

View File

@@ -0,0 +1,9 @@
package com.hierynomus.sshj.test;
/**
* Marker interface for JUnit Categories.
*
* This denotes that the test is known to fail, and should be fixed at some time.
*/
public interface KnownFailingTests {
}

View File

@@ -0,0 +1,4 @@
package com.hierynomus.sshj.test;
public interface SlowTests {
}

View File

@@ -0,0 +1,161 @@
package com.hierynomus.sshj.test;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.userauth.UserAuthException;
import net.schmizz.sshj.util.gss.BogusGSSAuthenticator;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.sftp.SftpSubsystem;
import org.apache.sshd.server.shell.ProcessShellFactory;
import org.junit.rules.ExternalResource;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Can be used as a rule to ensure the server is teared down after each test.
*/
public class SshFixture extends ExternalResource {
public static final String hostkey = "src/test/resources/hostkey.pem";
public static final String fingerprint = "ce:a7:c1:cf:17:3f:96:49:6a:53:1a:05:0b:ba:90:db";
private SshServer server = defaultSshServer();
private SSHClient client = null;
private AtomicBoolean started = new AtomicBoolean(false);
private boolean autoStart = true;
public SshFixture(boolean autoStart) {
this.autoStart = autoStart;
}
public SshFixture() {
}
@Override
protected void before() throws Throwable {
if (autoStart) {
start();
}
}
@Override
protected void after() {
stopClient();
stopServer();
}
public void start() throws IOException {
server.start();
started.set(true);
}
public SSHClient setupConnectedDefaultClient() throws IOException {
return connectClient(setupDefaultClient());
}
public SSHClient setupDefaultClient() {
return setupClient(new DefaultConfig());
}
public SSHClient setupClient(Config config) {
if (client == null) {
client = new SSHClient(config);
client.addHostKeyVerifier(fingerprint);
}
return client;
}
public SSHClient getClient() {
if (client != null) {
return client;
}
throw new IllegalStateException("First call one of the setup*Client methods");
}
public SSHClient connectClient(SSHClient client) throws IOException {
client.connect(server.getHost(), server.getPort());
return client;
}
private SshServer defaultSshServer() {
SshServer sshServer = SshServer.setUpDefaultServer();
sshServer.setPort(randomPort());
sshServer.setKeyPairProvider(new FileKeyPairProvider(new String[]{hostkey}));
sshServer.setPasswordAuthenticator(new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
return username.equals(password);
}
});
sshServer.setGSSAuthenticator(new BogusGSSAuthenticator());
sshServer.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
sshServer.setCommandFactory(new ScpCommandFactory(new CommandFactory() {
public Command createCommand(String command) {
EnumSet<ProcessShellFactory.TtyOptions> ttyOptions;
if (OsUtils.isUNIX()) {
ttyOptions = EnumSet.of(ProcessShellFactory.TtyOptions.ONlCr);
} else {
ttyOptions = EnumSet.of(ProcessShellFactory.TtyOptions.Echo, ProcessShellFactory.TtyOptions.ICrNl, ProcessShellFactory.TtyOptions.ONlCr);
}
return new ProcessShellFactory(command.split(" "), ttyOptions).create();
}
}));
return sshServer;
}
private int randomPort() {
try {
ServerSocket s = null;
try {
s = new ServerSocket(0);
return s.getLocalPort();
} finally {
if (s != null)
s.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void stopClient() {
if (client != null && client.isConnected()) {
try {
client.disconnect();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
client = null;
}
} else if (client != null) {
client = null;
}
}
public void stopServer() {
if (started.get()) {
try {
server.stop();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,20 @@
package com.hierynomus.sshj.test.util;
import net.schmizz.sshj.common.IOUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class FileUtil {
public static void writeToFile(File f, String content) throws IOException {
FileWriter w = new FileWriter(f);
try {
w.write(content);
} finally {
IOUtils.closeQuietly(w);
}
}
}

View File

@@ -1,58 +1,39 @@
/**
* Copyright 2009 sshj contributors
*
* Licensed 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 net.schmizz.sshj.transport;
package com.hierynomus.sshj.transport;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.util.BasicFixture;
import org.junit.After;
import net.schmizz.sshj.transport.DisconnectListener;
import net.schmizz.sshj.transport.TransportException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class Disconnection {
public class DisconnectionTest {
private AtomicBoolean disconnected = null;
private final BasicFixture fixture = new BasicFixture();
private boolean notified;
@Rule
public SshFixture fixture = new SshFixture();
@Before
public void setUp()
throws IOException {
fixture.init();
notified = false;
fixture.getClient().getTransport().setDisconnectListener(new DisconnectListener() {
public void setupFlag() throws IOException {
disconnected = new AtomicBoolean(false);
// Initialize the client
SSHClient defaultClient = fixture.setupDefaultClient();
defaultClient.getTransport().setDisconnectListener(new DisconnectListener() {
@Override
public void notifyDisconnect(DisconnectReason reason, String message) {
notified = true;
disconnected.set(true);
}
});
}
@After
public void tearDown()
throws IOException, InterruptedException {
fixture.done();
fixture.connectClient(defaultClient);
}
private boolean joinToClientTransport(int seconds) {
@@ -67,8 +48,8 @@ public class Disconnection {
@Test
public void listenerNotifiedOnClientDisconnect()
throws IOException {
fixture.stopClient();
assertTrue(notified);
fixture.getClient().disconnect();
assertTrue(disconnected.get());
}
@Test
@@ -76,13 +57,13 @@ public class Disconnection {
throws InterruptedException, IOException {
fixture.stopServer();
joinToClientTransport(2);
assertTrue(notified);
assertTrue(disconnected.get());
}
@Test
public void joinNotifiedOnClientDisconnect()
throws IOException {
fixture.stopClient();
fixture.getClient().disconnect();
assertTrue(joinToClientTransport(2));
}
@@ -93,4 +74,4 @@ public class Disconnection {
assertFalse(joinToClientTransport(2));
}
}
}

View File

@@ -0,0 +1,52 @@
package com.hierynomus.sshj.userauth;
import com.hierynomus.sshj.test.SshFixture;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.method.AuthGssApiWithMic;
import net.schmizz.sshj.util.gss.BogusGSSManager;
import org.junit.Rule;
import org.junit.Test;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import java.util.Collections;
import static org.junit.Assert.assertTrue;
public class GssApiTest {
@Rule
public SshFixture fixture = new SshFixture();
private static final String LOGIN_CONTEXT_NAME = "TestLoginContext";
private static class TestAuthConfiguration extends Configuration {
private AppConfigurationEntry entry = new AppConfigurationEntry(
"testLoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
Collections.<String, Object> emptyMap());
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
if (name.equals(LOGIN_CONTEXT_NAME)) {
return new AppConfigurationEntry[] { entry };
} else {
return new AppConfigurationEntry[0];
}
}
}
@Test
public void authenticated() throws Exception {
AuthGssApiWithMic authMethod = new AuthGssApiWithMic(
new LoginContext(LOGIN_CONTEXT_NAME, null, null, new TestAuthConfiguration()),
Collections.singletonList(BogusGSSManager.KRB5_MECH),
new BogusGSSManager());
SSHClient defaultClient = fixture.setupConnectedDefaultClient();
defaultClient.auth("user", authMethod);
assertTrue(defaultClient.isAuthenticated());
}
}

View File

@@ -29,6 +29,7 @@ import java.security.PublicKey;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -78,6 +79,7 @@ public class OpenSSHKnownHostsTest {
assertTrue(kh.verify("schmizz.net", 22, key));
assertTrue(kh.verify("69.163.155.180", 22, key));
assertFalse(kh.verify("69.163.155.18", 22, key));
}
@Test

View File

@@ -33,9 +33,11 @@ package net.schmizz.sshj.util;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.userauth.UserAuthException;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import java.io.IOException;
@@ -50,6 +52,8 @@ public class BasicFixture {
public static final String hostname = "localhost";
public final int port = gimmeAPort();
private GSSAuthenticator gssAuthenticator;
private SSHClient client;
private SshServer server;
@@ -99,6 +103,7 @@ public class BasicFixture {
return false;
}
});
server.setGSSAuthenticator(gssAuthenticator);
server.start();
serverRunning = true;
}
@@ -137,6 +142,10 @@ public class BasicFixture {
return client;
}
public void setGssAuthenticator(GSSAuthenticator gssAuthenticator) {
this.gssAuthenticator = gssAuthenticator;
}
public void dummyAuth()
throws UserAuthException, TransportException {
server.setPasswordAuthenticator(new BogusPasswordAuthenticator());

View File

@@ -0,0 +1,22 @@
package net.schmizz.sshj.util.gss;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
public class BogusGSSAuthenticator
extends GSSAuthenticator {
private final GSSManager manager = new BogusGSSManager();
@Override
public GSSManager getGSSManager() {
return manager;
}
@Override
public GSSCredential getGSSCredential(GSSManager mgr) throws GSSException {
return manager.createCredential(GSSCredential.ACCEPT_ONLY);
}
}

View File

@@ -0,0 +1,243 @@
package net.schmizz.sshj.util.gss;
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.ietf.jgss.ChannelBinding;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid;
public class BogusGSSContext
implements GSSContext {
private static final byte[] INIT_TOKEN = fromString("INIT");
private static final byte[] ACCEPT_TOKEN = fromString("ACCEPT");
private static final byte[] MIC = fromString("LGTM");
private static byte[] fromString(String s) {
return s.getBytes(Charset.forName("UTF-8"));
}
private boolean initialized = false;
private boolean accepted = false;
private boolean integState = false;
private boolean mutualAuthState = false;
@Override
public byte[] initSecContext(byte[] inputBuf, int offset, int len) throws GSSException {
initialized = true;
return INIT_TOKEN;
}
@Override
public int initSecContext(InputStream inStream, OutputStream outStream) throws GSSException {
throw unavailable();
}
@Override
public byte[] acceptSecContext(byte[] inToken, int offset, int len) throws GSSException {
accepted = Arrays.equals(INIT_TOKEN, Arrays.copyOfRange(inToken, offset, offset + len));
return ACCEPT_TOKEN;
}
@Override
public void acceptSecContext(InputStream inStream, OutputStream outStream) throws GSSException {
throw unavailable();
}
@Override
public boolean isEstablished() {
return initialized || accepted;
}
@Override
public void dispose() throws GSSException {}
@Override
public int getWrapSizeLimit(int qop, boolean confReq, int maxTokenSize) throws GSSException {
throw unavailable();
}
@Override
public byte[] wrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException {
throw unavailable();
}
@Override
public void wrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
throw unavailable();
}
@Override
public byte[] unwrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException {
throw unavailable();
}
@Override
public void unwrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
throw unavailable();
}
@Override
public byte[] getMIC(byte[] inMsg, int offset, int len, MessageProp msgProp) throws GSSException {
return MIC;
}
@Override
public void getMIC(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException {
throw unavailable();
}
@Override
public void verifyMIC(byte[] inToken, int tokOffset, int tokLen, byte[] inMsg, int msgOffset, int msgLen, MessageProp msgProp) throws GSSException {
if (!Arrays.equals(MIC, Arrays.copyOfRange(inToken, tokOffset, tokOffset + tokLen))) {
throw new GSSException(GSSException.BAD_MIC);
}
}
@Override
public void verifyMIC(InputStream tokStream, InputStream msgStream, MessageProp msgProp) throws GSSException {
throw unavailable();
}
@Override
public byte[] export() throws GSSException {
throw unavailable();
}
@Override
public void requestMutualAuth(boolean state) throws GSSException {
this.mutualAuthState = state;
}
@Override
public void requestInteg(boolean state) throws GSSException {
this.integState = state;
}
@Override
public void requestReplayDet(boolean state) throws GSSException {
throw unavailable();
}
@Override
public void requestSequenceDet(boolean state) throws GSSException {
throw unavailable();
}
@Override
public void requestCredDeleg(boolean state) throws GSSException {
throw unavailable();
}
@Override
public void requestAnonymity(boolean state) throws GSSException {
throw unavailable();
}
@Override
public void requestConf(boolean state) throws GSSException {
throw unavailable();
}
@Override
public void requestLifetime(int lifetime) throws GSSException {
throw unavailable();
}
@Override
public void setChannelBinding(ChannelBinding cb) throws GSSException {
throw unavailable();
}
@Override
public boolean getMutualAuthState() {
return mutualAuthState;
}
@Override
public boolean getIntegState() {
return integState;
}
@Override
public boolean getCredDelegState() {
return false;
}
@Override
public boolean getReplayDetState() {
return false;
}
@Override
public boolean getSequenceDetState() {
return false;
}
@Override
public boolean getAnonymityState() {
return false;
}
@Override
public boolean isTransferable() throws GSSException {
return false;
}
@Override
public boolean isProtReady() {
return false;
}
@Override
public boolean getConfState() {
return false;
}
@Override
public int getLifetime() {
return INDEFINITE_LIFETIME;
}
@Override
public GSSName getSrcName() throws GSSException {
try {
String hostname = InetAddress.getLocalHost().getCanonicalHostName();
return new BogusGSSName("user@" + hostname, GSSName.NT_HOSTBASED_SERVICE);
} catch (UnknownHostException e) {
throw new IllegalStateException(e);
}
}
@Override
public GSSName getTargName() throws GSSException {
throw unavailable();
}
@Override
public Oid getMech() throws GSSException {
return BogusGSSManager.KRB5_MECH;
}
@Override
public GSSCredential getDelegCred() throws GSSException {
throw unavailable();
}
@Override
public boolean isInitiator() throws GSSException {
return false;
}
}

View File

@@ -0,0 +1,87 @@
package net.schmizz.sshj.util.gss;
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
public class BogusGSSCredential
implements GSSCredential {
private final GSSName name;
private final int usage;
public BogusGSSCredential(GSSName name, int usage) {
this.name = name;
this.usage = usage;
}
@Override
public void dispose() throws GSSException {}
@Override
public GSSName getName() throws GSSException {
return name;
}
@Override
public GSSName getName(Oid mech) throws GSSException {
return name.canonicalize(mech);
}
@Override
public int getRemainingLifetime() throws GSSException {
return INDEFINITE_LIFETIME;
}
@Override
public int getRemainingInitLifetime(Oid mech) throws GSSException {
return INDEFINITE_LIFETIME;
}
@Override
public int getRemainingAcceptLifetime(Oid mech) throws GSSException {
return INDEFINITE_LIFETIME;
}
@Override
public int getUsage() throws GSSException {
return usage;
}
@Override
public int getUsage(Oid mech) throws GSSException {
return usage;
}
@Override
public Oid[] getMechs() throws GSSException {
return new Oid[] { BogusGSSManager.KRB5_MECH };
}
@Override
public void add(GSSName name, int initLifetime, int acceptLifetime, Oid mech, int usage) throws GSSException {
throw unavailable();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public int hashCode() {
return (name == null ? 0 : name.hashCode());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BogusGSSCredential)) {
return false;
}
GSSName otherName = ((BogusGSSCredential) obj).name;
return name == null ? otherName == null : name.equals((Object) otherName);
}
}

View File

@@ -0,0 +1,106 @@
package net.schmizz.sshj.util.gss;
import java.security.Provider;
import org.apache.sshd.server.auth.gss.UserAuthGSS;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements a fake Kerberos 5 mechanism. MINA only supports Kerberos 5 over
* GSS-API, so we can't implement a separate mechanism.
*/
public class BogusGSSManager
extends GSSManager {
public static final Oid KRB5_MECH = UserAuthGSS.KRB5_MECH;
private static final Logger log = LoggerFactory.getLogger(BogusGSSManager.class);
@Override
public Oid[] getMechs() {
return new Oid[] { KRB5_MECH };
}
@Override
public Oid[] getNamesForMech(Oid mech) throws GSSException {
return new Oid[] { GSSName.NT_EXPORT_NAME, GSSName.NT_HOSTBASED_SERVICE };
}
@Override
public Oid[] getMechsForName(Oid nameType) {
return new Oid[] { KRB5_MECH };
}
@Override
public GSSName createName(String nameStr, Oid nameType) throws GSSException {
return new BogusGSSName(nameStr, nameType);
}
@Override
public GSSName createName(byte[] name, Oid nameType) throws GSSException {
throw unavailable();
}
@Override
public GSSName createName(String nameStr, Oid nameType, Oid mech) throws GSSException {
return this.createName(nameStr, nameType);
}
@Override
public GSSName createName(byte[] name, Oid nameType, Oid mech) throws GSSException {
throw unavailable();
}
@Override
public GSSCredential createCredential(int usage) throws GSSException {
return new BogusGSSCredential(null, usage);
}
@Override
public GSSCredential createCredential(GSSName name, int lifetime, Oid mech, int usage) throws GSSException {
return new BogusGSSCredential(name, usage);
}
@Override
public GSSCredential createCredential(GSSName name, int lifetime, Oid[] mechs, int usage) throws GSSException {
return new BogusGSSCredential(name, usage);
}
@Override
public GSSContext createContext(GSSName peer, Oid mech, GSSCredential myCred, int lifetime) throws GSSException {
return new BogusGSSContext();
}
@Override
public GSSContext createContext(GSSCredential myCred) throws GSSException {
return new BogusGSSContext();
}
@Override
public GSSContext createContext(byte[] interProcessToken) throws GSSException {
throw unavailable();
}
@Override
public void addProviderAtFront(Provider p, Oid mech) throws GSSException {
throw unavailable();
}
@Override
public void addProviderAtEnd(Provider p, Oid mech) throws GSSException {
throw unavailable();
}
static GSSException unavailable() throws GSSException {
GSSException e = new GSSException(GSSException.UNAVAILABLE);
log.error(e.getMessage(), e);
throw e;
}
}

View File

@@ -0,0 +1,58 @@
package net.schmizz.sshj.util.gss;
import static net.schmizz.sshj.util.gss.BogusGSSManager.unavailable;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
public class BogusGSSName
implements GSSName {
private final String name;
private final Oid oid;
public BogusGSSName(String name, Oid oid) {
this.name = name;
this.oid = oid;
}
@Override
public boolean equals(GSSName another) throws GSSException {
if (!(another instanceof BogusGSSName)) {
throw new GSSException(GSSException.BAD_NAMETYPE);
}
BogusGSSName otherName = (BogusGSSName) another;
return name.equals(otherName.name) && oid.equals(otherName.oid);
}
@Override
public GSSName canonicalize(Oid mech) throws GSSException {
return this;
}
@Override
public byte[] export() throws GSSException {
throw unavailable();
}
@Override
public Oid getStringNameType() throws GSSException {
return oid;
}
@Override
public boolean isAnonymous() {
return false;
}
@Override
public boolean isMN() {
return false;
}
@Override
public String toString() {
return name;
}
}