import local

This commit is contained in:
Shikhar Bhushan
2010-02-27 15:36:58 +01:00
commit 1595e7a99c
187 changed files with 20583 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.concurrent;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/*
* Syntactic sugar around Future
*/
/**
* A kind of {@link Future} that caters to boolean values.
* <p/>
* An event can be set, cleared, or awaited, similar to Python's {@code threading.event}. The key difference is that a
* waiter may be delivered an exception of parameterized type {@code T}. Furthermore, an event {@link #isSet()} when it
* is not {@code null} i.e. it can be either {@code true} or {@code false} when set.
*
* @see Future
*/
public class Event<T extends Exception> extends Future<Boolean, T> {
/**
* Creates this event with given {@code name} and exception {@code chainer}. Allocates a new {@link
* java.util.concurrent.locks.Lock Lock} object for this event.
*
* @param name name of this event
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
*/
public Event(String name, ExceptionChainer<T> chainer) {
super(name, chainer);
}
/**
* Creates this event with given {@code name}, exception {@code chainer}, and associated {@code lock}.
*
* @param name name of this event
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
* @param lock lock to use
*/
public Event(String name, ExceptionChainer<T> chainer, ReentrantLock lock) {
super(name, chainer, lock);
}
/** Sets this event to be {@code true}. Short for {@code set(true)}. */
public void set() {
super.set(true);
}
/**
* Await this event to have a definite {@code true} or {@code false} value.
*
* @throws T if another thread meanwhile informs this event of an error
*/
public void await() throws T {
super.get();
}
/**
* Await this event to have a definite {@code true} or {@code false} value, for {@code timeout} seconds.
*
* @param timeout timeout in seconds
* @param unit
*
* @throws T if another thread meanwhile informs this event of an error, or timeout expires
*/
public void await(long timeout, TimeUnit unit) throws T {
super.get(timeout, unit);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.concurrent;
/**
* Chains an exception to desired type. For example: </p>
* <p/>
* <pre>
* ExceptionChainer&lt;SomeException&gt; chainer = new ExceptionChainer&lt;SomeException&gt;()
* {
* public SomeException chain(Throwable t)
* {
* if (t instanceof SomeException)
* return (SomeException) t;
* else
* return new SomeExcepion(t);
* }
* };
* </pre>
*
* @param <Z> Throwable type
*/
public interface ExceptionChainer<Z extends Throwable> {
Z chain(Throwable t);
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.concurrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Represents future data of the parameterized type {@code V} and allows waiting on it. An exception may also be
* delivered to a waiter, and will be of the parameterized type {@code T}.
* <p/>
* For atomic operations on a future, e.g. checking if a value is set and if it is not then setting it - in other words,
* Compare-And-Set type operations - the associated lock for the future should be acquired while doing so.
*/
public class Future<V, T extends Exception> {
private final Logger log;
private final ExceptionChainer<T> chainer;
private final ReentrantLock lock;
private final Condition cond;
private V val;
private T pendingEx;
/**
* Creates this future with given {@code name} and exception {@code chainer}. Allocates a new {@link
* java.util.concurrent.locks.Lock lock} object for this future.
*
* @param name name of this future
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
*/
public Future(String name, ExceptionChainer<T> chainer) {
this(name, chainer, null);
}
/**
* Creates this future with given {@code name}, exception {@code chainer}, and associated {@code lock}.
*
* @param name name of this future
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
* @param lock lock to use
*/
public Future(String name, ExceptionChainer<T> chainer, ReentrantLock lock) {
this.log = LoggerFactory.getLogger("<< " + name + " >>");
this.chainer = chainer;
this.lock = lock == null ? new ReentrantLock() : lock;
this.cond = this.lock.newCondition();
}
/**
* Set this future's value to {@code val}. Any waiters will be delivered this value.
*
* @param val the value
*/
public void set(V val) {
lock();
try {
log.debug("Setting to `{}`", val);
this.val = val;
cond.signalAll();
} finally {
unlock();
}
}
/**
* Queues error that will be thrown in any waiting thread or any thread that attempts to wait on this future
* hereafter.
*
* @param e the error
*/
public void error(Exception e) {
lock();
try {
pendingEx = chainer.chain(e);
cond.signalAll();
} finally {
unlock();
}
}
/** Clears this future by setting its value and queued exception to {@code null}. */
public void clear() {
lock();
try {
pendingEx = null;
set(null);
} finally {
unlock();
}
}
/**
* Wait indefinitely for this future's value to be set.
*
* @return the value
*
* @throws T in case another thread informs the future of an error meanwhile
*/
public V get() throws T {
return get(0, TimeUnit.SECONDS);
}
/**
* Wait for {@code timeout} seconds for this future's value to be set.
*
* @param timeout the timeout in seconds
* @param unit
*
* @return the value
*
* @throws T in case another thread informs the future of an error meanwhile, or the timeout expires
*/
public V get(long timeout, TimeUnit unit) throws T {
lock();
try {
if (pendingEx != null)
throw pendingEx;
if (val != null)
return val;
log.debug("Awaiting");
while (val == null && pendingEx == null)
if (timeout == 0)
cond.await();
else if (!cond.await(timeout, unit))
throw chainer.chain(new TimeoutException("Timeout expired"));
if (pendingEx != null) {
log.error("Woke to: {}", pendingEx.toString());
throw pendingEx;
}
return val;
} catch (InterruptedException ie) {
throw chainer.chain(ie);
} finally {
unlock();
}
}
/** @return Whether this future has a value set, and no error waiting to pop. */
public boolean isSet() {
lock();
try {
return pendingEx == null && val != null;
} finally {
unlock();
}
}
/** @return Whether this future currently has an error set. */
public boolean hasError() {
lock();
try {
return pendingEx != null;
} finally {
unlock();
}
}
/** @return Whether this future has threads waiting on it. */
public boolean hasWaiters() {
lock();
try {
return lock.hasWaiters(cond);
} finally {
unlock();
}
}
/**
* Lock using the associated lock. Use as part of a {@code try-finally} construct in conjunction with {@link
* #unlock()}.
*/
public void lock() {
lock.lock();
}
/**
* Unlock using the associated lock. Use as part of a {@code try-finally} construct in conjunction with {@link
* #lock()}.
*/
public void unlock() {
lock.unlock();
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.concurrent;
import java.util.Collection;
public class FutureUtils {
public static void alertAll(Exception x, Future... futures) {
for (Future f : futures)
f.error(x);
}
public static void alertAll(Exception x, Collection<? extends Future> futures) {
for (Future f : futures)
f.error(x);
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** An abstract class for {@link Service} that implements common or default functionality. */
public abstract class AbstractService implements Service {
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
/** Assigned name of this service */
protected final String name;
/** Transport layer */
protected final Transport trans;
/** Timeout for blocking operations */
protected int timeout;
public AbstractService(String name, Transport trans) {
this.name = name;
this.trans = trans;
timeout = trans.getTimeout();
}
public String getName() {
return name;
}
public int getTimeout() {
return this.timeout;
}
public void handle(Message msg, SSHPacket buf) throws SSHException {
trans.sendUnimplemented();
}
public void notifyError(SSHException error) {
log.debug("Was notified of {}", error.toString());
}
public void notifyUnimplemented(long seqNum) throws SSHException {
throw new SSHException(DisconnectReason.PROTOCOL_ERROR, "Unexpected: SSH_MSG_UNIMPLEMENTED");
}
public void request() throws TransportException {
final Service active = trans.getService();
if (!equals(active))
if (name.equals(active.getName()))
trans.setService(this);
else
trans.reqService(this);
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void notifyDisconnect() throws SSHException {
log.debug("Was notified of disconnect");
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.signature.Signature;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.Compression;
import net.schmizz.sshj.transport.kex.KeyExchange;
import net.schmizz.sshj.transport.mac.MAC;
import net.schmizz.sshj.transport.random.Random;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import java.util.List;
/**
* Holds configuration information and factories. Acts a container for factories of {@link KeyExchange}, {@link Cipher},
* {@link Compression}, {@link MAC}, {@link Signature}, {@link Random}, and {@link FileKeyProvider}.
*/
public interface Config {
/**
* Retrieve the list of named factories for {@code Cipher}.
*
* @return a list of named {@code Cipher} factories
*/
List<Factory.Named<Cipher>> getCipherFactories();
/**
* Retrieve the list of named factories for {@code Compression}.
*
* @return a list of named {@code Compression} factories
*/
List<Factory.Named<Compression>> getCompressionFactories();
/**
* Retrieve the list of named factories for {@code FileKeyProvider}.
*
* @return a list of named {@code FileKeyProvider} factories
*/
List<Factory.Named<FileKeyProvider>> getFileKeyProviderFactories();
/**
* Retrieve the list of named factories for <code>KeyExchange</code>.
*
* @return a list of named <code>KeyExchange</code> factories
*/
List<Factory.Named<KeyExchange>> getKeyExchangeFactories();
/**
* Retrieve the list of named factories for <code>MAC</code>.
*
* @return a list of named <code>MAC</code> factories
*/
List<Factory.Named<MAC>> getMACFactories();
/**
* Retrieve the {@link net.schmizz.sshj.transport.random.Random} factory.
*
* @return the {@link net.schmizz.sshj.transport.random.Random} factory
*/
Factory<Random> getRandomFactory();
/**
* Retrieve the list of named factories for {@link net.schmizz.sshj.signature.Signature}
*
* @return a list of named {@link net.schmizz.sshj.signature.Signature} factories
*/
List<Factory.Named<Signature>> getSignatureFactories();
/**
* Returns the software version information for identification during SSH connection initialization. For example,
* {@code "NET_3_0"}.
*/
String getVersion();
/**
* Set the named factories for {@link net.schmizz.sshj.transport.cipher.Cipher}.
*
* @param cipherFactories a list of named factories
*/
void setCipherFactories(List<Factory.Named<Cipher>> cipherFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.transport.compression.Compression}.
*
* @param compressionFactories a list of named factories
*/
void setCompressionFactories(List<Factory.Named<Compression>> compressionFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.userauth.keyprovider.FileKeyProvider}.
*
* @param fileKeyProviderFactories a list of named factories
*/
void setFileKeyProviderFactories(List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.transport.kex.KeyExchange}.
*
* @param kexFactories a list of named factories
*/
void setKeyExchangeFactories(List<Factory.Named<KeyExchange>> kexFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.transport.mac.MAC}.
*
* @param macFactories a list of named factories
*/
void setMACFactories(List<Factory.Named<MAC>> macFactories);
/**
* Set the factory for {@link net.schmizz.sshj.transport.random.Random}.
*
* @param randomFactory the factory
*/
void setRandomFactory(Factory<Random> randomFactory);
/**
* Set the named factories for {@link net.schmizz.sshj.signature.Signature}.
*
* @param signatureFactories a list of named factories
*/
void setSignatureFactories(List<Factory.Named<Signature>> signatureFactories);
/**
* Set the software version information for identification during SSH connection initialization. For example, {@code
* "NET_3_0"}.
*
* @param version software version info
*/
void setVersion(String version);
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.signature.Signature;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.Compression;
import net.schmizz.sshj.transport.kex.KeyExchange;
import net.schmizz.sshj.transport.mac.MAC;
import net.schmizz.sshj.transport.random.Random;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import java.util.Arrays;
import java.util.List;
public class ConfigImpl implements Config {
private String version;
private Factory<Random> randomFactory;
private List<Factory.Named<KeyExchange>> kexFactories;
private List<Factory.Named<Cipher>> cipherFactories;
private List<Factory.Named<Compression>> compressionFactories;
private List<Factory.Named<MAC>> macFactories;
private List<Factory.Named<Signature>> signatureFactories;
private List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories;
public List<Factory.Named<Cipher>> getCipherFactories() {
return cipherFactories;
}
public List<Factory.Named<Compression>> getCompressionFactories() {
return compressionFactories;
}
public List<Factory.Named<FileKeyProvider>> getFileKeyProviderFactories() {
return fileKeyProviderFactories;
}
public List<Factory.Named<KeyExchange>> getKeyExchangeFactories() {
return kexFactories;
}
public List<Factory.Named<MAC>> getMACFactories() {
return macFactories;
}
public Factory<Random> getRandomFactory() {
return randomFactory;
}
public List<Factory.Named<Signature>> getSignatureFactories() {
return signatureFactories;
}
public String getVersion() {
return version;
}
public void setCipherFactories(Factory.Named<Cipher>... cipherFactories) {
setCipherFactories(Arrays.<Factory.Named<Cipher>>asList(cipherFactories));
}
public void setCipherFactories(List<Factory.Named<Cipher>> cipherFactories) {
this.cipherFactories = cipherFactories;
}
public void setCompressionFactories(Factory.Named<Compression>... compressionFactories) {
setCompressionFactories(Arrays.<Factory.Named<Compression>>asList(compressionFactories));
}
public void setCompressionFactories(List<Factory.Named<Compression>> compressionFactories) {
this.compressionFactories = compressionFactories;
}
public void setFileKeyProviderFactories(Factory.Named<FileKeyProvider>... fileKeyProviderFactories) {
setFileKeyProviderFactories(Arrays.<Factory.Named<FileKeyProvider>>asList(fileKeyProviderFactories));
}
public void setFileKeyProviderFactories(List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories) {
this.fileKeyProviderFactories = fileKeyProviderFactories;
}
public void setKeyExchangeFactories(Factory.Named<KeyExchange>... kexFactories) {
setKeyExchangeFactories(Arrays.<Factory.Named<KeyExchange>>asList(kexFactories));
}
public void setKeyExchangeFactories(List<Factory.Named<KeyExchange>> kexFactories) {
this.kexFactories = kexFactories;
}
public void setMACFactories(Factory.Named<MAC>... macFactories) {
setMACFactories(Arrays.<Factory.Named<MAC>>asList(macFactories));
}
public void setMACFactories(List<Factory.Named<MAC>> macFactories) {
this.macFactories = macFactories;
}
public void setRandomFactory(Factory<Random> randomFactory) {
this.randomFactory = randomFactory;
}
public void setSignatureFactories(Factory.Named<Signature>... signatureFactories) {
setSignatureFactories(Arrays.<Factory.Named<Signature>>asList(signatureFactories));
}
public void setSignatureFactories(List<Factory.Named<Signature>> signatureFactories) {
this.signatureFactories = signatureFactories;
}
public void setVersion(String version) {
this.version = version;
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.signature.SignatureDSA;
import net.schmizz.sshj.signature.SignatureRSA;
import net.schmizz.sshj.transport.cipher.AES128CBC;
import net.schmizz.sshj.transport.cipher.AES128CTR;
import net.schmizz.sshj.transport.cipher.AES192CBC;
import net.schmizz.sshj.transport.cipher.AES192CTR;
import net.schmizz.sshj.transport.cipher.AES256CBC;
import net.schmizz.sshj.transport.cipher.AES256CTR;
import net.schmizz.sshj.transport.cipher.BlowfishCBC;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.cipher.TripleDESCBC;
import net.schmizz.sshj.transport.compression.NoneCompression;
import net.schmizz.sshj.transport.kex.DHG1;
import net.schmizz.sshj.transport.kex.DHG14;
import net.schmizz.sshj.transport.mac.HMACMD5;
import net.schmizz.sshj.transport.mac.HMACMD596;
import net.schmizz.sshj.transport.mac.HMACSHA1;
import net.schmizz.sshj.transport.mac.HMACSHA196;
import net.schmizz.sshj.transport.random.BouncyCastleRandom;
import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Constructor for a {@link ConfigImpl} that is initialized as follows. Items marked with an asterisk are added to the
* config only if BouncyCastle is in the classpath.
* <p/>
* <ul> <li>{@link ConfigImpl#setKeyExchangeFactories Key exchange}: {@link DHG14}*, {@link DHG1}</li> <li>{@link
* ConfigImpl#setCipherFactories Ciphers} [1]: {@link AES128CTR}, {@link AES192CTR}, {@link AES256CTR}, {@link
* AES128CBC}, {@link AES192CBC}, {@link AES256CBC}, {@link AES192CBC}, {@link TripleDESCBC}, {@link BlowfishCBC}</li>
* <li>{@link ConfigImpl#setMACFactories MAC}: {@link HMACSHA1}, {@link HMACSHA196}, {@link HMACMD5}, {@link
* HMACMD596}</li> <li>{@link ConfigImpl#setCompressionFactories Compression}: {@link NoneCompression}</li> <li>{@link
* ConfigImpl#setSignatureFactories Signature}: {@link SignatureRSA}, {@link SignatureDSA}</li> <li>{@link
* ConfigImpl#setRandomFactory PRNG}: {@link BouncyCastleRandom}* or {@link JCERandom}</li> <li>{@link
* ConfigImpl#setFileKeyProviderFactories Key file support}: {@link PKCS8KeyFile}*, {@link OpenSSHKeyFile}*</li>
* <li>{@link ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li> </ul>
* <p/>
* [1] It is worth noting that Sun's JRE does not have the unlimited cryptography extension enabled by default. This
* prevents using ciphers with strength greater than 128.
*/
public class DefaultConfig extends ConfigImpl {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String VERSION = "SSHJ_0_1";
public DefaultConfig() {
setVersion(VERSION);
final boolean bouncyCastleRegistered = SecurityUtils.isBouncyCastleRegistered();
initKeyExchangeFactories(bouncyCastleRegistered);
initRandomFactory(bouncyCastleRegistered);
initFileKeyProviderFactories(bouncyCastleRegistered);
initCipherFactories();
initCompressionFactories();
initMACFactories();
initSignatureFactories();
}
protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) {
if (bouncyCastleRegistered)
setKeyExchangeFactories(new DHG14.Factory(), new DHG1.Factory());
else
setKeyExchangeFactories(new DHG1.Factory());
}
protected void initRandomFactory(boolean bouncyCastleRegistered) {
setRandomFactory(new SingletonRandomFactory(bouncyCastleRegistered ? new BouncyCastleRandom.Factory() : new JCERandom.Factory()));
}
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) {
if (bouncyCastleRegistered) {
setFileKeyProviderFactories(new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory());
}
}
protected void initCipherFactories() {
List<Factory.Named<Cipher>> avail = new LinkedList<Factory.Named<Cipher>>(Arrays.<Factory.Named<Cipher>>asList(
new AES128CTR.Factory(), //
new AES192CTR.Factory(), //
new AES256CTR.Factory(), //
new AES128CBC.Factory(), //
new AES192CBC.Factory(), //
new AES256CBC.Factory(), //
new TripleDESCBC.Factory(), //
new BlowfishCBC.Factory()));
// Ref. https://issues.apache.org/jira/browse/SSHD-24
// "AES256 and AES192 requires unlimited cryptography extension"
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext();) {
final Factory.Named<Cipher> f = i.next();
try {
final Cipher c = f.create();
final byte[] key = new byte[c.getBlockSize()];
final byte[] iv = new byte[c.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (Exception e) {
log.warn("Disabling cipher `{}`: cipher strengths apparently limited by JCE policy", f.getName());
i.remove();
}
}
setCipherFactories(avail);
}
protected void initSignatureFactories() {
setSignatureFactories(new SignatureRSA.Factory(), new SignatureDSA.Factory());
}
protected void initMACFactories() {
setMACFactories(new HMACSHA1.Factory(), new HMACSHA196.Factory(), new HMACMD5.Factory(),
new HMACMD596.Factory());
}
protected void initCompressionFactories() {
setCompressionFactories(new NoneCompression.Factory());
}
}

View File

@@ -0,0 +1,642 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.ConnectionProtocol;
import net.schmizz.sshj.connection.channel.direct.LocalPortForwarder;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.connection.channel.direct.SessionChannel;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import net.schmizz.sshj.connection.channel.forwarded.ConnectListener;
import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder;
import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder.ForwardedTCPIPChannel;
import net.schmizz.sshj.connection.channel.forwarded.X11Forwarder;
import net.schmizz.sshj.connection.channel.forwarded.X11Forwarder.X11Channel;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.sftp.StatefulSFTPClient;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.transport.TransportProtocol;
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.HostKeyVerifier;
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
import net.schmizz.sshj.userauth.UserAuth;
import net.schmizz.sshj.userauth.UserAuthException;
import net.schmizz.sshj.userauth.UserAuthProtocol;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
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.AuthMethod;
import net.schmizz.sshj.userauth.method.AuthPassword;
import net.schmizz.sshj.userauth.method.AuthPublickey;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.SocketAddress;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* Secure SHell client API.
* <p/>
* Before connection is established, host key verification needs to be accounted for. This is done by {@link
* #addHostKeyVerifier(HostKeyVerifier) specifying} one or more {@link HostKeyVerifier} objects. Database of known
* hostname-key pairs in the OpenSSH {@code "known_hosts"} format can be {@link #loadKnownHosts(File) loaded} for host
* key verification.
* <p/>
* User authentication can be performed by any of the {@code auth*()} method.
* <p/>
* {@link #startSession()} caters to the most typical use case of starting a {@code session} channel and executing a
* remote command, starting a subsystem, etc. If you wish to request X11 forwarding for some session, first {@link
* #registerX11Forwarder(net.schmizz.sshj.connection.channel.forwarded.ConnectListener) register} a {@link
* net.schmizz.sshj.connection.channel.forwarded.ConnectListener} for {@code x11} channels.
* <p/>
* {@link #newLocalPortForwarder Local} and {@link #getRemotePortForwarder() remote} port forwarding is possible. There
* are also utility method for easily creating {@link #newSCPFileTransfer SCP} and {@link #newSFTPClient() SFTP}
* implementations.
* <p/>
* <em>A simple example:</em>
* <p/>
* <p/>
* <pre>
* client = new SSHClient();
* client.initUserKnownHosts();
* client.connect(&quot;hostname&quot;);
* try
* {
* client.authPassword(&quot;username&quot;, &quot;password&quot;);
* client.startSession().exec(&quot;true&quot;);
* client.getConnection().join();
* } finally
* {
* client.disconnect();
* }
* </pre>
* <p/>
* Where a password or passphrase is required, if you're extra-paranoid use the {@code char[]} based method. The {@code
* char[]} will be blanked out after use.
*/
public class SSHClient extends SocketClient implements SessionFactory {
/** Default port for SSH */
public static final int DEFAULT_PORT = 22;
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
/** Transport layer */
protected final Transport trans;
/** {@code ssh-userauth} service */
protected final UserAuth auth;
/** {@code ssh-connection} service */
protected final ConnectionProtocol conn;
/** Default constructor. Initializes this object using {@link DefaultConfig}. */
public SSHClient() {
this(new DefaultConfig());
}
/**
* Constructor that allows specifying a {@code config} to be used.
*
* @param config {@link ConfigImpl} instance
*/
public SSHClient(Config config) {
super(DEFAULT_PORT);
this.trans = new TransportProtocol(config);
this.auth = new UserAuthProtocol(trans);
this.conn = new ConnectionProtocol(trans);
}
/**
* Add a {@link HostKeyVerifier} which will be invoked for verifying host key during connection establishment and
* future key exchanges.
*
* @param hostKeyVerifier {@link HostKeyVerifier} instance
*/
public void addHostKeyVerifier(HostKeyVerifier hostKeyVerifier) {
trans.addHostKeyVerifier(hostKeyVerifier);
}
/**
* Add a {@link HostKeyVerifier} that will verify any host at given {@code hostname:port} and a host key that has
* the given {@code fingerprint}, e.g. {@code "4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"}
*
* @param host the hostname / IP address
* @param port the port for which the {@code fingerprint} applies
* @param fingerprint expected fingerprint in colon-delimited format (16 octets in hex delimited by a colon)
*
* @see net.schmizz.sshj.common.SecurityUtils#getFingerprint
*/
public void addHostKeyVerifier(final String host, final int port, final String fingerprint) {
addHostKeyVerifier(new HostKeyVerifier() {
public boolean verify(String h, int p, PublicKey k) {
return host.equals(h) && port == p && SecurityUtils.getFingerprint(k).equals(fingerprint);
}
});
}
/**
* Authenticate {@code username} using the supplied {@code methods}.
*
* @param username user to authenticate
* @param methods one or more authentication method
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void auth(String username, AuthMethod... methods) throws UserAuthException, TransportException {
assert isConnected();
auth(username, Arrays.<AuthMethod>asList(methods));
}
/**
* Authenticate {@code username} using the supplied {@code methods}.
*
* @param username user to authenticate
* @param methods one or more authentication method
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void auth(String username, Iterable<AuthMethod> methods) throws UserAuthException, TransportException {
assert isConnected();
auth.authenticate(username, conn, methods);
}
/**
* Authenticate {@code username} using the {@code "password"} authentication method. The {@code password} array is
* blanked out after use.
*
* @param username user to authenticate
* @param password the password to use for authentication
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authPassword(String username, char[] password) throws UserAuthException, TransportException {
authPassword(username, PasswordUtils.createOneOff(password));
}
/**
* Authenticate {@code username} using the {@code "password"} authentication method.
*
* @param username user to authenticate
* @param pfinder the {@link net.schmizz.sshj.userauth.password.PasswordFinder} to use for authentication
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authPassword(String username, PasswordFinder pfinder) throws UserAuthException, TransportException {
auth(username, new AuthPassword(pfinder));
}
/**
* Authenticate {@code username} using the {@code "password"} authentication method.
*
* @param username user to authenticate
* @param password the password to use for authentication
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authPassword(String username, String password) throws UserAuthException, TransportException {
authPassword(username, password.toCharArray());
}
/**
* Authenticate {@code username} using the {@code "publickey"} authentication method, with keys from some commons
* locations on the file system. This method relies on {@code ~/.ssh/id_rsa} and {@code ~/.ssh/id_dsa}.
* <p/>
* This method does not provide a way to specify a passphrase.
*
* @param username user to authenticate
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authPublickey(String username) throws UserAuthException, TransportException {
String base = System.getProperty("user.home") + File.separator + ".ssh" + File.separator;
authPublickey(username, base + "id_rsa", base + "id_dsa");
}
/**
* Authenticate {@code username} using the {@code "publickey"} authentication method.
* <p/>
* {@link KeyProvider} instances can be created using any of the of the {@code loadKeys()} method provided in this
* class. In case multiple {@code keyProviders} are specified; authentication is attempted in order as long as the
* {@code "publickey"} authentication method is available.
*
* @param username user to authenticate
* @param keyProviders one or more {@link KeyProvider} instances
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authPublickey(String username, Iterable<KeyProvider> keyProviders) throws UserAuthException,
TransportException {
List<AuthMethod> am = new LinkedList<AuthMethod>();
for (KeyProvider kp : keyProviders)
am.add(new AuthPublickey(kp));
auth(username, am);
}
/**
* Authenticate {@code username} using the {@code "publickey"} authentication method.
* <p/>
* {@link KeyProvider} instances can be created using any of the {@code loadKeys()} method provided in this class.
* In case multiple {@code keyProviders} are specified; authentication is attempted in order as long as the {@code
* "publickey"} authentication method is available.
*
* @param username user to authenticate
* @param keyProviders one or more {@link KeyProvider} instances
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authPublickey(String username, KeyProvider... keyProviders) throws UserAuthException,
TransportException {
authPublickey(username, Arrays.<KeyProvider>asList(keyProviders));
}
/**
* Authenticate {@code username} using the {@code "publickey"} authentication method, with keys from one or more
* {@code locations} in the file system.
* <p/>
* In case multiple {@code locations} are specified; authentication is attempted in order as long as the {@code
* "publickey"} authentication method is available. If there is an error loading keys from any of them (e.g. file
* could not be read, file format not recognized) that key file it is ignored.
* <p/>
* This method does not provide a way to specify a passphrase.
*
* @param username user to authenticate
* @param locations one or more locations in the file system containing the private key
*
* @throws UserAuthException in case of authentication failure
* @throws TransportException if there was a transport-layer error
*/
public void authPublickey(String username, String... locations) throws UserAuthException, TransportException {
List<KeyProvider> keyProviders = new LinkedList<KeyProvider>();
for (String loc : locations)
try {
log.debug("Attempting to load key from: {}", loc);
keyProviders.add(loadKeys(loc));
} catch (IOException logged) {
log.warn("Could not load keys due to: {}", logged);
}
authPublickey(username, keyProviders);
}
/**
* 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.
* <p/>
* This method should be called from a {@code finally} construct after connection is established; so that proper
* cleanup is done and the thread spawned by the transport layer for dealing with incoming packets is stopped.
*/
@Override
public void disconnect() throws IOException {
assert isConnected();
trans.disconnect();
super.disconnect();
assert !isConnected();
}
/** @return associated {@link Connection} instance. */
public Connection getConnection() {
return conn;
}
/** @return a {@link RemotePortForwarder} that allows requesting remote forwarding over this connection. */
public RemotePortForwarder getRemotePortForwarder() {
synchronized (conn) {
RemotePortForwarder rpf = (RemotePortForwarder) conn.get(ForwardedTCPIPChannel.TYPE);
if (rpf == null)
conn.attach(rpf = new RemotePortForwarder(conn));
return rpf;
}
}
/** @return the associated {@link Transport} instance. */
public Transport getTransport() {
return trans;
}
/**
* @return the associated {@link UserAuth} instance. This allows access to information like the {@link
* UserAuth#getBanner() authentication banner}, whether authentication was at least {@link
* UserAuth#hadPartialSuccess() partially successful}, and any {@link UserAuth#getSavedExceptions() saved
* exceptions} that were ignored because there were more authentication method that could be tried.
*/
public UserAuth getUserAuth() {
return auth;
}
/** @return whether authenticated. */
public boolean isAuthenticated() {
return trans.isAuthenticated();
}
/** @return whether connected. */
@Override
public boolean isConnected() {
return super.isConnected() && trans.isRunning();
}
/**
* Creates a {@link KeyProvider} from supplied {@link KeyPair}.
*
* @param kp the key pair
*
* @return the key provider ready for use in authentication
*/
public KeyProvider loadKeys(KeyPair kp) {
return new KeyPairWrapper(kp);
}
/**
* Returns a {@link KeyProvider} instance created from a location on the file system where an <em>unencrypted</em>
* private key file (does not require a passphrase) can be found. Simply calls {@link #loadKeys(String,
* PasswordFinder)} with the {@link net.schmizz.sshj.userauth.password.PasswordFinder} argument as {@code null}.
*
* @param location the location for the key file
*
* @return the key provider ready for use in authentication
*
* @throws SSHException if there was no suitable key provider available for the file format; typically because
* BouncyCastle is not in the classpath
* @throws IOException if the key file format is not known, if the file could not be read, etc.
*/
public KeyProvider loadKeys(String location) throws IOException {
return loadKeys(location, (PasswordFinder) null);
}
/**
* Utility function for createing a {@link KeyProvider} instance from given location on the file system. Creates a
* one-off {@link PasswordFinder} using {@link net.schmizz.sshj.userauth.password.PasswordUtils#createOneOff(char[])},
* and calls {@link #loadKeys(String,PasswordFinder)}.
*
* @param location location of the key file
* @param passphrase passphrase as a char-array
*
* @return the key provider ready for use in authentication
*
* @throws net.schmizz.sshj.common.SSHException
* if there was no suitable key provider available for the file format; typically because
* BouncyCastle is not in the classpath
* @throws IOException if the key file format is not known, if the file could not be read, etc.
*/
public KeyProvider loadKeys(String location, char[] passphrase) throws IOException {
return loadKeys(location, PasswordUtils.createOneOff(passphrase));
}
/**
* Creates a {@link KeyProvider} instance from given location on the file system. Currently only PKCS8 format
* private key files are supported (OpenSSH uses this format).
* <p/>
*
* @param location the location of the key file
* @param passwordFinder the {@link PasswordFinder} that can supply the passphrase for decryption (may be {@code
* null} in case keyfile is not encrypted)
*
* @return the key provider ready for use in authentication
*
* @throws SSHException if there was no suitable key provider available for the file format; typically because
* BouncyCastle is not in the classpath
* @throws IOException if the key file format is not known, if the file could not be read, etc.
*/
public KeyProvider loadKeys(String location, PasswordFinder passwordFinder) throws IOException {
File loc = new File(location);
FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(loc);
FileKeyProvider fkp = Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format
.toString());
if (fkp == null)
throw new SSHException("No provider available for " + format + " key file");
fkp.init(loc, passwordFinder);
return fkp;
}
/**
* Convenience method for creating a {@link KeyProvider} instance from a {@code location} where an <i>encrypted</i>
* key file is located. Calls {@link #loadKeys(String, char[])} with a character array created from the supplied
* {@code passphrase} string.
*
* @param location location of the key file
* @param passphrase passphrase as a string
*
* @return the key provider for use in authentication
*
* @throws IOException if the key file format is not known, if the file could not be read etc.
*/
public KeyProvider loadKeys(String location, String passphrase) throws IOException {
return loadKeys(location, passphrase.toCharArray());
}
/**
* Attempts loading the user's {@code known_hosts} file from the default locations, i.e. {@code ~/.ssh/known_hosts}
* and {@code ~/.ssh/known_hosts2} on most platforms. Adds the resulting {@link OpenSSHKnownHosts} object as a host
* key verifier.
* <p/>
* For finer control over which file is used, see {@link #loadKnownHosts(File)}.
*
* @throws IOException if there is an error loading from <em>both</em> locations
*/
public void loadKnownHosts() throws IOException {
boolean loaded = false;
final File sshDir = OpenSSHKnownHosts.detectSSHDir();
if (sshDir != null) {
for (File loc : Arrays.asList(new File(sshDir, "known_hosts"), new File(sshDir, "known_hosts2"))) {
loadKnownHosts(loc);
loaded = true;
}
}
if (!loaded)
throw new IOException("Could not load known_hosts");
}
/**
* Adds a {@link OpenSSHKnownHosts} object created from the specified location as a host key verifier.
*
* @param location location for {@code known_hosts} file
*
* @throws IOException if there is an error loading from any of these locations
*/
public void loadKnownHosts(File location) throws IOException {
addHostKeyVerifier(new OpenSSHKnownHosts(location));
}
/**
* Create a {@link LocalPortForwarder} that will listen on {@code address} and forward incoming connections to the
* server; which will further forward them to {@code host:port}.
* <p/>
* The returned forwarder's {@link LocalPortForwarder#listen() listen()} method should be called to actually start
* listening, this method just creates an instance.
*
* @param address defines where the {@link net.schmizz.sshj.connection.channel.direct.LocalPortForwarder} listens
* @param host hostname to which the server will forward
* @param port the port at {@code hostname} to which the server wil forward
*
* @return a {@link LocalPortForwarder}
*
* @throws IOException if there is an error opening a local server socket
*/
public LocalPortForwarder newLocalPortForwarder(SocketAddress address, String host, int port) throws IOException {
return new LocalPortForwarder(getServerSocketFactory(), conn, address, host, port);
}
/**
* Register a {@code listener} for handling forwarded X11 channels. Without having done this, an incoming X11
* forwarding will be summarily rejected.
* <p/>
* It should be clarified that multiple listeners for X11 forwarding over a single SSH connection are not supported
* (and don't make much sense). So a subsequent call to this method is only going to replace the registered {@code
* listener}.
*
* @param listener the {@link ConnectListener} that should be delegated the responsibility of handling forwarded
* {@link X11Channel} 's
*
* @return an {@link net.schmizz.sshj.connection.channel.forwarded.X11Forwarder} that allows to {@link
* X11Forwarder#stop() stop acting} on X11 requests from server
*/
public X11Forwarder registerX11Forwarder(ConnectListener listener) {
X11Forwarder x11f = new X11Forwarder(conn, listener);
conn.attach(x11f);
return x11f;
}
/** @return instantiated {@link SCPFileTransfer} implementation. */
public SCPFileTransfer newSCPFileTransfer() {
assert isConnected() && isAuthenticated();
return new SCPFileTransfer(this);
}
/**
* @return instantiated {@link SFTPClient} implementation.
*
* @throws IOException if there is an error starting the {@code sftp} subsystem
* @see StatefulSFTPClient
*/
public SFTPClient newSFTPClient() throws IOException {
assert isConnected() && isAuthenticated();
return new SFTPClient(this);
}
/**
* Does key re-exchange.
*
* @throws TransportException if an error occurs during key exchange
*/
public void rekey() throws TransportException {
doKex();
}
public Session startSession() throws ConnectionException, TransportException {
assert isConnected() && isAuthenticated();
SessionChannel sess = new SessionChannel(conn);
sess.open();
assert sess.isOpen();
return sess;
}
/**
* Adds {@code zlib} compression to preferred compression algorithms. There is no guarantee that it will be
* successfully negotiatied.
* <p/>
* If the client is already connected renegotiation is done; otherwise this method simply returns (and compression
* will be negotiated during connection establishment).
*
* @throws ClassNotFoundException if {@code JZlib} is not in classpath
* @throws TransportException if an error occurs during renegotiation
*/
public void useCompression() throws ClassNotFoundException, TransportException {
trans.getConfig().setCompressionFactories(Arrays.asList(
new DelayedZlibCompression.Factory(),
new ZlibCompression.Factory(),
new NoneCompression.Factory()));
if (isConnected())
rekey();
}
/** On connection establishment, also initialize the SSH transport via {@link Transport#init} and {@link #doKex()}. */
@Override
protected void onConnect() throws IOException {
super.onConnect();
trans.init(getRemoteHostname(), getRemotePort(), getInputStream(), getOutputStream());
doKex();
}
/**
* Do key exchange.
*
* @throws TransportException if error during kex
*/
protected void doKex() throws TransportException {
assert trans.isRunning();
long start = System.currentTimeMillis();
try {
trans.doKex();
} catch (TransportException te) {
trans.disconnect(DisconnectReason.KEY_EXCHANGE_FAILED);
throw te;
}
log.info("Key exchange took {} seconds", (System.currentTimeMillis() - start) / 1000.0);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacketHandler;
import net.schmizz.sshj.transport.TransportException;
/** Represents a service running on top of the SSH {@link net.schmizz.sshj.transport.Transport transport layer}. */
public interface Service extends SSHPacketHandler, ErrorNotifiable {
/** @return The assigned name for this SSH service. */
String getName();
/**
* Notifies this service that a {@code SSH_MSG_UNIMPLEMENTED} was received for packet with given sequence number.
* Meant to be invoked as a callback by the transport layer.
*
* @param seqNum sequence number of the packet which the server claims is unimplemented
*
* @throws SSHException if the packet is unexpected and may represent a disruption
*/
void notifyUnimplemented(long seqNum) throws SSHException;
/**
* Request and install this service with the associated transport. Implementations should aim to make this method
* idempotent by first checking the {@link net.schmizz.sshj.transport.Transport#getService() currently active
* service}.
*
* @throws TransportException if there is an error sending the service request
*/
void request() throws TransportException;
void notifyDisconnect() throws SSHException;
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
abstract class SocketClient {
private final int defaultPort;
private Socket socket;
private InputStream input;
private OutputStream output;
private SocketFactory socketFactory = SocketFactory.getDefault();
private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
private static final int DEFAULT_CONNECT_TIMEOUT = 0;
private int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
private int timeout = 0;
private String hostname;
SocketClient(int defaultPort) {
this.defaultPort = defaultPort;
}
public void connect(InetAddress host, int port)
throws IOException {
socket = socketFactory.createSocket();
socket.connect(new InetSocketAddress(host, port), connectTimeout);
onConnect();
}
public void connect(String hostname, int port)
throws SocketException, IOException {
this.hostname = hostname;
connect(InetAddress.getByName(hostname), port);
}
public void connect(InetAddress host, int port,
InetAddress localAddr, int localPort)
throws SocketException, IOException {
socket = socketFactory.createSocket();
socket.bind(new InetSocketAddress(localAddr, localPort));
socket.connect(new InetSocketAddress(host, port), connectTimeout);
onConnect();
}
public void connect(String hostname, int port,
InetAddress localAddr, int localPort)
throws SocketException, IOException {
this.hostname = hostname;
connect(InetAddress.getByName(hostname), port, localAddr, localPort);
}
public void connect(InetAddress host) throws SocketException, IOException {
connect(host, defaultPort);
}
public void connect(String hostname) throws SocketException, IOException {
connect(hostname, defaultPort);
}
public void disconnect() throws IOException {
if (socket != null) {
socket.close();
socket = null;
}
if (input != null) {
input.close();
input = null;
}
if (output != null) {
output.close();
output = null;
}
input = null;
output = null;
}
public boolean isConnected() {
return (socket != null) && socket.isConnected();
}
public int getLocalPort() {
return socket.getLocalPort();
}
public InetAddress getLocalAddress() {
return socket.getLocalAddress();
}
public String getRemoteHostname() {
return hostname == null ? (hostname = getRemoteAddress().getHostName()) : hostname;
}
public int getRemotePort() {
return socket.getPort();
}
public InetAddress getRemoteAddress() {
return socket.getInetAddress();
}
public void setSocketFactory(SocketFactory factory) {
if (factory == null)
socketFactory = SocketFactory.getDefault();
else
socketFactory = factory;
}
public SocketFactory getSocketFactory() {
return socketFactory;
}
public void setServerSocketFactory(ServerSocketFactory factory) {
if (factory == null)
serverSocketFactory = ServerSocketFactory.getDefault();
else
serverSocketFactory = factory;
}
public ServerSocketFactory getServerSocketFactory() {
return serverSocketFactory;
}
public int getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public Socket getSocket() {
return socket;
}
InputStream getInputStream() {
return input;
}
OutputStream getOutputStream() {
return output;
}
void onConnect() throws IOException {
socket.setSoTimeout(timeout);
input = socket.getInputStream();
output = socket.getOutputStream();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,531 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
public class Buffer<T extends Buffer<T>> {
public static class BufferException extends SSHRuntimeException {
public BufferException(String message) {
super(message);
}
}
/** The default size for a {@code Buffer} (256 bytes) */
public static final int DEFAULT_SIZE = 256;
protected static int getNextPowerOf2(int i) {
int j = 1;
while (j < i)
j <<= 1;
return j;
}
protected byte[] data;
protected int rpos;
protected int wpos;
/** @see {@link #DEFAULT_SIZE} */
public Buffer() {
this(DEFAULT_SIZE);
}
public Buffer(Buffer<?> from) {
data = new byte[(wpos = from.wpos - from.rpos)];
System.arraycopy(from.data, from.rpos, data, 0, wpos);
}
public Buffer(byte[] data) {
this(data, true);
}
public Buffer(int size) {
this(new byte[getNextPowerOf2(size)], false);
}
private Buffer(byte[] data, boolean read) {
this.data = data;
rpos = 0;
wpos = read ? data.length : 0;
}
public byte[] array() {
return data;
}
public int available() {
return wpos - rpos;
}
/** Resets this buffer. The object becomes ready for reuse. */
public void clear() {
rpos = 0;
wpos = 0;
}
public int rpos() {
return rpos;
}
public void rpos(int rpos) {
this.rpos = rpos;
}
public int wpos() {
return wpos;
}
public void wpos(int wpos) {
ensureCapacity(wpos - this.wpos);
this.wpos = wpos;
}
protected void ensureAvailable(int a) {
if (available() < a)
throw new BufferException("Underflow");
}
public void ensureCapacity(int capacity) {
if (data.length - wpos < capacity) {
int cw = wpos + capacity;
byte[] tmp = new byte[getNextPowerOf2(cw)];
System.arraycopy(data, 0, tmp, 0, data.length);
data = tmp;
}
}
/** Compact this {@link SSHPacket} */
public void compact() {
System.err.println("COMPACTING");
if (available() > 0)
System.arraycopy(data, rpos, data, 0, wpos - rpos);
wpos -= rpos;
rpos = 0;
}
public byte[] getCompactData() {
final int len = available();
if (len > 0) {
byte[] b = new byte[len];
System.arraycopy(data, rpos, b, 0, len);
return b;
} else
return new byte[0];
}
/**
* Read an SSH boolean byte
*
* @return the {@code true} or {@code false} value read
*/
public boolean readBoolean() {
return readByte() != 0;
}
/**
* Puts an SSH boolean value
*
* @param b the value
*
* @return this
*/
public T putBoolean(boolean b) {
return putByte(b ? (byte) 1 : (byte) 0);
}
/**
* Read a byte from the buffer
*
* @return the byte read
*/
public byte readByte() {
ensureAvailable(1);
return data[rpos++];
}
/**
* Writes a single byte into this buffer
*
* @param b
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putByte(byte b) {
ensureCapacity(1);
data[wpos++] = b;
return (T) this;
}
/**
* Read an SSH byte-array
*
* @return the byte-array read
*/
public byte[] readBytes() {
int len = readInt();
if (len < 0 || len > 32768)
throw new BufferException("Bad item length: " + len);
byte[] b = new byte[len];
readRawBytes(b);
return b;
}
/**
* Writes Java byte-array as an SSH byte-array
*
* @param b Java byte-array
*
* @return this
*/
public T putBytes(byte[] b) {
return putBytes(b, 0, b.length);
}
/**
* Writes Java byte-array as an SSH byte-array
*
* @param b Java byte-array
* @param off offset
* @param len length
*
* @return this
*/
public T putBytes(byte[] b, int off, int len) {
return putInt(len - off).putRawBytes(b, off, len);
}
public void readRawBytes(byte[] buf) {
readRawBytes(buf, 0, buf.length);
}
public void readRawBytes(byte[] buf, int off, int len) {
ensureAvailable(len);
System.arraycopy(data, rpos, buf, off, len);
rpos += len;
}
public T putRawBytes(byte[] d) {
return putRawBytes(d, 0, d.length);
}
@SuppressWarnings("unchecked")
public T putRawBytes(byte[] d, int off, int len) {
ensureCapacity(len);
System.arraycopy(d, off, data, wpos, len);
wpos += len;
return (T) this;
}
/**
* Copies the contents of provided buffer into this buffer
*
* @param buffer the {@code Buffer} to copy
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putBuffer(Buffer<? extends Buffer<?>> buffer) {
if (buffer != null) {
int r = buffer.available();
ensureCapacity(r);
System.arraycopy(buffer.data, buffer.rpos, data, wpos, r);
wpos += r;
}
return (T) this;
}
public int readInt() {
return (int) readLong();
}
public long readLong() {
ensureAvailable(4);
return data[rpos++] << 24 & 0xff000000L |
data[rpos++] << 16 & 0x00ff0000L |
data[rpos++] << 8 & 0x0000ff00L |
data[rpos++] & 0x000000ffL;
}
/**
* Writes a uint32 integer
*
* @param uint32
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putInt(long uint32) {
ensureCapacity(4);
if (uint32 < 0 || uint32 > 0xffffffffL)
throw new BufferException("Invalid value: " + uint32);
data[wpos++] = (byte) (uint32 >> 24);
data[wpos++] = (byte) (uint32 >> 16);
data[wpos++] = (byte) (uint32 >> 8);
data[wpos++] = (byte) uint32;
return (T) this;
}
/**
* Read an SSH multiple-precision integer
*
* @return the MP integer as a {@code BigInteger}
*/
public BigInteger readMPInt() {
return new BigInteger(readMPIntAsBytes());
}
/**
* Writes an SSH multiple-precision integer from a {@code BigInteger}
*
* @param bi {@code BigInteger} to write
*
* @return this
*/
public T putMPInt(BigInteger bi) {
return putMPInt(bi.toByteArray());
}
/**
* Writes an SSH multiple-precision integer from a Java byte-array
*
* @param foo byte-array
*
* @return this
*/
public T putMPInt(byte[] foo) {
int i = foo.length;
if ((foo[0] & 0x80) != 0) {
i++;
putInt(i);
putByte((byte) 0);
} else
putInt(i);
return putRawBytes(foo);
}
public byte[] readMPIntAsBytes() {
return readBytes();
}
public long readUINT64() {
long uint64 = (readLong() << 32) + (readLong() & 0xffffffffL);
if (uint64 < 0)
throw new BufferException("Cannot handle values > Long.MAX_VALUE");
return uint64;
}
@SuppressWarnings("unchecked")
public T putUINT64(long uint64) {
if (uint64 < 0)
throw new BufferException("Invalid value: " + uint64);
data[wpos++] = (byte) (uint64 >> 56);
data[wpos++] = (byte) (uint64 >> 48);
data[wpos++] = (byte) (uint64 >> 40);
data[wpos++] = (byte) (uint64 >> 32);
data[wpos++] = (byte) (uint64 >> 24);
data[wpos++] = (byte) (uint64 >> 16);
data[wpos++] = (byte) (uint64 >> 8);
data[wpos++] = (byte) uint64;
return (T) this;
}
/**
* Reads an SSH string
*
* @return the string as a Java {@code String}
*/
public String readString() {
int len = readInt();
if (len < 0 || len > 32768)
throw new BufferException("Bad item length: " + len);
ensureAvailable(len);
String s;
try {
s = new String(data, rpos, len, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new SSHRuntimeException(e);
}
rpos += len;
return s;
}
/**
* Reads an SSH string
*
* @return the string as a byte-array
*/
public byte[] readStringAsBytes() {
return readBytes();
}
public T putString(byte[] str) {
return putBytes(str);
}
public T putString(byte[] str, int offset, int len) {
return putBytes(str, offset, len);
}
public T putString(String string) {
try {
return putString(string.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new SSHRuntimeException(e);
}
}
/**
* Writes a char-array as an SSH string and then blanks it out.
* <p/>
* This is useful when a plaintext password needs to be sent. If {@code passwd} is {@code null}, an empty string is
* written.
*
* @param passwd (null-ok) the password as a character array
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putPassword(char[] passwd) {
if (passwd == null)
return putString("");
putInt(passwd.length);
ensureCapacity(passwd.length);
for (char c : passwd)
data[wpos++] = (byte) c;
Arrays.fill(passwd, ' ');
return (T) this;
}
public PublicKey readPublicKey() {
PublicKey key = null;
try {
switch (KeyType.fromString(readString())) {
case RSA: {
BigInteger e = readMPInt();
BigInteger n = readMPInt();
KeyFactory keyFactory = SecurityUtils.getKeyFactory("RSA");
key = keyFactory.generatePublic(new RSAPublicKeySpec(n, e));
break;
}
case DSA: {
BigInteger p = readMPInt();
BigInteger q = readMPInt();
BigInteger g = readMPInt();
BigInteger y = readMPInt();
KeyFactory keyFactory = SecurityUtils.getKeyFactory("DSA");
key = keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
break;
}
default:
assert false;
}
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException(e);
}
return key;
}
@SuppressWarnings("unchecked")
public T putPublicKey(PublicKey key) {
KeyType type = KeyType.fromKey(key);
switch (type) {
case RSA:
putString(type.toString()) // ssh-rsa
.putMPInt(((RSAPublicKey) key).getPublicExponent()) // e
.putMPInt(((RSAPublicKey) key).getModulus()); // n
break;
case DSA:
putString(type.toString()) // ssh-dss
.putMPInt(((DSAPublicKey) key).getParams().getP()) // p
.putMPInt(((DSAPublicKey) key).getParams().getQ()) // q
.putMPInt(((DSAPublicKey) key).getParams().getG()) // g
.putMPInt(((DSAPublicKey) key).getY()); // y
break;
default:
assert false;
}
return (T) this;
}
public T putSignature(String sigFormat, byte[] sigData) {
return putString(new PlainBuffer().putString(sigFormat).putBytes(sigData).getCompactData());
}
/**
* Gives a readable snapshot of the buffer in hex. This is useful for debugging.
*
* @return snapshot of the buffer as a hex string with each octet delimited by a space
*/
public String printHex() {
return ByteArrayUtils.printHex(array(), rpos(), available());
}
@Override
public String toString() {
return "Buffer [rpos=" + rpos + ", wpos=" + wpos + ", size=" + data.length + "]";
}
public static class PlainBuffer extends Buffer<PlainBuffer> {
public PlainBuffer() {
super();
}
public PlainBuffer(Buffer<?> from) {
super(from);
}
public PlainBuffer(byte[] b) {
super(b);
}
public PlainBuffer(int size) {
super(size);
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
import java.util.Arrays;
/** Utility functions for byte arrays. */
public class ByteArrayUtils {
final static char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* Check whether two byte arrays are the equal.
*
* @param a1
* @param a2
*
* @return <code>true</code> or <code>false</code>
*/
public static boolean equals(byte[] a1, byte[] a2) {
return (a1.length != a2.length && equals(a1, 0, a2, 0, a1.length));
}
/**
* Check whether some part or whole of two byte arrays is equal, for <code>length</code> bytes starting at some
* offset.
*
* @param a1
* @param a1Offset
* @param a2
* @param a2Offset
* @param length
*
* @return <code>true</code> or <code>false</code>
*/
public static boolean equals(byte[] a1, int a1Offset, byte[] a2, int a2Offset, int length) {
if (a1.length < a1Offset + length || a2.length < a2Offset + length)
return false;
while (length-- > 0)
if (a1[a1Offset++] != a2[a2Offset++])
return false;
return true;
}
/**
* Get a hexadecimal representation of <code>array</code>, with each octet separated by a space.
*
* @param array
*
* @return hex string, each octet delimited by a space
*/
public static String printHex(byte[] array) {
return printHex(array, 0, array.length);
}
/**
* Get a hexadecimal representation of a byte array starting at <code>offset</code> index for <code>len</code>
* bytes, with each octet separated by a space.
*
* @param array
* @param offset
* @param len
*
* @return hex string, each octet delimited by a space
*/
public static String printHex(byte[] array, int offset, int len) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
byte b = array[offset + i];
if (sb.length() > 0)
sb.append(' ');
sb.append(digits[b >> 4 & 0x0F]);
sb.append(digits[b & 0x0F]);
}
return sb.toString();
}
/**
* Get the hexadecimal representation of a byte array.
*
* @param array
*
* @return hex string
*/
public static String toHex(byte[] array) {
return toHex(array, 0, array.length);
}
/**
* Get the hexadecimal representation of a byte array starting at <code>offset</code> index for <code>len</code>
* bytes.
*
* @param array
* @param offset
* @param len
*
* @return hex string
*/
public static String toHex(byte[] array, int offset, int len) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
byte b = array[offset + i];
sb.append(digits[b >> 4 & 0x0F]);
sb.append(digits[b & 0x0F]);
}
return sb.toString();
}
public static byte[] copyOf(byte[] array) {
return Arrays.copyOf(array, array.length);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.common;
/** Disconnect error codes */
public enum DisconnectReason {
UNKNOWN(0),
HOST_NOT_ALLOWED_TO_CONNECT(1),
PROTOCOL_ERROR(2),
KEY_EXCHANGE_FAILED(3),
HOST_AUTHENTICATION_FAILED(4),
RESERVED(4),
MAC_ERROR(5),
COMPRESSION_ERROR(6),
SERVICE_NOT_AVAILABLE(7),
PROTOCOL_VERSION_NOT_SUPPORTED(8),
HOST_KEY_NOT_VERIFIABLE(9),
CONNECTION_LOST(10),
BY_APPLICATION(11),
TOO_MANY_CONNECTIONS(12),
AUTH_CANCELLED_BY_USER(13),
NO_MORE_AUTH_METHODS_AVAILABLE(14),
ILLEGAL_USER_NAME(15);
public static DisconnectReason fromInt(int code) {
for (DisconnectReason dc : values())
if (dc.code == code)
return dc;
return UNKNOWN;
}
private final int code;
private DisconnectReason(int code) {
this.code = code;
}
public int toInt() {
return code;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.common;
import java.util.Collection;
/** API for classes that are capable of being notified on an error so they can cleanup. */
public interface ErrorNotifiable {
/** Utility functions. */
class Util {
/** Notify all {@code notifiables} of given {@code error}. */
public static void alertAll(SSHException error, ErrorNotifiable... notifiables) {
for (ErrorNotifiable notifiable : notifiables)
notifiable.notifyError(error);
}
/** Notify all {@code notifiables} of given {@code error}. */
public static void alertAll(SSHException error, Collection<? extends ErrorNotifiable> notifiables) {
for (ErrorNotifiable notifiable : notifiables)
notifiable.notifyError(error);
}
}
/** Notifies this object of an {@code error}. */
void notifyError(SSHException error);
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
import java.util.LinkedList;
import java.util.List;
/**
* A basic factory interface.
*
* @param <T> the type of object created by this factory
*/
public interface Factory<T> {
/**
* Inteface for a named factory. Named factories are simply factories that are identified by a name. Such names are
* used mainly in SSH algorithm negotiation.
*
* @param <T> type of object created by this factory
*/
interface Named<T> extends Factory<T> {
/** Utility functions */
public static class Util {
/**
* Creates an object by picking a factory from {@code factories} that is identified by {@code name} from a
* list of named {@code factories}. Uses the first match.
*
* @param factories list of available factories
* @param name name of the desired factory
* @param <T> type of the {@code factories}
*
* @return a newly created instance of {@code T} or {@code null} if there was no match
*/
public static <T> T create(List<Named<T>> factories, String name) {
if (factories != null)
for (Named<T> f : factories)
if (f.getName().equals(name))
return f.create();
return null;
}
/**
* Retrieve a particular factory as identified by {@code name} from a list of named {@code factories}.
* Returns the first match.
*
* @param factories list of factories
* @param name the name of the factory to retrieve
* @param <T> type of the {@code factories}
*
* @return a factory or {@code null} if there was no match
*/
public static <T> Named<T> get(List<Named<T>> factories, String name) {
for (Named<T> f : factories)
if (f.getName().equals(name))
return f;
return null;
}
/**
* Get a comma-delimited string containing the factory names from the given list of {@code factories}.
*
* @param factories list of available factories
* @param <T> type of the {@code factories}
*
* @return a comma separated list of factory names
*/
public static <T> List<String> getNames(List<Named<T>> factories) {
List<String> list = new LinkedList<String>();
for (Named<T> f : factories)
list.add(f.getName());
return list;
}
}
/** @return the name of this factory. */
String getName();
}
/** @return a new object created using this factory. */
T create();
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
public class IOUtils {
private static final Logger LOG = LoggerFactory.getLogger(IOUtils.class);
public static void closeQuietly(Closeable... closeables) {
for (Closeable c : closeables)
try {
if (c != null)
c.close();
} catch (IOException logged) {
LOG.warn("Error closing {} - {}", c, logged);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.common;
import java.security.Key;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public enum KeyType {
/** SSH identifier for RSA keys */
RSA("ssh-rsa", new KeyChecker() {
public boolean isMyType(Key key) {
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
}
}),
/** SSH identifier for DSA keys */
DSA("ssh-dss", new KeyChecker() {
public boolean isMyType(Key key) {
return (key instanceof DSAPublicKey || key instanceof DSAPrivateKey);
}
}),
/** Unrecognized */
UNKNOWN("unknown", null);
private static interface KeyChecker {
boolean isMyType(Key key);
}
private final String sType;
private final KeyChecker checker;
private KeyType(String type, KeyChecker checker) {
this.sType = type;
this.checker = checker;
}
public static KeyType fromKey(Key key) {
for (KeyType kt : values())
if (kt.checker != null && kt.checker.isMyType((key)))
return kt;
return UNKNOWN;
}
public static KeyType fromString(String sType) {
for (KeyType kt : values())
if (kt.sType.equals(sType))
return kt;
return UNKNOWN;
}
@Override
public String toString() {
return sType;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.common;
/** SSH message identifiers */
public enum Message {
DISCONNECT(1),
IGNORE(2),
UNIMPLEMENTED(3),
DEBUG(4),
SERVICE_REQUEST(5),
SERVICE_ACCEPT(6),
KEXINIT(20),
NEWKEYS(21),
KEXDH_INIT(30),
/** { KEXDH_REPLY, KEXDH_GEX_GROUP } */
KEXDH_31(31),
KEX_DH_GEX_INIT(32),
KEX_DH_GEX_REPLY(33),
KEX_DH_GEX_REQUEST(34),
USERAUTH_REQUEST(50),
USERAUTH_FAILURE(51),
USERAUTH_SUCCESS(52),
USERAUTH_BANNER(53),
/** { USERAUTH_PASSWD_CHANGREQ, USERAUTH_PK_OK, USERAUTH_INFO_REQUEST } */
USERAUTH_60(60),
USERAUTH_INFO_RESPONSE(61),
GLOBAL_REQUEST(80),
REQUEST_SUCCESS(81),
REQUEST_FAILURE(82),
CHANNEL_OPEN(90),
CHANNEL_OPEN_CONFIRMATION(91),
CHANNEL_OPEN_FAILURE(92),
CHANNEL_WINDOW_ADJUST(93),
CHANNEL_DATA(94),
CHANNEL_EXTENDED_DATA(95),
CHANNEL_EOF(96),
CHANNEL_CLOSE(97),
CHANNEL_REQUEST(98),
CHANNEL_SUCCESS(99),
CHANNEL_FAILURE(100);
private final byte b;
private static final Message[] commands = new Message[256];
static {
for (Message c : Message.values())
if (commands[c.toByte()] == null)
commands[c.toByte()] = c;
}
public static Message fromByte(byte b) {
return commands[b];
}
Message(int b) {
this.b = (byte) b;
}
public boolean geq(int num) {
return b >= num;
}
public boolean gt(int num) {
return b > num;
}
public boolean in(int x, int y) {
return b >= x && b <= y;
}
public boolean leq(int num) {
return b <= num;
}
public boolean lt(int num) {
return b < num;
}
public byte toByte() {
return b;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
import net.schmizz.concurrent.ExceptionChainer;
import java.io.IOException;
/**
* Most exceptions in {@code org.apache.commons.net.ssh} are instances of this class. An {@link SSHException} is itself
* an {@link IOException} and can be caught like that if this level of granularity is not desired.
*/
public class SSHException extends IOException {
public static final ExceptionChainer<SSHException> chainer = new ExceptionChainer<SSHException>() {
public SSHException chain(Throwable t) {
if (t instanceof SSHException)
return (SSHException) t;
else
return new SSHException(t);
}
};
private final DisconnectReason reason;
public SSHException() {
this(DisconnectReason.UNKNOWN, null, null);
}
public SSHException(DisconnectReason code) {
this(code, null, null);
}
public SSHException(DisconnectReason code, String message) {
this(code, message, null);
}
public SSHException(DisconnectReason code, String message, Throwable cause) {
super(message);
this.reason = code;
if (cause != null)
initCause(cause);
}
public SSHException(DisconnectReason code, Throwable cause) {
this(code, null, cause);
}
public SSHException(String message) {
this(DisconnectReason.UNKNOWN, message, null);
}
public SSHException(String message, Throwable cause) {
this(DisconnectReason.UNKNOWN, message, cause);
}
public SSHException(Throwable cause) {
this(DisconnectReason.UNKNOWN, null, cause);
}
public int getDisconnectCode() {
return reason.toInt();
}
public DisconnectReason getDisconnectReason() {
return reason;
}
@Override
public String getMessage() {
if (super.getMessage() != null)
return super.getMessage();
else if (getCause() != null && getCause().getMessage() != null)
return getCause().getMessage();
else
return null;
}
@Override
public String toString() {
final String cls = getClass().getName();
final String code = reason != DisconnectReason.UNKNOWN ? "[" + reason + "] " : "";
final String msg = getMessage() != null ? getMessage() : "";
return cls + (code.equals("") && msg.equals("") ? "" : ": ") + code + msg;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
import java.util.Arrays;
public class SSHPacket extends Buffer<SSHPacket> {
public SSHPacket() {
super();
}
public SSHPacket(int size) {
super(size);
}
public SSHPacket(byte[] data) {
super(data);
}
/**
* Constructs new buffer for the specified SSH packet and reserves the needed space (5 bytes) for the packet
* header.
*
* @param msg the SSH command
*/
public SSHPacket(Message msg) {
super();
rpos = wpos = 5;
putMessageID(msg);
}
public SSHPacket(SSHPacket p) {
this.data = Arrays.copyOf(p.data, p.wpos);
this.rpos = p.rpos;
this.wpos = p.wpos;
}
/**
* Reads an SSH byte and returns it as {@link Message}
*
* @return the message identifier
*/
public Message readMessageID() {
byte b = readByte();
Message cmd = Message.fromByte(b);
if (cmd == null)
throw new BufferException("Unknown message ID: " + b);
return cmd;
}
/**
* Writes a byte indicating the SSH message identifier
*
* @param msg the identifier as a {@link Message} type
*
* @return this
*/
public SSHPacket putMessageID(Message msg) {
return putByte(msg.toByte());
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.common;
/**
* An interface for classes to which packet handling may be delegated. Chains of such delegations may be used, e.g.
* {@code packet decoder -> (SSHPacketHandler) transport layer -> (SSHPacketHandler) connection layer ->
* (SSHPacketHandler) channel}.
*/
public interface SSHPacketHandler {
/**
* Delegate handling of some SSH packet to this object.
*
* @param msg the SSH {@link Message message identifier}
* @param buf {@link SSHPacket} containing rest of the request
*
* @throws SSHException if there is a non-recoverable error
*/
void handle(Message msg, SSHPacket buf) throws SSHException;
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
/** Represents unrecoverable exceptions in the {@code org.apache.commons.net.ssh} package. */
public class SSHRuntimeException extends RuntimeException {
public SSHRuntimeException() {
this(null, null);
}
public SSHRuntimeException(String message) {
this(message, null);
}
public SSHRuntimeException(String message, Throwable cause) {
super(message);
if (cause != null)
initCause(cause);
}
public SSHRuntimeException(Throwable cause) {
this(null, cause);
}
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.common;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Signature;
/** Static utility method relating to security facilities. */
public class SecurityUtils {
private static class BouncyCastleRegistration {
public void run() throws Exception {
if (java.security.Security.getProvider(BOUNCY_CASTLE) == null) {
LOG.info("Trying to register BouncyCastle as a JCE provider");
java.security.Security.addProvider(new BouncyCastleProvider());
MessageDigest.getInstance("MD5", BOUNCY_CASTLE);
KeyAgreement.getInstance("DH", BOUNCY_CASTLE);
LOG.info("Registration succeeded");
} else
LOG.info("BouncyCastle already registered as a JCE provider");
securityProvider = BOUNCY_CASTLE;
}
}
private static final Logger LOG = LoggerFactory.getLogger(SecurityUtils.class);
/** Identifier for the BouncyCastle JCE provider */
public static final String BOUNCY_CASTLE = "BC";
/*
* Security provider identifier. null = default JCE
*/
private static String securityProvider = null;
// relate to BC registration
private static Boolean registerBouncyCastle;
private static boolean registrationDone;
public static synchronized Cipher getCipher(String transformation) throws NoSuchAlgorithmException,
NoSuchPaddingException, NoSuchProviderException {
register();
if (getSecurityProvider() == null)
return Cipher.getInstance(transformation);
else
return Cipher.getInstance(transformation, getSecurityProvider());
}
/**
* Computes the fingerprint for a public key, in the standard SSH format, e.g. "4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"
*
* @param key the public key
*
* @return the fingerprint
*
* @see <a href="http://tools.ietf.org/html/draft-friedl-secsh-fingerprint-00">specification</a>
*/
public static String getFingerprint(PublicKey key) {
MessageDigest md5;
try {
md5 = getMessageDigest("MD5");
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException(e);
}
md5.update(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
final String undelimited = ByteArrayUtils.toHex(md5.digest());
assert undelimited.length() == 32 : "md5 contract";
StringBuilder fp = new StringBuilder(undelimited.substring(0, 2));
for (int i = 2; i <= undelimited.length() - 2; i += 2)
fp.append(":").append(undelimited.substring(i, i + 2));
return fp.toString();
}
/**
* Creates a new instance of {@link KeyAgreement} with the given algorithm.
*
* @param algorithm key agreement algorithm
*
* @return new instance
*
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
public static synchronized KeyAgreement getKeyAgreement(String algorithm) throws NoSuchAlgorithmException,
NoSuchProviderException {
register();
if (getSecurityProvider() == null)
return KeyAgreement.getInstance(algorithm);
else
return KeyAgreement.getInstance(algorithm, getSecurityProvider());
}
/**
* Creates a new instance of {@link KeyFactory} with the given algorithm.
*
* @param algorithm key factory algorithm e.g. RSA, DSA
*
* @return new instance
*
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
public static synchronized KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException,
NoSuchProviderException {
register();
if (getSecurityProvider() == null)
return KeyFactory.getInstance(algorithm);
else
return KeyFactory.getInstance(algorithm, getSecurityProvider());
}
/**
* Creates a new instance of {@link KeyPairGenerator} with the given algorithm.
*
* @param algorithm key pair generator algorithm
*
* @return new instance
*
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
public static synchronized KeyPairGenerator getKeyPairGenerator(String algorithm) throws NoSuchAlgorithmException,
NoSuchProviderException {
register();
if (getSecurityProvider() == null)
return KeyPairGenerator.getInstance(algorithm);
else
return KeyPairGenerator.getInstance(algorithm, getSecurityProvider());
}
/**
* Create a new instance of {@link Mac} with the given algorithm.
*
* @param algorithm MAC algorithm
*
* @return new instance
*
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
public static synchronized Mac getMAC(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException {
register();
if (getSecurityProvider() == null)
return Mac.getInstance(algorithm);
else
return Mac.getInstance(algorithm, getSecurityProvider());
}
/**
* Create a new instance of {@link MessageDigest} with the given algorithm.
*
* @param algorithm MessageDigest algorithm name
*
* @return
*
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
public static synchronized MessageDigest getMessageDigest(String algorithm) throws NoSuchAlgorithmException,
NoSuchProviderException {
register();
if (getSecurityProvider() == null)
return MessageDigest.getInstance(algorithm);
else
return MessageDigest.getInstance(algorithm, getSecurityProvider());
}
/**
* Get the identifier for the registered security provider.
*
* @return JCE provider identifier
*/
public static synchronized String getSecurityProvider() {
register();
return securityProvider;
}
public static synchronized Signature getSignature(String algorithm) throws NoSuchAlgorithmException,
NoSuchProviderException {
register();
if (getSecurityProvider() == null)
return Signature.getInstance(algorithm);
else
return Signature.getInstance(algorithm, getSecurityProvider());
}
/**
* Attempts registering BouncyCastle as security provider if it has not been previously attempted and returns
* whether the registration succeeded.
*
* @return whether BC registered
*/
public static synchronized boolean isBouncyCastleRegistered() {
register();
return BOUNCY_CASTLE.equals(securityProvider);
}
public static synchronized void setRegisterBouncyCastle(boolean registerBouncyCastle) {
SecurityUtils.registerBouncyCastle = registerBouncyCastle;
registrationDone = false;
}
/**
* Specifies the JCE security provider that should be used.
*
* @param securityProvider identifier for the security provider
*/
public static synchronized void setSecurityProvider(String securityProvider) {
SecurityUtils.securityProvider = securityProvider;
registrationDone = false;
}
private static void register() {
if (!registrationDone) {
if (securityProvider == null && (registerBouncyCastle == null || registerBouncyCastle))
// Use an inner class to avoid a strong dependency on BouncyCastle
try {
new BouncyCastleRegistration().run();
} catch (Throwable t) {
if (registerBouncyCastle == null)
LOG.info("BouncyCastle not registered, using the default JCE provider");
else {
LOG.error("Failed to register BouncyCastle as the defaut JCE provider");
throw new SSHRuntimeException("Failed to register BouncyCastle as the defaut JCE provider", t);
}
}
registrationDone = true;
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
public class StreamCopier extends Thread {
private static final Logger LOG = LoggerFactory.getLogger(StreamCopier.class);
public interface ErrorCallback {
void onError(IOException ioe);
}
public static ErrorCallback closeOnErrorCallback(final Closeable... toClose) {
final Closeable[] closeables = Arrays.copyOf(toClose, toClose.length);
return new ErrorCallback() {
public void onError(IOException ioe) {
IOUtils.closeQuietly(closeables);
}
};
}
public static long copy(InputStream in, OutputStream out, int bufSize, boolean keepFlushing) throws IOException {
long count = 0;
final long startTime = System.currentTimeMillis();
final byte[] buf = new byte[bufSize];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
count += read;
if (keepFlushing)
out.flush();
}
if (!keepFlushing)
out.flush();
final double sizeKiB = count / 1024.0;
final double timeSeconds = (System.currentTimeMillis() - startTime) / 1000.0;
LOG.info(sizeKiB + " KiB transferred in {} seconds ({} KiB/s)", timeSeconds, (sizeKiB / timeSeconds));
return count;
}
public static String copyStreamToString(InputStream stream) throws IOException {
final StringBuilder sb = new StringBuilder();
int read;
while ((read = stream.read()) != -1)
sb.append((char) read);
return sb.toString();
}
private final Logger log;
private final InputStream in;
private final OutputStream out;
private int bufSize = 1;
private boolean keepFlushing = true;
private ErrorCallback errCB = new ErrorCallback() {
public void onError(IOException ioe) {
}
}; // Default null cb
public StreamCopier(String name, InputStream in, OutputStream out) {
this.in = in;
this.out = out;
setName("streamCopier");
log = LoggerFactory.getLogger(name);
}
public StreamCopier bufSize(int size) {
bufSize = size;
return this;
}
public StreamCopier keepFlushing(boolean choice) {
keepFlushing = choice;
return this;
}
public StreamCopier daemon(boolean choice) {
setDaemon(choice);
return this;
}
public StreamCopier errorCallback(ErrorCallback errCB) {
this.errCB = errCB;
return this;
}
@Override
public void run() {
try {
log.debug("Wil pipe from {} to {}", in, out);
copy(in, out, bufSize, keepFlushing);
log.debug("EOF on {}", in);
} catch (IOException ioe) {
log.error("In pipe from {} to {}: " + ioe.toString(), in, out);
errCB.onError(ioe);
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection;
import net.schmizz.concurrent.Future;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.connection.channel.OpenFailException;
import net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
/**
* Connection layer of the SSH protocol.
*
* @see rfc4254
*/
public interface Connection {
/**
* Attach a {@link net.schmizz.sshj.connection.channel.Channel} to this connection. A channel must be attached to
* the connection if it is to receive any channel-specific data that is received.
*/
void attach(Channel chan);
/**
* Attach a {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener} to this connection, which
* will be delegated opening of any {@code CHANNEL_OPEN} packets {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener#getChannelType()
* for which it is responsible}.
*/
void attach(ForwardedChannelOpener opener);
/** Forget an attached {@link Channel}. */
void forget(Channel chan);
/** Forget an attached {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener}. */
void forget(ForwardedChannelOpener handler);
/** Returns an attached {@link Channel} of specified channel-id, or {@code null} if no such channel was attached */
Channel get(int id);
/** Wait for the situation that no channels are attached (e.g., got closed). */
void join() throws InterruptedException;
/**
* Returns an attached {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener} of specified
* channel-type, or {@code null} if no such channel was attached
*/
ForwardedChannelOpener get(String chanType);
/** Returns an available ID a {@link net.schmizz.sshj.connection.channel.Channel} can rightfully claim. */
int nextID();
/**
* Send an SSH global request.
*
* @param name request name
* @param wantReply whether a reply is requested
* @param specifics {@link net.schmizz.sshj.common.SSHPacket} containing fields specific to the request
*
* @return a {@link net.schmizz.concurrent.Future} for the reply data (in case {@code wantReply} is true) which
* allows waiting on the reply, or {@code null} if a reply is not requested.
*
* @throws TransportException if there is an error sending the request
*/
public Future<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
Buffer.PlainBuffer specifics) throws TransportException;
/**
* Send a {@code SSH_MSG_OPEN_FAILURE} for specified {@code Reason} and {@code message}.
*
* @param recipient
* @param reason
* @param message
*
* @throws TransportException
*/
void sendOpenFailure(int recipient, OpenFailException.Reason reason, String message) throws TransportException;
/**
* Get the maximum packet size for the local window this connection recommends to any {@link Channel}'s that ask for
* it.
*/
int getMaxPacketSize();
/**
* Set the maximum packet size for the local window this connection recommends to any {@link Channel}'s that ask for
* it.
*/
void setMaxPacketSize(int maxPacketSize);
/**
* Get the size for the local window this connection recommends to any {@link net.schmizz.sshj.connection.channel.Channel}'s
* that ask for it.
*/
int getWindowSize();
/** Set the size for the local window this connection recommends to any {@link Channel}'s that ask for it. */
void setWindowSize(int windowSize);
/** Get the associated {@link Transport}. */
Transport getTransport();
/**
* Get the {@code timeout} this connection uses for blocking operations and recommends to any {@link Channel other}
* {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener classes} that ask for it.
*/
int getTimeout();
/**
* Set the {@code timeout} this connection uses for blocking operations and recommends to any {@link
* net.schmizz.sshj.connection.channel.Channel other} {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener
* classes} that ask for it.
*/
void setTimeout(int timeout);
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection;
import net.schmizz.concurrent.ExceptionChainer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHException;
/** Connection-layer exception. */
public class ConnectionException extends SSHException {
public static final ExceptionChainer<ConnectionException> chainer = new ExceptionChainer<ConnectionException>() {
public ConnectionException chain(Throwable t) {
if (t instanceof ConnectionException)
return (ConnectionException) t;
else
return new ConnectionException(t);
}
};
public ConnectionException() {
super();
}
public ConnectionException(DisconnectReason code) {
super(code);
}
public ConnectionException(DisconnectReason code, String message) {
super(code, message);
}
public ConnectionException(DisconnectReason code, String message, Throwable cause) {
super(code, message, cause);
}
public ConnectionException(DisconnectReason code, Throwable cause) {
super(code, cause);
}
public ConnectionException(String message) {
super(message);
}
public ConnectionException(String message, Throwable cause) {
super(message, cause);
}
public ConnectionException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.connection;
import net.schmizz.concurrent.Future;
import net.schmizz.concurrent.FutureUtils;
import net.schmizz.sshj.AbstractService;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.connection.channel.OpenFailException;
import net.schmizz.sshj.connection.channel.OpenFailException.Reason;
import net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/** {@link Connection} implementation. */
public class ConnectionProtocol extends AbstractService implements Connection {
private final Object internalSynchronizer = new Object();
private final AtomicInteger nextID = new AtomicInteger();
private final Map<Integer, Channel> channels = new ConcurrentHashMap<Integer, Channel>();
private final Map<String, ForwardedChannelOpener> openers = new ConcurrentHashMap<String, ForwardedChannelOpener>();
private final Queue<Future<SSHPacket, ConnectionException>> globalReqFutures = new LinkedList<Future<SSHPacket, ConnectionException>>();
private int windowSize = 2048 * 1024;
private int maxPacketSize = 32 * 1024;
/**
* Create with an associated {@link Transport}.
*
* @param trans transport layer
*/
public ConnectionProtocol(Transport trans) {
super("ssh-connection", trans);
}
public void attach(Channel chan) {
log.info("Attaching `{}` channel (#{})", chan.getType(), chan.getID());
channels.put(chan.getID(), chan);
}
public Channel get(int id) {
return channels.get(id);
}
public ForwardedChannelOpener get(String chanType) {
return openers.get(chanType);
}
public void forget(Channel chan) {
log.info("Forgetting `{}` channel (#{})", chan.getType(), chan.getID());
channels.remove(chan.getID());
synchronized (internalSynchronizer) {
if (channels.isEmpty())
internalSynchronizer.notifyAll();
}
}
public void forget(ForwardedChannelOpener opener) {
log.info("Forgetting opener for `{}` channels: {}", opener.getChannelType(), opener);
openers.remove(opener.getChannelType());
}
public void attach(ForwardedChannelOpener opener) {
log.info("Attaching opener for `{}` channels: {}", opener.getChannelType(), opener);
openers.put(opener.getChannelType(), opener);
}
private Channel getChannel(SSHPacket buffer) throws ConnectionException {
int recipient = buffer.readInt();
Channel channel = get(recipient);
if (channel != null)
return channel;
else {
buffer.rpos(buffer.rpos() - 5);
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Received " + buffer.readMessageID()
+ " on unknown channel #" + recipient);
}
}
@Override
public void handle(Message msg, SSHPacket buf) throws SSHException {
if (msg.in(91, 100))
getChannel(buf).handle(msg, buf);
else if (msg.in(80, 90))
switch (msg) {
case REQUEST_SUCCESS:
gotGlobalReqResponse(buf);
break;
case REQUEST_FAILURE:
gotGlobalReqResponse(null);
break;
case CHANNEL_OPEN:
gotChannelOpen(buf);
break;
default:
super.handle(msg, buf);
}
else
super.handle(msg, buf);
}
@Override
public void notifyError(SSHException error) {
super.notifyError(error);
synchronized (globalReqFutures) {
FutureUtils.alertAll(error, globalReqFutures);
globalReqFutures.clear();
}
ErrorNotifiable.Util.alertAll(error, channels.values());
channels.clear();
}
public int getMaxPacketSize() {
return maxPacketSize;
}
public Transport getTransport() {
return trans;
}
public void setMaxPacketSize(int maxPacketSize) {
this.maxPacketSize = maxPacketSize;
}
public int getWindowSize() {
return windowSize;
}
public void setWindowSize(int windowSize) {
this.windowSize = windowSize;
}
public void join() throws InterruptedException {
synchronized (internalSynchronizer) {
while (!channels.isEmpty())
internalSynchronizer.wait();
}
}
public int nextID() {
return nextID.getAndIncrement();
}
public Future<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
Buffer.PlainBuffer specifics) throws TransportException {
synchronized (globalReqFutures) {
log.info("Making global request for `{}`", name);
trans.write(new SSHPacket(Message.GLOBAL_REQUEST) //
.putString(name) //
.putBoolean(wantReply) //
.putBuffer(specifics)); //
Future<SSHPacket, ConnectionException> future = null;
if (wantReply) {
future = new Future<SSHPacket, ConnectionException>("global req for " + name, ConnectionException.chainer);
globalReqFutures.add(future);
}
return future;
}
}
private void gotGlobalReqResponse(SSHPacket response) throws ConnectionException {
synchronized (globalReqFutures) {
Future<SSHPacket, ConnectionException> gr = globalReqFutures.poll();
if (gr == null)
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR,
"Got a global request response when none was requested");
else if (response == null)
gr.error(new ConnectionException("Global request [" + gr + "] failed"));
else
gr.set(response);
}
}
private void gotChannelOpen(SSHPacket buf) throws ConnectionException, TransportException {
final String type = buf.readString();
log.debug("Received CHANNEL_OPEN for `{}` channel", type);
if (openers.containsKey(type))
openers.get(type).handleOpen(buf);
else {
log.warn("No opener found for `{}` CHANNEL_OPEN request -- rejecting", type);
sendOpenFailure(buf.readInt(), OpenFailException.Reason.UNKNOWN_CHANNEL_TYPE, "");
}
}
public void sendOpenFailure(int recipient, Reason reason, String message) throws TransportException {
trans.write(new SSHPacket(Message.CHANNEL_OPEN_FAILURE) //
.putInt(recipient) //
.putInt(reason.getCode()) //
.putString(message));
}
@Override
public void notifyDisconnect() throws SSHException {
super.notifyDisconnect();
// wh'about them futures?
for (Channel chan : channels.values())
chan.close();
}
}

View File

@@ -0,0 +1,382 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.FutureUtils;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.common.DisconnectReason;
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.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public abstract class AbstractChannel implements Channel {
/** Logger */
protected final Logger log;
/** Transport layer */
protected final Transport trans;
/** Connection layer */
protected final Connection conn;
/** Channel type */
private final String type;
/** Channel ID */
private final int id;
/** Remote recipient ID */
private int recipient;
private final Queue<Event<ConnectionException>> chanReqResponseEvents = new LinkedList<Event<ConnectionException>>();
/* The lock used by #newEvent to create open & close events */
private final ReentrantLock lock = new ReentrantLock();
/** Channel open event */
protected final Event<ConnectionException> open;
/** Channel close event */
private final Event<ConnectionException> close;
/* Access to these fields should be synchronized using this object */
private boolean eofSent;
private boolean eofGot;
private boolean closeRequested;
/** Local window */
protected final Window.Local lwin;
/** stdout stream */
private final ChannelInputStream in;
/** Remote window */
protected Window.Remote rwin;
/** stdin stream */
private ChannelOutputStream out;
private volatile boolean autoExpand = false;
protected AbstractChannel(String type, Connection conn) {
this.type = type;
this.conn = conn;
this.trans = conn.getTransport();
id = conn.nextID();
log = LoggerFactory.getLogger("chan#" + id);
lwin = new Window.Local(id, conn.getWindowSize(), conn.getMaxPacketSize());
in = new ChannelInputStream(this, trans, lwin);
open = new Event<ConnectionException>("chan#" + id + " / " + "open", ConnectionException.chainer, lock);
close = new Event<ConnectionException>("chan#" + id + " / " + "close", ConnectionException.chainer, lock);
}
protected void init(int recipient, int remoteWinSize, int remoteMaxPacketSize) {
this.recipient = recipient;
rwin = new Window.Remote(id, remoteWinSize, remoteMaxPacketSize);
out = new ChannelOutputStream(this, trans, rwin);
log.info("Initialized - {}", this);
}
public boolean getAutoExpand() {
return autoExpand;
}
public int getID() {
return id;
}
public InputStream getInputStream() {
return in;
}
public int getLocalMaxPacketSize() {
return lwin.getMaxPacketSize();
}
public int getLocalWinSize() {
return lwin.getSize();
}
public OutputStream getOutputStream() {
return out;
}
public int getRecipient() {
return recipient;
}
public int getRemoteMaxPacketSize() {
return rwin.getMaxPacketSize();
}
public int getRemoteWinSize() {
return rwin.getSize();
}
public String getType() {
return type;
}
public void handle(Message msg, SSHPacket buf) throws ConnectionException, TransportException {
switch (msg) {
case CHANNEL_DATA:
receiveInto(in, buf);
break;
case CHANNEL_EXTENDED_DATA:
gotExtendedData(buf.readInt(), buf);
break;
case CHANNEL_WINDOW_ADJUST:
gotWindowAdjustment(buf.readInt());
break;
case CHANNEL_REQUEST:
gotChannelRequest(buf);
break;
case CHANNEL_SUCCESS:
gotResponse(true);
break;
case CHANNEL_FAILURE:
gotResponse(false);
break;
case CHANNEL_EOF:
gotEOF();
break;
case CHANNEL_CLOSE:
gotClose();
break;
default:
gotUnknown(msg, buf);
}
}
private void gotClose() throws TransportException {
log.info("Got close");
try {
closeAllStreams();
sendClose();
} finally {
finishOff();
}
}
/** Called when all I/O streams should be closed. Subclasses can override but must call super. */
protected void closeAllStreams() {
IOUtils.closeQuietly(in, out);
}
public void notifyError(SSHException error) {
log.debug("Channel #{} got notified of {}", getID(), error.toString());
FutureUtils.alertAll(error, open, close);
FutureUtils.alertAll(error, chanReqResponseEvents);
in.notifyError(error);
out.notifyError(error);
finishOff();
}
public void setAutoExpand(boolean autoExpand) {
this.autoExpand = autoExpand;
}
public void close() throws ConnectionException, TransportException {
lock.lock();
try {
try {
sendClose();
} catch (TransportException e) {
if (!close.hasError())
throw e;
}
close.await(conn.getTimeout(), TimeUnit.SECONDS);
} finally {
lock.unlock();
}
}
protected synchronized void sendClose() throws TransportException {
try {
if (!closeRequested) {
log.info("Sending close");
trans.write(newBuffer(Message.CHANNEL_CLOSE));
}
} finally {
closeRequested = true;
}
}
public synchronized boolean isOpen() {
lock.lock();
try {
return open.isSet() && !close.isSet() && !closeRequested;
} finally {
lock.unlock();
}
}
private void gotChannelRequest(SSHPacket buf) throws ConnectionException, TransportException {
final String reqType = buf.readString();
buf.readBoolean(); // We don't care about the 'want-reply' value
log.info("Got chan request for `{}`", reqType);
handleRequest(reqType, buf);
}
private void gotWindowAdjustment(int howMuch) {
log.info("Received window adjustment for {} bytes", howMuch);
rwin.expand(howMuch);
}
/** Called when this channel's end-of-life has been reached. Subclasses may override but must call super. */
protected void finishOff() {
conn.forget(this);
close.set();
}
protected void gotExtendedData(int dataTypeCode, SSHPacket buf) throws ConnectionException, TransportException {
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Extended data not supported on " + type
+ " channel");
}
protected void gotUnknown(Message msg, SSHPacket buf) throws ConnectionException, TransportException {
}
protected void handleRequest(String reqType, SSHPacket buf) throws ConnectionException, TransportException {
trans.write(newBuffer(Message.CHANNEL_FAILURE));
}
protected SSHPacket newBuffer(Message cmd) {
return new SSHPacket(cmd).putInt(recipient);
}
protected void receiveInto(ChannelInputStream stream, SSHPacket buf) throws ConnectionException, TransportException {
final int len = buf.readInt();
if (len < 0 || len > getLocalMaxPacketSize() || len != buf.available())
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Bad item length: " + len);
if (log.isTraceEnabled())
log.trace("IN #{}: {}", id, ByteArrayUtils.printHex(buf.array(), buf.rpos(), len));
stream.receive(buf.array(), buf.rpos(), len);
}
protected synchronized Event<ConnectionException> sendChannelRequest(String reqType, boolean wantReply,
Buffer.PlainBuffer reqSpecific) throws TransportException {
log.info("Sending channel request for `{}`", reqType);
trans.write(
newBuffer(Message.CHANNEL_REQUEST)
.putString(reqType)
.putBoolean(wantReply)
.putBuffer(reqSpecific)
);
Event<ConnectionException> responseEvent = null;
if (wantReply) {
responseEvent = new Event<ConnectionException>("chan#" + id + " / " + "chanreq for " + reqType, ConnectionException.chainer, lock);
chanReqResponseEvents.add(responseEvent);
}
return responseEvent;
}
private synchronized void gotResponse(boolean success) throws ConnectionException {
final Event<ConnectionException> responseEvent = chanReqResponseEvents.poll();
if (responseEvent != null) {
if (success)
responseEvent.set();
else
responseEvent.error(new ConnectionException("Request failed"));
} else
throw new ConnectionException(
DisconnectReason.PROTOCOL_ERROR,
"Received response to channel request when none was requested");
}
private synchronized void gotEOF() throws TransportException {
log.info("Got EOF");
eofGot = true;
eofInputStreams();
if (eofSent)
sendClose();
}
/** Called when EOF has been received. Subclasses can override but must call super. */
protected void eofInputStreams() {
in.eof();
}
public synchronized void sendEOF() throws TransportException {
try {
if (!closeRequested && !eofSent) {
log.info("Sending EOF");
trans.write(newBuffer(Message.CHANNEL_EOF));
if (eofGot)
sendClose();
}
} finally {
eofSent = true;
out.setClosed();
}
}
@Override
public String toString() {
return "< " + type + " channel: id=" + id + ", recipient=" + recipient + ", localWin=" + lwin + ", remoteWin="
+ rwin + " >";
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.SSHPacketHandler;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.TransportException;
import java.io.Closeable;
import java.io.InputStream;
import java.io.OutputStream;
/** A channel is the basic medium for application-layer data on top of an SSH transport. */
public interface Channel extends Closeable, SSHPacketHandler, ErrorNotifiable {
/** Direct channels are those that are initiated by us. */
interface Direct extends Channel {
/**
* Request opening this channel from remote end.
*
* @throws OpenFailException in case the channel open request was rejected
* @throws net.schmizz.sshj.connection.ConnectionException
* other connection-layer error
* @throws TransportException error writing packets etc.
*/
void open() throws OpenFailException, ConnectionException, TransportException;
}
/** Forwarded channels are those that are initiated by the server. */
interface Forwarded extends Channel {
/**
* Confirm {@code CHANNEL_OPEN} request.
*
* @throws TransportException error sending confirmation packet
*/
void confirm() throws TransportException;
/** Returns the IP of where the forwarded connection originates. */
String getOriginatorIP();
/** Returns port from which the forwarded connection originates. */
int getOriginatorPort();
/**
* Indicate rejection to remote end.
*
* @param reason indicate {@link OpenFailException.Reason reason} for rejection of the request
* @param message indicate a message for why the request is rejected
*
* @throws TransportException error sending rejection packet
*/
void reject(OpenFailException.Reason reason, String message) throws TransportException;
}
/** Close this channel. */
void close() throws TransportException, ConnectionException;
/**
* Returns whether auto-expansion of local window is set.
*
* @see #setAutoExpand(boolean)
*/
boolean getAutoExpand();
/** Returns the channel ID */
int getID();
/** Returns the {@code InputStream} for this channel. */
InputStream getInputStream();
/** Returns the maximum packet size that we have specified. */
int getLocalMaxPacketSize();
/** Returns the current local window size. */
int getLocalWinSize();
/** Returns an {@code OutputStream} for this channel. */
OutputStream getOutputStream();
/** Returns the channel ID at the remote end. */
int getRecipient();
/** Returns the maximum packet size as specified by the remote end. */
int getRemoteMaxPacketSize();
/** Returns the current remote window size. */
int getRemoteWinSize();
/** Returns the channel type identifier. */
String getType();
/** Returns whether the channel is open. */
boolean isOpen();
/**
* Sends an EOF message to the server for this channel; indicating that no more data will be sent by us. The {@code
* OutputStream} for this channel will be closed and no longer usable.
*/
void sendEOF() throws TransportException;
/**
* Set whether local window should automatically expand when data is received, irrespective of whether data has been
* read from that stream. This is useful e.g. when a remote command produces a lot of output that would fill the
* local window but you are not interested in reading from its {@code InputStream}.
*
* @param autoExpand
*/
void setAutoExpand(boolean autoExpand);
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
/**
* {@link InputStream} for channels. Can {@link #receive(byte[], int, int) receive} data into its buffer for serving to
* readers.
*/
public final class ChannelInputStream extends InputStream implements ErrorNotifiable {
private final Logger log;
private final Channel chan;
private final Transport trans;
private final Window.Local win;
private final Buffer.PlainBuffer buf;
private final byte[] b = new byte[1];
private boolean eof;
private SSHException error;
public ChannelInputStream(Channel chan, Transport trans, Window.Local win) {
log = LoggerFactory.getLogger("<< chan#" + chan.getID() + " / input stream >>");
this.chan = chan;
this.trans = trans;
this.win = win;
buf = new Buffer.PlainBuffer(chan.getLocalMaxPacketSize());
}
@Override
public int available() {
synchronized (buf) {
return buf.available();
}
}
@Override
public void close() {
eof();
}
public void eof() {
synchronized (buf) {
if (!eof) {
eof = true;
buf.notifyAll();
}
}
}
public synchronized void notifyError(SSHException error) {
this.error = error;
eof();
}
@Override
public int read() throws IOException {
synchronized (b) {
return read(b, 0, 1) == -1 ? -1 : b[0];
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
synchronized (buf) {
for (; ;) {
if (buf.available() > 0)
break;
if (eof)
if (error != null)
throw error;
else
return -1;
try {
buf.wait();
} catch (InterruptedException e) {
throw (IOException) new InterruptedIOException().initCause(e);
}
}
if (len > buf.available())
len = buf.available();
buf.readRawBytes(b, off, len);
if (buf.rpos() > win.getMaxPacketSize() && buf.available() == 0)
buf.clear();
}
if (!chan.getAutoExpand())
checkWindow();
return len;
}
public void receive(byte[] data, int offset, int len) throws ConnectionException, TransportException {
if (eof)
throw new ConnectionException("Getting data on EOF'ed stream");
synchronized (buf) {
buf.putRawBytes(data, offset, len);
buf.notifyAll();
}
win.consume(len);
if (chan.getAutoExpand())
checkWindow();
}
private void checkWindow() throws TransportException {
synchronized (win) {
final int adjustment = win.neededAdjustment();
if (adjustment > 0) {
log.info("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST to #{} for {} bytes", chan.getRecipient(), adjustment);
trans.write(new SSHPacket(Message.CHANNEL_WINDOW_ADJUST).putInt(chan.getRecipient()).putInt(adjustment));
win.expand(adjustment);
}
}
}
@Override
public String toString() {
return "< ChannelInputStream for Channel #" + chan.getID() + " >";
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.Transport;
import java.io.IOException;
import java.io.OutputStream;
/**
* {@link OutputStream} for channels. Buffers data upto the remote window's maximum packet size. Data can also be
* flushed via {@link #flush()} and is also flushed on {@link #close()}.
*/
public final class ChannelOutputStream extends OutputStream implements ErrorNotifiable {
private final Channel chan;
private Transport trans;
private final Window.Remote win;
private final SSHPacket buffer = new SSHPacket();
private final byte[] b = new byte[1];
private int bufferLength;
private boolean closed;
private SSHException error;
public ChannelOutputStream(Channel chan, Transport trans, Window.Remote win) {
this.chan = chan;
this.trans = trans;
this.win = win;
prepBuffer();
}
private void prepBuffer() {
bufferLength = 0;
buffer.rpos(5);
buffer.wpos(5);
buffer.putMessageID(Message.CHANNEL_DATA);
buffer.putInt(0); // meant to be recipient
buffer.putInt(0); // meant to be data length
}
@Override
public synchronized void write(int w) throws IOException {
b[0] = (byte) w;
write(b, 0, 1);
}
@Override
public synchronized void write(byte[] data, int off, int len) throws IOException {
checkClose();
while (len > 0) {
final int x = Math.min(len, win.getMaxPacketSize() - bufferLength);
if (x <= 0) {
flush();
continue;
}
buffer.putRawBytes(data, off, x);
bufferLength += x;
off += x;
len -= x;
}
}
public synchronized void notifyError(SSHException error) {
this.error = error;
}
private synchronized void checkClose() throws SSHException {
if (closed)
if (error != null)
throw error;
else
throw new ConnectionException("Stream closed");
}
@Override
public synchronized void close() throws IOException {
if (!closed)
try {
flush();
chan.sendEOF();
} finally {
setClosed();
}
}
public synchronized void setClosed() {
closed = true;
}
@Override
public synchronized void flush() throws IOException {
checkClose();
if (bufferLength <= 0) // No data to send
return;
putRecipientAndLength();
try {
win.waitAndConsume(bufferLength);
trans.write(buffer);
} finally {
prepBuffer();
}
}
private void putRecipientAndLength() {
final int origPos = buffer.wpos();
buffer.wpos(6);
buffer.putInt(chan.getRecipient());
buffer.putInt(bufferLength);
buffer.wpos(origPos);
}
@Override
public String toString() {
return "< ChannelOutputStream for Channel #" + chan.getID() + " >";
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.sshj.connection.ConnectionException;
public class OpenFailException extends ConnectionException {
public enum Reason {
UNKNOWN(0), ADMINISTRATIVELY_PROHIBITED(1), CONNECT_FAILED(2), UNKNOWN_CHANNEL_TYPE(3), RESOURCE_SHORTAGE(4);
public static Reason fromInt(int code) {
for (Reason rc : Reason.values())
if (rc.code == code)
return rc;
return UNKNOWN;
}
private final int code;
private Reason(int rc) {
this.code = rc;
}
public int getCode() {
return code;
}
}
private final String channelType;
private final Reason reason;
private final String message;
public OpenFailException(String channelType, int reasonCode, String message) {
super(message);
this.channelType = channelType;
this.reason = Reason.fromInt(reasonCode);
this.message = message;
}
public OpenFailException(String channelType, Reason reason, String message) {
super(message);
this.channelType = channelType;
this.reason = reason;
this.message = message;
}
public String getChannelType() {
return channelType;
}
@Override
public String getMessage() {
return message;
}
public Reason getReason() {
return reason;
}
@Override
public String toString() {
return "Opening `" + channelType + "` channel failed: " + getMessage();
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.connection.channel;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class Window {
protected final Logger log;
protected final Object lock = new Object();
protected final int maxPacketSize;
protected int size;
public Window(int chanID, String kindOfWindow, int initialWinSize, int maxPacketSize) {
log = LoggerFactory.getLogger("<< chan#" + chanID + " / " + kindOfWindow + " >>");
size = initialWinSize;
this.maxPacketSize = maxPacketSize;
}
public void expand(int inc) {
synchronized (lock) {
log.debug("Increasing by {} up to {}", inc, size);
size += inc;
lock.notifyAll();
}
}
public int getMaxPacketSize() {
return maxPacketSize;
}
public int getSize() {
return size;
}
public void consume(int dec) {
synchronized (lock) {
log.debug("Consuming by " + dec + " down to " + size);
size -= dec;
if (size < 0)
throw new SSHRuntimeException("Window consumed to below 0");
}
}
@Override
public String toString() {
return "[winSize=" + size + "]";
}
/** Controls how much data we can send before an adjustment notification from remote end is required. */
public static final class Remote extends Window {
public Remote(int chanID, int initialWinSize, int maxPacketSize) {
super(chanID, "remote win", initialWinSize, maxPacketSize);
}
public void waitAndConsume(int howMuch) throws ConnectionException {
synchronized (lock) {
while (size < howMuch) {
log.debug("Waiting, need window space for {} bytes", howMuch);
try {
lock.wait();
} catch (InterruptedException ie) {
throw new ConnectionException(ie);
}
}
consume(howMuch);
}
}
}
/** Controls how much data remote end can send before an adjustment notification from us is required. */
public static final class Local extends Window {
private final int initialSize;
private final int threshold;
public Local(int chanID, int initialWinSize, int maxPacketSize) {
super(chanID, "local win", initialWinSize, maxPacketSize);
this.initialSize = initialWinSize;
threshold = Math.min(maxPacketSize * 20, initialSize / 4);
}
public int neededAdjustment() throws TransportException {
synchronized (lock) {
return (size - threshold <= 0) ? (initialSize - size) : 0;
}
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.AbstractChannel;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.connection.channel.OpenFailException;
import net.schmizz.sshj.transport.TransportException;
import java.util.concurrent.TimeUnit;
/** Base class for direct channels whose open is initated by the client. */
public abstract class AbstractDirectChannel extends AbstractChannel implements Channel.Direct {
protected AbstractDirectChannel(String name, Connection conn) {
super(name, conn);
/*
* We expect to receive channel open confirmation/rejection and want to be able to next this packet.
*/
conn.attach(this);
}
public void open() throws ConnectionException, TransportException {
trans.write(buildOpenReq());
open.await(conn.getTimeout(), TimeUnit.SECONDS);
}
private void gotOpenConfirmation(SSHPacket buf) {
init(buf.readInt(), buf.readInt(), buf.readInt());
open.set();
}
private void gotOpenFailure(SSHPacket buf) {
open.error(new OpenFailException(getType(), buf.readInt(), buf.readString()));
finishOff();
}
protected SSHPacket buildOpenReq() {
return new SSHPacket(Message.CHANNEL_OPEN)
.putString(getType())
.putInt(getID())
.putInt(getLocalWinSize())
.putInt(getLocalMaxPacketSize());
}
@Override
protected void gotUnknown(Message cmd, SSHPacket buf) throws ConnectionException, TransportException {
switch (cmd) {
case CHANNEL_OPEN_CONFIRMATION:
gotOpenConfirmation(buf);
break;
case CHANNEL_OPEN_FAILURE:
gotOpenFailure(buf);
break;
default:
super.gotUnknown(cmd, buf);
}
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.common.StreamCopier.ErrorCallback;
import net.schmizz.sshj.connection.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ServerSocketFactory;
import java.io.Closeable;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class LocalPortForwarder {
private class DirectTCPIPChannel extends AbstractDirectChannel {
private final Socket sock;
private DirectTCPIPChannel(Connection conn, Socket sock) {
super("direct-tcpip", conn);
this.sock = sock;
}
private void start() throws IOException {
sock.setSendBufferSize(getLocalMaxPacketSize());
sock.setReceiveBufferSize(getRemoteMaxPacketSize());
final ErrorCallback closer = StreamCopier.closeOnErrorCallback(this,
new Closeable() {
public void close() throws IOException {
sock.close();
}
});
new StreamCopier("chan2soc", getInputStream(), sock.getOutputStream()) //
.bufSize(getLocalMaxPacketSize()) //
.errorCallback(closer) //
.daemon(true) //
.start();
new StreamCopier("soc2chan", sock.getInputStream(), getOutputStream()) //
.bufSize(getRemoteMaxPacketSize()) //
.errorCallback(closer) //
.daemon(true) //
.start();
}
@Override
protected SSHPacket buildOpenReq() {
return super.buildOpenReq() //
.putString(host) //
.putInt(port) //
.putString(ss.getInetAddress().getHostAddress()) //
.putInt(ss.getLocalPort());
}
}
private final Logger log = LoggerFactory.getLogger(getClass());
private final Connection conn;
private final ServerSocket ss;
private final String host;
private final int port;
public LocalPortForwarder(Connection conn, SocketAddress listeningAddr, String host, int port) throws IOException {
this(ServerSocketFactory.getDefault(), conn, listeningAddr, host, port);
}
/**
* Create a local port forwarder with specified binding ({@code listeningAddr}. It does not, however, start
* listening unless {@link #listen() explicitly told to}.
*
* @param conn {@link Connection} implementation
* @param listeningAddr {@link SocketAddress} this forwarder will listen on, if {@code null} then an ephemeral port
* and valid local address will be picked to bind the server socket
* @param host what host the SSH server will further forward to
* @param port port on {@code toHost}
*
* @throws IOException if there is an error binding on specified {@code listeningAddr}
*/
public LocalPortForwarder(ServerSocketFactory ssf, Connection conn, SocketAddress listeningAddr, String host, int port) throws IOException {
this.conn = conn;
this.host = host;
this.port = port;
this.ss = ssf.createServerSocket();
ss.setReceiveBufferSize(conn.getMaxPacketSize());
ss.bind(listeningAddr);
}
public SocketAddress getListeningAddress() {
return ss.getLocalSocketAddress();
}
/** Start listening for incoming connections and forward to remote host as a channel. */
public void listen() throws IOException {
log.info("Listening on {}", ss.getLocalSocketAddress());
Socket sock;
while (true) {
sock = ss.accept();
log.info("Got connection from {}", sock.getRemoteSocketAddress());
DirectTCPIPChannel chan = new DirectTCPIPChannel(conn, sock);
chan.open();
chan.start();
}
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.sshj.common.Buffer;
import java.util.Map;
import java.util.Map.Entry;
/** Various modes for a psuedo-terminal. They are meant to have integer parameters. */
public enum PTYMode {
/**
* Interrupt character; 255 if none. Similarly for the other characters. Not all of these characters are supported
* on all systems.
*/
VINTR(1),
/** The quit character (sends SIGQUIT signal on POSIX systems). */
VQUIT(2),
/** Erase the character to left of the cursor. */
VERASE(3),
/** Kill the current input line. */
VKILL(4),
/** End-of-file character (sends EOF from the terminal). */
VEOF(5),
/** End-of-line character in addition to carriage return and/or linefeed. */
VEOL(6),
/** Additional end-of-line character. */
VEOL2(7),
/** Continues paused output (normally control-Q). */
VSTART(8),
/** Pauses output (normally control-S). */
VSTOP(9),
/** Suspends the current program. */
VSUSP(10),
/** Another suspend character. */
VDSUSP(11),
/** Reprints the current input line. */
VREPRINT(12),
/** Erases a word left of cursor. */
VWERASE(13),
/** Enter the next character typed literally, even if it is a special character. */
VLNEXT(14),
/** Character to flush output. */
VFLUSH(15),
/** Switch to a different shell layer. */
VSWTCH(16),
/** Prints system status line (load, command, pid, etc). */
VSTATUS(17),
/** Toggles the flushing of terminal output. */
VDISCARD(18),
/** The ignore parity flag. The parameter SHOULD be 0 if this flag is FALSE, and 1 if it is TRUE. */
IGNPAR(30),
/** Mark parity and framing errors. */
PARMRK(31),
/** Enable checking of parity errors. */
INPCK(32),
/** Strip 8th bit off characters. */
ISTRIP(33),
/** Map NL into CR on input. */
INLCR(34),
/** Ignore CR on input. */
IGNCR(35),
/** Map CR to NL on input. */
ICRNL(36),
/** Translate uppercase characters to lowercase. */
IUCLC(37),
/** Enable output flow control. */
IXON(38),
/** Any char will restart after stop. */
IXANY(39),
/** Enable input flow control. */
IXOFF(40),
/** Ring bell on input queue full. */
IMAXBEL(41),
/** Enable signals INTR, QUIT, [D]SUSP. */
ISIG(50),
/** Canonicalize input lines. */
ICANON(51),
/** Enable input and output of uppercase characters by preceding their lowercase equivalents with &quot;\&quot;. */
XCASE(52),
/** Enable echoing. */
ECHO(53),
/** Visually erase chars. */
ECHOE(54),
/** Kill character discards current line. */
ECHOK(55),
/** Echo NL even if ECHO is off. */
ECHONL(56),
/** Don't flush after interrupt. */
NOFLSH(57),
/** Stop background jobs from output. */
TOSTOP(58),
/** Enable extensions. */
IEXTEN(59),
/** Echo control characters as &circ;(Char). */
ECHOCTL(60),
/** Visual erase for line kill. */
ECHOKE(61),
/** Retype pending input. */
PENDIN(62),
/** Enable output processing. */
OPOST(70),
/** Convert lowercase to uppercase. */
OLCUC(71),
/** Map NL to CR-NL. */
ONLCR(72),
/** Translate carriage return to newline (output). */
OCRNL(73),
/** Translate newline to carriage return-newline (output). */
ONOCR(74),
/** Newline performs a carriage return (output). */
ONLRET(75),
/** 7 bit mode. */
CS7(90),
/** 8 bit mode. */
CS8(91),
/** Parity enable. */
PARENB(92),
/** Odd parity, else even. */
PARODD(93),
/** Specifies the input baud rate in bits per second. */
TTY_OP_ISPEED(128),
/** Specifies the output baud rate in bits per second. */
TTY_OP_OSPEED(129);
public static byte[] encode(Map<PTYMode, Integer> modes) {
Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
for (Entry<PTYMode, Integer> entry : modes.entrySet()) {
buf.putByte(entry.getKey().getOpcode());
buf.putInt(entry.getValue());
}
buf.putByte((byte) 0);
return buf.getCompactData();
}
private final byte opcode;
private PTYMode(int opcode) {
this.opcode = (byte) opcode;
}
public byte getOpcode() {
return opcode;
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.transport.TransportException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* A {@code session} channel provides for execution of a remote {@link Command command}, {@link Shell shell} or {@link
* Subsystem subsystem}. Before this requests like starting X11 forwarding, setting environment variables, allocating a
* PTY etc. can be made.
* <p/>
* It is not legal to reuse a {@code session} channel for more than one of command, shell, or subsystem. Once one of
* these has been started this instance's API is invalid and that of the {@link Command specific} {@link Shell targets}
* {@link Subsystem returned} should be used.
*
* @see Command
* @see Shell
* @see Subsystem
*/
public interface Session extends Channel {
/** Command API. */
interface Command extends Channel {
/**
* Read from the command's {@code stderr} stream into a string (blocking).
*
* @return the commands {@code stderr} output as a string
*
* @throws IOException if error reading from the stream
*/
String getErrorAsString() throws IOException;
/** Returns the command's {@code stderr} stream. */
InputStream getErrorStream();
/**
* If the command exit violently {@link #getExitSignal() with a signal}, an error message would have been
* received and can be retrieved via this method. Otherwise, this method will return {@code null}.
*/
String getExitErrorMessage();
/**
* Returns the {@link Signal signal} if the command exit violently, or {@code null} if this information was not
* received.
*/
Signal getExitSignal();
/**
* Returns the exit status of the command if it was received, or {@code null} if this information was not
* received.
*/
Integer getExitStatus();
/**
* If the command exit violently {@link #getExitSignal() with a signal}, information about whether a core dump
* took place would have been received and can be retrieved via this method. Otherwise, this method will return
* {@code null}.
*/
Boolean getExitWasCoreDumped();
/**
* Read from the command's {@code stdout} stream into a string (blocking).
*
* @return the command's {@code stdout} output as a string
*
* @throws IOException if error reading from the stream
*/
String getOutputAsString() throws IOException;
/**
* Send a signal to the remote command.
*
* @param signal the signal
*
* @throws TransportException if error sending the signal
*/
void signal(Signal signal) throws TransportException;
}
/** Shell API. */
interface Shell extends Channel {
/**
* Whether the client can do local flow control using {@code control-S} and {@code control-Q}.
*
* @return boolean value indicating whether 'client can do', or {@code null} if no such information was
* received
*/
Boolean canDoFlowControl();
/**
* Sends a window dimension change message.
*
* @param cols terminal width, columns
* @param rows terminal height, rows
* @param width terminal width, pixels
* @param height terminal height, pixels
*
* @throws TransportException
*/
void changeWindowDimensions(int cols, int rows, int width, int height) throws TransportException;
/** Returns the shell's {@code stderr} stream. */
InputStream getErrorStream();
/**
* Send a signal.
*
* @param signal the signal
*
* @throws TransportException if error sending the signal
*/
void signal(Signal signal) throws TransportException;
}
/** Subsystem API. */
interface Subsystem extends Channel {
Integer getExitStatus();
}
/**
* Allocates a default PTY. The default PTY is {@code "vt100"} with the echo modes disabled.
*
* @throws net.schmizz.sshj.connection.ConnectionException
*
* @throws TransportException
*/
void allocateDefaultPTY() throws ConnectionException, TransportException;
/**
* Allocate a psuedo-terminal for this session.
* <p/>
* {@code 0} dimension parameters will be ignored by the server.
*
* @param term {@code TERM} environment variable value (e.g., {@code vt100})
* @param cols terminal width, cols (e.g., 80)
* @param rows terminal height, rows (e.g., 24)
* @param width terminal width, pixels (e.g., 640)
* @param height terminal height, pixels (e.g., 480)
* @param modes
*
* @throws ConnectionException
* @throws TransportException
*/
void allocatePTY(String term, int cols, int rows, int width, int height, Map<PTYMode, Integer> modes)
throws ConnectionException, TransportException;
/**
* Execute a remote command.
*
* @param command
*
* @return {@link Command} instance which should now be used
*
* @throws ConnectionException if the request to execute the command failed
* @throws TransportException if there is an error sending the request
*/
Command exec(String command) throws ConnectionException, TransportException;
/**
* Request X11 forwarding.
*
* @param authProto X11 authentication protocol name
* @param authCookie X11 authentication cookie
* @param screen X11 screen number
*
* @throws ConnectionException if the request failed
* @throws TransportException if there was an error sending the request
*/
void reqX11Forwarding(String authProto, String authCookie, int screen) throws ConnectionException,
TransportException;
/**
* Set an enviornment variable.
*
* @param name name of the variable
* @param value value to set
*
* @throws ConnectionException if the request failed
* @throws TransportException if there was an error sending the request
*/
void setEnvVar(String name, String value) throws ConnectionException, TransportException;
/**
* Request a shell.
*
* @return {@link Shell} instance which should now be used
*
* @throws ConnectionException if the request failed
* @throws TransportException if there was an error sending the request
*/
Shell startShell() throws ConnectionException, TransportException;
/**
* Request a subsystem.
*
* @param name subsystem name
*
* @return {@link Subsystem} instance which should now be used
*
* @throws ConnectionException if the request failed
* @throws TransportException if there was an error sending the request
*/
Subsystem startSubsystem(String name) throws ConnectionException, TransportException;
}

View File

@@ -0,0 +1,219 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.ChannelInputStream;
import net.schmizz.sshj.transport.TransportException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/** {@link Session} implementation. */
public class
SessionChannel extends AbstractDirectChannel implements Session, Session.Command, Session.Shell,
Session.Subsystem {
private Integer exitStatus;
private Signal exitSignal;
private Boolean wasCoreDumped;
private String exitErrMsg;
private Boolean canDoFlowControl;
private ChannelInputStream err = new ChannelInputStream(this, trans, lwin);
public SessionChannel(Connection conn) {
super("session", conn);
}
public void allocateDefaultPTY() throws ConnectionException, TransportException {
// TODO FIXME (maybe?): These modes were originally copied from what SSHD was doing;
// and then the echo modes were set to 0 to better serve the PTY example.
// Not sure what default PTY modes should be.
final Map<PTYMode, Integer> modes = new HashMap<PTYMode, Integer>();
modes.put(PTYMode.ISIG, 1);
modes.put(PTYMode.ICANON, 1);
modes.put(PTYMode.ECHO, 0);
modes.put(PTYMode.ECHOE, 0);
modes.put(PTYMode.ECHOK, 0);
modes.put(PTYMode.ECHONL, 0);
modes.put(PTYMode.NOFLSH, 0);
allocatePTY("vt100", 0, 0, 0, 0, modes);
}
public void allocatePTY(String term, int cols, int rows, int width, int height, Map<PTYMode, Integer> modes)
throws ConnectionException, TransportException {
sendChannelRequest(
"pty-req",
true,
new Buffer.PlainBuffer()
.putString(term)
.putInt(cols)
.putInt(rows)
.putInt(width)
.putInt(height)
.putBytes(PTYMode.encode(modes))
).await(conn.getTimeout(), TimeUnit.SECONDS);
}
public Boolean canDoFlowControl() {
return canDoFlowControl;
}
public void changeWindowDimensions(int cols, int rows, int width, int height) throws TransportException {
sendChannelRequest(
"pty-req",
false,
new Buffer.PlainBuffer()
.putInt(cols)
.putInt(rows)
.putInt(width)
.putInt(height)
);
}
public Command exec(String command) throws ConnectionException, TransportException {
log.info("Will request to exec `{}`", command);
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command)).await(conn.getTimeout(), TimeUnit.SECONDS);
return this;
}
public String getErrorAsString() throws IOException {
return StreamCopier.copyStreamToString(err);
}
public InputStream getErrorStream() {
return err;
}
public String getExitErrorMessage() {
return exitErrMsg;
}
public Signal getExitSignal() {
return exitSignal;
}
public Integer getExitStatus() {
return exitStatus;
}
public String getOutputAsString() throws IOException {
return StreamCopier.copyStreamToString(getInputStream());
}
@Override
public void handleRequest(String req, SSHPacket buf) throws ConnectionException, TransportException {
if ("xon-xoff".equals(req))
canDoFlowControl = buf.readBoolean();
else if ("exit-status".equals(req))
exitStatus = buf.readInt();
else if ("exit-signal".equals(req)) {
exitSignal = Signal.fromString(buf.readString());
wasCoreDumped = buf.readBoolean(); // core dumped
exitErrMsg = buf.readString();
sendClose();
} else
super.handleRequest(req, buf);
}
public void reqX11Forwarding(String authProto, String authCookie, int screen) throws ConnectionException,
TransportException {
sendChannelRequest(
"x11-req",
true,
new Buffer.PlainBuffer()
.putBoolean(false)
.putString(authProto)
.putString(authCookie)
.putInt(screen)
).await(conn.getTimeout(), TimeUnit.SECONDS);
}
public void setEnvVar(String name, String value) throws ConnectionException, TransportException {
sendChannelRequest("env", true, new Buffer.PlainBuffer().putString(name).putString(value)).await(conn.getTimeout(), TimeUnit.SECONDS);
}
public void signal(Signal sig) throws TransportException {
sendChannelRequest("signal", false, new Buffer.PlainBuffer().putString(sig.toString()));
}
public Shell startShell() throws ConnectionException, TransportException {
sendChannelRequest("shell", true, null).await(conn.getTimeout(), TimeUnit.SECONDS);
return this;
}
public Subsystem startSubsystem(String name) throws ConnectionException, TransportException {
log.info("Will request `{}` subsystem", name);
sendChannelRequest("subsystem", true, new Buffer.PlainBuffer().putString(name)).await(conn.getTimeout(), TimeUnit.SECONDS);
return this;
}
public Boolean getExitWasCoreDumped() {
return wasCoreDumped;
}
@Override
protected void closeAllStreams() {
IOUtils.closeQuietly(err);
super.closeAllStreams();
}
@Override
protected void eofInputStreams() {
err.eof(); // also close the stderr stream
super.eofInputStreams();
}
@Override
protected void gotExtendedData(int dataTypeCode, SSHPacket buf) throws ConnectionException, TransportException {
if (dataTypeCode == 1)
receiveInto(err, buf);
else
super.gotExtendedData(dataTypeCode, buf);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.connection.channel.direct;
import net.schmizz.sshj.common.SSHException;
/** A factory interface for creating SSH {@link Session session channels}. */
public interface SessionFactory {
/**
* Opens a {@code session} channel. The returned {@link Session} instance allows {@link Session#exec(String)
* executing a remote command}, {@link Session#startSubsystem(String) starting a subsystem}, or {@link
* Session#startShell() starting a shell}.
*
* @return the opened {@code session} channel
*
* @throws SSHException
* @see {@link Session}
*/
Session startSession() throws SSHException;
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
/** Various signals that may be sent or received. The signals are from POSIX and simply miss the {@code "SIG_"} prefix. */
public enum Signal {
ABRT("ABRT"), ALRM("ALRM"), FPE("FPE"), HUP("HUP"), ILL("ILL"), INT("INT"), KILL("KILL"), PIPE("PIPE"), QUIT(
"QUIT"), SEGV("SEGV"), TERM("TERM"), USR1("USR1"), USR2("USR2"), UNKNOWN("UNKNOWN");
/**
* Create from the string representation used when the signal is received as part of an SSH packet.
*
* @param name name of the signal as received
*
* @return the enum constant inferred
*/
public static Signal fromString(String name) {
for (Signal sig : Signal.values())
if (sig.name.equals(name))
return sig;
return UNKNOWN;
}
private final String name;
private Signal(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.forwarded;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.channel.AbstractChannel;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.connection.channel.OpenFailException.Reason;
import net.schmizz.sshj.transport.TransportException;
/** Base class for forwarded channels whose open is initiated by the server. */
public abstract class AbstractForwardedChannel extends AbstractChannel implements Channel.Forwarded {
protected final String origIP;
protected final int origPort;
/*
* First 2 args are standard; the others can be parsed from a CHANNEL_OPEN packet.
*/
protected AbstractForwardedChannel(String type, Connection conn, int recipient, int remoteWinSize,
int remoteMaxPacketSize, String origIP, int origPort) {
super(type, conn);
this.origIP = origIP;
this.origPort = origPort;
init(recipient, remoteWinSize, remoteMaxPacketSize);
}
public void confirm() throws TransportException {
log.info("Confirming `{}` channel #{}", getType(), getID());
// Must ensure channel is attached before confirming, data could start coming in immediately!
conn.attach(this);
trans.write(newBuffer(Message.CHANNEL_OPEN_CONFIRMATION)
.putInt(getID())
.putInt(getLocalWinSize())
.putInt(getLocalMaxPacketSize()));
open.set();
}
public void reject(Reason reason, String message) throws TransportException {
log.info("Rejecting `{}` channel: {}", getType(), message);
conn.sendOpenFailure(getRecipient(), reason, message);
}
public String getOriginatorIP() {
return origIP;
}
public int getOriginatorPort() {
return origPort;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.forwarded;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.connection.channel.OpenFailException;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/** Base class for {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener}'s. */
public abstract class AbstractForwardedChannelOpener implements ForwardedChannelOpener {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final String chanType;
protected final Connection conn;
protected AbstractForwardedChannelOpener(String chanType, Connection conn) {
this.chanType = chanType;
this.conn = conn;
}
public String getChannelType() {
return chanType;
}
/*
* Calls the listener with the new channel in a separate thread.
*/
protected void callListener(final ConnectListener listener, final Channel.Forwarded chan) {
new Thread() {
{
setName("ConnectListener");
}
@Override
public void run() {
try {
listener.gotConnect(chan);
} catch (IOException logged) {
log.warn("In callback to {}: {}", listener, logged);
if (chan.isOpen())
IOUtils.closeQuietly(chan);
else
try {
chan.reject(OpenFailException.Reason.CONNECT_FAILED, "");
} catch (TransportException cantdonthn) {
log.warn("Error rejecting {}: {}", chan, cantdonthn);
}
}
}
}.start();
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.connection.channel.forwarded;
import net.schmizz.sshj.connection.channel.Channel;
import java.io.IOException;
/** A connect listener is just that: it listens for new forwarded channels and can be delegated charge of them. */
public interface ConnectListener {
/**
* Notify this listener of a new forwarded channel. An implementation should firstly {@link
* net.schmizz.sshj.connection.channel.Channel.Forwarded#confirm() confirm} or {@link
* net.schmizz.sshj.connection.channel.Channel.Forwarded#reject() reject} that channel.
*
* @param chan the {@link net.schmizz.sshj.connection.channel.Channel.Forwarded forwarded channel}
*
* @throws java.io.IOException
*/
void gotConnect(Channel.Forwarded chan) throws IOException;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.connection.channel.forwarded;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.TransportException;
/** Takes care of handling {@code SSH_MSG_CHANNEL_OPEN} requests for forwarded channels of a specific type. */
public interface ForwardedChannelOpener {
/** Returns the name of the channel type this opener can next. */
String getChannelType();
/**
* Delegates a {@code SSH_MSG_CHANNEL_OPEN} request for the channel type claimed by this opener.
*
* @param buf {@link net.schmizz.sshj.common.SSHPacket} containg the request except for the message identifier and
* channel type field
*/
void handleOpen(SSHPacket buf) throws ConnectionException, TransportException;
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.connection.channel.forwarded;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.OpenFailException;
import net.schmizz.sshj.transport.TransportException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/** Handles remote port forwarding. */
public class RemotePortForwarder extends AbstractForwardedChannelOpener {
/**
* Represents a particular forwarding. From RFC 4254, s. 7.1
* <p/>
* <pre>
* The 'address to bind' and 'port number to bind' specify the IP
* address (or domain name) and port on which connections for forwarding
* are to be accepted. Some strings used for 'address to bind' have
* special-case semantics.
* <p/>
* o &quot;&quot; means that connections are to be accepted on all protocol
* families supported by the SSH implementation.
* <p/>
* o &quot;0.0.0.0&quot; means to listen on all IPv4 addresses.
* <p/>
* o &quot;::&quot; means to listen on all IPv6 addresses.
* <p/>
* o &quot;localhost&quot; means to listen on all protocol families supported by
* the SSH implementation on loopback addresses only ([RFC3330] and
* [RFC3513]).
* <p/>
* o &quot;127.0.0.1&quot; and &quot;::1&quot; indicate listening on the loopback
* interfaces for IPv4 and IPv6, respectively.
* </pre>
*/
public static final class Forward {
private final String address;
private int port;
/**
* Creates this forward with address as {@code ""} and specified {@code port}.
*
* @param port
*/
public Forward(int port) {
this("", port);
}
/**
* Creates this forward with specified {@code address} and port as {@code 0}.
*
* @param address
*/
public Forward(String address) {
this(address, 0);
}
/**
* Creates this forward with specified {@code address} and {@code port} number.
*
* @param address address to bind
* @param port port number
*/
public Forward(String address, int port) {
this.address = address;
this.port = port;
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass())
return false;
Forward other = (Forward) obj;
return address.equals(other.address) && port == other.port;
}
/** Returns the address represented by this forward. */
public String getAddress() {
return address;
}
/** Returns the port represented by this forward. */
public int getPort() {
return port;
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
return address + ":" + port;
}
}
/** A {@code forwarded-tcpip} channel. */
public static class ForwardedTCPIPChannel extends AbstractForwardedChannel {
public static final String TYPE = "forwarded-tcpip";
private final Forward fwd;
public ForwardedTCPIPChannel(Connection conn, int recipient, int remoteWinSize, int remoteMaxPacketSize,
Forward fwd, String origIP, int origPort) throws TransportException {
super(TYPE, conn, recipient, remoteWinSize, remoteMaxPacketSize, origIP, origPort);
this.fwd = fwd;
}
/** Returns the forwarding from which this channel originates. */
public Forward getParentForward() {
return fwd;
}
}
protected static final String PF_REQ = "tcpip-forward";
protected static final String PF_CANCEL = "cancel-tcpip-forward";
protected final Map<Forward, ConnectListener> listeners = new ConcurrentHashMap<Forward, ConnectListener>();
public RemotePortForwarder(Connection conn) {
super(ForwardedTCPIPChannel.TYPE, conn);
}
/**
* Request forwarding from the remote host on the specified {@link Forward}. Forwarded connections will be handled
* by supplied {@code listener}.
* <p/>
* If {@code forward} specifies as 0, the returned forward will have the correct port number as informed by remote
* host.
*
* @param forward the {@link Forward} to put in place on remote host
* @param listener the listener which will next forwarded connection
*
* @return the {@link Forward} which was put into place on the remote host
*
* @throws net.schmizz.sshj.connection.ConnectionException
* if there is an error requesting the forwarding
*/
public Forward bind(Forward forward, ConnectListener listener) throws ConnectionException, TransportException {
SSHPacket reply = req(PF_REQ, forward);
if (forward.port == 0)
forward.port = reply.readInt();
log.info("Remote end listening on {}", forward);
listeners.put(forward, listener);
return forward;
}
/**
* Request cancellation of some forwarding.
*
* @param forward the forward which is being cancelled
*
* @throws ConnectionException if there is an error with the cancellation request
*/
public void cancel(Forward forward) throws ConnectionException, TransportException {
try {
req(PF_CANCEL, forward);
} finally {
listeners.remove(forward);
}
}
protected SSHPacket req(String reqName, Forward forward) throws ConnectionException, TransportException {
return conn.sendGlobalRequest(PF_REQ, true, new Buffer.PlainBuffer() //
.putString(forward.address) //
.putInt(forward.port)) //
.get(conn.getTimeout(), TimeUnit.SECONDS);
}
/** Returns the active forwards. */
public Set<Forward> getActiveForwards() {
return listeners.keySet();
}
/**
* Internal API. Creates a {@link ForwardedTCPIPChannel} from the {@code CHANNEL_OPEN} request and calls associated
* {@code ConnectListener} for that forward in a separate thread.
*/
public void handleOpen(SSHPacket buf) throws ConnectionException, TransportException {
ForwardedTCPIPChannel chan = new ForwardedTCPIPChannel(conn, buf.readInt(), buf.readInt(), buf.readInt(), //
new Forward(buf.readString(), buf.readInt()), //
buf.readString(), buf.readInt());
if (listeners.containsKey(chan.getParentForward()))
callListener(listeners.get(chan.getParentForward()), chan);
else
chan.reject(OpenFailException.Reason.ADMINISTRATIVELY_PROHIBITED, "Forwarding was not requested on `"
+ chan.getParentForward() + "`");
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.connection.channel.forwarded;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.common.StreamCopier.ErrorCallback;
import net.schmizz.sshj.connection.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
/**
* A {@link net.schmizz.sshj.connection.channel.forwarded.ConnectListener} that forwards what is received over the
* channel to a socket and vice-versa.
*/
public class SocketForwardingConnectListener implements ConnectListener {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final SocketAddress addr;
/** Create with a {@link SocketAddress} this listener will forward to. */
public SocketForwardingConnectListener(SocketAddress addr) {
this.addr = addr;
}
/** On connect, confirm the channel and start forwarding. */
public void gotConnect(Channel.Forwarded chan) throws IOException {
log.info("New connection from " + chan.getOriginatorIP() + ":" + chan.getOriginatorPort());
final Socket sock = new Socket();
sock.setSendBufferSize(chan.getLocalMaxPacketSize());
sock.setReceiveBufferSize(chan.getRemoteMaxPacketSize());
sock.connect(addr);
// ok so far -- could connect, let's confirm the channel
chan.confirm();
final ErrorCallback closer = StreamCopier.closeOnErrorCallback(chan, new Closeable() {
public void close() throws IOException {
sock.close();
}
});
new StreamCopier("soc2chan", sock.getInputStream(), chan.getOutputStream())
.bufSize(chan.getRemoteMaxPacketSize())
.errorCallback(closer)
.daemon(true)
.start();
new StreamCopier("chan2soc", chan.getInputStream(), sock.getOutputStream())
.bufSize(chan.getLocalMaxPacketSize())
.errorCallback(closer)
.daemon(true)
.start();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.connection.channel.forwarded;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.TransportException;
/**
* Handles forwarded {@code x11} channels. The actual request to forward X11 should be made from the specific {@link
* net.schmizz.sshj.connection.channel.direct.Session}.
*/
public class X11Forwarder extends AbstractForwardedChannelOpener {
/** An {@code x11} forwarded channel. */
public static class X11Channel extends AbstractForwardedChannel {
public static final String TYPE = "x11";
public X11Channel(Connection conn, int recipient, int remoteWinSize, int remoteMaxPacketSize, String origIP,
int origPort) {
super(TYPE, conn, recipient, remoteWinSize, remoteMaxPacketSize, origIP, origPort);
}
}
private final ConnectListener listener;
/**
* Creates and registers itself with {@code conn}.
*
* @param conn connection layer
* @param listener listener which will be delegated {@link X11Channel}'s to next
*/
public X11Forwarder(Connection conn, ConnectListener listener) {
super(X11Channel.TYPE, conn);
this.listener = listener;
}
/** Internal API */
public void handleOpen(SSHPacket buf) throws ConnectionException, TransportException {
callListener(listener, new X11Channel(conn, buf.readInt(), buf.readInt(), buf.readInt(), buf.readString(), buf
.readInt()));
}
/** Stop handling {@code x11} channel open requests. De-registers itself with connection layer. */
public void stop() {
conn.forget(this);
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.xfer.FilePermission;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public final class FileAttributes {
public static enum Flag {
SIZE(0x00000001), UIDGID(0x00000002), MODE(0x00000004), ACMODTIME(0x00000008), EXTENDED(0x80000000);
private final int flag;
private Flag(int flag) {
this.flag = flag;
}
public boolean isSet(int mask) {
return (mask & flag) == flag;
}
public int get() {
return flag;
}
}
private final FileMode mode;
private final int mask;
private final long size;
private final int uid;
private final int gid;
private final long atime;
private final long mtime;
private final Map<String, String> ext = new HashMap<String, String>();
public FileAttributes() {
size = atime = mtime = uid = gid = mask = 0;
mode = new FileMode(0);
}
public FileAttributes(int mask, long size, int uid, int gid, FileMode mode, long atime, long mtime,
Map<String, String> ext) {
this.mask = mask;
this.size = size;
this.uid = uid;
this.gid = gid;
this.mode = mode;
this.atime = atime;
this.mtime = mtime;
this.ext.putAll(ext);
}
public boolean has(Flag flag) {
return flag.isSet(mask);
}
public long getSize() {
return size;
}
public int getUID() {
return uid;
}
public int getGID() {
return gid;
}
public FileMode getMode() {
return mode;
}
public Set<FilePermission> getPermissions() {
return mode.getPermissions();
}
public FileMode.Type getType() {
return mode.getType();
}
public long getAtime() {
return atime;
}
public long getMtime() {
return mtime;
}
public String getExtended(String type) {
return ext.get(type);
}
public byte[] toBytes() {
Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
buf.putInt(mask);
if (has(Flag.SIZE))
buf.putUINT64(size);
if (has(Flag.UIDGID)) {
buf.putInt(uid);
buf.putInt(gid);
}
if (has(Flag.MODE))
buf.putInt(mode.getMask());
if (has(Flag.ACMODTIME)) {
buf.putInt(atime);
buf.putInt(mtime);
}
if (has(Flag.EXTENDED)) {
buf.putInt(ext.size());
for (Entry<String, String> entry : ext.entrySet()) {
buf.putString(entry.getKey());
buf.putString(entry.getValue());
}
}
return buf.getCompactData();
}
public static class Builder {
private int mask;
private long size;
private long atime;
private long mtime;
private FileMode mode = new FileMode(0);
private int uid;
private int gid;
private final Map<String, String> ext = new HashMap<String, String>();
public Builder withSize(long size) {
mask |= Flag.SIZE.get();
this.size = size;
return this;
}
public Builder withAtimeMtime(long atime, long mtime) {
mask |= Flag.ACMODTIME.get();
this.atime = atime;
this.mtime = mtime;
return this;
}
public Builder withUIDGID(int uid, int gid) {
mask |= Flag.UIDGID.get();
this.uid = uid;
this.gid = gid;
return this;
}
public Builder withPermissions(Set<FilePermission> perms) {
mask |= Flag.MODE.get();
this.mode = new FileMode(FilePermission.toMask(perms));
return this;
}
public Builder withPermissions(int perms) {
mask |= Flag.MODE.get();
this.mode = new FileMode(perms);
return this;
}
public Builder withExtended(String type, String data) {
mask |= Flag.EXTENDED.get();
ext.put(type, data);
return this;
}
public Builder withExtended(Map<String, String> ext) {
mask |= Flag.EXTENDED.get();
this.ext.putAll(ext);
return this;
}
public FileAttributes build() {
return new FileAttributes(mask, size, uid, gid, mode, atime, mtime, ext);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
if (has(Flag.SIZE))
sb.append("size=").append(size).append(";");
if (has(Flag.UIDGID))
sb.append("uid=").append(size).append(",gid=").append(gid).append(";");
if (has(Flag.MODE))
sb.append("mode=").append(mode.toString()).append(";");
if (has(Flag.ACMODTIME))
sb.append("atime=").append(atime).append(",mtime=").append(mtime).append(";");
if (has(Flag.EXTENDED))
sb.append("ext=").append(ext);
sb.append("]");
return sb.toString();
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.xfer.FilePermission;
import java.util.Collections;
import java.util.Set;
public class FileMode {
public static enum Type {
/** block special */
BLOCK_SPECIAL(0060000),
/** character special */
CHAR_SPECIAL(0020000),
/** FIFO special */
FIFO_SPECIAL(0010000),
/** socket special */
SOCKET_SPECIAL(0140000),
/** regular */
REGULAR(0100000),
/** directory */
DIRECTORY(0040000),
/** symbolic link */
SYMKLINK(0120000),
/** unknown */
UNKNOWN(0);
private final int val;
private Type(int val) {
this.val = val;
}
public static Type fromMask(int mask) {
for (Type t : Type.values())
if (t.val == mask)
return t;
return UNKNOWN;
}
public static int toMask(Type t) {
return t.val;
}
}
private final int mask;
private final Type type;
private final Set<FilePermission> perms;
public FileMode(int mask) {
this.mask = mask;
this.type = Type.fromMask(getTypeMask());
this.perms = FilePermission.fromMask(getPermissionsMask());
}
public int getMask() {
return mask;
}
public int getTypeMask() {
return mask & 0770000;
}
public int getPermissionsMask() {
return mask & 07777;
}
public Type getType() {
return type;
}
public Set<FilePermission> getPermissions() {
return Collections.unmodifiableSet(perms);
}
@Override
public String toString() {
return "[mask=" + Integer.toOctalString(mask) + "]";
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import java.util.Set;
public enum OpenMode {
/** Open the file for reading. */
READ(0x00000001),
/**
* Open the file for writing. If both this and {@link OpenMode#READ} are specified, the file is opened for both
* reading and writing.
*/
WRITE(0x00000002),
/** Force all writes to append data at the end of the file. */
APPEND(0x00000004),
/**
* If this flag is specified, then a new file will be created if one does not already exist (if {@link
* OpenMode#TRUNC} is specified, the new file will be truncated to zero length if it previously exists).
*/
CREAT(0x00000008),
/**
* Forces an existing file with the same name to be truncated to zero length when creating a file by specifying
* {@link OpenMode#CREAT}. {@link OpenMode#CREAT} MUST also be specified if this flag is used.
*/
TRUNC(0x00000010),
/**
* Causes the request to fail if the named file already exists. {@link OpenMode#CREAT} MUST also be specified if
* this flag is used.
*/
EXCL(0x00000020);
private final int pflag;
private OpenMode(int pflag) {
this.pflag = pflag;
}
public static int toMask(Set<OpenMode> modes) {
int mask = 0;
for (OpenMode m : modes)
mask |= m.pflag;
return mask;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PacketReader extends Thread {
/** Logger */
private final Logger log = LoggerFactory.getLogger(getClass());
private final InputStream in;
private final Map<Long, Future<Response, SFTPException>> futures = new ConcurrentHashMap<Long, Future<Response, SFTPException>>();
private final SFTPPacket<Response> packet = new SFTPPacket<Response>();
private final byte[] lenBuf = new byte[4];
public PacketReader(InputStream in) {
this.in = in;
setName("sftp reader");
}
private void readIntoBuffer(byte[] buf, int off, int len) throws IOException {
int count = 0;
int read = 0;
while (count < len && ((read = in.read(buf, off + count, len - count)) != -1))
count += read;
if (read == -1)
throw new SFTPException("EOF while reading packet");
}
private int getPacketLength() throws IOException {
readIntoBuffer(lenBuf, 0, lenBuf.length);
return (int) (lenBuf[0] << 24 & 0xff000000L
| lenBuf[1] << 16 & 0x00ff0000L
| lenBuf[2] << 8 & 0x0000ff00L
| lenBuf[3] & 0x000000ffL
);
}
public SFTPPacket<Response> readPacket() throws IOException {
int len = getPacketLength();
packet.rpos(0);
packet.wpos(0);
packet.ensureCapacity(len);
readIntoBuffer(packet.array(), 0, len);
packet.wpos(len);
return packet;
}
@Override
public void run() {
try {
while (true) {
readPacket();
handle();
}
} catch (IOException e) {
for (Future<Response, SFTPException> future : futures.values())
future.error(e);
}
}
public void handle() throws SFTPException {
Response resp = new Response(packet);
Future<Response, SFTPException> future = futures.remove(resp.getRequestID());
log.debug("Received {} packet", resp.getType());
if (future == null)
throw new SFTPException("Received [" + resp.readType() + "] response for request-id " + resp.getRequestID()
+ ", no such request was made");
else
future.set(resp);
}
public void expectResponseTo(Request req) {
futures.put(req.getRequestID(), req.getResponseFuture());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
public enum PacketType {
UNKNOWN(0),
INIT(1),
VERSION(2),
OPEN(3),
CLOSE(4),
READ(5),
WRITE(6),
LSTAT(7),
FSTAT(8),
SETSTAT(9),
FSETSTAT(10),
OPENDIR(11),
READDIR(12),
REMOVE(13),
MKDIR(14),
RMDIR(15),
REALPATH(16),
STAT(17),
RENAME(18),
READLINK(19),
SYMLINK(20),
STATUS(101),
HANDLE(102),
DATA(103),
NAME(104),
ATTRS(105),
EXTENDED(200),
EXTENDED_REPLY(201);
private final byte b;
private static final PacketType[] cache = new PacketType[256];
static {
for (PacketType t : PacketType.values())
if (cache[t.toByte() & 0xff] == null)
cache[t.toByte() & 0xff] = t;
}
private PacketType(int b) {
this.b = (byte) b;
}
public static PacketType fromByte(byte b) {
return cache[b & 0xff];
}
public byte toByte() {
return b;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
class PathComponents {
public static String adjustForParent(String parent, String path) {
return (path.startsWith("/")) ? path // Absolute path, nothing to adjust
: (parent + (parent.endsWith("/") ? "" : "/") + path); // Relative path
}
private static String trimFinalSlash(String path) {
return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
}
private final String parent;
private final String name;
private final String path;
public PathComponents(String parent, String name) {
this.parent = parent;
this.name = name;
this.path = adjustForParent(parent, name);
}
public String getParent() {
return parent;
}
public String getName() {
return name;
}
public String getPath() {
return path;
}
@Override
public boolean equals(Object o) {
if (o instanceof PathComponents) {
final PathComponents that = (PathComponents) o;
return (trimFinalSlash(path).equals(trimFinalSlash(that.path)));
}
return false;
}
@Override
public int hashCode() {
return trimFinalSlash(path).hashCode();
}
@Override
public String toString() {
return "[parent=" + parent + "; name=" + name + "; path=" + path + "]";
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import java.io.IOException;
class PathHelper {
private final SFTPEngine sftp;
private String dotDir;
public PathHelper(SFTPEngine sftp) {
this.sftp = sftp;
}
public PathComponents getComponents(String path) throws IOException {
if (path.isEmpty() || path.equals("."))
return getComponents(getDotDir());
final int lastSlash = path.lastIndexOf("/");
if (lastSlash == -1)
if (path.equals(".."))
return getComponents(canon(path));
else
return new PathComponents(getDotDir(), path);
final String name = path.substring(lastSlash + 1);
if (name.equals(".") || name.equals(".."))
return getComponents(canon(path));
else {
final String parent = path.substring(0, lastSlash);
return new PathComponents(parent, name);
}
}
private String getDotDir() throws IOException {
return (dotDir != null) ? dotDir : (dotDir = canon("."));
}
private String canon(String path) throws IOException {
return sftp.canonicalize(path);
}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
public class RandomAccessRemoteFile implements DataInput, DataOutput {
private final byte[] singleByte = new byte[1];
private final RemoteFile rf;
private long fp;
public RandomAccessRemoteFile(RemoteFile rf) {
this.rf = rf;
}
public long getFilePointer() {
return fp;
}
public void seek(long fp) {
this.fp = fp;
}
public int read() throws IOException {
return read(singleByte, 0, 1) == -1 ? -1 : singleByte[0];
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
final int count = rf.read(fp, b, off, len);
fp += count;
return count;
}
public boolean readBoolean() throws IOException {
final int ch = read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
public byte readByte() throws IOException {
final int ch = this.read();
if (ch < 0)
throw new EOFException();
return (byte) (ch);
}
public char readChar() throws IOException {
final int ch1 = this.read();
final int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (char) ((ch1 << 8) + ch2);
}
public double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
public float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public void readFully(byte[] b, int off, int len) throws IOException {
int n = 0;
do {
int count = read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
} while (n < len);
}
public int readInt() throws IOException {
final int ch1 = read();
final int ch2 = read();
final int ch3 = read();
final int ch4 = read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
}
public String readLine() throws IOException {
StringBuffer input = new StringBuffer();
int c = -1;
boolean eol = false;
while (!eol)
switch (c = read()) {
case -1:
case '\n':
eol = true;
break;
case '\r':
eol = true;
long cur = getFilePointer();
if ((read()) != '\n')
seek(cur);
break;
default:
input.append((char) c);
break;
}
if ((c == -1) && (input.length() == 0))
return null;
return input.toString();
}
public long readLong() throws IOException {
return ((long) (readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
}
public short readShort() throws IOException {
final int ch1 = this.read();
final int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (short) ((ch1 << 8) + ch2);
}
public String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
public int readUnsignedByte() throws IOException {
final int ch = this.read();
if (ch < 0)
throw new EOFException();
return ch;
}
public int readUnsignedShort() throws IOException {
final int ch1 = this.read();
final int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (ch1 << 8) + ch2;
}
public int skipBytes(int n) throws IOException {
if (n <= 0)
return 0;
final long pos = getFilePointer();
final long len = rf.length();
long newpos = pos + n;
if (newpos > len)
newpos = len;
seek(newpos);
/* return the actual number of bytes skipped */
return (int) (newpos - pos);
}
public void write(int i) throws IOException {
singleByte[0] = (byte) i;
write(singleByte);
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
rf.write(fp, b, off, len);
fp += (len - off);
}
public void writeBoolean(boolean v) throws IOException {
write(v ? 1 : 0);
}
public void writeByte(int v) throws IOException {
write(v);
}
public void writeBytes(String s) throws IOException {
final byte[] b = s.getBytes();
write(b, 0, b.length);
}
public void writeChar(int v) throws IOException {
write((v >>> 8) & 0xFF);
write(v & 0xFF);
}
public void writeChars(String s) throws IOException {
final int clen = s.length();
final int blen = 2 * clen;
final byte[] b = new byte[blen];
final char[] c = new char[clen];
s.getChars(0, clen, c, 0);
for (int i = 0, j = 0; i < clen; i++) {
b[j++] = (byte) (c[i] >>> 8);
b[j++] = (byte) c[i];
}
write(b, 0, blen);
}
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
public void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
public void writeInt(int v) throws IOException {
write((v >>> 24) & 0xFF);
write((v >>> 16) & 0xFF);
write((v >>> 8) & 0xFF);
write(v & 0xFF);
}
public void writeLong(long v) throws IOException {
write((int) (v >>> 56) & 0xFF);
write((int) (v >>> 48) & 0xFF);
write((int) (v >>> 40) & 0xFF);
write((int) (v >>> 32) & 0xFF);
write((int) (v >>> 24) & 0xFF);
write((int) (v >>> 16) & 0xFF);
write((int) (v >>> 8) & 0xFF);
write((int) v & 0xFF);
}
public void writeShort(int v) throws IOException {
write((v >>> 8) & 0xFF);
write(v & 0xFF);
}
public void writeUTF(String str) throws IOException {
final DataOutputStream dos = new DataOutputStream(rf.new RemoteFileOutputStream(fp));
try {
dos.writeUTF(str);
} finally {
dos.close();
}
fp += dos.size();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.sftp.Response.StatusCode;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class RemoteDirectory extends RemoteResource {
public RemoteDirectory(Requester requester, String path, String handle) {
super(requester, path, handle);
}
public List<RemoteResourceInfo> scan(RemoteResourceFilter filter) throws IOException {
List<RemoteResourceInfo> rri = new LinkedList<RemoteResourceInfo>();
loop:
for (; ;) {
Response res = requester.doRequest(newRequest(PacketType.READDIR));
switch (res.getType()) {
case NAME:
final int count = res.readInt();
for (int i = 0; i < count; i++) {
final String name = res.readString();
res.readString(); // long name - IGNORED - shdve never been in the protocol
final FileAttributes attrs = res.readFileAttributes();
RemoteResourceInfo inf = new RemoteResourceInfo(path, name, attrs);
if (!(name.equals(".") || name.equals("..")) && (filter == null || filter.accept(inf)))
rri.add(inf);
}
break loop;
case STATUS:
res.ensureStatusIs(StatusCode.EOF);
break loop;
default:
throw new SFTPException("Unexpected packet: " + res.getType());
}
}
return rri;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.sftp.Response.StatusCode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class RemoteFile extends RemoteResource {
public RemoteFile(Requester requester, String path, String handle) {
super(requester, path, handle);
}
public RemoteFileInputStream getInputStream() {
return new RemoteFileInputStream();
}
public RemoteFileOutputStream getOutputStream() {
return new RemoteFileOutputStream();
}
public FileAttributes fetchAttributes() throws IOException {
return requester.doRequest(newRequest(PacketType.FSTAT)) //
.ensurePacketTypeIs(PacketType.ATTRS) //
.readFileAttributes();
}
public long length() throws IOException {
return fetchAttributes().getSize();
}
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 {
Response res = requester.doRequest(newRequest(PacketType.READ).putUINT64(fileOffset).putInt(len));
switch (res.getType()) {
case DATA:
int recvLen = res.readInt();
System.arraycopy(res.array(), res.rpos(), to, offset, recvLen);
return recvLen;
case STATUS:
res.ensureStatusIs(StatusCode.EOF);
return -1;
default:
throw new SFTPException("Unexpected packet: " + res.getType());
}
}
public void write(long fileOffset, byte[] data, int off, int len) throws IOException {
requester.doRequest( //
newRequest(PacketType.WRITE) //
.putUINT64(fileOffset) //
.putInt(len - off) //
.putRawBytes(data, off, len) //
).ensureStatusPacketIsOK();
}
public void setAttributes(FileAttributes attrs) throws IOException {
requester.doRequest(newRequest(PacketType.FSETSTAT).putFileAttributes(attrs)).ensureStatusPacketIsOK();
}
public int getOutgoingPacketOverhead() {
return 1 + // packet type
4 + // request id
4 + // next length
handle.length() + // next
8 + // file offset
4 + // data length
4; // packet length
}
public class RemoteFileOutputStream extends OutputStream {
private final byte[] b = new byte[1];
private long fileOffset;
public RemoteFileOutputStream() {
this(0);
}
public RemoteFileOutputStream(long fileOffset) {
this.fileOffset = fileOffset;
}
@Override
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 {
RemoteFile.this.write(fileOffset, buf, off, len);
fileOffset += len;
}
}
public class RemoteFileInputStream extends InputStream {
private final byte[] b = new byte[1];
private long fileOffset;
private long markPos;
private long readLimit;
public RemoteFileInputStream() {
this(0);
}
public RemoteFileInputStream(int fileOffset) {
this.fileOffset = fileOffset;
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readLimit) {
this.readLimit = readLimit;
markPos = fileOffset;
}
@Override
public void reset() throws IOException {
fileOffset = markPos;
}
@Override
public long skip(long n) throws IOException {
return (this.fileOffset = Math.min(fileOffset + n, length()));
}
@Override
public int read() throws IOException {
return read(b, 0, 1) == -1 ? -1 : b[0];
}
@Override
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
markPos = 0;
}
return read;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
abstract class RemoteResource implements Closeable {
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final Requester requester;
protected final String path;
protected final String handle;
protected RemoteResource(Requester requester, String path, String handle) {
this.requester = requester;
this.path = path;
this.handle = handle;
}
public String getPath() {
return path;
}
protected Request newRequest(PacketType type) {
return requester.newRequest(type).putString(handle);
}
public void close() throws IOException {
log.info("Closing `{}`", this);
requester.doRequest(newRequest(PacketType.CLOSE)).ensureStatusPacketIsOK();
}
@Override
public String toString() {
return "RemoteResource{" + path + "}";
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
public interface RemoteResourceFilter {
boolean accept(RemoteResourceInfo resource);
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
public class RemoteResourceInfo {
private final PathComponents comps;
private final FileAttributes attrs;
public RemoteResourceInfo(String parent, String name, FileAttributes attrs) {
this.comps = new PathComponents(parent, name);
this.attrs = attrs;
}
public String getParent() {
return comps.getParent();
}
public String getPath() {
return comps.getPath();
}
public String getName() {
return comps.getName();
}
public FileAttributes getAttributes() {
return attrs;
}
public boolean isType(FileMode.Type type) {
return attrs.getType() == type;
}
public boolean isRegularFile() {
return isType(FileMode.Type.REGULAR);
}
public boolean isDirectory() {
return isType(FileMode.Type.DIRECTORY);
}
public boolean isSymlink() {
return isType(FileMode.Type.SYMKLINK);
}
@Override
public boolean equals(Object o) {
if (o instanceof RemoteResourceInfo) {
final RemoteResourceInfo that = (RemoteResourceInfo) o;
if (comps.equals(that.comps))
return true;
}
return false;
}
@Override
public int hashCode() {
return comps.hashCode();
}
@Override
public String toString() {
return "[" + attrs.getType() + "] " + getPath();
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.concurrent.Future;
public class Request extends SFTPPacket<Request> {
private final PacketType type;
private final long reqID;
private final Future<Response, SFTPException> responseFuture;
public Request(PacketType type, long reqID) {
super();
this.reqID = reqID;
this.type = type;
responseFuture = new Future<Response, SFTPException>("sftp / " + reqID, SFTPException.chainer);
putByte(type.toByte());
putInt(reqID);
}
public long getRequestID() {
return reqID;
}
public PacketType getType() {
return type;
}
public Future<Response, SFTPException> getResponseFuture() {
return responseFuture;
}
@Override
public String toString() {
return "Request{" + reqID + ";" + type + "}";
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import java.io.IOException;
public interface Requester {
Request newRequest(PacketType type);
Response doRequest(Request req) throws IOException;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.common.Buffer;
public class Response extends SFTPPacket<Response> {
public static enum StatusCode {
UNKNOWN(-1),
OK(0),
EOF(1),
NO_SUCH_FILE(2),
PERMISSION_DENIED(3),
FAILURE(4),
BAD_MESSAGE(5),
NO_CONNECTION(6),
CONNECITON_LOST(7),
OP_UNSUPPORTED(8);
private final int code;
public static StatusCode fromInt(int code) {
for (StatusCode s : StatusCode.values())
if (s.code == code)
return s;
return UNKNOWN;
}
private StatusCode(int code) {
this.code = code;
}
}
private final PacketType type;
private final long reqID;
public Response(Buffer<Response> pk) {
super(pk);
this.type = readType();
this.reqID = readLong();
}
public long getRequestID() {
return reqID;
}
public PacketType getType() {
return type;
}
public StatusCode readStatusCode() {
return StatusCode.fromInt(readInt());
}
public Response ensurePacketTypeIs(PacketType pt) throws SFTPException {
if (getType() != pt)
if (getType() == PacketType.STATUS)
throw new SFTPException(readStatusCode(), readString());
else
throw new SFTPException("Unexpected packet " + getType());
return this;
}
public Response ensureStatusPacketIsOK() throws SFTPException {
return ensurePacketTypeIs(PacketType.STATUS).ensureStatusIs(StatusCode.OK);
}
public Response ensureStatusIs(StatusCode acceptable) throws SFTPException {
StatusCode sc = readStatusCode();
if (sc != acceptable)
throw new SFTPException(sc, readString());
return this;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import net.schmizz.sshj.xfer.FilePermission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
public class SFTPClient {
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
private final SFTPEngine sftp;
private final SFTPFileTransfer xfer;
public SFTPClient(SessionFactory ssh) throws IOException {
this.sftp = new SFTPEngine(ssh).init();
this.xfer = new SFTPFileTransfer(sftp);
}
public SFTPEngine getSFTPEngine() {
return sftp;
}
public SFTPFileTransfer getFileTansfer() {
return xfer;
}
public List<RemoteResourceInfo> ls(String path) throws IOException {
return ls(path, null);
}
public List<RemoteResourceInfo> ls(String path, RemoteResourceFilter filter) throws IOException {
final RemoteDirectory dir = sftp.openDir(path);
try {
return dir.scan(filter);
} finally {
dir.close();
}
}
public RemoteFile open(String filename, Set<OpenMode> mode, FileAttributes attrs) throws IOException {
log.debug("Opening `{}`", filename);
return sftp.open(filename, mode, attrs);
}
public RemoteFile open(String filename, Set<OpenMode> mode) throws IOException {
return open(filename, mode, new FileAttributes());
}
public RemoteFile open(String filename) throws IOException {
return open(filename, EnumSet.of(OpenMode.READ));
}
public void mkdir(String dirname) throws IOException {
sftp.makeDir(dirname);
}
public void rename(String oldpath, String newpath) throws IOException {
sftp.rename(oldpath, newpath);
}
public void rm(String filename) throws IOException {
sftp.remove(filename);
}
public void rmdir(String dirname) throws IOException {
sftp.removeDir(dirname);
}
public void symlink(String linkpath, String targetpath) throws IOException {
sftp.symlink(linkpath, targetpath);
}
public int version() {
return sftp.getOperativeProtocolVersion();
}
public void setattr(String path, FileAttributes attrs) throws IOException {
sftp.setAttributes(path, attrs);
}
public int uid(String path) throws IOException {
return stat(path).getUID();
}
public int gid(String path) throws IOException {
return stat(path).getGID();
}
public long atime(String path) throws IOException {
return stat(path).getAtime();
}
public long mtime(String path) throws IOException {
return stat(path).getMtime();
}
public Set<FilePermission> perms(String path) throws IOException {
return stat(path).getPermissions();
}
public FileMode mode(String path) throws IOException {
return stat(path).getMode();
}
public FileMode.Type type(String path) throws IOException {
return stat(path).getType();
}
public String readlink(String path) throws IOException {
return sftp.readLink(path);
}
public FileAttributes stat(String path) throws IOException {
return sftp.stat(path);
}
public FileAttributes lstat(String path) throws IOException {
return sftp.lstat(path);
}
public void chown(String path, int uid) throws IOException {
setattr(path, new FileAttributes.Builder().withUIDGID(uid, gid(path)).build());
}
public void chmod(String path, int perms) throws IOException {
setattr(path, new FileAttributes.Builder().withPermissions(perms).build());
}
public void chgrp(String path, int gid) throws IOException {
setattr(path, new FileAttributes.Builder().withUIDGID(uid(path), gid).build());
}
public void truncate(String path, long size) throws IOException {
setattr(path, new FileAttributes.Builder().withSize(size).build());
}
public String canonicalize(String path) throws IOException {
return sftp.canonicalize(path);
}
public long size(String path) throws IOException {
return stat(path).getSize();
}
public void get(String source, String dest) throws IOException {
xfer.download(source, dest);
}
public void put(String source, String dest) throws IOException {
xfer.upload(source, dest);
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.connection.channel.direct.Session.Subsystem;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class SFTPEngine implements Requester {
/** Logger */
private final Logger log = LoggerFactory.getLogger(getClass());
public static final int PROTOCOL_VERSION = 3;
public static final int DEFAULT_TIMEOUT = 30;
private volatile int timeout = DEFAULT_TIMEOUT;
private final Subsystem sub;
private final PacketReader reader;
private final OutputStream out;
private long reqID;
private int negotiatedVersion;
private final Map<String, String> serverExtensions = new HashMap<String, String>();
public SFTPEngine(SessionFactory ssh) throws SSHException {
sub = ssh.startSession().startSubsystem("sftp");
out = sub.getOutputStream();
reader = new PacketReader(sub.getInputStream());
}
public Subsystem getSubsystem() {
return sub;
}
public SFTPEngine init() throws IOException {
transmit(new SFTPPacket<Request>(PacketType.INIT).putInt(PROTOCOL_VERSION));
final SFTPPacket<Response> response = reader.readPacket();
final PacketType type = response.readType();
if (type != PacketType.VERSION)
throw new SFTPException("Expected INIT packet, received: " + type);
negotiatedVersion = response.readInt();
log.info("Client version {}, server version {}", PROTOCOL_VERSION, negotiatedVersion);
if (negotiatedVersion < PROTOCOL_VERSION)
throw new SFTPException("Server reported protocol version: " + negotiatedVersion);
while (response.available() > 0)
serverExtensions.put(response.readString(), response.readString());
// Start reader thread
reader.start();
return this;
}
public int getOperativeProtocolVersion() {
return negotiatedVersion;
}
public synchronized Request newRequest(PacketType type) {
return new Request(type, reqID = reqID + 1 & 0xffffffffL);
}
public Response doRequest(Request req) throws IOException {
reader.expectResponseTo(req);
log.debug("Sending {}", req);
transmit(req);
return req.getResponseFuture().get(timeout, TimeUnit.SECONDS);
}
private synchronized void transmit(SFTPPacket<Request> payload) throws IOException {
final int len = payload.available();
out.write((len >>> 24) & 0xff);
out.write((len >>> 16) & 0xff);
out.write((len >>> 8) & 0xff);
out.write(len & 0xff);
out.write(payload.array(), 0, len);
out.flush();
}
public RemoteFile open(String path, Set<OpenMode> modes, FileAttributes fa) throws IOException {
final String handle = doRequest(
newRequest(PacketType.OPEN).putString(path).putInt(OpenMode.toMask(modes)).putFileAttributes(fa)
).ensurePacketTypeIs(PacketType.HANDLE).readString();
return new RemoteFile(this, path, handle);
}
public RemoteFile open(String filename, Set<OpenMode> modes) throws IOException {
return open(filename, modes, new FileAttributes());
}
public RemoteFile open(String filename) throws IOException {
return open(filename, EnumSet.of(OpenMode.READ));
}
public RemoteDirectory openDir(String path) throws IOException {
final String handle = doRequest(
newRequest(PacketType.OPENDIR).putString(path)
).ensurePacketTypeIs(PacketType.HANDLE).readString();
return new RemoteDirectory(this, path, handle);
}
public void setAttributes(String path, FileAttributes attrs) throws IOException {
doRequest(
newRequest(PacketType.SETSTAT).putString(path).putFileAttributes(attrs)
).ensureStatusPacketIsOK();
}
public String readLink(String path) throws IOException {
return readSingleName(
doRequest(
newRequest(PacketType.READLINK).putString(path)
));
}
public void makeDir(String path, FileAttributes attrs) throws IOException {
doRequest(
newRequest(PacketType.MKDIR).putString(path).putFileAttributes(attrs)
).ensureStatusPacketIsOK();
}
public void makeDir(String path) throws IOException {
makeDir(path, new FileAttributes());
}
public void symlink(String linkpath, String targetpath) throws IOException {
doRequest(
newRequest(PacketType.SYMLINK).putString(linkpath).putString(targetpath)
).ensureStatusPacketIsOK();
}
public void remove(String filename) throws IOException {
doRequest(
newRequest(PacketType.REMOVE).putString(filename)
).ensureStatusPacketIsOK();
}
public void removeDir(String path) throws IOException {
doRequest(
newRequest(PacketType.RMDIR).putString(path)
).ensureStatusIs(Response.StatusCode.OK);
}
private FileAttributes stat(PacketType pt, String path) throws IOException {
return doRequest(newRequest(pt).putString(path))
.ensurePacketTypeIs(PacketType.ATTRS)
.readFileAttributes();
}
public FileAttributes stat(String path) throws IOException {
return stat(PacketType.STAT, path);
}
public FileAttributes lstat(String path) throws IOException {
return stat(PacketType.LSTAT, path);
}
public void rename(String oldPath, String newPath) throws IOException {
doRequest(
newRequest(PacketType.RENAME).putString(oldPath).putString(newPath)
).ensureStatusPacketIsOK();
}
public String canonicalize(String path) throws IOException {
return readSingleName(
doRequest(
newRequest(PacketType.REALPATH).putString(path)
));
}
private static String readSingleName(Response res) throws IOException {
res.ensurePacketTypeIs(PacketType.NAME);
if (res.readInt() == 1)
return res.readString();
else
throw new SFTPException("Unexpected data in " + res.getType() + " packet");
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getTimeout() {
return timeout;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.concurrent.ExceptionChainer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.sftp.Response.StatusCode;
public class SFTPException extends SSHException {
public static final ExceptionChainer<SFTPException> chainer = new ExceptionChainer<SFTPException>() {
public SFTPException chain(Throwable t) {
if (t instanceof SFTPException)
return (SFTPException) t;
else
return new SFTPException(t);
}
};
public SFTPException() {
super();
}
public SFTPException(DisconnectReason code) {
super(code);
}
public SFTPException(DisconnectReason code, String message) {
super(code, message);
}
public SFTPException(DisconnectReason code, String message, Throwable cause) {
super(code, message, cause);
}
public SFTPException(DisconnectReason code, Throwable cause) {
super(code, cause);
}
public SFTPException(String message) {
super(message);
}
public SFTPException(String message, Throwable cause) {
super(message, cause);
}
public SFTPException(Throwable cause) {
super(cause);
}
private StatusCode sc;
public StatusCode getStatusCode() {
return (sc == null) ? StatusCode.UNKNOWN : sc;
}
public SFTPException(StatusCode sc, String msg) {
this(msg);
this.sc = sc;
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.sftp.Response.StatusCode;
import net.schmizz.sshj.xfer.AbstractFileTransfer;
import net.schmizz.sshj.xfer.FileTransfer;
import net.schmizz.sshj.xfer.FileTransferUtil;
import net.schmizz.sshj.xfer.ModeGetter;
import net.schmizz.sshj.xfer.ModeSetter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.EnumSet;
public class SFTPFileTransfer extends AbstractFileTransfer implements FileTransfer {
private final SFTPEngine sftp;
private final PathHelper pathHelper;
private volatile FileFilter uploadFilter = defaultLocalFilter;
private volatile RemoteResourceFilter downloadFilter = defaultRemoteFilter;
private static final FileFilter defaultLocalFilter = new FileFilter() {
public boolean accept(File pathName) {
return true;
}
};
private static final RemoteResourceFilter defaultRemoteFilter = new RemoteResourceFilter() {
public boolean accept(RemoteResourceInfo resource) {
return true;
}
};
public SFTPFileTransfer(SFTPEngine sftp) {
this.sftp = sftp;
this.pathHelper = new PathHelper(sftp);
}
public void upload(String source, String dest) throws IOException {
new Uploader(getModeGetter(), getUploadFilter()).upload(new File(source), dest);
}
public void download(String source, String dest) throws IOException {
PathComponents src = pathHelper.getComponents(source);
new Downloader(getModeSetter(), getDownloadFilter()).download(new RemoteResourceInfo(src.getParent(), src
.getName(), sftp.stat(source)), new File(dest));
}
public void setUploadFilter(FileFilter uploadFilter) {
this.uploadFilter = (this.uploadFilter == null) ? defaultLocalFilter : uploadFilter;
}
public void setDownloadFilter(RemoteResourceFilter downloadFilter) {
this.downloadFilter = (this.downloadFilter == null) ? defaultRemoteFilter : downloadFilter;
}
public FileFilter getUploadFilter() {
return uploadFilter;
}
public RemoteResourceFilter getDownloadFilter() {
return downloadFilter;
}
private class Downloader {
private final ModeSetter modeSetter;
private final RemoteResourceFilter filter;
Downloader(ModeSetter modeSetter, RemoteResourceFilter filter) {
this.modeSetter = modeSetter;
this.filter = filter;
}
private void setAttributes(RemoteResourceInfo remote, File local) throws IOException {
final FileAttributes attrs = remote.getAttributes();
modeSetter.setPermissions(local, attrs.getMode().getPermissionsMask());
if (modeSetter.preservesTimes() && attrs.has(FileAttributes.Flag.ACMODTIME)) {
modeSetter.setLastAccessedTime(local, attrs.getAtime());
modeSetter.setLastModifiedTime(local, attrs.getMtime());
}
}
private void downloadFile(RemoteResourceInfo remote, File local) throws IOException {
local = FileTransferUtil.getTargetFile(local, remote.getName());
setAttributes(remote, local);
RemoteFile rf = sftp.open(remote.getPath());
try {
final FileOutputStream fos = new FileOutputStream(local);
try {
StreamCopier.copy(rf.getInputStream(), fos, sftp.getSubsystem()
.getLocalMaxPacketSize(), false);
} finally {
fos.close();
}
} finally {
rf.close();
}
}
private void downloadDir(RemoteResourceInfo remote, File local) throws IOException {
local = FileTransferUtil.getTargetDirectory(local, remote.getName());
setAttributes(remote, local);
final RemoteDirectory rd = sftp.openDir(remote.getPath());
for (RemoteResourceInfo rri : rd.scan(filter))
download(rri, new File(local.getPath(), rri.getName()));
rd.close();
}
void download(RemoteResourceInfo remote, File local) throws IOException {
log.info("Downloading [{}] to [{}]", remote, local);
if (remote.isDirectory())
downloadDir(remote, local);
else if (remote.isRegularFile())
downloadFile(remote, local);
else
throw new IOException(remote + " is not a regular file or directory");
}
}
private class Uploader {
private final ModeGetter modeGetter;
private final FileFilter filter;
Uploader(ModeGetter modeGetter, FileFilter filter) {
this.modeGetter = modeGetter;
this.filter = filter;
}
public FileAttributes getAttributes(File local) throws IOException {
FileAttributes.Builder builder = new FileAttributes.Builder().withPermissions(modeGetter
.getPermissions(local));
if (modeGetter.preservesTimes())
builder.withAtimeMtime(modeGetter.getLastAccessTime(local), modeGetter.getLastModifiedTime(local));
return builder.build();
}
// tread carefully
private void setAttributes(FileAttributes current, File local, String remote) throws IOException {
final FileAttributes attrs = getAttributes(local);
// TODO whoaaa.. simplify?
if (!(current != null
&& current.getMode().getPermissionsMask() == attrs.getMode().getPermissionsMask()
&& (!modeGetter.preservesTimes() || (attrs.getAtime() == current.getAtime() && attrs.getMtime() == current
.getMtime()))))
sftp.setAttributes(remote, attrs);
}
private String prepareDir(File local, String remote) throws IOException {
FileAttributes attrs;
try {
attrs = sftp.stat(remote);
} catch (SFTPException e) {
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
log.debug("probeDir: {} does not exist, creating", remote);
sftp.makeDir(remote, getAttributes(local));
return remote;
} else
throw e;
}
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY)
if (pathHelper.getComponents(remote).getName().equals(local.getName())) {
log.debug("probeDir: {} already exists", remote);
setAttributes(attrs, local, remote);
return remote;
} else {
log.debug("probeDir: {} already exists, path adjusted for {}", remote, local.getName());
return prepareDir(local, PathComponents.adjustForParent(remote, local.getName()));
}
else
throw new IOException(attrs.getMode().getType() + " file already exists at " + remote);
}
private String prepareFile(File local, String remote) throws IOException {
FileAttributes attrs;
try {
attrs = sftp.stat(remote);
} catch (SFTPException e) {
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
log.debug("probeFile: {} does not exist", remote);
return remote;
} else
throw e;
}
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY) {
log.debug("probeFile: {} was directory, path adjusted for {}", remote, local.getName());
remote = PathComponents.adjustForParent(remote, local.getName());
return remote;
} else {
log.debug("probeFile: {} is a {} file that will be replaced", remote, attrs.getMode().getType());
return remote;
}
}
private void uploadDir(File local, String remote) throws IOException {
final String adjusted = prepareDir(local, remote);
for (File f : local.listFiles(filter))
upload(f, adjusted);
}
private void uploadFile(File local, String remote) throws IOException {
final String adjusted = prepareFile(local, remote);
final RemoteFile rf = sftp.open(adjusted, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC),
getAttributes(local));
try {
final FileInputStream fis = new FileInputStream(local);
try {
StreamCopier.copy(fis, //
rf.getOutputStream(), sftp.getSubsystem().getRemoteMaxPacketSize()
- rf.getOutgoingPacketOverhead(), false);
} finally {
fis.close();
}
} finally {
rf.close();
}
}
void upload(File local, String remote) throws IOException {
log.info("Uploading [{}] to [{}]", local, remote);
if (local.isDirectory())
uploadDir(local, remote);
else if (local.isFile())
uploadFile(local, remote);
else
throw new IOException(local + " is not a file or directory");
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.common.Buffer;
public class SFTPPacket<T extends SFTPPacket<T>> extends Buffer<T> {
public SFTPPacket() {
super();
}
public SFTPPacket(Buffer<T> buf) {
super(buf);
}
public SFTPPacket(PacketType pt) {
super();
putByte(pt.toByte());
}
public FileAttributes readFileAttributes() {
final FileAttributes.Builder builder = new FileAttributes.Builder();
final int mask = readInt();
if (FileAttributes.Flag.SIZE.isSet(mask))
builder.withSize(readUINT64());
if (FileAttributes.Flag.UIDGID.isSet(mask))
builder.withUIDGID(readInt(), readInt());
if (FileAttributes.Flag.MODE.isSet(mask))
builder.withPermissions(readInt());
if (FileAttributes.Flag.ACMODTIME.isSet(mask))
builder.withAtimeMtime(readInt(), readInt());
if (FileAttributes.Flag.EXTENDED.isSet(mask)) {
final int extCount = readInt();
for (int i = 0; i < extCount; i++)
builder.withExtended(readString(), readString());
}
return builder.build();
}
public PacketType readType() {
return PacketType.fromByte(readByte());
}
public T putFileAttributes(FileAttributes fa) {
return putRawBytes(fa.toBytes());
}
public T putType(PacketType type) {
return putByte(type.toByte());
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.sftp;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import java.io.IOException;
import java.util.List;
import java.util.Set;
public class StatefulSFTPClient extends SFTPClient {
private String cwd;
public StatefulSFTPClient(SessionFactory ssh) throws IOException {
super(ssh);
this.cwd = getSFTPEngine().canonicalize(".");
log.info("Start dir = " + cwd);
}
private synchronized String cwdify(String path) {
return PathComponents.adjustForParent(cwd, path);
}
public synchronized void cd(String dirname) {
cwd = cwdify(dirname);
log.info("CWD = " + cwd);
}
public synchronized List<RemoteResourceInfo> ls() throws IOException {
return ls(cwd, null);
}
public synchronized List<RemoteResourceInfo> ls(RemoteResourceFilter filter) throws IOException {
return ls(cwd, filter);
}
public synchronized String pwd() throws IOException {
return super.canonicalize(cwd);
}
@Override
public List<RemoteResourceInfo> ls(String path) throws IOException {
return ls(path, null);
}
@Override
public List<RemoteResourceInfo> ls(String path, RemoteResourceFilter filter) throws IOException {
final RemoteDirectory dir = getSFTPEngine().openDir(cwdify(path));
try {
return dir.scan(filter);
} finally {
dir.close();
}
}
@Override
public RemoteFile open(String filename, Set<OpenMode> mode, FileAttributes attrs) throws IOException {
return super.open(cwdify(filename), mode, attrs);
}
@Override
public RemoteFile open(String filename, Set<OpenMode> mode) throws IOException {
return super.open(cwdify(filename), mode);
}
@Override
public RemoteFile open(String filename) throws IOException {
return super.open(cwdify(filename));
}
@Override
public void mkdir(String dirname) throws IOException {
super.mkdir(cwdify(dirname));
}
@Override
public void rename(String oldpath, String newpath) throws IOException {
super.rename(cwdify(oldpath), cwdify(newpath));
}
@Override
public void rm(String filename) throws IOException {
super.rm(cwdify(filename));
}
@Override
public void rmdir(String dirname) throws IOException {
super.rmdir(cwdify(dirname));
}
@Override
public void symlink(String linkpath, String targetpath) throws IOException {
super.symlink(cwdify(linkpath), cwdify(targetpath));
}
@Override
public void setattr(String path, FileAttributes attrs) throws IOException {
super.setattr(cwdify(path), attrs);
}
@Override
public String readlink(String path) throws IOException {
return super.readlink(cwdify(path));
}
@Override
public FileAttributes stat(String path) throws IOException {
return super.stat(cwdify(path));
}
@Override
public FileAttributes lstat(String path) throws IOException {
return super.lstat(cwdify(path));
}
@Override
public void truncate(String path, long size) throws IOException {
super.truncate(cwdify(path), size);
}
@Override
public String canonicalize(String path) throws IOException {
return super.canonicalize(cwdify(path));
}
@Override
public void get(String source, String dest) throws IOException {
super.get(cwdify(source), dest);
}
@Override
public void put(String source, String dest) throws IOException {
super.get(source, cwdify(dest));
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.signature;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
/** An abstract class for {@link Signature} that implements common functionality. */
public abstract class AbstractSignature implements Signature {
protected java.security.Signature signature;
protected String algorithm;
protected AbstractSignature(String algorithm) {
this.algorithm = algorithm;
}
public void init(PublicKey pubkey, PrivateKey prvkey) {
try {
signature = SecurityUtils.getSignature(algorithm);
if (pubkey != null)
signature.initVerify(pubkey);
if (prvkey != null)
signature.initSign(prvkey);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException(e);
}
}
public void update(byte[] foo) {
update(foo, 0, foo.length);
}
public void update(byte[] foo, int off, int len) {
try {
signature.update(foo, off, len);
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
}
protected byte[] extractSig(byte[] sig) {
if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0) {
int i = 0;
int j;
j = sig[i++] << 24 & 0xff000000 //
| sig[i++] << 16 & 0x00ff0000 //
| sig[i++] << 8 & 0x0000ff00 //
| sig[i++] & 0x000000ff;
i += j;
j = sig[i++] << 24 & 0xff000000 //
| sig[i++] << 16 & 0x00ff0000 //
| sig[i++] << 8 & 0x0000ff00 //
| sig[i++] & 0x000000ff;
byte[] tmp = new byte[j];
System.arraycopy(sig, i, tmp, 0, j);
sig = tmp;
}
return sig;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.signature;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* Signature interface for SSH used to sign or verify data.
* <p/>
* Usually wraps a javax.crypto.Signature object.
*/
public interface Signature {
/**
* Initialize this signature with the given public key and private key. If the private key is null, only signature
* verification can be performed.
*
* @param pubkey (null-ok) specify in case verification is needed
* @param prvkey (null-ok) specify in case signing is needed
*/
void init(PublicKey pubkey, PrivateKey prvkey);
/**
* Compute the signature
*
* @return the computed signature
*/
byte[] sign();
/**
* Convenience method for {@link #update(byte[], int, int)}
*
* @param H the byte-array to update with
*/
void update(byte[] H);
/**
* Update the computed signature with the given data
*
* @param H byte-array to update with
* @param off offset within the array
* @param len length until which to compute
*/
void update(byte[] H, int off, int len);
/**
* Verify against the given signature
*
* @param sig
*
* @return {@code true} on successful verification, {@code false} on failure
*/
boolean verify(byte[] sig);
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.signature;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.security.SignatureException;
/** DSA {@link Signature} */
public class SignatureDSA extends AbstractSignature {
/** A named factory for DSA signature */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Signature> {
public Signature create() {
return new SignatureDSA();
}
public String getName() {
return KeyType.DSA.toString();
}
}
public SignatureDSA() {
super("SHA1withDSA");
}
public byte[] sign() {
byte[] sig;
try {
sig = signature.sign();
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
// sig is in ASN.1
// SEQUENCE::={ r INTEGER, s INTEGER }
int len = 0;
int index = 3;
len = sig[index++] & 0xff;
byte[] r = new byte[len];
System.arraycopy(sig, index, r, 0, r.length);
index = index + len + 1;
len = sig[index++] & 0xff;
byte[] s = new byte[len];
System.arraycopy(sig, index, s, 0, s.length);
byte[] result = new byte[40];
// result must be 40 bytes, but length of r and s may not be 20 bytes
System.arraycopy(r, r.length > 20 ? 1 : 0, result, r.length > 20 ? 0 : 20 - r.length, r.length > 20 ? 20
: r.length);
System.arraycopy(s, s.length > 20 ? 1 : 0, result, s.length > 20 ? 20 : 40 - s.length, s.length > 20 ? 20
: s.length);
return result;
}
public boolean verify(byte[] sig) {
sig = extractSig(sig);
// ASN.1
int frst = (sig[0] & 0x80) != 0 ? 1 : 0;
int scnd = (sig[20] & 0x80) != 0 ? 1 : 0;
int length = sig.length + 6 + frst + scnd;
byte[] tmp = new byte[length];
tmp[0] = (byte) 0x30;
tmp[1] = (byte) 0x2c;
tmp[1] += frst;
tmp[1] += scnd;
tmp[2] = (byte) 0x02;
tmp[3] = (byte) 0x14;
tmp[3] += frst;
System.arraycopy(sig, 0, tmp, 4 + frst, 20);
tmp[4 + tmp[3]] = (byte) 0x02;
tmp[5 + tmp[3]] = (byte) 0x14;
tmp[5 + tmp[3]] += scnd;
System.arraycopy(sig, 20, tmp, 6 + tmp[3] + scnd, 20);
sig = tmp;
try {
return signature.verify(sig);
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.signature;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SSHRuntimeException;
import java.security.SignatureException;
/** RSA {@link Signature} */
public class SignatureRSA extends AbstractSignature {
/** A named factory for RSA {@link Signature} */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Signature> {
public Signature create() {
return new SignatureRSA();
}
public String getName() {
return KeyType.RSA.toString();
}
}
public SignatureRSA() {
super("SHA1withRSA");
}
public byte[] sign() {
try {
return signature.sign();
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
}
public boolean verify(byte[] sig) {
sig = extractSig(sig);
try {
return signature.verify(sig);
} catch (SignatureException e) {
throw new SSHRuntimeException(e);
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.cipher.NoneCipher;
import net.schmizz.sshj.transport.compression.Compression;
import net.schmizz.sshj.transport.mac.MAC;
/**
* Base class for {@link Encoder} and {@link Decoder}.
* <p/>
* From RFC 4253, p. 6
* <p/>
* <pre>
* Each packet is in the following format:
* <p/>
* uint32 packet_length
* byte padding_length
* byte[n1] payload; n1 = packet_length - padding_length - 1
* byte[n2] random padding; n2 = padding_length
* byte[m] mac (Message Authentication Code - MAC); m = mac_length
* </pre>
*/
abstract class Converter {
protected Cipher cipher = new NoneCipher();
protected MAC mac = null;
protected Compression compression = null;
protected int cipherSize = 8;
protected long seq = -1;
protected boolean authed;
long getSequenceNumber() {
return seq;
}
void setAlgorithms(Cipher cipher, MAC mac, Compression compression) {
this.cipher = cipher;
this.mac = mac;
this.compression = compression;
if (compression != null)
compression.init(getCompressionType(), -1);
this.cipherSize = cipher.getIVSize();
}
void setAuthenticated() {
this.authed = true;
}
boolean usingCompression() {
return compression != null && (authed || !compression.isDelayed());
}
abstract Compression.Type getCompressionType();
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.sshj.common.ByteArrayUtils;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.common.SSHPacketHandler;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.Compression;
import net.schmizz.sshj.transport.mac.MAC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Decodes packets from the SSH binary protocol per the current algorithms. */
final class Decoder extends Converter {
private static final int MAX_PACKET_LEN = 256 * 1024;
private final Logger log = LoggerFactory.getLogger(getClass());
/** What we pass decoded packets to */
private final SSHPacketHandler packetHandler;
/** Buffer where as-yet undecoded data lives */
private final SSHPacket inputBuffer = new SSHPacket();
/** Used in case compression is active to store the uncompressed data */
private final SSHPacket uncompressBuffer = new SSHPacket();
/** MAC result is stored here */
private byte[] macResult;
/** -1 if packet length not yet been decoded, else the packet length */
private int packetLength = -1;
/**
* How many bytes do we need, before a call to decode() can succeed at decoding at least packet length, OR the whole
* packet?
*/
private int needed = 8;
Decoder(SSHPacketHandler packetHandler) {
this.packetHandler = packetHandler;
}
/**
* Returns advised number of bytes that should be made available in decoderBuffer before the method should be called
* again.
*
* @return number of bytes needed before further decoding possible
*/
private int decode() throws SSHException {
int need;
/* Decoding loop */
for (; ;)
if (packetLength == -1) // Waiting for beginning of packet
{
assert inputBuffer.rpos() == 0 : "buffer cleared";
need = cipherSize - inputBuffer.available();
if (need <= 0)
packetLength = decryptLength();
else
// Need more data
break;
} else {
assert inputBuffer.rpos() == 4 : "packet length read";
need = packetLength + (mac != null ? mac.getBlockSize() : 0) - inputBuffer.available();
if (need <= 0) {
decryptPayload(inputBuffer.array());
seq = seq + 1 & 0xffffffffL;
if (mac != null)
checkMAC(inputBuffer.array());
// Exclude the padding & MAC
inputBuffer.wpos(packetLength + 4 - inputBuffer.readByte());
final SSHPacket plain = usingCompression() ? decompressed() : inputBuffer;
if (log.isTraceEnabled())
log.trace("Received packet #{}: {}", seq, plain.printHex());
packetHandler.handle(plain.readMessageID(), plain); // Process the decoded packet //
inputBuffer.clear();
packetLength = -1;
} else
// Need more data
break;
}
return need;
}
private void checkMAC(final byte[] data) throws TransportException {
mac.update(seq); // seq num
mac.update(data, 0, packetLength + 4); // packetLength+4 = entire packet w/o mac
mac.doFinal(macResult, 0); // compute
// Check against the received MAC
if (!ByteArrayUtils.equals(macResult, 0, data, packetLength + 4, mac.getBlockSize()))
throw new TransportException(DisconnectReason.MAC_ERROR, "MAC Error");
}
private SSHPacket decompressed() throws TransportException {
uncompressBuffer.clear();
compression.uncompress(inputBuffer, uncompressBuffer);
return uncompressBuffer;
}
private int decryptLength() throws TransportException {
cipher.update(inputBuffer.array(), 0, cipherSize);
final int len = inputBuffer.readInt(); // Read packet length
if (isInvalidPacketLength(len)) { // Check packet length validity
log.info("Error decoding packet (invalid length) {}", inputBuffer.printHex());
throw new TransportException(DisconnectReason.PROTOCOL_ERROR, "invalid packet length: " + len);
}
return len;
}
private static boolean isInvalidPacketLength(int len) {
return len < 5 || len > MAX_PACKET_LEN;
}
private void decryptPayload(final byte[] data) {
cipher.update(data, cipherSize, packetLength + 4 - cipherSize);
}
/**
* Adds {@code len} bytes from {@code b} to the decoder buffer. When a packet has been successfully decoded, hooks
* in to {@link net.schmizz.sshj.common.SSHPacketHandler#handle} of the {@link net.schmizz.sshj.common.SSHPacketHandler}
* this decoder was initialized with.
* <p/>
* Returns the number of bytes expected in the next call in order to decode the packet length, and if the packet
* length has already been decoded; to decode the payload. This number is accurate and should be taken to heart.
*/
int received(byte[] b, int len) throws SSHException {
inputBuffer.putRawBytes(b, 0, len);
if (needed <= len)
needed = decode();
else
needed -= len;
return needed;
}
@Override
void setAlgorithms(Cipher cipher, MAC mac, Compression compression) {
super.setAlgorithms(cipher, mac, compression);
macResult = new byte[mac.getBlockSize()];
}
@Override
Compression.Type getCompressionType() {
return Compression.Type.Inflater;
}
int getMaxPacketLength() {
return MAX_PACKET_LEN;
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.Compression;
import net.schmizz.sshj.transport.mac.MAC;
import net.schmizz.sshj.transport.random.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.locks.Lock;
/** Encodes packets into the SSH binary protocol per the current algorithms. */
final class Encoder extends Converter {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Random prng;
private final Lock encodeLock;
Encoder(Random prng, Lock encodeLock) {
this.prng = prng;
this.encodeLock = encodeLock;
}
private SSHPacket checkHeaderSpace(SSHPacket buffer) {
if (buffer.rpos() < 5) {
log.warn("Performance cost: when sending a packet, ensure that "
+ "5 bytes are available in front of the buffer");
SSHPacket nb = new SSHPacket(buffer.available() + 5);
nb.rpos(5);
nb.wpos(5);
nb.putBuffer(buffer);
buffer = nb;
}
return buffer;
}
private void compress(SSHPacket buffer) {
compression.compress(buffer);
}
private void putMAC(SSHPacket buffer, int startOfPacket, int endOfPadding) {
buffer.wpos(endOfPadding + mac.getBlockSize());
mac.update(seq);
mac.update(buffer.array(), startOfPacket, endOfPadding);
mac.doFinal(buffer.array(), endOfPadding);
}
/**
* Encode a buffer into the SSH binary protocol per the current algorithms.
*
* @param buffer the buffer to encode
*
* @return the sequence no. of encoded packet
*
* @throws TransportException
*/
long encode(SSHPacket buffer) {
encodeLock.lock();
try {
buffer = checkHeaderSpace(buffer);
if (log.isTraceEnabled())
log.trace("Encoding packet #{}: {}", seq, buffer.printHex());
if (usingCompression())
compress(buffer);
final int payloadSize = buffer.available();
// Compute padding length
int padLen = -(payloadSize + 5) & cipherSize - 1;
if (padLen < cipherSize)
padLen += cipherSize;
final int startOfPacket = buffer.rpos() - 5;
final int packetLen = payloadSize + 1 + padLen;
// Put packet header
buffer.wpos(startOfPacket);
buffer.putInt(packetLen);
buffer.putByte((byte) padLen);
// Now wpos will mark end of padding
buffer.wpos(startOfPacket + 5 + payloadSize + padLen);
// Fill padding
prng.fill(buffer.array(), buffer.wpos() - padLen, padLen);
seq = seq + 1 & 0xffffffffL;
if (mac != null)
putMAC(buffer, startOfPacket, buffer.wpos());
cipher.update(buffer.array(), startOfPacket, 4 + packetLen);
buffer.rpos(startOfPacket); // Make ready-to-read
return seq;
} finally {
encodeLock.unlock();
}
}
@Override
void setAlgorithms(Cipher cipher, MAC mac, Compression compression) {
encodeLock.lock();
try {
super.setAlgorithms(cipher, mac, compression);
} finally {
encodeLock.unlock();
}
}
@Override
void setAuthenticated() {
encodeLock.lock();
try {
super.setAuthenticated();
} finally {
encodeLock.unlock();
}
}
@Override
Compression.Type getCompressionType() {
return Compression.Type.Deflater;
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class Heartbeater extends Thread {
private final Logger log = LoggerFactory.getLogger(getClass());
private final TransportProtocol trans;
private int interval;
private boolean started;
Heartbeater(TransportProtocol trans) {
this.trans = trans;
setName("heartbeater");
}
synchronized void setInterval(int interval) {
this.interval = interval;
if (interval != 0) {
if (!started)
start();
notify();
}
}
synchronized int getInterval() {
return interval;
}
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
int hi;
synchronized (this) {
while ((hi = interval) == 0)
wait();
}
if (!started)
started = true;
else if (trans.isRunning()) {
log.info("Sending heartbeat since {} seconds elapsed", hi);
trans.write(new SSHPacket(Message.IGNORE));
}
Thread.sleep(hi * 1000);
}
} catch (Exception e) {
if (Thread.currentThread().isInterrupted()) {
// We are meant to shut up and draw to a close if interrupted
} else
trans.die(e);
}
log.debug("Stopping");
}
}

View File

@@ -0,0 +1,365 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.FutureUtils;
import net.schmizz.sshj.common.*;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.Compression;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/** Algorithm negotiation and key exchange. */
final class KeyExchanger implements SSHPacketHandler, ErrorNotifiable {
private static enum Expected {
/** we have sent or are sending KEXINIT, and expect the server's KEXINIT */
KEXINIT,
/** we are expecting some followup data as part of the exchange */
FOLLOWUP,
/** we are expecting SSH_MSG_NEWKEYS */
NEWKEYS,
}
private final Logger log = LoggerFactory.getLogger(getClass());
private final TransportProtocol transport;
/**
* {@link HostKeyVerifier#verify(String, int, java.security.PublicKey)} is invoked by {@link #verifyHost(PublicKey)}
* when we are ready to verify the the server's host key.
*/
private final Queue<HostKeyVerifier> hostVerifiers = new LinkedList<HostKeyVerifier>();
private final AtomicBoolean kexOngoing = new AtomicBoolean();
/** What we are expecting from the next packet */
private Expected expected = Expected.KEXINIT;
/** Instance of negotiated key exchange algorithm */
private KeyExchange kex;
/** Computed session ID */
private byte[] sessionID;
private Proposal clientProposal;
private NegotiatedAlgorithms negotiatedAlgs;
private final Event<TransportException> kexInitSent = new Event<TransportException>("kexinit sent",
TransportException.chainer);
private final Event<TransportException> done;
KeyExchanger(TransportProtocol trans) {
this.transport = trans;
/*
* Use TransportProtocol's writeLock, since TransportProtocol.write() may wait on this event and the lock should
* be released while waiting.
*/
this.done = new Event<TransportException>("kex done", TransportException.chainer, trans.getWriteLock());
}
/**
* Add a callback for host key verification.
* <p/>
* Any of the {@link HostKeyVerifier} implementations added this way can deem a host key to be acceptable, allowing
* key exchange to successfuly complete. Otherwise, a {@link TransportException} will result during key exchange.
*
* @param hkv object whose {@link HostKeyVerifier#verify} method will be invoked
*/
synchronized void addHostKeyVerifier(HostKeyVerifier hkv) {
hostVerifiers.add(hkv);
}
/**
* Returns the session identifier computed during key exchange.
*
* @return session identifier as a byte array
*/
byte[] getSessionID() {
return Arrays.copyOf(sessionID, sessionID.length);
}
/** @return Whether key exchange has been completed */
boolean isKexDone() {
return done.isSet();
}
/** @return Whether key exchange is currently ongoing */
boolean isKexOngoing() {
return kexOngoing.get();
}
/**
* Starts key exchange by sending a {@code SSH_MSG_KEXINIT} packet. Key exchange needs to be done once mandatorily
* after initializing the {@link Transport} for it to be usable and may be initiated at any later point e.g. if
* {@link Transport#getConfig() algorithms} have changed and should be renegotiated.
*
* @param waitForDone whether should block till key exchange completed
*
* @throws TransportException if there is an error during key exchange
* @see {@link Transport#setTimeout} for setting timeout for kex
*/
void startKex(boolean waitForDone) throws TransportException {
if (!kexOngoing.getAndSet(true)) {
done.clear();
sendKexInit();
}
if (waitForDone)
waitForDone();
}
void waitForDone() throws TransportException {
done.await(transport.getTimeout(), TimeUnit.SECONDS);
}
private synchronized void ensureKexOngoing() throws TransportException {
if (!isKexOngoing())
throw new TransportException(DisconnectReason.PROTOCOL_ERROR,
"Key exchange packet received when key exchange was not ongoing");
}
private static void ensureReceivedMatchesExpected(Message got, Message expected) throws TransportException {
if (got != expected)
throw new TransportException(DisconnectReason.PROTOCOL_ERROR, "Was expecting " + expected);
}
/**
* Sends SSH_MSG_KEXINIT and sets the {@link #kexInitSent} event.
*
* @throws TransportException
*/
private void sendKexInit() throws TransportException {
log.info("Sending SSH_MSG_KEXINIT");
clientProposal = new Proposal(transport.getConfig());
transport.write(clientProposal.getPacket());
kexInitSent.set();
}
private void sendNewKeys() throws TransportException {
log.info("Sending SSH_MSG_NEWKEYS");
transport.write(new SSHPacket(Message.NEWKEYS));
}
/**
* Tries to validate host key with all the host key verifiers known to this instance ( {@link #hostVerifiers})
*
* @param key the host key to verify
*
* @throws TransportException
*/
private synchronized void verifyHost(PublicKey key) throws TransportException {
for (HostKeyVerifier hkv : hostVerifiers) {
log.debug("Trying to verify host key with {}", hkv);
if (hkv.verify(transport.getRemoteHost(), transport.getRemotePort(), key))
return;
}
throw new TransportException(DisconnectReason.HOST_KEY_NOT_VERIFIABLE, "Could not verify `"
+ KeyType.fromKey(key) + "` host key with fingerprint `" + SecurityUtils.getFingerprint(key)
+ "` for `" + transport.getRemoteHost() + "` on port " + transport.getRemotePort());
}
private void setKexDone() {
kexOngoing.set(false);
kexInitSent.clear();
done.set();
}
private void gotKexInit(SSHPacket buf) throws TransportException {
Proposal serverProposal = new Proposal(buf);
negotiatedAlgs = clientProposal.negotiate(serverProposal);
log.debug("Negotiated algorithms: {}", negotiatedAlgs);
kex = Factory.Named.Util.create(transport.getConfig().getKeyExchangeFactories(), negotiatedAlgs
.getKeyExchangeAlgorithm());
kex.init(transport, transport.getServerID().getBytes(), transport.getClientID().getBytes(), buf
.getCompactData(), clientProposal.getPacket().getCompactData());
}
/**
* Private method used while putting new keys into use that will resize the key used to initialize the cipher to the
* needed length.
*
* @param E the key to resize
* @param blockSize the cipher block size
* @param hash the hash algorithm
* @param K the key exchange K parameter
* @param H the key exchange H parameter
*
* @return the resized key
*/
private static byte[] resizedKey(byte[] E, int blockSize, Digest hash, byte[] K, byte[] H) {
while (blockSize > E.length) {
Buffer.PlainBuffer buffer = new Buffer.PlainBuffer().putMPInt(K).putRawBytes(H).putRawBytes(E);
hash.update(buffer.array(), 0, buffer.available());
byte[] foo = hash.digest();
byte[] bar = new byte[E.length + foo.length];
System.arraycopy(E, 0, bar, 0, E.length);
System.arraycopy(foo, 0, bar, E.length, foo.length);
E = bar;
}
return E;
}
/* See Sec. 7.2. "Output from Key Exchange", RFC 4253 */
private void gotNewKeys() {
final Digest hash = kex.getHash();
if (sessionID == null)
// session id is 'H' from the first key exchange and does not change thereafter
sessionID = Arrays.copyOf(kex.getH(), kex.getH().length);
final Buffer.PlainBuffer hashInput = new Buffer.PlainBuffer() //
.putMPInt(kex.getK()) //
.putRawBytes(kex.getH()) //
.putByte((byte) 0) // <placeholder>
.putRawBytes(sessionID);
final int pos = hashInput.available() - sessionID.length - 1; // Position of <placeholder>
hashInput.array()[pos] = 'A';
hash.update(hashInput.array(), 0, hashInput.available());
final byte[] initialIV_C2S = hash.digest();
hashInput.array()[pos] = 'B';
hash.update(hashInput.array(), 0, hashInput.available());
final byte[] initialIV_S2C = hash.digest();
hashInput.array()[pos] = 'C';
hash.update(hashInput.array(), 0, hashInput.available());
final byte[] encryptionKey_C2S = hash.digest();
hashInput.array()[pos] = 'D';
hash.update(hashInput.array(), 0, hashInput.available());
final byte[] encryptionKey_S2C = hash.digest();
hashInput.array()[pos] = 'E';
hash.update(hashInput.array(), 0, hashInput.available());
final byte[] integrityKey_C2S = hash.digest();
hashInput.array()[pos] = 'F';
hash.update(hashInput.array(), 0, hashInput.available());
final byte[] integrityKey_S2C = hash.digest();
final Cipher cipher_C2S = Factory.Named.Util.create(transport.getConfig().getCipherFactories(), negotiatedAlgs
.getClient2ServerCipherAlgorithm());
cipher_C2S.init(Cipher.Mode.Encrypt, //
resizedKey(encryptionKey_C2S, cipher_C2S.getBlockSize(), hash, kex.getK(), kex.getH()), //
initialIV_C2S);
final Cipher cipher_S2C = Factory.Named.Util.create(transport.getConfig().getCipherFactories(), //
negotiatedAlgs.getServer2ClientCipherAlgorithm());
cipher_S2C.init(Cipher.Mode.Decrypt, //
resizedKey(encryptionKey_S2C, cipher_S2C.getBlockSize(), hash, kex.getK(), kex.getH()), //
initialIV_S2C);
final MAC mac_C2S = Factory.Named.Util.create(transport.getConfig().getMACFactories(), negotiatedAlgs
.getClient2ServerMACAlgorithm());
mac_C2S.init(integrityKey_C2S);
final MAC mac_S2C = Factory.Named.Util.create(transport.getConfig().getMACFactories(), //
negotiatedAlgs.getServer2ClientMACAlgorithm());
mac_S2C.init(integrityKey_S2C);
final Compression compression_S2C = Factory.Named.Util.create(transport.getConfig().getCompressionFactories(),
negotiatedAlgs.getServer2ClientCompressionAlgorithm());
final Compression compression_C2S = Factory.Named.Util.create(transport.getConfig().getCompressionFactories(),
negotiatedAlgs.getClient2ServerCompressionAlgorithm());
transport.getEncoder().setAlgorithms(cipher_C2S, mac_C2S, compression_C2S);
transport.getDecoder().setAlgorithms(cipher_S2C, mac_S2C, compression_S2C);
}
public void handle(Message msg, SSHPacket buf) throws TransportException {
switch (expected) {
case KEXINIT:
ensureReceivedMatchesExpected(msg, Message.KEXINIT);
log.info("Received SSH_MSG_KEXINIT");
startKex(false); // Will start key exchange if not already on
/*
* We block on this event to prevent a race condition where we may have received a SSH_MSG_KEXINIT before
* having sent the packet ourselves (would cause gotKexInit() to fail)
*/
kexInitSent.await(transport.getTimeout(), TimeUnit.SECONDS);
buf.rpos(buf.rpos() - 1);
gotKexInit(buf);
expected = Expected.FOLLOWUP;
break;
case FOLLOWUP:
ensureKexOngoing();
log.info("Received kex followup data");
if (kex.next(msg, buf)) {
verifyHost(kex.getHostKey());
sendNewKeys();
expected = Expected.NEWKEYS;
}
break;
case NEWKEYS:
ensureReceivedMatchesExpected(msg, Message.NEWKEYS);
ensureKexOngoing();
log.info("Received SSH_MSG_NEWKEYS");
gotNewKeys();
setKexDone();
expected = Expected.KEXINIT;
break;
default:
assert false;
}
}
public void notifyError(SSHException error) {
log.debug("Got notified of {}", error.toString());
FutureUtils.alertAll(error, kexInitSent, done);
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
public final class NegotiatedAlgorithms {
private final String kex;
private final String sig;
private final String c2sCipher;
private final String s2cCipher;
private final String c2sMAC;
private final String s2cMAC;
private final String c2sComp;
private final String s2cComp;
NegotiatedAlgorithms(String kex, String sig, String c2sCipher, String s2cCipher, String c2sMAC, String s2cMAC,
String c2sComp, String s2cComp) {
this.kex = kex;
this.sig = sig;
this.c2sCipher = c2sCipher;
this.s2cCipher = s2cCipher;
this.c2sMAC = c2sMAC;
this.s2cMAC = s2cMAC;
this.c2sComp = c2sComp;
this.s2cComp = s2cComp;
}
public String getKeyExchangeAlgorithm() {
return kex;
}
public String getSignatureAlgorithm() {
return sig;
}
public String getClient2ServerCipherAlgorithm() {
return c2sCipher;
}
public String getServer2ClientCipherAlgorithm() {
return s2cCipher;
}
public String getClient2ServerMACAlgorithm() {
return c2sMAC;
}
public String getServer2ClientMACAlgorithm() {
return s2cMAC;
}
public String getClient2ServerCompressionAlgorithm() {
return c2sComp;
}
public String getServer2ClientCompressionAlgorithm() {
return s2cComp;
}
@Override
public String toString() {
return ("[ " + //
"kex=" + kex + "; " + //
"sig=" + sig + "; " + //
"c2sCipher=" + c2sCipher + "; " + //
"s2cCipher=" + s2cCipher + "; " + //
"c2sMAC=" + c2sMAC + "; " + //
"s2cMAC=" + s2cMAC + "; " + //
"c2sComp=" + c2sComp + "; " + //
"s2cComp=" + s2cComp + //
" ]");
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHPacket;
import java.util.Arrays;
import java.util.List;
class Proposal {
private final List<String> kex;
private final List<String> sig;
private final List<String> c2sCipher;
private final List<String> s2cCipher;
private final List<String> c2sMAC;
private final List<String> s2cMAC;
private final List<String> c2sComp;
private final List<String> s2cComp;
private final SSHPacket packet;
public Proposal(Config config) {
kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories());
sig = Factory.Named.Util.getNames(config.getSignatureFactories());
c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories());
c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories());
c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories());
packet = new SSHPacket(Message.KEXINIT);
// Put cookie
packet.ensureCapacity(16);
config.getRandomFactory().create().fill(packet.array(), packet.wpos(), 16);
packet.wpos(packet.wpos() + 16);
// Put algorithm lists
packet.putString(toCommaString(kex));
packet.putString(toCommaString(sig));
packet.putString(toCommaString(c2sCipher));
packet.putString(toCommaString(s2cCipher));
packet.putString(toCommaString(c2sMAC));
packet.putString(toCommaString(s2cMAC));
packet.putString(toCommaString(c2sComp));
packet.putString(toCommaString(s2cComp));
packet.putString("");
packet.putString("");
packet.putBoolean(false); // Optimistic next packet does not follow
packet.putInt(0); // "Reserved" for future by spec
}
public Proposal(SSHPacket packet) {
this.packet = packet;
final int savedPos = packet.rpos();
packet.rpos(packet.rpos() + 17); // Skip message ID & cookie
kex = fromCommaString(packet.readString());
sig = fromCommaString(packet.readString());
c2sCipher = fromCommaString(packet.readString());
s2cCipher = fromCommaString(packet.readString());
c2sMAC = fromCommaString(packet.readString());
s2cMAC = fromCommaString(packet.readString());
c2sComp = fromCommaString(packet.readString());
s2cComp = fromCommaString(packet.readString());
packet.rpos(savedPos);
}
public List<String> getKeyExchangeAlgorithms() {
return kex;
}
public List<String> getSignatureAlgorithms() {
return sig;
}
public List<String> getClient2ServerCipherAlgorithms() {
return c2sCipher;
}
public List<String> getServer2ClientCipherAlgorithms() {
return s2cCipher;
}
public List<String> getClient2ServerMACAlgorithms() {
return c2sMAC;
}
public List<String> getServer2ClientMACAlgorithms() {
return s2cMAC;
}
public List<String> getClient2ServerCompressionAlgorithms() {
return c2sComp;
}
public List<String> getServer2ClientCompressionAlgorithms() {
return s2cComp;
}
public SSHPacket getPacket() {
return new SSHPacket(packet);
}
public NegotiatedAlgorithms negotiate(Proposal other) throws TransportException {
return new NegotiatedAlgorithms(
firstMatch(this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()), //
firstMatch(this.getSignatureAlgorithms(), other.getSignatureAlgorithms()), //
firstMatch(this.getClient2ServerCipherAlgorithms(), other.getClient2ServerCipherAlgorithms()), //
firstMatch(this.getServer2ClientCipherAlgorithms(), other.getServer2ClientCipherAlgorithms()), //
firstMatch(this.getClient2ServerMACAlgorithms(), other.getClient2ServerMACAlgorithms()), //
firstMatch(this.getServer2ClientMACAlgorithms(), other.getServer2ClientMACAlgorithms()), //
firstMatch(this.getClient2ServerCompressionAlgorithms(), other.getClient2ServerCompressionAlgorithms()), //
firstMatch(this.getServer2ClientCompressionAlgorithms(), other.getServer2ClientCompressionAlgorithms()) //
);
}
private static String firstMatch(List<String> a, List<String> b) throws TransportException {
for (String aa : a)
for (String bb : b)
if (aa.equals(bb))
return aa;
throw new TransportException("Unable to reach a settlement: " + a + " and " + b);
}
private static String toCommaString(List<String> sl) {
StringBuilder sb = new StringBuilder();
int i = 0;
for (String s : sl) {
if (i++ != 0)
sb.append(",");
sb.append(s);
}
return sb.toString();
}
private static List<String> fromCommaString(String s) {
return Arrays.asList(s.split(","));
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
final class Reader extends Thread {
private final Logger log = LoggerFactory.getLogger(getClass());
private final TransportProtocol trans;
Reader(TransportProtocol trans) {
this.trans = trans;
setName("reader");
}
@Override
public void run() {
final Thread curThread = Thread.currentThread();
try {
final Decoder decoder = trans.getDecoder();
final InputStream inp = trans.getConnInfo().in;
final byte[] recvbuf = new byte[decoder.getMaxPacketLength()];
int needed = 1;
while (!curThread.isInterrupted()) {
int read = inp.read(recvbuf, 0, needed);
if (read == -1)
throw new TransportException("Broken transport; encountered EOF");
else
needed = decoder.received(recvbuf, read);
}
} catch (Exception e) {
if (curThread.isInterrupted()) {
// We are meant to shut up and draw to a close if interrupted
} else
trans.die(e);
}
log.debug("Stopping");
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.sshj.Config;
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.HostKeyVerifier;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/** Transport layer of the SSH protocol. */
public interface Transport extends SSHPacketHandler {
/**
* Sets the {@code socket} to be used by this transport; and identification information is exchanged. A {@link
* TransportException} is thrown in case of SSH protocol version incompatibility.
*
* @throws TransportException if there is an error during exchange of identification information
*/
void init(String host, int port, InputStream in, OutputStream out) throws TransportException;
void addHostKeyVerifier(HostKeyVerifier hkv);
void doKex() throws TransportException;
/** @return the version string used by this client to identify itself to an SSH server, e.g. "SSHJ_3_0" */
String getClientVersion();
/** @return the {@link net.schmizz.sshj.ConfigImpl} associated with this transport. */
Config getConfig();
/** @return the timeout that is currently set for blocking operations. */
int getTimeout();
/**
* Set a timeout for method that may block, e.g. {@link #reqService(net.schmizz.sshj.Service)}, {@link
* KeyExchanger#waitForDone()}.
*
* @param timeout the timeout in seconds
*/
void setTimeout(int timeout);
int getHeartbeatInterval();
void setHeartbeatInterval(int interval);
/** Returns the hostname to which this transport is connected. */
String getRemoteHost();
/** Returns the port number on the {@link #getRemoteHost() remote host} to which this transport is connected. */
int getRemotePort();
/**
* Returns the version string as sent by the SSH server for identification purposes, e.g. "OpenSSH_$version".
* <p/>
* If the transport has not yet been initialized via {@link #init}, it will be {@code null}.
*
* @return server's version string (may be {@code null})
*/
String getServerVersion();
byte[] getSessionID();
/** Returns the currently active {@link net.schmizz.sshj.Service} instance. */
Service getService();
/**
* Request a SSH service represented by a {@link net.schmizz.sshj.Service} instance. A separate call to {@link
* #setService} is not needed.
*
* @param service the SSH service to be requested
*
* @throws IOException if the request failed for any reason
*/
void reqService(Service service) throws TransportException;
/**
* Sets the currently active {@link net.schmizz.sshj.Service}. Handling of non-transport-layer packets is {@link
* net.schmizz.sshj.Service#handle delegated} to that service.
* <p/>
* For this method to be successful, at least one service request via {@link #reqService} must have been successful
* (not necessarily for the service being set).
*
* @param service (null-ok) the {@link net.schmizz.sshj.Service}
*/
void setService(Service service);
/** Returns whether the transport thinks it is authenticated. */
boolean isAuthenticated();
/**
* Informs this transport that authentication has been completed. This method <strong>must</strong> be called after
* successful authentication, so that delayed compression may become effective if applicable.
*/
void setAuthenticated();
/**
* Sends SSH_MSG_UNIMPLEMENTED in response to the last packet received.
*
* @return the sequence number of the packet sent
*
* @throws TransportException if an error occured sending the packet
*/
long sendUnimplemented() throws TransportException;
/**
* Returns whether this transport is active.
* <p/>
* The transport is considered to be running if it has been initialized without error via {@link #init} and has not
* been disconnected.
*/
boolean isRunning();
/**
* Joins the thread calling this method to the transport's death. The transport dies of exceptional events.
*
* @throws TransportException
*/
void join() throws TransportException;
/** Send a disconnection packet with reason as {@link DisconnectReason#BY_APPLICATION}, and closes this transport. */
void disconnect();
/**
* Send a disconnect packet with the given {@link net.schmizz.sshj.common.DisconnectReason reason}, and closes this
* transport.
*/
void disconnect(DisconnectReason reason);
/**
* Send a disconnect packet with the given {@link DisconnectReason reason} and {@code message}, and closes this
* transport.
*
* @param reason the reason code for this disconnect
* @param message the text message
*/
void disconnect(DisconnectReason reason, String message);
/**
* Write a packet over this transport.
* <p/>
* The {@code payload} {@link net.schmizz.sshj.common.SSHPacket} should have 5 bytes free at the beginning to avoid
* a performance penalty associated with making space for header bytes (packet length, padding length).
*
* @param payload the {@link net.schmizz.sshj.common.SSHPacket} containing data to send
*
* @return sequence number of the sent packet
*
* @throws TransportException if an error occurred sending the packet
*/
long write(SSHPacket payload) throws TransportException;
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.concurrent.ExceptionChainer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHException;
/** Transport-layer exception */
public class TransportException extends SSHException {
/** @see {@link net.schmizz.concurrent.ExceptionChainer} */
public static final ExceptionChainer<TransportException> chainer = new ExceptionChainer<TransportException>() {
public TransportException chain(Throwable t) {
if (t instanceof TransportException)
return (TransportException) t;
else
return new TransportException(t);
}
};
public TransportException() {
super();
}
public TransportException(DisconnectReason code) {
super(code);
}
public TransportException(DisconnectReason code, String message) {
super(code, message);
}
public TransportException(DisconnectReason code, String message, Throwable cause) {
super(code, message, cause);
}
public TransportException(DisconnectReason code, Throwable cause) {
super(code, cause);
}
public TransportException(String message) {
super(message);
}
public TransportException(String message, Throwable cause) {
super(message, cause);
}
public TransportException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,548 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport;
import net.schmizz.concurrent.Event;
import net.schmizz.concurrent.FutureUtils;
import net.schmizz.sshj.AbstractService;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.Service;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.DisconnectReason;
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.HostKeyVerifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/** A thread-safe {@link Transport} implementation. */
public final class TransportProtocol implements Transport {
private static final class NullService extends AbstractService {
NullService(Transport trans) {
super("null-service", trans);
}
}
static final class ConnInfo {
final String host;
final int port;
final InputStream in;
final OutputStream out;
public ConnInfo(String host, int port, InputStream in, OutputStream out) {
this.host = host;
this.port = port;
this.in = in;
this.out = out;
}
}
private final Logger log = LoggerFactory.getLogger(getClass());
private final Service nullService = new NullService(this);
private final Config config;
private final KeyExchanger kexer;
private final Reader reader;
private final Heartbeater heartbeater;
private final Encoder encoder;
private final Decoder decoder;
private final Event<TransportException> serviceAccept = new Event<TransportException>("service accept",
TransportException.chainer);
private final Event<TransportException> close = new Event<TransportException>("transport close",
TransportException.chainer);
/** Client version identification string */
private final String clientID;
private volatile int timeout = 30;
private volatile boolean authed = false;
/** Currently active service e.g. UserAuthService, ConnectionService */
private volatile Service service = nullService;
private ConnInfo connInfo;
/** Server version identification string */
private String serverID;
/** Message identifier of last packet received */
private Message msg;
private final ReentrantLock writeLock = new ReentrantLock();
public TransportProtocol(Config config) {
this.config = config;
this.reader = new Reader(this);
this.heartbeater = new Heartbeater(this);
this.encoder = new Encoder(config.getRandomFactory().create(), writeLock);
this.decoder = new Decoder(this);
this.kexer = new KeyExchanger(this);
clientID = "SSH-2.0-" + config.getVersion();
}
public void init(String remoteHost, int remotePort, InputStream in, OutputStream out) throws TransportException {
connInfo = new ConnInfo(remoteHost, remotePort, in, out);
try {
log.info("Client identity string: {}", clientID);
connInfo.out.write((clientID + "\r\n").getBytes());
// Read server's ID
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
while ((serverID = readIdentification(buf)).isEmpty()) {
buf.putByte((byte) connInfo.in.read());
}
log.info("Server identity string: {}", serverID);
} catch (IOException e) {
throw new TransportException(e);
}
reader.start();
}
/**
* Reads the identification string from the SSH server. This is the very first string that is sent upon connection
* by the server. It takes the form of, e.g. "SSH-2.0-OpenSSH_ver".
* <p/>
* Several concerns are taken care of here, e.g. verifying protocol version, correct line endings as specified in
* RFC and such.
* <p/>
* This is not efficient but is only done once.
*
* @param buffer
*
* @return
*
* @throws IOException
*/
private String readIdentification(Buffer.PlainBuffer buffer) throws IOException {
String ident;
byte[] data = new byte[256];
for (; ;) {
int savedBufPos = buffer.rpos();
int pos = 0;
boolean needLF = false;
for (; ;) {
if (buffer.available() == 0) {
// Need more data, so undo reading and return null
buffer.rpos(savedBufPos);
return "";
}
byte b = buffer.readByte();
if (b == '\r') {
needLF = true;
continue;
}
if (b == '\n')
break;
if (needLF)
throw new TransportException("Incorrect identification: bad line ending");
if (pos >= data.length)
throw new TransportException("Incorrect identification: line too long");
data[pos++] = b;
}
ident = new String(data, 0, pos);
if (ident.startsWith("SSH-"))
break;
if (buffer.rpos() > 16 * 1024)
throw new TransportException("Incorrect identification: too many header lines");
}
if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
"Server does not support SSHv2, identified as: " + ident);
return ident;
}
public void addHostKeyVerifier(HostKeyVerifier hkv) {
kexer.addHostKeyVerifier(hkv);
}
public void doKex() throws TransportException {
kexer.startKex(true);
}
public boolean isKexDone() {
return kexer.isKexDone();
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getHeartbeatInterval() {
return heartbeater.getInterval();
}
public void setHeartbeatInterval(int interval) {
heartbeater.setInterval(interval);
}
public String getRemoteHost() {
return connInfo.host;
}
public int getRemotePort() {
return connInfo.port;
}
public String getClientVersion() {
return clientID.substring(8);
}
public Config getConfig() {
return config;
}
public String getServerVersion() {
return serverID == null ? serverID : serverID.substring(8);
}
public byte[] getSessionID() {
return kexer.getSessionID();
}
public synchronized Service getService() {
return service;
}
public synchronized void setService(Service service) {
if (service == null)
service = nullService;
log.info("Setting active service to {}", service.getName());
this.service = service;
}
public void reqService(Service service) throws TransportException {
serviceAccept.lock();
try {
serviceAccept.clear();
sendServiceRequest(service.getName());
serviceAccept.await(timeout, TimeUnit.SECONDS);
setService(service);
} finally {
serviceAccept.unlock();
}
}
/**
* Sends a service request for the specified service
*
* @param serviceName name of the service being requested
*
* @throws TransportException if there is an error while sending the request
*/
private void sendServiceRequest(String serviceName) throws TransportException {
log.debug("Sending SSH_MSG_SERVICE_REQUEST for {}", serviceName);
write(new SSHPacket(Message.SERVICE_REQUEST).putString(serviceName));
}
public void setAuthenticated() {
this.authed = true;
encoder.setAuthenticated();
decoder.setAuthenticated();
}
public boolean isAuthenticated() {
return authed;
}
public long sendUnimplemented() throws TransportException {
final long seq = decoder.getSequenceNumber();
log.info("Sending SSH_MSG_UNIMPLEMENTED for packet #{}", seq);
return write(new SSHPacket(Message.UNIMPLEMENTED).putInt(seq));
}
public void join() throws TransportException {
close.await();
}
public boolean isRunning() {
return reader.isAlive() && !close.isSet();
}
public void disconnect() {
disconnect(DisconnectReason.BY_APPLICATION);
}
public void disconnect(DisconnectReason reason) {
disconnect(reason, "");
}
public void disconnect(DisconnectReason reason, String message) {
close.lock(); // CAS type operation on close
try {
try {
service.notifyDisconnect();
} catch (SSHException logged) {
log.warn("{} did not handle disconnect cleanly: {}", service, logged);
}
if (!close.isSet()) {
sendDisconnect(reason, message);
finishOff();
close.set();
}
} finally {
close.unlock();
}
}
public long write(SSHPacket payload) throws TransportException {
writeLock.lock();
try {
if (kexer.isKexOngoing()) {
// Only transport layer packets (1 to 49) allowed except SERVICE_REQUEST
final Message m = Message.fromByte(payload.array()[payload.rpos()]);
if (!m.in(1, 49) || m == Message.SERVICE_REQUEST) {
assert m != Message.KEXINIT;
kexer.waitForDone();
}
} else if (encoder.getSequenceNumber() == 0) // We get here every 2**32th packet
kexer.startKex(true);
final long seq = encoder.encode(payload);
try {
connInfo.out.write(payload.array(), payload.rpos(), payload.available());
connInfo.out.flush();
} catch (IOException ioe) {
throw new TransportException(ioe);
}
return seq;
} finally {
writeLock.unlock();
}
}
private void sendDisconnect(DisconnectReason reason, String message) {
if (message == null)
message = "";
log.debug("Sending SSH_MSG_DISCONNECT: reason=[{}], msg=[{}]", reason, message);
try {
write(new SSHPacket(Message.DISCONNECT)
.putInt(reason.toInt())
.putString(message)
.putString(""));
} catch (IOException logged) {
log.warn("Error writing packet: {}", logged);
}
}
/**
* This is where all incoming packets are handled. If they pertain to the transport layer, they are handled here;
* otherwise they are delegated to the active service instance if any via {@link Service#handle}.
* <p/>
* Even among the transport layer specific packets, key exchange packets are delegated to {@link
* KeyExchanger#handle}.
* <p/>
* This method is called in the context of the {@link #reader} thread via {@link Decoder#received} when a full
* packet has been decoded.
*
* @param msg the message identifer
* @param buf buffer containg rest of the packet
*
* @throws SSHException if an error occurs during handling (unrecoverable)
*/
public void handle(Message msg, SSHPacket buf) throws SSHException {
this.msg = msg;
log.trace("Received packet {}", msg);
if (msg.geq(50)) // not a transport layer packet
service.handle(msg, buf);
else if (msg.in(20, 21) || msg.in(30, 49)) // kex packet
kexer.handle(msg, buf);
else
switch (msg) {
case DISCONNECT: {
gotDisconnect(buf);
break;
}
case IGNORE: {
log.info("Received SSH_MSG_IGNORE");
break;
}
case UNIMPLEMENTED: {
gotUnimplemented(buf);
break;
}
case DEBUG: {
gotDebug(buf);
break;
}
case SERVICE_ACCEPT: {
gotServiceAccept();
break;
}
default:
sendUnimplemented();
}
}
private void gotDebug(SSHPacket buf) {
boolean display = buf.readBoolean();
String message = buf.readString();
log.info("Received SSH_MSG_DEBUG (display={}) '{}'", display, message);
}
private void gotDisconnect(SSHPacket buf) throws TransportException {
DisconnectReason code = DisconnectReason.fromInt(buf.readInt());
String message = buf.readString();
log.info("Received SSH_MSG_DISCONNECT (reason={}, msg={})", code, message);
throw new TransportException(code, "Disconnected; server said: " + message);
}
private void gotServiceAccept() throws TransportException {
serviceAccept.lock();
try {
if (!serviceAccept.hasWaiters())
throw new TransportException(DisconnectReason.PROTOCOL_ERROR,
"Got a service accept notification when none was awaited");
serviceAccept.set();
} finally {
serviceAccept.unlock();
}
}
/**
* Got an SSH_MSG_UNIMPLEMENTED, so lets see where we're at and act accordingly.
*
* @param buf
*
* @throws TransportException
*/
private void gotUnimplemented(SSHPacket buf) throws SSHException {
long seqNum = buf.readLong();
log.info("Received SSH_MSG_UNIMPLEMENTED #{}", seqNum);
if (kexer.isKexOngoing())
throw new TransportException("Received SSH_MSG_UNIMPLEMENTED while exchanging keys");
getService().notifyUnimplemented(seqNum);
}
private void finishOff() {
reader.interrupt();
heartbeater.interrupt();
IOUtils.closeQuietly(connInfo.in);
IOUtils.closeQuietly(connInfo.out);
}
void die(Exception ex) {
close.lock();
try {
if (!close.isSet()) {
log.error("Dying because - {}", ex.toString());
final SSHException causeOfDeath = SSHException.chainer.chain(ex);
FutureUtils.alertAll(causeOfDeath, close, serviceAccept);
kexer.notifyError(causeOfDeath);
getService().notifyError(causeOfDeath);
setService(nullService);
{ // Perhaps can send disconnect packet to server
final boolean didNotReceiveDisconnect = msg != Message.DISCONNECT;
final boolean gotRequiredInfo = causeOfDeath.getDisconnectReason() != DisconnectReason.UNKNOWN;
if (didNotReceiveDisconnect && gotRequiredInfo)
sendDisconnect(causeOfDeath.getDisconnectReason(), causeOfDeath.getMessage());
}
finishOff();
close.set();
}
} finally {
close.unlock();
}
}
String getClientID() {
return clientID;
}
String getServerID() {
return serverID;
}
Encoder getEncoder() {
return encoder;
}
Decoder getDecoder() {
return decoder;
}
ReentrantLock getWriteLock() {
return writeLock;
}
ConnInfo getConnInfo() {
return connInfo;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** AES128CBC cipher */
public class AES128CBC extends BaseCipher {
/** Named factory for AES128CBC Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new AES128CBC();
}
public String getName() {
return "aes128-cbc";
}
}
public AES128CBC() {
super(16, 16, "AES", "AES/CBC/NoPadding");
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** {@code aes128-ctr} cipher */
public class AES128CTR extends BaseCipher {
/** Named factory for AES128CBC Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new AES128CTR();
}
public String getName() {
return "aes128-ctr";
}
}
public AES128CTR() {
super(16, 16, "AES", "AES/CTR/NoPadding");
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** {@code aes192-cbc} cipher */
public class AES192CBC extends BaseCipher {
/** Named factory for AES192CBC Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new AES192CBC();
}
public String getName() {
return "aes192-cbc";
}
}
public AES192CBC() {
super(16, 24, "AES", "AES/CBC/NoPadding");
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** {@code aes192-ctr} cipher */
public class AES192CTR extends BaseCipher {
/** Named factory for AES192CTR Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new AES192CTR();
}
public String getName() {
return "aes192-ctr";
}
}
public AES192CTR() {
super(16, 24, "AES", "AES/CTR/NoPadding");
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** {@code aes256-ctr} cipher */
public class AES256CBC extends BaseCipher {
/** Named factory for AES256CBC Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new AES256CBC();
}
public String getName() {
return "aes256-cbc";
}
}
public AES256CBC() {
super(16, 32, "AES", "AES/CBC/NoPadding");
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** {@code aes256-ctr} cipher */
public class AES256CTR extends BaseCipher {
/** Named factory for AES256CBC Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new AES256CTR();
}
public String getName() {
return "aes256-ctr";
}
}
public AES256CTR() {
super(16, 32, "AES", "AES/CTR/NoPadding");
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
/** Base class for all Cipher implementations delegating to the JCE provider. */
public class BaseCipher implements Cipher {
private static byte[] resize(byte[] data, int size) {
if (data.length > size) {
final byte[] tmp = new byte[size];
System.arraycopy(data, 0, tmp, 0, size);
data = tmp;
}
return data;
}
private final int ivsize;
private final int bsize;
private final String algorithm;
private final String transformation;
private javax.crypto.Cipher cipher;
public BaseCipher(int ivsize, int bsize, String algorithm, String transformation) {
this.ivsize = ivsize;
this.bsize = bsize;
this.algorithm = algorithm;
this.transformation = transformation;
}
public int getBlockSize() {
return bsize;
}
public int getIVSize() {
return ivsize;
}
public void init(Mode mode, byte[] key, byte[] iv) {
key = BaseCipher.resize(key, bsize);
iv = BaseCipher.resize(iv, ivsize);
try {
cipher = SecurityUtils.getCipher(transformation);
cipher.init((mode == Mode.Encrypt ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE),
new SecretKeySpec(key, algorithm), new IvParameterSpec(iv));
} catch (GeneralSecurityException e) {
cipher = null;
throw new SSHRuntimeException(e);
}
}
public void update(byte[] input, int inputOffset, int inputLen) {
try {
cipher.update(input, inputOffset, inputLen, input, inputOffset);
} catch (ShortBufferException e) {
throw new SSHRuntimeException(e);
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** {@code blowfish-ctr} cipher */
public class BlowfishCBC extends BaseCipher {
/** Named factory for BlowfishCBC Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new BlowfishCBC();
}
public String getName() {
return "blowfish-cbc";
}
}
public BlowfishCBC() {
super(8, 16, "Blowfish", "Blowfish/CBC/NoPadding");
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** Wrapper for a cryptographic cipher, used either for encryption or decryption. */
public interface Cipher {
enum Mode {
Encrypt, Decrypt
}
/**
* Retrieves the block size for this cipher
*
* @return
*/
int getBlockSize();
/**
* Retrieves the size of the initialization vector
*
* @return
*/
int getIVSize();
/**
* Initialize the cipher for encryption or decryption with the given private key and initialization vector
*
* @param mode
* @param key
* @param iv
*/
void init(Mode mode, byte[] key, byte[] iv);
/**
* Performs in-place encryption or decryption on the given data.
*
* @param input
* @param inputOffset
* @param inputLen
*/
void update(byte[] input, int inputOffset, int inputLen);
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** Represents a no-op cipher. */
public class NoneCipher implements Cipher {
/** Named factory for the no-op Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new NoneCipher();
}
public String getName() {
return "none";
}
}
public int getBlockSize() {
return 8;
}
public int getIVSize() {
return 8;
}
public void init(Mode mode, byte[] bytes, byte[] bytes1) {
}
public void update(byte[] input, int inputOffset, int inputLen) {
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.cipher;
/** {@code 3des-cbc} cipher */
public class TripleDESCBC extends BaseCipher {
/** Named factory for TripleDESCBC Cipher */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Cipher> {
public Cipher create() {
return new TripleDESCBC();
}
public String getName() {
return "3des-cbc";
}
}
public TripleDESCBC() {
super(8, 24, "DESede", "DESede/CBC/NoPadding");
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.compression;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.TransportException;
/** Interface used to compress the stream of data between the SSH server and clients. */
public interface Compression {
/** Enum identifying if this object will be used to compress or uncompress data. */
enum Type {
Inflater, Deflater
}
/**
* Compress the given buffer in place.
*
* @param buffer the buffer containing the data to compress s
*/
void compress(SSHPacket buffer);
/**
* Initialize this object to either compress or uncompress data. This method must be called prior to any calls to
* either <code>compress</code> or <code>uncompress</code>. Once the object has been initialized, only one of
* <code>compress</code> or <code>uncompress</code> method can be called.
*
* @param type
* @param level
*/
void init(Type type, int level);
/**
* Delayed compression is an Open-SSH specific feature which informs both the client and server to not compress data
* before the session has been authenticated.
*
* @return if the compression is delayed after authentication or not
*/
boolean isDelayed();
/**
* Uncompress the data in a buffer into another buffer.
*
* @param from the buffer containing the data to uncompress
* @param to the buffer receiving the uncompressed data
*/
void uncompress(SSHPacket from, SSHPacket to) throws TransportException;
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.compression;
/**
* ZLib delayed compression.
*
* @see Compression#isDelayed()
*/
public class DelayedZlibCompression extends ZlibCompression {
/** Named factory for the ZLib Delayed Compression. */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Compression> {
public Compression create() {
return new DelayedZlibCompression();
}
public String getName() {
return "zlib@openssh.com";
}
}
@Override
public boolean isDelayed() {
return true;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* 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.
*
* This file may incorporate work covered by the following copyright and
* permission notice:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.schmizz.sshj.transport.compression;
/** No-op <code>Compression</code>. */
public abstract class NoneCompression implements Compression {
/** Named factory for the no-op <code>Compression</code> */
public static class Factory implements net.schmizz.sshj.common.Factory.Named<Compression> {
public Compression create() {
return null;
}
public String getName() {
return "none";
}
}
}

Some files were not shown because too many files have changed in this diff Show More