mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +03:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c749da957 | ||
|
|
742553912c | ||
|
|
84d15f4cf5 | ||
|
|
1ebcbb07ba | ||
|
|
9982e5c30e | ||
|
|
3f340d6927 | ||
|
|
b8eec64a37 | ||
|
|
314d9d01cf | ||
|
|
c526f8e3de | ||
|
|
9529c30105 | ||
|
|
6a476858d1 | ||
|
|
6bfb268c11 | ||
|
|
e334525da5 | ||
|
|
8776500fa0 | ||
|
|
a747db88ed | ||
|
|
97065264de | ||
|
|
7c26ac669a | ||
|
|
1c5b462206 | ||
|
|
4cb9610cdd | ||
|
|
b9d0a03cb3 | ||
|
|
4adc83b9df | ||
|
|
14edb33fa9 | ||
|
|
8e74330b0b | ||
|
|
5217d34198 | ||
|
|
d3d019c1c2 | ||
|
|
49185b044d |
@@ -1 +1,2 @@
|
||||
language: java
|
||||
language: java
|
||||
sudo: false
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
344
pom.xml
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/test/java/com/hierynomus/sshj/sftp/RemoteFileTest.java
Normal file
74
src/test/java/com/hierynomus/sshj/sftp/RemoteFileTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
39
src/test/java/com/hierynomus/sshj/sftp/SFTPClientTest.java
Normal file
39
src/test/java/com/hierynomus/sshj/sftp/SFTPClientTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
4
src/test/java/com/hierynomus/sshj/test/SlowTests.java
Normal file
4
src/test/java/com/hierynomus/sshj/test/SlowTests.java
Normal file
@@ -0,0 +1,4 @@
|
||||
package com.hierynomus.sshj.test;
|
||||
|
||||
public interface SlowTests {
|
||||
}
|
||||
161
src/test/java/com/hierynomus/sshj/test/SshFixture.java
Normal file
161
src/test/java/com/hierynomus/sshj/test/SshFixture.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/test/java/com/hierynomus/sshj/test/util/FileUtil.java
Normal file
20
src/test/java/com/hierynomus/sshj/test/util/FileUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
52
src/test/java/com/hierynomus/sshj/userauth/GssApiTest.java
Normal file
52
src/test/java/com/hierynomus/sshj/userauth/GssApiTest.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
243
src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java
Normal file
243
src/test/java/net/schmizz/sshj/util/gss/BogusGSSContext.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
106
src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java
Normal file
106
src/test/java/net/schmizz/sshj/util/gss/BogusGSSManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
58
src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java
Normal file
58
src/test/java/net/schmizz/sshj/util/gss/BogusGSSName.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user