From 1595e7a99c5540d31397f89eee5183b8eed75933 Mon Sep 17 00:00:00 2001
From: Shikhar Bhushan
Date: Sat, 27 Feb 2010 15:36:58 +0100
Subject: [PATCH] import local
---
LICENSE | 202 ++
NOTICE | 8 +
README | 9 +
buildfile | 27 +
src/main/java/examples/Exec.java | 48 +
src/main/java/examples/LocalPF.java | 55 +
src/main/java/examples/MTTest.java | 56 +
src/main/java/examples/RemotePF.java | 62 +
src/main/java/examples/RudimentaryPTY.java | 76 +
src/main/java/examples/SCPDownload.java | 43 +
src/main/java/examples/SCPUpload.java | 46 +
src/main/java/examples/SFTPDownload.java | 42 +
src/main/java/examples/SFTPUpload.java | 40 +
src/main/java/examples/X11.java | 72 +
.../java/net/schmizz/concurrent/Event.java | 84 +
.../schmizz/concurrent/ExceptionChainer.java | 40 +
.../java/net/schmizz/concurrent/Future.java | 204 ++
.../net/schmizz/concurrent/FutureUtils.java | 32 +
.../net/schmizz/sshj/AbstractService.java | 83 +
src/main/java/net/schmizz/sshj/Config.java | 145 ++
.../java/net/schmizz/sshj/ConfigImpl.java | 152 ++
.../java/net/schmizz/sshj/DefaultConfig.java | 168 ++
src/main/java/net/schmizz/sshj/SSHClient.java | 642 +++++++
src/main/java/net/schmizz/sshj/Service.java | 50 +
.../java/net/schmizz/sshj/SocketClient.java | 206 ++
.../java/net/schmizz/sshj/common/Base64.java | 1705 +++++++++++++++++
.../java/net/schmizz/sshj/common/Buffer.java | 531 +++++
.../schmizz/sshj/common/ByteArrayUtils.java | 146 ++
.../schmizz/sshj/common/DisconnectReason.java | 56 +
.../schmizz/sshj/common/ErrorNotifiable.java | 41 +
.../java/net/schmizz/sshj/common/Factory.java | 119 ++
.../java/net/schmizz/sshj/common/IOUtils.java | 58 +
.../java/net/schmizz/sshj/common/KeyType.java | 74 +
.../java/net/schmizz/sshj/common/Message.java | 106 +
.../net/schmizz/sshj/common/SSHException.java | 122 ++
.../net/schmizz/sshj/common/SSHPacket.java | 96 +
.../schmizz/sshj/common/SSHPacketHandler.java | 35 +
.../sshj/common/SSHRuntimeException.java | 59 +
.../schmizz/sshj/common/SecurityUtils.java | 277 +++
.../net/schmizz/sshj/common/StreamCopier.java | 128 ++
.../schmizz/sshj/connection/Connection.java | 150 ++
.../sshj/connection/ConnectionException.java | 86 +
.../sshj/connection/ConnectionProtocol.java | 234 +++
.../connection/channel/AbstractChannel.java | 382 ++++
.../sshj/connection/channel/Channel.java | 148 ++
.../channel/ChannelInputStream.java | 171 ++
.../channel/ChannelOutputStream.java | 158 ++
.../connection/channel/OpenFailException.java | 100 +
.../sshj/connection/channel/Window.java | 113 ++
.../channel/direct/AbstractDirectChannel.java | 101 +
.../channel/direct/LocalPortForwarder.java | 147 ++
.../connection/channel/direct/PTYMode.java | 179 ++
.../connection/channel/direct/Session.java | 248 +++
.../channel/direct/SessionChannel.java | 219 +++
.../channel/direct/SessionFactory.java | 34 +
.../connection/channel/direct/Signal.java | 70 +
.../forwarded/AbstractForwardedChannel.java | 87 +
.../AbstractForwardedChannelOpener.java | 97 +
.../channel/forwarded/ConnectListener.java | 36 +
.../forwarded/ForwardedChannelOpener.java | 36 +
.../forwarded/RemotePortForwarder.java | 236 +++
.../SocketForwardingConnectListener.java | 76 +
.../channel/forwarded/X11Forwarder.java | 65 +
.../net/schmizz/sshj/sftp/FileAttributes.java | 229 +++
.../java/net/schmizz/sshj/sftp/FileMode.java | 97 +
.../java/net/schmizz/sshj/sftp/OpenMode.java | 60 +
.../net/schmizz/sshj/sftp/PacketReader.java | 103 +
.../net/schmizz/sshj/sftp/PacketType.java | 71 +
.../net/schmizz/sshj/sftp/PathComponents.java | 71 +
.../net/schmizz/sshj/sftp/PathHelper.java | 59 +
.../sshj/sftp/RandomAccessRemoteFile.java | 272 +++
.../schmizz/sshj/sftp/RemoteDirectory.java | 60 +
.../net/schmizz/sshj/sftp/RemoteFile.java | 175 ++
.../net/schmizz/sshj/sftp/RemoteResource.java | 57 +
.../sshj/sftp/RemoteResourceFilter.java | 22 +
.../schmizz/sshj/sftp/RemoteResourceInfo.java | 80 +
.../java/net/schmizz/sshj/sftp/Request.java | 52 +
.../java/net/schmizz/sshj/sftp/Requester.java | 26 +
.../java/net/schmizz/sshj/sftp/Response.java | 90 +
.../net/schmizz/sshj/sftp/SFTPClient.java | 175 ++
.../net/schmizz/sshj/sftp/SFTPEngine.java | 215 +++
.../net/schmizz/sshj/sftp/SFTPException.java | 80 +
.../schmizz/sshj/sftp/SFTPFileTransfer.java | 251 +++
.../net/schmizz/sshj/sftp/SFTPPacket.java | 66 +
.../schmizz/sshj/sftp/StatefulSFTPClient.java | 150 ++
.../sshj/signature/AbstractSignature.java | 100 +
.../net/schmizz/sshj/signature/Signature.java | 89 +
.../schmizz/sshj/signature/SignatureDSA.java | 125 ++
.../schmizz/sshj/signature/SignatureRSA.java | 80 +
.../net/schmizz/sshj/transport/Converter.java | 91 +
.../net/schmizz/sshj/transport/Decoder.java | 205 ++
.../net/schmizz/sshj/transport/Encoder.java | 167 ++
.../schmizz/sshj/transport/Heartbeater.java | 98 +
.../schmizz/sshj/transport/KeyExchanger.java | 365 ++++
.../sshj/transport/NegotiatedAlgorithms.java | 107 ++
.../net/schmizz/sshj/transport/Proposal.java | 176 ++
.../net/schmizz/sshj/transport/Reader.java | 86 +
.../net/schmizz/sshj/transport/Transport.java | 190 ++
.../sshj/transport/TransportException.java | 87 +
.../sshj/transport/TransportProtocol.java | 548 ++++++
.../sshj/transport/cipher/AES128CBC.java | 56 +
.../sshj/transport/cipher/AES128CTR.java | 56 +
.../sshj/transport/cipher/AES192CBC.java | 56 +
.../sshj/transport/cipher/AES192CTR.java | 56 +
.../sshj/transport/cipher/AES256CBC.java | 56 +
.../sshj/transport/cipher/AES256CTR.java | 56 +
.../sshj/transport/cipher/BaseCipher.java | 101 +
.../sshj/transport/cipher/BlowfishCBC.java | 56 +
.../schmizz/sshj/transport/cipher/Cipher.java | 77 +
.../sshj/transport/cipher/NoneCipher.java | 66 +
.../sshj/transport/cipher/TripleDESCBC.java | 56 +
.../transport/compression/Compression.java | 82 +
.../compression/DelayedZlibCompression.java | 61 +
.../compression/NoneCompression.java | 52 +
.../compression/ZlibCompression.java | 123 ++
.../sshj/transport/digest/BaseDigest.java | 87 +
.../schmizz/sshj/transport/digest/Digest.java | 51 +
.../schmizz/sshj/transport/digest/MD5.java | 57 +
.../schmizz/sshj/transport/digest/SHA1.java | 57 +
.../sshj/transport/kex/AbstractDHG.java | 144 ++
.../net/schmizz/sshj/transport/kex/DH.java | 133 ++
.../net/schmizz/sshj/transport/kex/DHG1.java | 64 +
.../net/schmizz/sshj/transport/kex/DHG14.java | 65 +
.../sshj/transport/kex/DHGroupData.java | 104 +
.../sshj/transport/kex/KeyExchange.java | 87 +
.../schmizz/sshj/transport/mac/BaseMAC.java | 117 ++
.../schmizz/sshj/transport/mac/HMACMD5.java | 56 +
.../schmizz/sshj/transport/mac/HMACMD596.java | 57 +
.../schmizz/sshj/transport/mac/HMACSHA1.java | 56 +
.../sshj/transport/mac/HMACSHA196.java | 56 +
.../net/schmizz/sshj/transport/mac/MAC.java | 56 +
.../transport/random/BouncyCastleRandom.java | 70 +
.../sshj/transport/random/JCERandom.java | 82 +
.../schmizz/sshj/transport/random/Random.java | 50 +
.../random/SingletonRandomFactory.java | 56 +
.../verification/ConsoleHostKeyVerifier.java | 76 +
.../verification/HostKeyVerifier.java | 38 +
.../verification/InsecureAccepter.java | 26 +
.../verification/OpenSSHKnownHosts.java | 267 +++
.../net/schmizz/sshj/userauth/AuthParams.java | 56 +
.../net/schmizz/sshj/userauth/UserAuth.java | 99 +
.../sshj/userauth/UserAuthException.java | 88 +
.../sshj/userauth/UserAuthProtocol.java | 250 +++
.../userauth/keyprovider/FileKeyProvider.java | 53 +
.../userauth/keyprovider/KeyPairWrapper.java | 71 +
.../userauth/keyprovider/KeyProvider.java | 56 +
.../userauth/keyprovider/KeyProviderUtil.java | 57 +
.../userauth/keyprovider/OpenSSHKeyFile.java | 96 +
.../userauth/keyprovider/PKCS8KeyFile.java | 148 ++
.../userauth/method/AbstractAuthMethod.java | 94 +
.../sshj/userauth/method/AuthHostbased.java | 69 +
.../sshj/userauth/method/AuthMethod.java | 69 +
.../sshj/userauth/method/AuthNone.java | 49 +
.../sshj/userauth/method/AuthPassword.java | 94 +
.../sshj/userauth/method/AuthPublickey.java | 105 +
.../sshj/userauth/method/KeyedAuthMethod.java | 94 +
.../userauth/password/AccountResource.java | 24 +
.../userauth/password/PasswordFinder.java | 45 +
.../sshj/userauth/password/PasswordUtils.java | 68 +
.../password/PrivateKeyFileResource.java | 24 +
.../sshj/userauth/password/Resource.java | 36 +
.../sshj/xfer/AbstractFileTransfer.java | 48 +
.../schmizz/sshj/xfer/DefaultModeGetter.java | 49 +
.../schmizz/sshj/xfer/DefaultModeSetter.java | 41 +
.../net/schmizz/sshj/xfer/FilePermission.java | 85 +
.../net/schmizz/sshj/xfer/FileTransfer.java | 34 +
.../schmizz/sshj/xfer/FileTransferUtil.java | 50 +
.../net/schmizz/sshj/xfer/ModeGetter.java | 60 +
.../net/schmizz/sshj/xfer/ModeSetter.java | 57 +
.../sshj/xfer/scp/SCPDownloadClient.java | 188 ++
.../net/schmizz/sshj/xfer/scp/SCPEngine.java | 201 ++
.../schmizz/sshj/xfer/scp/SCPException.java | 28 +
.../sshj/xfer/scp/SCPFileTransfer.java | 47 +
.../sshj/xfer/scp/SCPUploadClient.java | 120 ++
src/test/java/net/schmizz/sshj/SmokeTest.java | 108 ++
.../sshj/keyprovider/OpenSSHKeyFileTest.java | 118 ++
.../sshj/keyprovider/PKCS8KeyFileTest.java | 62 +
.../verification/KnownHostsTest.java | 62 +
.../sshj/util/BogusPasswordAuthenticator.java | 31 +
.../net/schmizz/sshj/util/BufferTest.java | 107 ++
.../java/net/schmizz/sshj/util/KeyUtil.java | 65 +
src/test/resources/hostkey.pem | 15 +
src/test/resources/id_dsa | 15 +
src/test/resources/id_dsa.pub | 1 +
src/test/resources/id_rsa | 27 +
src/test/resources/id_rsa.pub | 1 +
src/test/resources/known_hosts | 3 +
187 files changed, 20583 insertions(+)
create mode 100644 LICENSE
create mode 100644 NOTICE
create mode 100644 README
create mode 100644 buildfile
create mode 100644 src/main/java/examples/Exec.java
create mode 100644 src/main/java/examples/LocalPF.java
create mode 100644 src/main/java/examples/MTTest.java
create mode 100644 src/main/java/examples/RemotePF.java
create mode 100644 src/main/java/examples/RudimentaryPTY.java
create mode 100644 src/main/java/examples/SCPDownload.java
create mode 100644 src/main/java/examples/SCPUpload.java
create mode 100644 src/main/java/examples/SFTPDownload.java
create mode 100644 src/main/java/examples/SFTPUpload.java
create mode 100644 src/main/java/examples/X11.java
create mode 100644 src/main/java/net/schmizz/concurrent/Event.java
create mode 100644 src/main/java/net/schmizz/concurrent/ExceptionChainer.java
create mode 100644 src/main/java/net/schmizz/concurrent/Future.java
create mode 100644 src/main/java/net/schmizz/concurrent/FutureUtils.java
create mode 100644 src/main/java/net/schmizz/sshj/AbstractService.java
create mode 100644 src/main/java/net/schmizz/sshj/Config.java
create mode 100644 src/main/java/net/schmizz/sshj/ConfigImpl.java
create mode 100644 src/main/java/net/schmizz/sshj/DefaultConfig.java
create mode 100644 src/main/java/net/schmizz/sshj/SSHClient.java
create mode 100644 src/main/java/net/schmizz/sshj/Service.java
create mode 100644 src/main/java/net/schmizz/sshj/SocketClient.java
create mode 100644 src/main/java/net/schmizz/sshj/common/Base64.java
create mode 100644 src/main/java/net/schmizz/sshj/common/Buffer.java
create mode 100644 src/main/java/net/schmizz/sshj/common/ByteArrayUtils.java
create mode 100644 src/main/java/net/schmizz/sshj/common/DisconnectReason.java
create mode 100644 src/main/java/net/schmizz/sshj/common/ErrorNotifiable.java
create mode 100644 src/main/java/net/schmizz/sshj/common/Factory.java
create mode 100644 src/main/java/net/schmizz/sshj/common/IOUtils.java
create mode 100644 src/main/java/net/schmizz/sshj/common/KeyType.java
create mode 100644 src/main/java/net/schmizz/sshj/common/Message.java
create mode 100644 src/main/java/net/schmizz/sshj/common/SSHException.java
create mode 100644 src/main/java/net/schmizz/sshj/common/SSHPacket.java
create mode 100644 src/main/java/net/schmizz/sshj/common/SSHPacketHandler.java
create mode 100644 src/main/java/net/schmizz/sshj/common/SSHRuntimeException.java
create mode 100644 src/main/java/net/schmizz/sshj/common/SecurityUtils.java
create mode 100644 src/main/java/net/schmizz/sshj/common/StreamCopier.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/Connection.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/ConnectionException.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/ConnectionProtocol.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/Channel.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/ChannelInputStream.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/ChannelOutputStream.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/OpenFailException.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/Window.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/direct/AbstractDirectChannel.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/direct/LocalPortForwarder.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/direct/PTYMode.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/direct/Session.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/direct/SessionChannel.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/direct/SessionFactory.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/direct/Signal.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannel.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannelOpener.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/forwarded/ConnectListener.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/forwarded/ForwardedChannelOpener.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/forwarded/RemotePortForwarder.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/forwarded/SocketForwardingConnectListener.java
create mode 100644 src/main/java/net/schmizz/sshj/connection/channel/forwarded/X11Forwarder.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/FileAttributes.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/FileMode.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/OpenMode.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/PacketReader.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/PacketType.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/PathComponents.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/PathHelper.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/RandomAccessRemoteFile.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/RemoteDirectory.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/RemoteFile.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/RemoteResource.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/RemoteResourceFilter.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/RemoteResourceInfo.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/Request.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/Requester.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/Response.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/SFTPClient.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/SFTPException.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/SFTPPacket.java
create mode 100644 src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java
create mode 100644 src/main/java/net/schmizz/sshj/signature/AbstractSignature.java
create mode 100644 src/main/java/net/schmizz/sshj/signature/Signature.java
create mode 100644 src/main/java/net/schmizz/sshj/signature/SignatureDSA.java
create mode 100644 src/main/java/net/schmizz/sshj/signature/SignatureRSA.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/Converter.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/Decoder.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/Encoder.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/Heartbeater.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/KeyExchanger.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/NegotiatedAlgorithms.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/Proposal.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/Reader.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/Transport.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/TransportException.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/TransportProtocol.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/AES128CBC.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/AES128CTR.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/AES192CBC.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/AES192CTR.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/AES256CBC.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/AES256CTR.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/BaseCipher.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/BlowfishCBC.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/Cipher.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/NoneCipher.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/cipher/TripleDESCBC.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/compression/Compression.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/compression/DelayedZlibCompression.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/compression/NoneCompression.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/compression/ZlibCompression.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/digest/BaseDigest.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/digest/Digest.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/digest/MD5.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/digest/SHA1.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/kex/AbstractDHG.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/kex/DH.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/kex/DHG1.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/kex/DHG14.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/kex/DHGroupData.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/kex/KeyExchange.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/HMACMD5.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/HMACMD596.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/HMACSHA1.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/HMACSHA196.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/MAC.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/random/BouncyCastleRandom.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/random/JCERandom.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/random/Random.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/random/SingletonRandomFactory.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/verification/ConsoleHostKeyVerifier.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/verification/HostKeyVerifier.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/verification/InsecureAccepter.java
create mode 100644 src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/AuthParams.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/UserAuth.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/UserAuthException.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/UserAuthProtocol.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyPairWrapper.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProvider.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/method/AbstractAuthMethod.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/method/AuthHostbased.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/method/AuthMethod.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/method/AuthNone.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/method/AuthPassword.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/method/AuthPublickey.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/method/KeyedAuthMethod.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/password/AccountResource.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/password/PasswordFinder.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/password/PasswordUtils.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/password/PrivateKeyFileResource.java
create mode 100644 src/main/java/net/schmizz/sshj/userauth/password/Resource.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/AbstractFileTransfer.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/DefaultModeGetter.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/DefaultModeSetter.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/FilePermission.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/FileTransfer.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/FileTransferUtil.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/ModeGetter.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/ModeSetter.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/scp/SCPDownloadClient.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/scp/SCPEngine.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/scp/SCPException.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/scp/SCPFileTransfer.java
create mode 100644 src/main/java/net/schmizz/sshj/xfer/scp/SCPUploadClient.java
create mode 100644 src/test/java/net/schmizz/sshj/SmokeTest.java
create mode 100644 src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java
create mode 100644 src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java
create mode 100644 src/test/java/net/schmizz/sshj/transport/verification/KnownHostsTest.java
create mode 100644 src/test/java/net/schmizz/sshj/util/BogusPasswordAuthenticator.java
create mode 100644 src/test/java/net/schmizz/sshj/util/BufferTest.java
create mode 100644 src/test/java/net/schmizz/sshj/util/KeyUtil.java
create mode 100644 src/test/resources/hostkey.pem
create mode 100644 src/test/resources/id_dsa
create mode 100644 src/test/resources/id_dsa.pub
create mode 100644 src/test/resources/id_rsa
create mode 100644 src/test/resources/id_rsa.pub
create mode 100644 src/test/resources/known_hosts
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000..c6955c3d
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,8 @@
+sshj - SSH library and client
+Copyright 2010 Shikhar Bhushan
+
+This product includes code derived from software developed at
+The Apache Software Foundation (http://www.apache.org/):
+
+- Apache MINA SSHD
+- Apache Commons-Net
diff --git a/README b/README
new file mode 100644
index 00000000..5d759618
--- /dev/null
+++ b/README
@@ -0,0 +1,9 @@
+sshj - SSHv2 library for Java
+==============================
+
+Required:
+* slf4j
+
+Optional:
+* bouncycastle for using high-strength ciphers and for reading openssh private key files
+* jzlib for using zlib compression
diff --git a/buildfile b/buildfile
new file mode 100644
index 00000000..6156c6af
--- /dev/null
+++ b/buildfile
@@ -0,0 +1,27 @@
+require 'buildr/scala'
+
+repositories.remote << 'http://www.ibiblio.org/maven2/'
+
+# Dependencies
+SLF4J = 'org.slf4j:slf4j-api:jar:1.5.10'
+SLF4J_LOG4J = 'org.slf4j:slf4j-log4j12:jar:1.5.10'
+LOG4J = 'log4j:log4j:jar:1.2.15'
+SSHD = transitive('org.apache.sshd:sshd-core:jar:0.3.0')
+JCRAFT = 'com.jcraft:jzlib:jar:1.0.7'
+BC = 'org.bouncycastle:bcprov-jdk16:jar:1.45'
+
+desc 'SSHv2 library for Java'
+define 'sshj', :version=>'0.1.0', :group=>'sshj' do
+
+ shell.using :scala
+
+ compile.with SLF4J, LOG4J, SLF4J_LOG4J, BC, JCRAFT
+
+ test.with SSHD, LOG4J, SLF4J, SLF4J_LOG4J
+
+ package(:jar).exclude('**/examples/*')
+ package(:sources).exclude('**/examples/*')
+ package(:javadoc).exclude('**/examples/*')
+ package(:zip, :classifier=>'examples').path('examples').include(_('**/examples/*.{java,class}'))
+
+end
diff --git a/src/main/java/examples/Exec.java b/src/main/java/examples/Exec.java
new file mode 100644
index 00000000..6096d714
--- /dev/null
+++ b/src/main/java/examples/Exec.java
@@ -0,0 +1,48 @@
+/*
+ * 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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.connection.channel.direct.Session.Command;
+
+/** This examples demonstrates how a remote command can be executed. */
+public class Exec {
+
+ // static {
+ // BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ // }
+
+ public static void main(String... args) throws Exception {
+ SSHClient ssh = new SSHClient();
+ ssh.loadKnownHosts();
+
+ ssh.connect("localhost");
+ try {
+
+ ssh.authPublickey(System.getProperty("user.name"));
+
+ Command cmd = ssh.startSession().exec("ping google.com -n 1");
+
+ // Pipe.pipe(cmd.getInputStream(), System.out, cmd.getLocalMaxPacketSize(), false);
+ System.out.print(cmd.getOutputAsString());
+ System.out.println("\n** exit status: " + cmd.getExitStatus());
+
+ } finally {
+ ssh.disconnect();
+ }
+ }
+
+}
diff --git a/src/main/java/examples/LocalPF.java b/src/main/java/examples/LocalPF.java
new file mode 100644
index 00000000..0a24f3ce
--- /dev/null
+++ b/src/main/java/examples/LocalPF.java
@@ -0,0 +1,55 @@
+/*
+ * 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 examples;
+
+import net.schmizz.sshj.SSHClient;
+
+import java.net.InetSocketAddress;
+
+/**
+ * This example demonstrates local port forwarding, i.e. when we listen on a particular address and port; and forward
+ * all incoming connections to SSH server which further forwards them to a specified address and port.
+ */
+public class LocalPF {
+
+ // static
+ // {
+ // BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ // }
+
+ public static void main(String... args) throws Exception {
+ SSHClient ssh = new SSHClient();
+
+ ssh.loadKnownHosts();
+
+ ssh.connect("localhost");
+ try {
+
+ ssh.authPublickey(System.getProperty("user.name"));
+
+ /*
+ * _We_ listen on localhost:8080 and forward all connections on to server, which then forwards it to
+ * google.com:80
+ */
+
+ ssh.newLocalPortForwarder(new InetSocketAddress("localhost", 8080), "google.com", 80).listen();
+
+ } finally {
+ ssh.disconnect();
+ }
+ }
+
+}
diff --git a/src/main/java/examples/MTTest.java b/src/main/java/examples/MTTest.java
new file mode 100644
index 00000000..3fd2e290
--- /dev/null
+++ b/src/main/java/examples/MTTest.java
@@ -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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+
+/** This example demonstrates uploading of a file over SCP to the SSH server. */
+public class MTTest {
+
+ static {
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ }
+
+ public static void main(String[] args) throws Exception {
+ final SSHClient ssh = new SSHClient();
+ ssh.loadKnownHosts();
+ ssh.connect("localhost");
+ try {
+ ssh.authPublickey(System.getProperty("user.name"));
+
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(1000);
+ // Compression = significant speedup for large file transfers on fast links
+ // present here to demo algorithm renegotiation - could have just put this before connect()
+ ssh.useCompression();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }.start();
+
+ ssh.newSCPFileTransfer().upload("/Users/shikhar/well", "/tmp/");
+ } finally {
+ ssh.disconnect();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/examples/RemotePF.java b/src/main/java/examples/RemotePF.java
new file mode 100644
index 00000000..868130f8
--- /dev/null
+++ b/src/main/java/examples/RemotePF.java
@@ -0,0 +1,62 @@
+/*
+ * 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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder.Forward;
+import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+
+import java.net.InetSocketAddress;
+
+/**
+ * This example demonstrates remote port forwarding i.e. when the remote host is made to listen on a specific address
+ * and port; and forwards us incoming connections.
+ */
+public class RemotePF {
+
+ static {
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ }
+
+ public static void main(String... args) throws Exception {
+ SSHClient client = new SSHClient();
+ client.loadKnownHosts();
+
+ client.connect("localhost");
+ try {
+
+ client.authPublickey(System.getProperty("user.name"));
+
+ /*
+ * We make _server_ listen on port 8080, which forwards all connections to us as a channel, and we further
+ * forward all such channels to google.com:80
+ */
+ client.getRemotePortForwarder().bind(new Forward(8080), //
+ new SocketForwardingConnectListener(new InetSocketAddress("google.com", 80)));
+
+ client.getTransport().setHeartbeatInterval(30);
+ // Something to hang on to so that the forwarding stays
+ client.getTransport().join();
+
+ } finally {
+ client.disconnect();
+ }
+ }
+
+}
diff --git a/src/main/java/examples/RudimentaryPTY.java b/src/main/java/examples/RudimentaryPTY.java
new file mode 100644
index 00000000..9b62c5f2
--- /dev/null
+++ b/src/main/java/examples/RudimentaryPTY.java
@@ -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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.common.StreamCopier;
+import net.schmizz.sshj.connection.channel.direct.Session;
+import net.schmizz.sshj.connection.channel.direct.Session.Shell;
+import net.schmizz.sshj.transport.verification.ConsoleHostKeyVerifier;
+import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
+
+import java.io.File;
+import java.io.IOException;
+
+/** A very rudimentary psuedo-terminal based on console I/O. */
+class RudimentaryPTY {
+
+// static {
+// BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+// }
+
+ public static void main(String... args) throws IOException {
+
+ final SSHClient ssh = new SSHClient();
+
+ ssh.addHostKeyVerifier(new ConsoleHostKeyVerifier(new File(OpenSSHKnownHosts.detectSSHDir(), "known_hosts"), System.console()));
+
+ ssh.connect("localhost");
+
+ Shell shell = null;
+
+ try {
+
+ ssh.authPublickey(System.getProperty("user.name"));
+
+ final Session session = ssh.startSession();
+ session.allocateDefaultPTY();
+
+ shell = session.startShell();
+
+ new StreamCopier("stdout", shell.getInputStream(), System.out)
+ .bufSize(shell.getLocalMaxPacketSize())
+ .start();
+
+ new StreamCopier("stderr", shell.getErrorStream(), System.err)
+ .bufSize(shell.getLocalMaxPacketSize())
+ .start();
+
+ // Now make System.in act as stdin. To exit, hit Ctrl+D (since that results in an EOF on System.in)
+ // This is kinda messy because java only allows console input after you hit return
+ // But this is just an example... a GUI app could implement a proper PTY
+ StreamCopier.copy(System.in, shell.getOutputStream(), shell.getRemoteMaxPacketSize(), true);
+
+ } finally {
+
+ if (shell != null)
+ shell.close();
+
+ ssh.disconnect();
+ }
+ }
+
+}
diff --git a/src/main/java/examples/SCPDownload.java b/src/main/java/examples/SCPDownload.java
new file mode 100644
index 00000000..52e3d15b
--- /dev/null
+++ b/src/main/java/examples/SCPDownload.java
@@ -0,0 +1,43 @@
+/*
+ * 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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+
+/** This example demonstrates downloading of a file over SCP from the SSH server. */
+public class SCPDownload {
+
+ static {
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ }
+
+ public static void main(String[] args) throws Exception {
+ SSHClient ssh = new SSHClient();
+ // ssh.useCompression(); // => significant speedup for large file transfers on fast links
+ ssh.loadKnownHosts();
+ ssh.connect("localhost");
+ try {
+ ssh.authPublickey(System.getProperty("user.name"));
+ ssh.newSCPFileTransfer().download("well", "/tmp/");
+ } finally {
+ ssh.disconnect();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/examples/SCPUpload.java b/src/main/java/examples/SCPUpload.java
new file mode 100644
index 00000000..c2965fb2
--- /dev/null
+++ b/src/main/java/examples/SCPUpload.java
@@ -0,0 +1,46 @@
+/*
+ * 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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+
+/** This example demonstrates uploading of a file over SCP to the SSH server. */
+public class SCPUpload {
+
+ static {
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ }
+
+ public static void main(String[] args) throws Exception {
+ SSHClient ssh = new SSHClient();
+ ssh.loadKnownHosts();
+ ssh.connect("localhost");
+ try {
+ ssh.authPublickey(System.getProperty("user.name"));
+
+ // Compression = significant speedup for large file transfers on fast links
+ // present here to demo algorithm renegotiation - could have just put this before connect()
+ ssh.useCompression();
+
+ ssh.newSCPFileTransfer().upload("/Users/shikhar/well", "/tmp/");
+ } finally {
+ ssh.disconnect();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/examples/SFTPDownload.java b/src/main/java/examples/SFTPDownload.java
new file mode 100644
index 00000000..fa1321e1
--- /dev/null
+++ b/src/main/java/examples/SFTPDownload.java
@@ -0,0 +1,42 @@
+/*
+ * 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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+
+/** This example demonstrates downloading of a file over SFTP from the SSH server. */
+public class SFTPDownload {
+
+ static {
+ BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ }
+
+ public static void main(String[] args) throws Exception {
+ SSHClient ssh = new SSHClient();
+ ssh.loadKnownHosts();
+ ssh.connect("localhost");
+ try {
+ ssh.authPublickey(System.getProperty("user.name"));
+ ssh.newSFTPClient().get("well", "/tmp/");
+ } finally {
+ ssh.disconnect();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/examples/SFTPUpload.java b/src/main/java/examples/SFTPUpload.java
new file mode 100644
index 00000000..545b3a87
--- /dev/null
+++ b/src/main/java/examples/SFTPUpload.java
@@ -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 examples;
+
+import net.schmizz.sshj.SSHClient;
+
+/** This example demonstrates uploading of a file over SFTP to the SSH server. */
+public class SFTPUpload {
+
+ // static
+ // {
+ // BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ // }
+
+ public static void main(String[] args) throws Exception {
+ SSHClient ssh = new SSHClient();
+ ssh.loadKnownHosts();
+ ssh.connect("localhost");
+ try {
+ ssh.authPublickey(System.getProperty("user.name"));
+ ssh.newSFTPClient().put("/Users/shikhar/well", "/tmp/");
+ } finally {
+ ssh.disconnect();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/examples/X11.java b/src/main/java/examples/X11.java
new file mode 100644
index 00000000..5186c2ed
--- /dev/null
+++ b/src/main/java/examples/X11.java
@@ -0,0 +1,72 @@
+/*
+ * 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 examples;
+
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.common.StreamCopier;
+import net.schmizz.sshj.connection.channel.direct.Session;
+import net.schmizz.sshj.connection.channel.direct.Session.Command;
+import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener;
+
+import java.net.InetSocketAddress;
+
+/** This example demonstrates how forwarding X11 connections from a remote host can be accomplished. */
+public class X11 {
+
+ // static {
+ // BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-15.15t] %-5p %-30.30c{1} - %m%n")));
+ // }
+
+ public static void main(String... args) throws Exception {
+ SSHClient ssh = new SSHClient();
+
+ // Compression makes X11 more feasible over slower connections
+ // ssh.useCompression();
+
+ ssh.loadKnownHosts();
+
+ /*
+ * NOTE: Forwarding incoming X connections to localhost:6000 only works if X is started without the
+ * "-nolisten tcp" option (this is usually not the default for good reason)
+ */
+ ssh.registerX11Forwarder(new SocketForwardingConnectListener(new InetSocketAddress("localhost", 6000)));
+
+ ssh.connect("localhost");
+ try {
+
+ ssh.authPublickey(System.getProperty("user.name"));
+
+ Session sess = ssh.startSession();
+
+ /*
+ * It is recommendable to send a fake cookie, and in your ConnectListener when a connection comes in replace
+ * it with the real one. But here simply one from `xauth list` is being used.
+ */
+ sess.reqX11Forwarding("MIT-MAGIC-COOKIE-1", "b0956167c9ad8f34c8a2788878307dc9", 0);
+
+ Command cmd = sess.exec("mate");
+
+ new StreamCopier("stdout", cmd.getInputStream(), System.out).start();
+ new StreamCopier("stderr", cmd.getErrorStream(), System.err).start();
+
+ // Wait for session & X11 channel to get closed
+ ssh.getConnection().join();
+
+ } finally {
+ ssh.disconnect();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/concurrent/Event.java b/src/main/java/net/schmizz/concurrent/Event.java
new file mode 100644
index 00000000..a3f00510
--- /dev/null
+++ b/src/main/java/net/schmizz/concurrent/Event.java
@@ -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.
+ *
+ * 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 extends Future {
+
+ /**
+ * 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 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 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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/concurrent/ExceptionChainer.java b/src/main/java/net/schmizz/concurrent/ExceptionChainer.java
new file mode 100644
index 00000000..6ca58486
--- /dev/null
+++ b/src/main/java/net/schmizz/concurrent/ExceptionChainer.java
@@ -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:
+ *
+ *
+ * ExceptionChainer<SomeException> chainer = new ExceptionChainer<SomeException>()
+ * {
+ * public SomeException chain(Throwable t)
+ * {
+ * if (t instanceof SomeException)
+ * return (SomeException) t;
+ * else
+ * return new SomeExcepion(t);
+ * }
+ * };
+ *
+ *
+ * @param Throwable type
+ */
+public interface ExceptionChainer {
+
+ Z chain(Throwable t);
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/concurrent/Future.java b/src/main/java/net/schmizz/concurrent/Future.java
new file mode 100644
index 00000000..3148de5d
--- /dev/null
+++ b/src/main/java/net/schmizz/concurrent/Future.java
@@ -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}.
+ *
+ * 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 {
+
+ private final Logger log;
+
+ private final ExceptionChainer 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 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 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();
+ }
+
+}
diff --git a/src/main/java/net/schmizz/concurrent/FutureUtils.java b/src/main/java/net/schmizz/concurrent/FutureUtils.java
new file mode 100644
index 00000000..dce60e5b
--- /dev/null
+++ b/src/main/java/net/schmizz/concurrent/FutureUtils.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/AbstractService.java b/src/main/java/net/schmizz/sshj/AbstractService.java
new file mode 100644
index 00000000..0a452c57
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/AbstractService.java
@@ -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");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/Config.java b/src/main/java/net/schmizz/sshj/Config.java
new file mode 100644
index 00000000..a0b56b4d
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/Config.java
@@ -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> getCipherFactories();
+
+ /**
+ * Retrieve the list of named factories for {@code Compression}.
+ *
+ * @return a list of named {@code Compression} factories
+ */
+ List> getCompressionFactories();
+
+ /**
+ * Retrieve the list of named factories for {@code FileKeyProvider}.
+ *
+ * @return a list of named {@code FileKeyProvider} factories
+ */
+ List> getFileKeyProviderFactories();
+
+ /**
+ * Retrieve the list of named factories for KeyExchange.
+ *
+ * @return a list of named KeyExchange factories
+ */
+ List> getKeyExchangeFactories();
+
+ /**
+ * Retrieve the list of named factories for MAC.
+ *
+ * @return a list of named MAC factories
+ */
+ List> getMACFactories();
+
+ /**
+ * Retrieve the {@link net.schmizz.sshj.transport.random.Random} factory.
+ *
+ * @return the {@link net.schmizz.sshj.transport.random.Random} factory
+ */
+ Factory 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> 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> cipherFactories);
+
+ /**
+ * Set the named factories for {@link net.schmizz.sshj.transport.compression.Compression}.
+ *
+ * @param compressionFactories a list of named factories
+ */
+ void setCompressionFactories(List> compressionFactories);
+
+ /**
+ * Set the named factories for {@link net.schmizz.sshj.userauth.keyprovider.FileKeyProvider}.
+ *
+ * @param fileKeyProviderFactories a list of named factories
+ */
+ void setFileKeyProviderFactories(List> fileKeyProviderFactories);
+
+ /**
+ * Set the named factories for {@link net.schmizz.sshj.transport.kex.KeyExchange}.
+ *
+ * @param kexFactories a list of named factories
+ */
+ void setKeyExchangeFactories(List> kexFactories);
+
+ /**
+ * Set the named factories for {@link net.schmizz.sshj.transport.mac.MAC}.
+ *
+ * @param macFactories a list of named factories
+ */
+ void setMACFactories(List> macFactories);
+
+ /**
+ * Set the factory for {@link net.schmizz.sshj.transport.random.Random}.
+ *
+ * @param randomFactory the factory
+ */
+ void setRandomFactory(Factory randomFactory);
+
+ /**
+ * Set the named factories for {@link net.schmizz.sshj.signature.Signature}.
+ *
+ * @param signatureFactories a list of named factories
+ */
+ void setSignatureFactories(List> 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);
+}
diff --git a/src/main/java/net/schmizz/sshj/ConfigImpl.java b/src/main/java/net/schmizz/sshj/ConfigImpl.java
new file mode 100644
index 00000000..f5a0d92d
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/ConfigImpl.java
@@ -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 randomFactory;
+
+ private List> kexFactories;
+ private List> cipherFactories;
+ private List> compressionFactories;
+ private List> macFactories;
+ private List> signatureFactories;
+ private List> fileKeyProviderFactories;
+
+ public List> getCipherFactories() {
+ return cipherFactories;
+ }
+
+ public List> getCompressionFactories() {
+ return compressionFactories;
+ }
+
+ public List> getFileKeyProviderFactories() {
+ return fileKeyProviderFactories;
+ }
+
+ public List> getKeyExchangeFactories() {
+ return kexFactories;
+ }
+
+ public List> getMACFactories() {
+ return macFactories;
+ }
+
+ public Factory getRandomFactory() {
+ return randomFactory;
+ }
+
+ public List> getSignatureFactories() {
+ return signatureFactories;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setCipherFactories(Factory.Named... cipherFactories) {
+ setCipherFactories(Arrays.>asList(cipherFactories));
+ }
+
+ public void setCipherFactories(List> cipherFactories) {
+ this.cipherFactories = cipherFactories;
+ }
+
+ public void setCompressionFactories(Factory.Named... compressionFactories) {
+ setCompressionFactories(Arrays.>asList(compressionFactories));
+ }
+
+ public void setCompressionFactories(List> compressionFactories) {
+ this.compressionFactories = compressionFactories;
+ }
+
+ public void setFileKeyProviderFactories(Factory.Named... fileKeyProviderFactories) {
+ setFileKeyProviderFactories(Arrays.>asList(fileKeyProviderFactories));
+ }
+
+ public void setFileKeyProviderFactories(List> fileKeyProviderFactories) {
+ this.fileKeyProviderFactories = fileKeyProviderFactories;
+ }
+
+ public void setKeyExchangeFactories(Factory.Named... kexFactories) {
+ setKeyExchangeFactories(Arrays.>asList(kexFactories));
+ }
+
+ public void setKeyExchangeFactories(List> kexFactories) {
+ this.kexFactories = kexFactories;
+ }
+
+ public void setMACFactories(Factory.Named... macFactories) {
+ setMACFactories(Arrays.>asList(macFactories));
+ }
+
+ public void setMACFactories(List> macFactories) {
+ this.macFactories = macFactories;
+ }
+
+ public void setRandomFactory(Factory randomFactory) {
+ this.randomFactory = randomFactory;
+ }
+
+ public void setSignatureFactories(Factory.Named... signatureFactories) {
+ setSignatureFactories(Arrays.>asList(signatureFactories));
+ }
+
+ public void setSignatureFactories(List> signatureFactories) {
+ this.signatureFactories = signatureFactories;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java
new file mode 100644
index 00000000..60b9ef49
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java
@@ -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.
+ *
+ * - {@link ConfigImpl#setKeyExchangeFactories Key exchange}: {@link DHG14}*, {@link DHG1}
- {@link
+ * ConfigImpl#setCipherFactories Ciphers} [1]: {@link AES128CTR}, {@link AES192CTR}, {@link AES256CTR}, {@link
+ * AES128CBC}, {@link AES192CBC}, {@link AES256CBC}, {@link AES192CBC}, {@link TripleDESCBC}, {@link BlowfishCBC}
+ * - {@link ConfigImpl#setMACFactories MAC}: {@link HMACSHA1}, {@link HMACSHA196}, {@link HMACMD5}, {@link
+ * HMACMD596}
- {@link ConfigImpl#setCompressionFactories Compression}: {@link NoneCompression}
- {@link
+ * ConfigImpl#setSignatureFactories Signature}: {@link SignatureRSA}, {@link SignatureDSA}
- {@link
+ * ConfigImpl#setRandomFactory PRNG}: {@link BouncyCastleRandom}* or {@link JCERandom}
- {@link
+ * ConfigImpl#setFileKeyProviderFactories Key file support}: {@link PKCS8KeyFile}*, {@link OpenSSHKeyFile}*
+ * - {@link ConfigImpl#setVersion Client version}: {@code "NET_3_0"}
+ *
+ * [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> avail = new LinkedList>(Arrays.>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> i = avail.iterator(); i.hasNext();) {
+ final Factory.Named 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());
+ }
+
+
+}
diff --git a/src/main/java/net/schmizz/sshj/SSHClient.java b/src/main/java/net/schmizz/sshj/SSHClient.java
new file mode 100644
index 00000000..386131dd
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/SSHClient.java
@@ -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.
+ *
+ * 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.
+ *
+ * User authentication can be performed by any of the {@code auth*()} method.
+ *
+ * {@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.
+ *
+ * {@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.
+ *
+ * A simple example:
+ *
+ *
+ *
+ * client = new SSHClient();
+ * client.initUserKnownHosts();
+ * client.connect("hostname");
+ * try
+ * {
+ * client.authPassword("username", "password");
+ * client.startSession().exec("true");
+ * client.getConnection().join();
+ * } finally
+ * {
+ * client.disconnect();
+ * }
+ *
+ *
+ * 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.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 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}.
+ *
+ * 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.
+ *
+ * {@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 keyProviders) throws UserAuthException,
+ TransportException {
+ List am = new LinkedList();
+ for (KeyProvider kp : keyProviders)
+ am.add(new AuthPublickey(kp));
+ auth(username, am);
+ }
+
+ /**
+ * Authenticate {@code username} using the {@code "publickey"} authentication method.
+ *
+ * {@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.asList(keyProviders));
+ }
+
+ /**
+ * Authenticate {@code username} using the {@code "publickey"} authentication method, with keys from one or more
+ * {@code locations} in the file system.
+ *
+ * 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.
+ *
+ * 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 keyProviders = new LinkedList();
+ 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.
+ *
+ * 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 unencrypted
+ * 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).
+ *
+ *
+ * @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 encrypted
+ * 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.
+ *
+ * For finer control over which file is used, see {@link #loadKnownHosts(File)}.
+ *
+ * @throws IOException if there is an error loading from both 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}.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/Service.java b/src/main/java/net/schmizz/sshj/Service.java
new file mode 100644
index 00000000..a319bfa1
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/Service.java
@@ -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;
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/SocketClient.java b/src/main/java/net/schmizz/sshj/SocketClient.java
new file mode 100644
index 00000000..71aac843
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/SocketClient.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/common/Base64.java b/src/main/java/net/schmizz/sshj/common/Base64.java
new file mode 100644
index 00000000..8d206563
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/Base64.java
@@ -0,0 +1,1705 @@
+package net.schmizz.sshj.common;
+
+
+/**
+ * Encodes and decodes to and from Base64 notation.
Homepage: http://iharder.net/base64.
+ *
Example:
String encoded = Base64.encode( myByteArray );
byte[]
+ * myByteArray = Base64.decode( encoded ); The options parameter, which appears in a few
+ * places, is used to pass several pieces of information to the encoder. In the "higher level" method such as
+ * encodeBytes( bytes, options ) the options parameter can be used to indicate such things as first gzipping the bytes
+ * before encoding them, not inserting linefeeds, and encoding using the URL-safe and Ordered dialects.
+ * Note, according to RFC3548, Section 2.1, implementations should
+ * not add line feeds unless explicitly told to do so. I've got Base64 set to this behavior now, although earlier
+ * versions broke lines by default.
The constants defined in Base64 can be OR-ed together to combine
+ * options, so you might make a call like this:
String encoded = Base64.encodeBytes( mybytes,
+ * Base64.GZIP | Base64.DO_BREAK_LINES ); to compress the data before encoding it and then making the output
+ * have newline characters.
Also...
String encoded = Base64.encodeBytes( crazyString.getBytes()
+ * ); I am placing this code in the Public Domain. Do with it as you will. This software comes with no
+ * guarantees or warranties but with plenty of well-wishing instead! Please visit http://iharder.net/base64 periodically to check for updates or to contribute
+ * improvements.
+ *
+ * @author Robert Harder
+ * @author rob@iharder.net
+ * @version 2.3.3
+ */
+public class Base64 {
+
+ /**
+ * A {@link Base64.InputStream} will read data from another java.io.InputStream, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class InputStream extends java.io.FilterInputStream {
+
+ private final boolean encode; // Encoding or decoding
+ private int position; // Current position in the buffer
+ private final byte[] buffer; // Small buffer holding converted data
+ private final int bufferLength; // Length of buffer (3 or 4)
+ private int numSigBytes; // Number of meaningful bytes in the buffer
+ private int lineLength;
+ private final boolean breakLines; // Break lines at less than 80 characters
+ private final int options; // Record options used to create the stream.
+ // private final byte[] alphabet; // Local copies to avoid extra method calls
+ private final byte[] decodabet; // Local copies to avoid extra method calls
+
+ /**
+ * Constructs a {@link Base64.InputStream} in DECODE mode.
+ *
+ * @param in the java.io.InputStream from which to read data.
+ *
+ * @since 1.3
+ */
+ public InputStream(java.io.InputStream in) {
+ this(in, DECODE);
+ } // end constructor
+
+ /**
+ * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode.
+ *
+ * Valid options:
+ *
+ *
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DO_BREAK_LINES: break lines at 76 characters
+ * (only meaningful when encoding)</i>
+ *
+ *
+ * Example: new Base64.InputStream( in, Base64.DECODE )
+ *
+ * @param in the java.io.InputStream from which to read data.
+ * @param options Specified options
+ *
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public InputStream(java.io.InputStream in, int options) {
+
+ super(in);
+ this.options = options; // Record for later
+ breakLines = (options & DO_BREAK_LINES) > 0;
+ encode = (options & ENCODE) > 0;
+ bufferLength = encode ? 4 : 3;
+ buffer = new byte[bufferLength];
+ position = -1;
+ lineLength = 0;
+ // alphabet = getAlphabet(options);
+ decodabet = getDecodabet(options);
+ } // end constructor
+
+ /**
+ * Reads enough of the input stream to convert to/from Base64 and returns the next byte.
+ *
+ * @return next byte
+ *
+ * @since 1.3
+ */
+ @Override
+ public int read() throws java.io.IOException {
+
+ // Do we need to get data?
+ if (position < 0)
+ if (encode) {
+ byte[] b3 = new byte[3];
+ int numBinaryBytes = 0;
+ for (int i = 0; i < 3; i++) {
+ int b = in.read();
+
+ // If end of stream, b is -1.
+ if (b >= 0) {
+ b3[i] = (byte) b;
+ numBinaryBytes++;
+ } else
+ break; // out of for loop
+
+ } // end for: each needed input byte
+
+ if (numBinaryBytes > 0) {
+ encode3to4(b3, 0, numBinaryBytes, buffer, 0, options);
+ position = 0;
+ numSigBytes = 4;
+ } // end if: got data
+ else
+ return -1; // Must be end of stream
+ } // end if: encoding
+
+ // Else decoding
+ else {
+ byte[] b4 = new byte[4];
+ int i = 0;
+ for (i = 0; i < 4; i++) {
+ // Read four "meaningful" bytes:
+ int b = 0;
+ do
+ b = in.read();
+ while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC);
+
+ if (b < 0)
+ break; // Reads a -1 if end of stream
+
+ b4[i] = (byte) b;
+ } // end for: each needed input byte
+
+ if (i == 4) {
+ numSigBytes = decode4to3(b4, 0, buffer, 0, options);
+ position = 0;
+ } // end if: got four characters
+ else if (i == 0)
+ return -1;
+ else
+ // Must have broken out from above.
+ throw new java.io.IOException("Improperly padded Base64 input.");
+
+ } // end else: decode
+
+ // Got data?
+ if (position >= 0) {
+ // End of relevant data?
+ if ( /* !encode && */position >= numSigBytes)
+ return -1;
+
+ if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
+ lineLength = 0;
+ return '\n';
+ } // end if
+ else {
+ lineLength++; // This isn't important when decoding
+ // but throwing an extra "if" seems
+ // just as wasteful.
+
+ int b = buffer[position++];
+
+ if (position >= bufferLength)
+ position = -1;
+
+ return b & 0xFF; // This is how you "cast" a byte that's
+ // intended to be unsigned.
+ } // end else
+ } // end if: position >= 0
+ else
+ throw new java.io.IOException("Error in Base64 code reading stream.");
+ } // end read
+
+ /**
+ * Calls {@link #read()} repeatedly until the end of stream is reached or len bytes are read. Returns
+ * number of bytes read into array or -1 if end of stream is encountered.
+ *
+ * @param dest array to hold values
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ *
+ * @return bytes read into array or -1 if end of stream is encountered.
+ *
+ * @since 1.3
+ */
+ @Override
+ public int read(byte[] dest, int off, int len) throws java.io.IOException {
+ int i;
+ int b;
+ for (i = 0; i < len; i++) {
+ b = read();
+
+ if (b >= 0)
+ dest[off + i] = (byte) b;
+ else if (i == 0)
+ return -1;
+ else
+ break; // Out of 'for' loop
+ } // end for: each byte read
+ return i;
+ } // end read
+
+ } // end inner class InputStream
+
+ /**
+ * A {@link Base64.OutputStream} will write data to another java.io.OutputStream, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class OutputStream extends java.io.FilterOutputStream {
+
+ private final boolean encode;
+ private int position;
+ private byte[] buffer;
+ private final int bufferLength;
+ private int lineLength;
+ private final boolean breakLines;
+ private final byte[] b4; // Scratch used in a few places
+ private boolean suspendEncoding;
+ private final int options; // Record for later
+ // private final byte[] alphabet; // Local copies to avoid extra method calls
+ private final byte[] decodabet; // Local copies to avoid extra method calls
+
+ /**
+ * Constructs a {@link Base64.OutputStream} in ENCODE mode.
+ *
+ * @param out the java.io.OutputStream to which data will be written.
+ *
+ * @since 1.3
+ */
+ public OutputStream(java.io.OutputStream out) {
+ this(out, ENCODE);
+ } // end constructor
+
+ /**
+ * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode.
+ *
+ * Valid options:
+ *
+ *
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DO_BREAK_LINES: don't break lines at 76 characters
+ * (only meaningful when encoding)</i>
+ *
+ *
+ * Example: new Base64.OutputStream( out, Base64.ENCODE )
+ *
+ * @param out the java.io.OutputStream to which data will be written.
+ * @param options Specified options.
+ *
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DO_BREAK_LINES
+ * @since 1.3
+ */
+ public OutputStream(java.io.OutputStream out, int options) {
+ super(out);
+ breakLines = (options & DO_BREAK_LINES) > 0;
+ encode = (options & ENCODE) > 0;
+ bufferLength = encode ? 3 : 4;
+ buffer = new byte[bufferLength];
+ position = 0;
+ lineLength = 0;
+ suspendEncoding = false;
+ b4 = new byte[4];
+ this.options = options;
+ // alphabet = getAlphabet(options);
+ decodabet = getDecodabet(options);
+ } // end constructor
+
+ /**
+ * Flushes and closes (I think, in the superclass) the stream.
+ *
+ * @since 1.3
+ */
+ @Override
+ public void close() throws java.io.IOException {
+ // 1. Ensure that pending characters are written
+ flush();
+
+ // 2. Actually close the stream
+ // Base class both flushes and closes.
+ super.close();
+
+ buffer = null;
+ out = null;
+ } // end close
+
+ /**
+ * Flushes the stream (and the enclosing streams).
+ *
+ * @throws java.io.IOException
+ * @since 2.3
+ */
+ @Override
+ public void flush() throws java.io.IOException {
+ flushBase64();
+ super.flush();
+ }
+
+ /**
+ * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream.
+ *
+ * @throws java.io.IOException if there's an error.
+ */
+ public void flushBase64() throws java.io.IOException {
+ if (position > 0)
+ if (encode) {
+ out.write(encode3to4(b4, buffer, position, options));
+ position = 0;
+ } // end if: encoding
+ else
+ throw new java.io.IOException("Base64 input not properly padded.");
+
+ } // end flush
+
+ /**
+ * Resumes encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a
+ * stream.
+ *
+ * @since 1.5.1
+ */
+ public void resumeEncoding() {
+ suspendEncoding = false;
+ } // end resumeEncoding
+
+ /**
+ * Suspends encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a
+ * stream.
+ *
+ * @throws java.io.IOException if there's an error flushing
+ * @since 1.5.1
+ */
+ public void suspendEncoding() throws java.io.IOException {
+ flushBase64();
+ suspendEncoding = true;
+ } // end suspendEncoding
+
+ /**
+ * Calls {@link #write(int)} repeatedly until len bytes are written.
+ *
+ * @param theBytes array from which to read bytes
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ *
+ * @since 1.3
+ */
+ @Override
+ public void write(byte[] theBytes, int off, int len) throws java.io.IOException {
+ // Encoding suspended?
+ if (suspendEncoding) {
+ super.out.write(theBytes, off, len);
+ return;
+ } // end if: supsended
+
+ for (int i = 0; i < len; i++)
+ write(theBytes[off + i]);
+
+ } // end write
+
+ /**
+ * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are
+ * buffered three at a time before the output stream actually gets a write() call. When decoding, bytes are
+ * buffered four at a time.
+ *
+ * @param theByte the byte to write
+ *
+ * @since 1.3
+ */
+ @Override
+ public void write(int theByte) throws java.io.IOException {
+ // Encoding suspended?
+ if (suspendEncoding) {
+ super.out.write(theByte);
+ return;
+ } // end if: supsended
+
+ // Encode?
+ if (encode) {
+ buffer[position++] = (byte) theByte;
+ if (position >= bufferLength) { // Enough to encode.
+
+ out.write(encode3to4(b4, buffer, bufferLength, options));
+
+ lineLength += 4;
+ if (breakLines && lineLength >= MAX_LINE_LENGTH) {
+ out.write(NEW_LINE);
+ lineLength = 0;
+ } // end if: end of line
+
+ position = 0;
+ } // end if: enough to output
+ } // end if: encoding
+ else // Meaningful Base64 character?
+ if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) {
+ buffer[position++] = (byte) theByte;
+ if (position >= bufferLength) { // Enough to output.
+
+ int len = Base64.decode4to3(buffer, 0, b4, 0, options);
+ out.write(b4, 0, len);
+ position = 0;
+ } // end if: enough to output
+ } // end if: meaningful base64 character
+ else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC)
+ throw new java.io.IOException("Invalid character in Base64 data.");
+ } // end write
+
+ } // end inner class OutputStream
+
+ /** No options specified. Value is zero. */
+ public final static int NO_OPTIONS = 0;
+
+ /** Specify encoding in first bit. Value is one. */
+ public final static int ENCODE = 1;
+
+ /** Specify decoding in first bit. Value is zero. */
+ public final static int DECODE = 0;
+
+ /** Specify that data should be gzip-compressed in second bit. Value is two. */
+ public final static int GZIP = 2;
+
+ /** Do break lines when encoding. Value is 8. */
+ public final static int DO_BREAK_LINES = 8;
+
+ /* ******** P R I V A T E F I E L D S ******** */
+
+ /**
+ * Encode using Base64-like encoding that is URL- and Filename-safe as described in Section 4 of RFC3548: http://www.faqs.org/rfcs/rfc3548.html. It is important to note
+ * that data encoded this way is not officially valid Base64, or at the very least should not be called
+ * Base64 without also specifying that is was encoded using the URL- and Filename-safe dialect.
+ */
+ public final static int URL_SAFE = 16;
+
+ /** Encode using the special "ordered" dialect of Base64 described here: http://www.faqs.org/qa/rfcc-1940.html. */
+ public final static int ORDERED = 32;
+
+ /** Maximum line length (76) of Base64 output. */
+ private final static int MAX_LINE_LENGTH = 76;
+
+ /** The equals sign (=) as a byte. */
+ private final static byte EQUALS_SIGN = (byte) '=';
+
+ /** The new line character (\n) as a byte. */
+ private final static byte NEW_LINE = (byte) '\n';
+ /** Preferred encoding. */
+ private final static String PREFERRED_ENCODING = "US-ASCII";
+
+ /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */
+
+ private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
+
+ private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
+
+ /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */
+
+ /** The 64 valid Base64 values. */
+ /* Host platform me be something funny like EBCDIC, so we hardcode these values. */
+ private final static byte[] _STANDARD_ALPHABET = {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E',
+ (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',
+ (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W',
+ (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',
+ (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x',
+ (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6',
+ (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'};
+
+ /**
+ * Translates a Base64 value to either its 6-bit reconstruction value or a negative number indicating some other
+ * meaning.
+ */
+ private final static byte[] _STANDARD_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal
+ // 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9, -9, -9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9 // Decimal 123 - 126
+ /*
+ * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
+ */
+ };
+
+ /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */
+
+ /**
+ * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: http://www.faqs.org/rfcs/rfc3548.html. Notice that the last two
+ * bytes become "hyphen" and "underscore" instead of "plus" and "slash."
+ */
+ private final static byte[] _URL_SAFE_ALPHABET = {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E',
+ (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',
+ (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W',
+ (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',
+ (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x',
+ (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6',
+ (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_'};
+
+ /** Used in decoding URL- and Filename-safe dialects of Base64. */
+ private final static byte[] _URL_SAFE_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal
+ // 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ -9, // Plus sign at decimal 43
+ -9, // Decimal 44
+ 62, // Minus sign at decimal 45
+ -9, // Decimal 46
+ -9, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, // Decimal 91 - 94
+ 63, // Underscore at decimal 95
+ -9, // Decimal 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9 // Decimal 123 - 126
+ /*
+ * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
+ */
+ };
+
+ /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */
+
+ /**
+ * I don't get the point of this technique, but someone requested it, and it is described here: http://www.faqs.org/qa/rfcc-1940.html.
+ */
+ private final static byte[] _ORDERED_ALPHABET = {(byte) '-', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C',
+ (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L',
+ (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) '_', (byte) 'a', (byte) 'b', (byte) 'c',
+ (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l',
+ (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
+ (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z'};
+
+ /** Used in decoding the "ordered" dialect of Base64. */
+ private final static byte[] _ORDERED_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal
+ // 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ -9, // Plus sign at decimal 43
+ -9, // Decimal 44
+ 0, // Minus sign at decimal 45
+ -9, // Decimal 46
+ -9, // Slash at decimal 47
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M'
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z'
+ -9, -9, -9, -9, // Decimal 91 - 94
+ 37, // Underscore at decimal 95
+ -9, // Decimal 96
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm'
+ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z'
+ -9, -9, -9, -9 // Decimal 123 - 126
+ /*
+ * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
+ */
+ };
+
+ /**
+ * Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if it's
+ * set. This is not generally a recommended method, although it is used internally as part of the decoding
+ * process. Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory
+ * footprint (and aren't gzipping), consider this method.
+ *
+ * @param source The Base64 encoded data
+ *
+ * @return decoded data
+ *
+ * @since 2.3.1
+ */
+ public static byte[] decode(byte[] source) {
+ byte[] decoded = null;
+ try {
+ decoded = decode(source, 0, source.length, Base64.NO_OPTIONS);
+ } catch (java.io.IOException ex) {
+ assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
+ }
+ return decoded;
+ }
+
+ /* ******** E N C O D I N G M E T H O D S ******** */
+
+ /**
+ * Low-level access to decoding ASCII characters in the form of a byte array. Ignores GUNZIP option, if it's
+ * set. This is not generally a recommended method, although it is used internally as part of the decoding
+ * process. Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory
+ * footprint (and aren't gzipping), consider this method.
+ *
+ * @param source The Base64 encoded data
+ * @param off The offset of where to begin decoding
+ * @param len The length of characters to decode
+ * @param options Can specify options such as alphabet type to use
+ *
+ * @return decoded data
+ *
+ * @throws java.io.IOException If bogus characters exist in source data
+ * @since 1.3
+ */
+ public static byte[] decode(byte[] source, int off, int len, int options) throws java.io.IOException {
+
+ // Lots of error checking and exception throwing
+ if (source == null)
+ throw new NullPointerException("Cannot decode null source array.");
+ if (off < 0 || off + len > source.length)
+ throw new IllegalArgumentException(String.format(
+ "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off,
+ len));
+
+ if (len == 0)
+ return new byte[0];
+ else if (len < 4)
+ throw new IllegalArgumentException(
+ "Base64-encoded string must have at least four characters, but length specified was " + len);
+
+ byte[] DECODABET = getDecodabet(options);
+
+ int len34 = len * 3 / 4; // Estimate on array size
+ byte[] outBuff = new byte[len34]; // Upper limit on size of output
+ int outBuffPosn = 0; // Keep track of where we're writing
+
+ byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space
+ int b4Posn = 0; // Keep track of four byte input buffer
+ int i = 0; // Source array counter
+ byte sbiCrop = 0; // Low seven bits (ASCII) of input
+ byte sbiDecode = 0; // Special value from DECODABET
+
+ for (i = off; i < off + len; i++) { // Loop through source
+
+ sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
+ sbiDecode = DECODABET[sbiCrop]; // Special value
+
+ // White space, Equals sign, or legit Base64 character
+ // Note the values such as -5 and -9 in the
+ // DECODABETs at the top of the file.
+ if (sbiDecode >= WHITE_SPACE_ENC) {
+ if (sbiDecode >= EQUALS_SIGN_ENC) {
+ b4[b4Posn++] = sbiCrop; // Save non-whitespace
+ if (b4Posn > 3) { // Time to decode?
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options);
+ b4Posn = 0;
+
+ // If that was the equals sign, break out of 'for' loop
+ if (sbiCrop == EQUALS_SIGN)
+ break;
+ } // end if: quartet built
+ } // end if: equals sign or better
+ } // end if: white space, equals sign or better
+ else
+ // There's a bad input character in the Base64 stream.
+ throw new java.io.IOException(String.format("Bad Base64 input character '%c' in array position %d",
+ source[i], i));
+ } // each input character
+
+ byte[] out = new byte[outBuffPosn];
+ System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
+ return out;
+ } // end decode
+
+ /**
+ * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
+ *
+ * @param s the string to decode
+ *
+ * @return the decoded data
+ *
+ * @throws java.io.IOException If there is a problem
+ * @since 1.4
+ */
+ public static byte[] decode(String s) throws java.io.IOException {
+ return decode(s, NO_OPTIONS);
+ }
+
+ /**
+ * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
+ *
+ * @param s the string to decode
+ * @param options encode options such as URL_SAFE
+ *
+ * @return the decoded data
+ *
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if s is null
+ * @since 1.4
+ */
+ public static byte[] decode(String s, int options) throws java.io.IOException {
+
+ if (s == null)
+ throw new NullPointerException("Input string was null.");
+
+ byte[] bytes;
+ try {
+ bytes = s.getBytes(PREFERRED_ENCODING);
+ } // end try
+ catch (java.io.UnsupportedEncodingException uee) {
+ bytes = s.getBytes();
+ } // end catch
+ //
+
+ // Decode
+ bytes = decode(bytes, 0, bytes.length, options);
+
+ // Check to see if it's gzip-compressed
+ // GZIP Magic Two-Byte Number: 0x8b1f (35615)
+ if (bytes != null && bytes.length >= 4) {
+
+ int head = bytes[0] & 0xff | bytes[1] << 8 & 0xff00;
+ if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
+ java.io.ByteArrayInputStream bais = null;
+ java.util.zip.GZIPInputStream gzis = null;
+ java.io.ByteArrayOutputStream baos = null;
+ byte[] buffer = new byte[2048];
+ int length = 0;
+
+ try {
+ baos = new java.io.ByteArrayOutputStream();
+ bais = new java.io.ByteArrayInputStream(bytes);
+ gzis = new java.util.zip.GZIPInputStream(bais);
+
+ while ((length = gzis.read(buffer)) >= 0)
+ baos.write(buffer, 0, length);
+
+ // No error? Get new bytes.
+ bytes = baos.toByteArray();
+
+ } // end try
+ catch (java.io.IOException e) {
+ // Just return originally-decoded bytes
+ } // end catch
+ finally {
+ try {
+ baos.close();
+ } catch (Exception e) {
+ }
+ try {
+ gzis.close();
+ } catch (Exception e) {
+ }
+ try {
+ bais.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ } // end if: gzipped
+ } // end if: bytes.length >= 2
+
+ return bytes;
+ } // end decode
+
+ /**
+ * Reads infile and decodes it to outfile.
+ *
+ * @param infile Input file
+ * @param outfile Output file
+ *
+ * @throws java.io.IOException if there is an error
+ * @since 2.2
+ */
+ public static void decodeFileToFile(String infile, String outfile) throws java.io.IOException {
+
+ byte[] decoded = Base64.decodeFromFile(infile);
+ java.io.OutputStream out = null;
+ try {
+ out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile));
+ out.write(decoded);
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch
+ finally {
+ try {
+ out.close();
+ } catch (Exception ex) {
+ }
+ } // end finally
+ } // end decodeFileToFile
+
+ /**
+ * Convenience method for reading a base64-encoded file and decoding it. As of v 2.3, if there is a error,
+ * the method will throw an java.io.IOException. This is new to v2.3! In earlier versions, it just returned
+ * false, but in retrospect that's a pretty poor way to next it.
+ *
+ * @param filename Filename for reading encoded data
+ *
+ * @return decoded byte array
+ *
+ * @throws java.io.IOException if there is an error
+ * @since 2.1
+ */
+ public static byte[] decodeFromFile(String filename) throws java.io.IOException {
+
+ byte[] decodedData = null;
+ Base64.InputStream bis = null;
+ try {
+ // Set up some useful variables
+ java.io.File file = new java.io.File(filename);
+ byte[] buffer = null;
+ int length = 0;
+ int numBytes = 0;
+
+ // Check for size of file
+ if (file.length() > Integer.MAX_VALUE)
+ throw new java.io.IOException("File is too big for this convenience method (" + file.length()
+ + " bytes).");
+ buffer = new byte[(int) file.length()];
+
+ // Open a stream
+ bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
+ Base64.DECODE);
+
+ // Read until done
+ while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
+ length += numBytes;
+
+ // Save in a variable to return
+ decodedData = new byte[length];
+ System.arraycopy(buffer, 0, decodedData, 0, length);
+
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bis.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return decodedData;
+ } // end decodeFromFile
+
+ /**
+ * Convenience method for decoding data to a file. As of v 2.3, if there is a error, the method will throw
+ * an java.io.IOException. This is new to v2.3! In earlier versions, it just returned false, but in
+ * retrospect that's a pretty poor way to next it.
+ *
+ * @param dataToDecode Base64-encoded data as a string
+ * @param filename Filename for saving decoded data
+ *
+ * @throws java.io.IOException if there is an error
+ * @since 2.1
+ */
+ public static void decodeToFile(String dataToDecode, String filename) throws java.io.IOException {
+
+ Base64.OutputStream bos = null;
+ try {
+ bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE);
+ bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and throw to execute finally{} block
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ } // end decodeToFile
+
+ /**
+ * Attempts to decode Base64 data and deserialize a Java Object within. Returns null if there was an
+ * error.
+ *
+ * @param encodedObject The Base64 data to decode
+ *
+ * @return The decoded and deserialized object
+ *
+ * @throws NullPointerException if encodedObject is null
+ * @throws java.io.IOException if there is a general error
+ * @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM
+ * @since 1.5
+ */
+ public static Object decodeToObject(String encodedObject) throws java.io.IOException,
+ java.lang.ClassNotFoundException {
+
+ // Decode and gunzip if necessary
+ byte[] objBytes = decode(encodedObject);
+
+ java.io.ByteArrayInputStream bais = null;
+ java.io.ObjectInputStream ois = null;
+ Object obj = null;
+
+ try {
+ bais = new java.io.ByteArrayInputStream(objBytes);
+ ois = new java.io.ObjectInputStream(bais);
+
+ obj = ois.readObject();
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and throw in order to execute finally{}
+ } // end catch
+ catch (java.lang.ClassNotFoundException e) {
+ throw e; // Catch and throw in order to execute finally{}
+ } // end catch
+ finally {
+ try {
+ bais.close();
+ } catch (Exception e) {
+ }
+ try {
+ ois.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return obj;
+ } // end decodeObject
+
+ /**
+ * Performs Base64 encoding on the raw ByteBuffer, writing it to the encoded ByteBuffer.
+ * This is an experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or
+ * {@link #GZIP}.
+ *
+ * @param raw input buffer
+ * @param encoded output buffer
+ *
+ * @since 2.3
+ */
+ public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) {
+ byte[] raw3 = new byte[3];
+ byte[] enc4 = new byte[4];
+
+ while (raw.hasRemaining()) {
+ int rem = Math.min(3, raw.remaining());
+ raw.get(raw3, 0, rem);
+ Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
+ encoded.put(enc4);
+ } // end input remaining
+ }
+
+ /**
+ * Performs Base64 encoding on the raw ByteBuffer, writing it to the encoded CharBuffer.
+ * This is an experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or
+ * {@link #GZIP}.
+ *
+ * @param raw input buffer
+ * @param encoded output buffer
+ *
+ * @since 2.3
+ */
+ public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) {
+ byte[] raw3 = new byte[3];
+ byte[] enc4 = new byte[4];
+
+ while (raw.hasRemaining()) {
+ int rem = Math.min(3, raw.remaining());
+ raw.get(raw3, 0, rem);
+ Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
+ for (int i = 0; i < 4; i++)
+ encoded.put((char) (enc4[i] & 0xFF));
+ } // end input remaining
+ }
+
+ /**
+ * Encodes a byte array into Base64 notation. Does not GZip-compress data.
+ *
+ * @param source The data to convert
+ *
+ * @return The data in Base64-encoded form
+ *
+ * @throws NullPointerException if source array is null
+ * @since 1.4
+ */
+ public static String encodeBytes(byte[] source) {
+ // Since we're not going to have the GZIP encoding turned on,
+ // we're not going to have an java.io.IOException thrown, so
+ // we should not force the user to have to catch it.
+ String encoded = null;
+ try {
+ encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
+ } catch (java.io.IOException ex) {
+ assert false : ex.getMessage();
+ } // end catch
+ assert encoded != null;
+ return encoded;
+ } // end encodeBytes
+
+ /**
+ * Encodes a byte array into Base64 notation. Example options:
+ *
+ * GZIP: gzip-compresses object before encoding it.
+ * DO_BREAK_LINES: break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ *
+ * Example: encodeBytes( myData, Base64.GZIP ) or
Example: encodeBytes( myData,
+ * Base64.GZIP | Base64.DO_BREAK_LINES )
As of v 2.3, if there is an error with the GZIP
+ * stream, the method will throw an java.io.IOException. This is new to v2.3! In earlier versions, it just
+ * returned a null value, but in retrospect that's a pretty poor way to next it.
+ *
+ * @param source The data to convert
+ * @param options Specified options
+ *
+ * @return The Base64-encoded data as a String
+ *
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if source array is null
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes(byte[] source, int options) throws java.io.IOException {
+ return encodeBytes(source, 0, source.length, options);
+ } // end encodeBytes
+
+ /**
+ * Encodes a byte array into Base64 notation. Does not GZip-compress data. As of v 2.3, if there is an
+ * error, the method will throw an java.io.IOException. This is new to v2.3! In earlier versions, it just
+ * returned a null value, but in retrospect that's a pretty poor way to next it.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ *
+ * @return The Base64-encoded data as a String
+ *
+ * @throws NullPointerException if source array is null
+ * @throws IllegalArgumentException if source array, offset, or length are invalid
+ * @since 1.4
+ */
+ public static String encodeBytes(byte[] source, int off, int len) {
+ // Since we're not going to have the GZIP encoding turned on,
+ // we're not going to have an java.io.IOException thrown, so
+ // we should not force the user to have to catch it.
+ String encoded = null;
+ try {
+ encoded = encodeBytes(source, off, len, NO_OPTIONS);
+ } catch (java.io.IOException ex) {
+ assert false : ex.getMessage();
+ } // end catch
+ assert encoded != null;
+ return encoded;
+ } // end encodeBytes
+
+ /**
+ * Encodes a byte array into Base64 notation. Example options:
+ *
+ * GZIP: gzip-compresses object before encoding it.
+ * DO_BREAK_LINES: break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ *
+ * Example: encodeBytes( myData, Base64.GZIP ) or
Example: encodeBytes( myData,
+ * Base64.GZIP | Base64.DO_BREAK_LINES )
As of v 2.3, if there is an error with the GZIP
+ * stream, the method will throw an java.io.IOException. This is new to v2.3! In earlier versions, it just
+ * returned a null value, but in retrospect that's a pretty poor way to next it.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param options Specified options
+ *
+ * @return The Base64-encoded data as a String
+ *
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if source array is null
+ * @throws IllegalArgumentException if source array, offset, or length are invalid
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
+ byte[] encoded = encodeBytesToBytes(source, off, len, options);
+
+ // Return value according to relevant encoding.
+ try {
+ return new String(encoded, PREFERRED_ENCODING);
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue) {
+ return new String(encoded);
+ } // end catch
+
+ } // end encodeBytes
+
+ /**
+ * Similar to {@link #encodeBytes(byte[])} but returns a byte array instead of instantiating a String. This is more
+ * efficient if you're working with I/O streams and have large data sets to encode.
+ *
+ * @param source The data to convert
+ *
+ * @return The Base64-encoded data as a byte[] (of ASCII characters)
+ *
+ * @throws NullPointerException if source array is null
+ * @since 2.3.1
+ */
+ public static byte[] encodeBytesToBytes(byte[] source) {
+ byte[] encoded = null;
+ try {
+ encoded = encodeBytesToBytes(source, 0, source.length, Base64.NO_OPTIONS);
+ } catch (java.io.IOException ex) {
+ assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
+ }
+ return encoded;
+ }
+
+ /**
+ * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte array instead of instantiating a
+ * String. This is more efficient if you're working with I/O streams and have large data sets to encode.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param options Specified options
+ *
+ * @return The Base64-encoded data as a String
+ *
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if source array is null
+ * @throws IllegalArgumentException if source array, offset, or length are invalid
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.3.1
+ */
+ public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
+
+ if (source == null)
+ throw new NullPointerException("Cannot serialize a null array.");
+
+ if (off < 0)
+ throw new IllegalArgumentException("Cannot have negative offset: " + off);
+
+ if (len < 0)
+ throw new IllegalArgumentException("Cannot have length offset: " + len);
+
+ if (off + len > source.length)
+ throw new IllegalArgumentException(String.format(
+ "Cannot have offset of %d and length of %d with array of length %d", off, len, source.length));
+
+ // Compress?
+ if ((options & GZIP) > 0) {
+ java.io.ByteArrayOutputStream baos = null;
+ java.util.zip.GZIPOutputStream gzos = null;
+ Base64.OutputStream b64os = null;
+
+ try {
+ // GZip -> Base64 -> ByteArray
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new Base64.OutputStream(baos, ENCODE | options);
+ gzos = new java.util.zip.GZIPOutputStream(b64os);
+
+ gzos.write(source, off, len);
+ gzos.close();
+ } // end try
+ catch (java.io.IOException e) {
+ // Catch it and then throw it immediately so that
+ // the finally{} block is called for cleanup.
+ throw e;
+ } // end catch
+ finally {
+ try {
+ gzos.close();
+ } catch (Exception e) {
+ }
+ try {
+ b64os.close();
+ } catch (Exception e) {
+ }
+ try {
+ baos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return baos.toByteArray();
+ } // end if: compress
+
+ // Else, don't compress. Better not to use streams at all then.
+ else {
+ boolean breakLines = (options & DO_BREAK_LINES) > 0;
+
+ // int len43 = len * 4 / 3;
+ // byte[] outBuff = new byte[ ( len43 ) // Main 4:3
+ // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
+ // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
+ // Try to determine more precisely how big the array needs to be.
+ // If we get it right, we don't have to do an array copy, and
+ // we save a bunch of memory.
+ int encLen = len / 3 * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding
+ if (breakLines)
+ encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters
+ byte[] outBuff = new byte[encLen];
+
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for (; d < len2; d += 3, e += 4) {
+ encode3to4(source, d + off, 3, outBuff, e, options);
+
+ lineLength += 4;
+ if (breakLines && lineLength >= MAX_LINE_LENGTH) {
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // en dfor: each piece of array
+
+ if (d < len) {
+ encode3to4(source, d + off, len - d, outBuff, e, options);
+ e += 4;
+ } // end if: some padding needed
+
+ // Only resize array if we didn't guess it right.
+ if (e < outBuff.length - 1) {
+ byte[] finalOut = new byte[e];
+ System.arraycopy(outBuff, 0, finalOut, 0, e);
+ // System.err.println("Having to resize array from " + outBuff.length + " to " + e
+ // );
+ return finalOut;
+ } else
+ // System.err.println("No need to resize array.");
+ return outBuff;
+
+ } // end else: don't compress
+
+ } // end encodeBytesToBytes
+
+ /**
+ * Reads infile and encodes it to outfile.
+ *
+ * @param infile Input file
+ * @param outfile Output file
+ *
+ * @throws java.io.IOException if there is an error
+ * @since 2.2
+ */
+ public static void encodeFileToFile(String infile, String outfile) throws java.io.IOException {
+
+ String encoded = Base64.encodeFromFile(infile);
+ java.io.OutputStream out = null;
+ try {
+ out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile));
+ out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output.
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch
+ finally {
+ try {
+ out.close();
+ } catch (Exception ex) {
+ }
+ } // end finally
+ } // end encodeFileToFile
+
+ /**
+ * Convenience method for reading a binary file and base64-encoding it. As of v 2.3, if there is a error,
+ * the method will throw an java.io.IOException. This is new to v2.3! In earlier versions, it just returned
+ * false, but in retrospect that's a pretty poor way to next it.
+ *
+ * @param filename Filename for reading binary data
+ *
+ * @return base64-encoded string
+ *
+ * @throws java.io.IOException if there is an error
+ * @since 2.1
+ */
+ public static String encodeFromFile(String filename) throws java.io.IOException {
+
+ String encodedData = null;
+ Base64.InputStream bis = null;
+ try {
+ // Set up some useful variables
+ java.io.File file = new java.io.File(filename);
+ byte[] buffer = new byte[Math.max((int) (file.length() * 1.4), 40)]; // Need max() for
+ // math on small
+ // files (v2.2.1)
+ int length = 0;
+ int numBytes = 0;
+
+ // Open a stream
+ bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
+ Base64.ENCODE);
+
+ // Read until done
+ while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
+ length += numBytes;
+
+ // Save in a variable to return
+ encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
+
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bis.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return encodedData;
+ } // end encodeFromFile
+
+ /**
+ * Serializes an object and returns the Base64-encoded version of that serialized object. As of v 2.3, if
+ * the object cannot be serialized or there is another error, the method will throw an java.io.IOException. This
+ * is new to v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor
+ * way to next it.
+ *
+ * The object is not GZip-compressed before being encoded.
+ *
+ * @param serializableObject The object to encode
+ *
+ * @return The Base64-encoded object
+ *
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if serializedObject is null
+ * @since 1.4
+ */
+ public static String encodeObject(java.io.Serializable serializableObject) throws java.io.IOException {
+ return encodeObject(serializableObject, NO_OPTIONS);
+ } // end encodeObject
+
+ /**
+ * Serializes an object and returns the Base64-encoded version of that serialized object. As of v 2.3, if
+ * the object cannot be serialized or there is another error, the method will throw an java.io.IOException. This
+ * is new to v2.3! In earlier versions, it just returned a null value, but in retrospect that's a pretty poor
+ * way to next it.
+ *
+ * The object is not GZip-compressed before being encoded.
+ *
+ * Example options:
+ *
+ *
+ * GZIP: gzip-compresses object before encoding it.
+ * DO_BREAK_LINES: break lines at 76 characters
+ *
+ *
+ * Example: encodeObject( myObj, Base64.GZIP ) or
+ *
+ * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )
+ *
+ * @param serializableObject The object to encode
+ * @param options Specified options
+ *
+ * @return The Base64-encoded object
+ *
+ * @throws java.io.IOException if there is an error
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeObject(java.io.Serializable serializableObject, int options) throws java.io.IOException {
+
+ if (serializableObject == null)
+ throw new NullPointerException("Cannot serialize a null object.");
+
+ // Streams
+ java.io.ByteArrayOutputStream baos = null;
+ java.io.OutputStream b64os = null;
+ java.io.ObjectOutputStream oos = null;
+
+ try {
+ // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
+ // Note that the optional GZIPping is handled by Base64.OutputStream.
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new Base64.OutputStream(baos, ENCODE | options);
+ oos = new java.io.ObjectOutputStream(b64os);
+ oos.writeObject(serializableObject);
+ } // end try
+ catch (java.io.IOException e) {
+ // Catch it and then throw it immediately so that
+ // the finally{} block is called for cleanup.
+ throw e;
+ } // end catch
+ finally {
+ try {
+ oos.close();
+ } catch (Exception e) {
+ }
+ try {
+ b64os.close();
+ } catch (Exception e) {
+ }
+ try {
+ baos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ // Return value according to relevant encoding.
+ try {
+ return new String(baos.toByteArray(), PREFERRED_ENCODING);
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue) {
+ // Fall back to some Java default
+ return new String(baos.toByteArray());
+ } // end catch
+
+ } // end encode
+
+ /**
+ * Convenience method for encoding data to a file. As of v 2.3, if there is a error, the method will throw
+ * an java.io.IOException. This is new to v2.3! In earlier versions, it just returned false, but in
+ * retrospect that's a pretty poor way to next it.
+ *
+ * @param dataToEncode byte array of data to encode in base64 form
+ * @param filename Filename for saving encoded data
+ *
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if dataToEncode is null
+ * @since 2.1
+ */
+ public static void encodeToFile(byte[] dataToEncode, String filename) throws java.io.IOException {
+
+ if (dataToEncode == null)
+ throw new NullPointerException("Data to encode was null.");
+
+ Base64.OutputStream bos = null;
+ try {
+ bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE);
+ bos.write(dataToEncode);
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and throw to execute finally{} block
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ } // end encodeToFile
+
+ /**
+ * Decodes four bytes from array source and writes the resulting bytes (up to three of them) to
+ * destination. The source and destination arrays can be manipulated anywhere along their length by
+ * specifying srcOffset and destOffset. This method does not check to make sure your arrays
+ * are large enough to accomodate srcOffset + 4 for the source array or destOffset
+ * + 3 for the destination array. This method returns the actual number of bytes that were converted from
+ * the Base64 encoding. This is the lowest level of the decoding method with all possible parameters.
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param options alphabet type is pulled from this (standard, url-safe, ordered)
+ *
+ * @return the number of decoded bytes converted
+ *
+ * @throws NullPointerException if source or destination arrays are null
+ * @throws IllegalArgumentException if srcOffset or destOffset are invalid or there is not enough room in the
+ * array.
+ * @since 1.3
+ */
+ private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, int options) {
+
+ // Lots of error checking and exception throwing
+ if (source == null)
+ throw new NullPointerException("Source array was null.");
+ if (destination == null)
+ throw new NullPointerException("Destination array was null.");
+ if (srcOffset < 0 || srcOffset + 3 >= source.length)
+ throw new IllegalArgumentException(String.format(
+ "Source array with length %d cannot have offset of %d and still process four bytes.",
+ source.length, srcOffset));
+ if (destOffset < 0 || destOffset + 2 >= destination.length)
+ throw new IllegalArgumentException(String.format(
+ "Destination array with length %d cannot have offset of %d and still store three bytes.",
+ destination.length, destOffset));
+
+ byte[] DECODABET = getDecodabet(options);
+
+ // Example: Dk==
+ if (source[srcOffset + 2] == EQUALS_SIGN) {
+ // Two ways to do the same thing. Don't know which way I like best.
+ // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
+ int outBuff = (DECODABET[source[srcOffset]] & 0xFF) << 18 | (DECODABET[source[srcOffset + 1]] & 0xFF) << 12;
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ return 1;
+ }
+
+ // Example: DkL=
+ else if (source[srcOffset + 3] == EQUALS_SIGN) {
+ // Two ways to do the same thing. Don't know which way I like best.
+ // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
+ int outBuff = (DECODABET[source[srcOffset]] & 0xFF) << 18 | (DECODABET[source[srcOffset + 1]] & 0xFF) << 12
+ | (DECODABET[source[srcOffset + 2]] & 0xFF) << 6;
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ destination[destOffset + 1] = (byte) (outBuff >>> 8);
+ return 2;
+ }
+
+ // Example: DkLE
+ else {
+ // Two ways to do the same thing. Don't know which way I like best.
+ // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
+ // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
+ int outBuff = (DECODABET[source[srcOffset]] & 0xFF) << 18 | (DECODABET[source[srcOffset + 1]] & 0xFF) << 12
+ | (DECODABET[source[srcOffset + 2]] & 0xFF) << 6 | DECODABET[source[srcOffset + 3]] & 0xFF;
+
+ destination[destOffset] = (byte) (outBuff >> 16);
+ destination[destOffset + 1] = (byte) (outBuff >> 8);
+ destination[destOffset + 2] = (byte) outBuff;
+
+ return 3;
+ }
+ } // end decodeToBytes
+
+ /**
+ * Encodes up to the first three bytes of array threeBytes and returns a four-byte array in Base64
+ * notation. The actual number of significant bytes in your array is given by numSigBytes. The array
+ * threeBytes needs only be as big as numSigBytes. Code can reuse a byte array by passing a
+ * four-byte array as b4.
+ *
+ * @param b4 A reusable byte array to reduce array instantiation
+ * @param threeBytes the array to convert
+ * @param numSigBytes the number of significant bytes in your array
+ *
+ * @return four byte array in Base64 notation.
+ *
+ * @since 1.5.1
+ */
+ private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) {
+ encode3to4(threeBytes, 0, numSigBytes, b4, 0, options);
+ return b4;
+ } // end encode3to4
+
+ /**
+ * Encodes up to three bytes of the array source and writes the resulting four Base64 bytes to
+ * destination. The source and destination arrays can be manipulated anywhere along their length by
+ * specifying srcOffset and destOffset. This method does not check to make sure your arrays
+ * are large enough to accomodate srcOffset + 3 for the source array or destOffset
+ * + 4 for the destination array. The actual number of significant bytes in your array is given by
+ * numSigBytes.
This is the lowest level of the encoding method with all possible parameters.
+ *
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param numSigBytes the number of significant bytes in your array
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ *
+ * @return the destination array
+ *
+ * @since 1.3
+ */
+ private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset,
+ int options) {
+
+ byte[] ALPHABET = getAlphabet(options);
+
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index ALPHABET
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff = (numSigBytes > 0 ? source[srcOffset] << 24 >>> 8 : 0)
+ | (numSigBytes > 1 ? source[srcOffset + 1] << 24 >>> 16 : 0)
+ | (numSigBytes > 2 ? source[srcOffset + 2] << 24 >>> 24 : 0);
+
+ switch (numSigBytes) {
+ case 3:
+ destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+ destination[destOffset + 1] = ALPHABET[inBuff >>> 12 & 0x3f];
+ destination[destOffset + 2] = ALPHABET[inBuff >>> 6 & 0x3f];
+ destination[destOffset + 3] = ALPHABET[inBuff & 0x3f];
+ return destination;
+
+ case 2:
+ destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+ destination[destOffset + 1] = ALPHABET[inBuff >>> 12 & 0x3f];
+ destination[destOffset + 2] = ALPHABET[inBuff >>> 6 & 0x3f];
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+
+ case 1:
+ destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+ destination[destOffset + 1] = ALPHABET[inBuff >>> 12 & 0x3f];
+ destination[destOffset + 2] = EQUALS_SIGN;
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+ /**
+ * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the options specified. It's possible, though
+ * silly, to specify ORDERED and URLSAFE in which case one of them will be picked, though there is no
+ * guarantee as to which one will be picked.
+ */
+ private static byte[] getAlphabet(int options) {
+ if ((options & URL_SAFE) == URL_SAFE)
+ return _URL_SAFE_ALPHABET;
+ else if ((options & ORDERED) == ORDERED)
+ return _ORDERED_ALPHABET;
+ else
+ return _STANDARD_ALPHABET;
+ } // end getAlphabet
+
+ /**
+ * Returns one of the _SOMETHING_DECODABET byte arrays depending on the options specified. It's possible, though
+ * silly, to specify ORDERED and URL_SAFE in which case one of them will be picked, though there is no guarantee as
+ * to which one will be picked.
+ */
+ private static byte[] getDecodabet(int options) {
+ if ((options & URL_SAFE) == URL_SAFE)
+ return _URL_SAFE_DECODABET;
+ else if ((options & ORDERED) == ORDERED)
+ return _ORDERED_DECODABET;
+ else
+ return _STANDARD_DECODABET;
+ } // end getAlphabet
+
+ /** Defeats instantiation. */
+ private Base64() {
+ }
+
+} // end class Base64
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/common/Buffer.java b/src/main/java/net/schmizz/sshj/common/Buffer.java
new file mode 100644
index 00000000..3dfc3970
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/Buffer.java
@@ -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> {
+
+ 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.
+ *
+ * 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 {
+
+ public PlainBuffer() {
+ super();
+ }
+
+ public PlainBuffer(Buffer> from) {
+ super(from);
+ }
+
+ public PlainBuffer(byte[] b) {
+ super(b);
+ }
+
+ public PlainBuffer(int size) {
+ super(size);
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/ByteArrayUtils.java b/src/main/java/net/schmizz/sshj/common/ByteArrayUtils.java
new file mode 100644
index 00000000..caac6886
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/ByteArrayUtils.java
@@ -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 true or false
+ */
+ 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 length bytes starting at some
+ * offset.
+ *
+ * @param a1
+ * @param a1Offset
+ * @param a2
+ * @param a2Offset
+ * @param length
+ *
+ * @return true or false
+ */
+ 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 array, 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 offset index for len
+ * 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 offset index for len
+ * 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);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/DisconnectReason.java b/src/main/java/net/schmizz/sshj/common/DisconnectReason.java
new file mode 100644
index 00000000..5712f4fe
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/DisconnectReason.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/ErrorNotifiable.java b/src/main/java/net/schmizz/sshj/common/ErrorNotifiable.java
new file mode 100644
index 00000000..b30ecf18
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/ErrorNotifiable.java
@@ -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);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/Factory.java b/src/main/java/net/schmizz/sshj/common/Factory.java
new file mode 100644
index 00000000..566b6449
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/Factory.java
@@ -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 the type of object created by this factory
+ */
+public interface Factory {
+
+ /**
+ * 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 type of object created by this factory
+ */
+ interface Named extends Factory {
+
+ /** 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 type of the {@code factories}
+ *
+ * @return a newly created instance of {@code T} or {@code null} if there was no match
+ */
+ public static T create(List> factories, String name) {
+ if (factories != null)
+ for (Named 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 type of the {@code factories}
+ *
+ * @return a factory or {@code null} if there was no match
+ */
+ public static Named get(List> factories, String name) {
+ for (Named 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 type of the {@code factories}
+ *
+ * @return a comma separated list of factory names
+ */
+ public static List getNames(List> factories) {
+ List list = new LinkedList();
+ for (Named 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();
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/IOUtils.java b/src/main/java/net/schmizz/sshj/common/IOUtils.java
new file mode 100644
index 00000000..e4d0f372
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/IOUtils.java
@@ -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);
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/KeyType.java b/src/main/java/net/schmizz/sshj/common/KeyType.java
new file mode 100644
index 00000000..08c1b355
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/KeyType.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/common/Message.java b/src/main/java/net/schmizz/sshj/common/Message.java
new file mode 100644
index 00000000..e9b59fe1
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/Message.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/SSHException.java b/src/main/java/net/schmizz/sshj/common/SSHException.java
new file mode 100644
index 00000000..87fc18bf
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/SSHException.java
@@ -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 chainer = new ExceptionChainer() {
+
+ 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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/SSHPacket.java b/src/main/java/net/schmizz/sshj/common/SSHPacket.java
new file mode 100644
index 00000000..4cc0fb9d
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/SSHPacket.java
@@ -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 {
+
+ 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());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/common/SSHPacketHandler.java b/src/main/java/net/schmizz/sshj/common/SSHPacketHandler.java
new file mode 100644
index 00000000..e8913703
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/SSHPacketHandler.java
@@ -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;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/SSHRuntimeException.java b/src/main/java/net/schmizz/sshj/common/SSHRuntimeException.java
new file mode 100644
index 00000000..92843343
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/SSHRuntimeException.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/common/SecurityUtils.java b/src/main/java/net/schmizz/sshj/common/SecurityUtils.java
new file mode 100644
index 00000000..0b37374c
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/SecurityUtils.java
@@ -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 specification
+ */
+ 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;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/common/StreamCopier.java b/src/main/java/net/schmizz/sshj/common/StreamCopier.java
new file mode 100644
index 00000000..d4da021e
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/common/StreamCopier.java
@@ -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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/Connection.java b/src/main/java/net/schmizz/sshj/connection/Connection.java
new file mode 100644
index 00000000..5906a16a
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/Connection.java
@@ -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 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);
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/ConnectionException.java b/src/main/java/net/schmizz/sshj/connection/ConnectionException.java
new file mode 100644
index 00000000..b3e1afd3
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/ConnectionException.java
@@ -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 chainer = new ExceptionChainer() {
+ 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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/ConnectionProtocol.java b/src/main/java/net/schmizz/sshj/connection/ConnectionProtocol.java
new file mode 100644
index 00000000..1daccef1
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/ConnectionProtocol.java
@@ -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 channels = new ConcurrentHashMap();
+
+ private final Map openers = new ConcurrentHashMap();
+
+ private final Queue> globalReqFutures = new LinkedList>();
+
+ 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 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 future = null;
+ if (wantReply) {
+ future = new Future("global req for " + name, ConnectionException.chainer);
+ globalReqFutures.add(future);
+ }
+ return future;
+ }
+ }
+
+ private void gotGlobalReqResponse(SSHPacket response) throws ConnectionException {
+ synchronized (globalReqFutures) {
+ Future 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();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java b/src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java
new file mode 100644
index 00000000..f0d2094d
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java
@@ -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> chanReqResponseEvents = new LinkedList>();
+
+ /* The lock used by #newEvent to create open & close events */
+ private final ReentrantLock lock = new ReentrantLock();
+ /** Channel open event */
+ protected final Event open;
+ /** Channel close event */
+ private final Event 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("chan#" + id + " / " + "open", ConnectionException.chainer, lock);
+ close = new Event("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 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 responseEvent = null;
+ if (wantReply) {
+ responseEvent = new Event("chan#" + id + " / " + "chanreq for " + reqType, ConnectionException.chainer, lock);
+ chanReqResponseEvents.add(responseEvent);
+ }
+ return responseEvent;
+ }
+
+ private synchronized void gotResponse(boolean success) throws ConnectionException {
+ final Event 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 + " >";
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/Channel.java b/src/main/java/net/schmizz/sshj/connection/channel/Channel.java
new file mode 100644
index 00000000..87621510
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/Channel.java
@@ -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);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/ChannelInputStream.java b/src/main/java/net/schmizz/sshj/connection/channel/ChannelInputStream.java
new file mode 100644
index 00000000..572ef769
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/ChannelInputStream.java
@@ -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() + " >";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/ChannelOutputStream.java b/src/main/java/net/schmizz/sshj/connection/channel/ChannelOutputStream.java
new file mode 100644
index 00000000..dce78d40
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/ChannelOutputStream.java
@@ -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() + " >";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/OpenFailException.java b/src/main/java/net/schmizz/sshj/connection/channel/OpenFailException.java
new file mode 100644
index 00000000..496116ec
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/OpenFailException.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/Window.java b/src/main/java/net/schmizz/sshj/connection/channel/Window.java
new file mode 100644
index 00000000..1d5e665e
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/Window.java
@@ -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;
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/direct/AbstractDirectChannel.java b/src/main/java/net/schmizz/sshj/connection/channel/direct/AbstractDirectChannel.java
new file mode 100644
index 00000000..85be1872
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/direct/AbstractDirectChannel.java
@@ -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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/direct/LocalPortForwarder.java b/src/main/java/net/schmizz/sshj/connection/channel/direct/LocalPortForwarder.java
new file mode 100644
index 00000000..a4744714
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/direct/LocalPortForwarder.java
@@ -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();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/direct/PTYMode.java b/src/main/java/net/schmizz/sshj/connection/channel/direct/PTYMode.java
new file mode 100644
index 00000000..8d0d01cb
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/direct/PTYMode.java
@@ -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 "\". */
+ 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 ˆ(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 modes) {
+ Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
+ for (Entry 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/direct/Session.java b/src/main/java/net/schmizz/sshj/connection/channel/direct/Session.java
new file mode 100644
index 00000000..c60f001d
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/direct/Session.java
@@ -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.
+ *
+ * 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.
+ *
+ * {@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 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;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/direct/SessionChannel.java b/src/main/java/net/schmizz/sshj/connection/channel/direct/SessionChannel.java
new file mode 100644
index 00000000..20d1b536
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/direct/SessionChannel.java
@@ -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 modes = new HashMap();
+ 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 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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/direct/SessionFactory.java b/src/main/java/net/schmizz/sshj/connection/channel/direct/SessionFactory.java
new file mode 100644
index 00000000..46a80018
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/direct/SessionFactory.java
@@ -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;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/direct/Signal.java b/src/main/java/net/schmizz/sshj/connection/channel/direct/Signal.java
new file mode 100644
index 00000000..a3590a47
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/direct/Signal.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannel.java b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannel.java
new file mode 100644
index 00000000..a4f69912
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannel.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannelOpener.java b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannelOpener.java
new file mode 100644
index 00000000..75436855
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/AbstractForwardedChannelOpener.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/forwarded/ConnectListener.java b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/ConnectListener.java
new file mode 100644
index 00000000..d3c39498
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/ConnectListener.java
@@ -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;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/forwarded/ForwardedChannelOpener.java b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/ForwardedChannelOpener.java
new file mode 100644
index 00000000..b200c168
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/ForwardedChannelOpener.java
@@ -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;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/forwarded/RemotePortForwarder.java b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/RemotePortForwarder.java
new file mode 100644
index 00000000..79c2f186
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/RemotePortForwarder.java
@@ -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
+ *
+ *
+ * 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.
+ *
+ * o "" means that connections are to be accepted on all protocol
+ * families supported by the SSH implementation.
+ *
+ * o "0.0.0.0" means to listen on all IPv4 addresses.
+ *
+ * o "::" means to listen on all IPv6 addresses.
+ *
+ * o "localhost" means to listen on all protocol families supported by
+ * the SSH implementation on loopback addresses only ([RFC3330] and
+ * [RFC3513]).
+ *
+ * o "127.0.0.1" and "::1" indicate listening on the loopback
+ * interfaces for IPv4 and IPv6, respectively.
+ *
+ */
+ 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 listeners = new ConcurrentHashMap();
+
+ 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}.
+ *
+ * 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 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() + "`");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/forwarded/SocketForwardingConnectListener.java b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/SocketForwardingConnectListener.java
new file mode 100644
index 00000000..d3ec0ef3
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/SocketForwardingConnectListener.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/connection/channel/forwarded/X11Forwarder.java b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/X11Forwarder.java
new file mode 100644
index 00000000..19e4dbfb
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/connection/channel/forwarded/X11Forwarder.java
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/sftp/FileAttributes.java b/src/main/java/net/schmizz/sshj/sftp/FileAttributes.java
new file mode 100644
index 00000000..3211ba63
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/FileAttributes.java
@@ -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 ext = new HashMap();
+
+ 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 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 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 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 ext = new HashMap();
+
+ 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 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 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();
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/FileMode.java b/src/main/java/net/schmizz/sshj/sftp/FileMode.java
new file mode 100644
index 00000000..eee1db9e
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/FileMode.java
@@ -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 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 getPermissions() {
+ return Collections.unmodifiableSet(perms);
+ }
+
+ @Override
+ public String toString() {
+ return "[mask=" + Integer.toOctalString(mask) + "]";
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/OpenMode.java b/src/main/java/net/schmizz/sshj/sftp/OpenMode.java
new file mode 100644
index 00000000..cf1cccdf
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/OpenMode.java
@@ -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 modes) {
+ int mask = 0;
+ for (OpenMode m : modes)
+ mask |= m.pflag;
+ return mask;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/PacketReader.java b/src/main/java/net/schmizz/sshj/sftp/PacketReader.java
new file mode 100644
index 00000000..056d403d
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/PacketReader.java
@@ -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> futures = new ConcurrentHashMap>();
+ private final SFTPPacket packet = new SFTPPacket();
+ 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 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 future : futures.values())
+ future.error(e);
+ }
+ }
+
+ public void handle() throws SFTPException {
+ Response resp = new Response(packet);
+ Future 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());
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/PacketType.java b/src/main/java/net/schmizz/sshj/sftp/PacketType.java
new file mode 100644
index 00000000..98a404a6
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/PacketType.java
@@ -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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/PathComponents.java b/src/main/java/net/schmizz/sshj/sftp/PathComponents.java
new file mode 100644
index 00000000..0e72f019
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/PathComponents.java
@@ -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 + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/sftp/PathHelper.java b/src/main/java/net/schmizz/sshj/sftp/PathHelper.java
new file mode 100644
index 00000000..2636bd18
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/PathHelper.java
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/sftp/RandomAccessRemoteFile.java b/src/main/java/net/schmizz/sshj/sftp/RandomAccessRemoteFile.java
new file mode 100644
index 00000000..a3331e40
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/RandomAccessRemoteFile.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteDirectory.java b/src/main/java/net/schmizz/sshj/sftp/RemoteDirectory.java
new file mode 100644
index 00000000..04cc3742
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/RemoteDirectory.java
@@ -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 scan(RemoteResourceFilter filter) throws IOException {
+ List rri = new LinkedList();
+ 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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java
new file mode 100644
index 00000000..e798eb6c
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java
@@ -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;
+ }
+
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteResource.java b/src/main/java/net/schmizz/sshj/sftp/RemoteResource.java
new file mode 100644
index 00000000..a878c77a
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/RemoteResource.java
@@ -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 + "}";
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteResourceFilter.java b/src/main/java/net/schmizz/sshj/sftp/RemoteResourceFilter.java
new file mode 100644
index 00000000..2fee9469
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/RemoteResourceFilter.java
@@ -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);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteResourceInfo.java b/src/main/java/net/schmizz/sshj/sftp/RemoteResourceInfo.java
new file mode 100644
index 00000000..1fddea69
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/RemoteResourceInfo.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/Request.java b/src/main/java/net/schmizz/sshj/sftp/Request.java
new file mode 100644
index 00000000..e75f8042
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/Request.java
@@ -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 {
+
+ private final PacketType type;
+ private final long reqID;
+ private final Future responseFuture;
+
+ public Request(PacketType type, long reqID) {
+ super();
+ this.reqID = reqID;
+ this.type = type;
+ responseFuture = new Future("sftp / " + reqID, SFTPException.chainer);
+ putByte(type.toByte());
+ putInt(reqID);
+ }
+
+ public long getRequestID() {
+ return reqID;
+ }
+
+ public PacketType getType() {
+ return type;
+ }
+
+ public Future getResponseFuture() {
+ return responseFuture;
+ }
+
+ @Override
+ public String toString() {
+ return "Request{" + reqID + ";" + type + "}";
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/Requester.java b/src/main/java/net/schmizz/sshj/sftp/Requester.java
new file mode 100644
index 00000000..d21ae05f
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/Requester.java
@@ -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;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/Response.java b/src/main/java/net/schmizz/sshj/sftp/Response.java
new file mode 100644
index 00000000..d0086099
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/Response.java
@@ -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 {
+
+ 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 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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPClient.java b/src/main/java/net/schmizz/sshj/sftp/SFTPClient.java
new file mode 100644
index 00000000..fb59063b
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/SFTPClient.java
@@ -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 ls(String path) throws IOException {
+ return ls(path, null);
+ }
+
+ public List 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 mode, FileAttributes attrs) throws IOException {
+ log.debug("Opening `{}`", filename);
+ return sftp.open(filename, mode, attrs);
+ }
+
+ public RemoteFile open(String filename, Set 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 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);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java b/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java
new file mode 100644
index 00000000..e974f3a0
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java
@@ -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 serverExtensions = new HashMap();
+
+ 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(PacketType.INIT).putInt(PROTOCOL_VERSION));
+
+ final SFTPPacket 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 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 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 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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPException.java b/src/main/java/net/schmizz/sshj/sftp/SFTPException.java
new file mode 100644
index 00000000..287e4e85
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/SFTPException.java
@@ -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 chainer = new ExceptionChainer() {
+
+ 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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java b/src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java
new file mode 100644
index 00000000..2a7425cb
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java
@@ -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");
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPPacket.java b/src/main/java/net/schmizz/sshj/sftp/SFTPPacket.java
new file mode 100644
index 00000000..8966ad06
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/SFTPPacket.java
@@ -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> extends Buffer {
+
+ public SFTPPacket() {
+ super();
+ }
+
+ public SFTPPacket(Buffer 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());
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java b/src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java
new file mode 100644
index 00000000..e00eb32f
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java
@@ -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 ls() throws IOException {
+ return ls(cwd, null);
+ }
+
+ public synchronized List ls(RemoteResourceFilter filter) throws IOException {
+ return ls(cwd, filter);
+ }
+
+ public synchronized String pwd() throws IOException {
+ return super.canonicalize(cwd);
+ }
+
+ @Override
+ public List ls(String path) throws IOException {
+ return ls(path, null);
+ }
+
+ @Override
+ public List 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 mode, FileAttributes attrs) throws IOException {
+ return super.open(cwdify(filename), mode, attrs);
+ }
+
+ @Override
+ public RemoteFile open(String filename, Set 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));
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/signature/AbstractSignature.java b/src/main/java/net/schmizz/sshj/signature/AbstractSignature.java
new file mode 100644
index 00000000..5d5106a1
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/signature/AbstractSignature.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/signature/Signature.java b/src/main/java/net/schmizz/sshj/signature/Signature.java
new file mode 100644
index 00000000..84b4f7b6
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/signature/Signature.java
@@ -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.
+ *
+ * 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);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureDSA.java b/src/main/java/net/schmizz/sshj/signature/SignatureDSA.java
new file mode 100644
index 00000000..f572ee96
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/signature/SignatureDSA.java
@@ -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 {
+
+ 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);
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureRSA.java b/src/main/java/net/schmizz/sshj/signature/SignatureRSA.java
new file mode 100644
index 00000000..51add884
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/signature/SignatureRSA.java
@@ -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 {
+
+ 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);
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/Converter.java b/src/main/java/net/schmizz/sshj/transport/Converter.java
new file mode 100644
index 00000000..eb541670
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/Converter.java
@@ -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}.
+ *
+ * From RFC 4253, p. 6
+ *
+ *
+ * Each packet is in the following format:
+ *
+ * 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
+ *
+ */
+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();
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/transport/Decoder.java b/src/main/java/net/schmizz/sshj/transport/Decoder.java
new file mode 100644
index 00000000..6f970041
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/Decoder.java
@@ -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.
+ *
+ * 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/transport/Encoder.java b/src/main/java/net/schmizz/sshj/transport/Encoder.java
new file mode 100644
index 00000000..46879198
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/Encoder.java
@@ -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;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/transport/Heartbeater.java b/src/main/java/net/schmizz/sshj/transport/Heartbeater.java
new file mode 100644
index 00000000..0464615f
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/Heartbeater.java
@@ -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");
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/KeyExchanger.java b/src/main/java/net/schmizz/sshj/transport/KeyExchanger.java
new file mode 100644
index 00000000..9393a8a9
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/KeyExchanger.java
@@ -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 hostVerifiers = new LinkedList();
+
+ 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 kexInitSent = new Event("kexinit sent",
+ TransportException.chainer);
+
+ private final Event 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("kex done", TransportException.chainer, trans.getWriteLock());
+ }
+
+ /**
+ * Add a callback for host key verification.
+ *
+ * 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) //
+ .putRawBytes(sessionID);
+ final int pos = hashInput.available() - sessionID.length - 1; // Position of
+
+ 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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/transport/NegotiatedAlgorithms.java b/src/main/java/net/schmizz/sshj/transport/NegotiatedAlgorithms.java
new file mode 100644
index 00000000..7685c7e5
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/NegotiatedAlgorithms.java
@@ -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 + //
+ " ]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/transport/Proposal.java b/src/main/java/net/schmizz/sshj/transport/Proposal.java
new file mode 100644
index 00000000..aff3844b
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/Proposal.java
@@ -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 kex;
+ private final List sig;
+ private final List c2sCipher;
+ private final List s2cCipher;
+ private final List c2sMAC;
+ private final List s2cMAC;
+ private final List c2sComp;
+ private final List 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 getKeyExchangeAlgorithms() {
+ return kex;
+ }
+
+ public List getSignatureAlgorithms() {
+ return sig;
+ }
+
+ public List getClient2ServerCipherAlgorithms() {
+ return c2sCipher;
+ }
+
+ public List getServer2ClientCipherAlgorithms() {
+ return s2cCipher;
+ }
+
+ public List getClient2ServerMACAlgorithms() {
+ return c2sMAC;
+ }
+
+ public List getServer2ClientMACAlgorithms() {
+ return s2cMAC;
+ }
+
+ public List getClient2ServerCompressionAlgorithms() {
+ return c2sComp;
+ }
+
+ public List 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 a, List 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 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 fromCommaString(String s) {
+ return Arrays.asList(s.split(","));
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/Reader.java b/src/main/java/net/schmizz/sshj/transport/Reader.java
new file mode 100644
index 00000000..a6fcee91
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/Reader.java
@@ -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");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/Transport.java b/src/main/java/net/schmizz/sshj/transport/Transport.java
new file mode 100644
index 00000000..b41f94d2
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/Transport.java
@@ -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".
+ *
+ * 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.
+ *
+ * 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 must 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.
+ *
+ * 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.
+ *
+ * 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;
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/transport/TransportException.java b/src/main/java/net/schmizz/sshj/transport/TransportException.java
new file mode 100644
index 00000000..d079afac
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/TransportException.java
@@ -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 chainer = new ExceptionChainer() {
+ 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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/transport/TransportProtocol.java b/src/main/java/net/schmizz/sshj/transport/TransportProtocol.java
new file mode 100644
index 00000000..d12c231a
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/TransportProtocol.java
@@ -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 serviceAccept = new Event("service accept",
+ TransportException.chainer);
+
+ private final Event close = new Event("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".
+ *
+ * Several concerns are taken care of here, e.g. verifying protocol version, correct line endings as specified in
+ * RFC and such.
+ *
+ * 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}.
+ *
+ * Even among the transport layer specific packets, key exchange packets are delegated to {@link
+ * KeyExchanger#handle}.
+ *
+ * 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;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/AES128CBC.java b/src/main/java/net/schmizz/sshj/transport/cipher/AES128CBC.java
new file mode 100644
index 00000000..55b70286
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/AES128CBC.java
@@ -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 {
+ public Cipher create() {
+ return new AES128CBC();
+ }
+
+ public String getName() {
+ return "aes128-cbc";
+ }
+ }
+
+ public AES128CBC() {
+ super(16, 16, "AES", "AES/CBC/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/AES128CTR.java b/src/main/java/net/schmizz/sshj/transport/cipher/AES128CTR.java
new file mode 100644
index 00000000..3629afa1
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/AES128CTR.java
@@ -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 {
+ public Cipher create() {
+ return new AES128CTR();
+ }
+
+ public String getName() {
+ return "aes128-ctr";
+ }
+ }
+
+ public AES128CTR() {
+ super(16, 16, "AES", "AES/CTR/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/AES192CBC.java b/src/main/java/net/schmizz/sshj/transport/cipher/AES192CBC.java
new file mode 100644
index 00000000..f539b8c6
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/AES192CBC.java
@@ -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 {
+ public Cipher create() {
+ return new AES192CBC();
+ }
+
+ public String getName() {
+ return "aes192-cbc";
+ }
+ }
+
+ public AES192CBC() {
+ super(16, 24, "AES", "AES/CBC/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/AES192CTR.java b/src/main/java/net/schmizz/sshj/transport/cipher/AES192CTR.java
new file mode 100644
index 00000000..00df71d5
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/AES192CTR.java
@@ -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 {
+ public Cipher create() {
+ return new AES192CTR();
+ }
+
+ public String getName() {
+ return "aes192-ctr";
+ }
+ }
+
+ public AES192CTR() {
+ super(16, 24, "AES", "AES/CTR/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/AES256CBC.java b/src/main/java/net/schmizz/sshj/transport/cipher/AES256CBC.java
new file mode 100644
index 00000000..f2213e97
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/AES256CBC.java
@@ -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 {
+ public Cipher create() {
+ return new AES256CBC();
+ }
+
+ public String getName() {
+ return "aes256-cbc";
+ }
+ }
+
+ public AES256CBC() {
+ super(16, 32, "AES", "AES/CBC/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/AES256CTR.java b/src/main/java/net/schmizz/sshj/transport/cipher/AES256CTR.java
new file mode 100644
index 00000000..653f5576
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/AES256CTR.java
@@ -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 {
+ public Cipher create() {
+ return new AES256CTR();
+ }
+
+ public String getName() {
+ return "aes256-ctr";
+ }
+ }
+
+ public AES256CTR() {
+ super(16, 32, "AES", "AES/CTR/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/BaseCipher.java b/src/main/java/net/schmizz/sshj/transport/cipher/BaseCipher.java
new file mode 100644
index 00000000..bc41d92a
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/BaseCipher.java
@@ -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);
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/BlowfishCBC.java b/src/main/java/net/schmizz/sshj/transport/cipher/BlowfishCBC.java
new file mode 100644
index 00000000..8212531b
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/BlowfishCBC.java
@@ -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 {
+ public Cipher create() {
+ return new BlowfishCBC();
+ }
+
+ public String getName() {
+ return "blowfish-cbc";
+ }
+ }
+
+ public BlowfishCBC() {
+ super(8, 16, "Blowfish", "Blowfish/CBC/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/Cipher.java b/src/main/java/net/schmizz/sshj/transport/cipher/Cipher.java
new file mode 100644
index 00000000..3a4a8824
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/Cipher.java
@@ -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);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/NoneCipher.java b/src/main/java/net/schmizz/sshj/transport/cipher/NoneCipher.java
new file mode 100644
index 00000000..d930049b
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/NoneCipher.java
@@ -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 {
+ 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) {
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/cipher/TripleDESCBC.java b/src/main/java/net/schmizz/sshj/transport/cipher/TripleDESCBC.java
new file mode 100644
index 00000000..0538272a
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/cipher/TripleDESCBC.java
@@ -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 {
+ public Cipher create() {
+ return new TripleDESCBC();
+ }
+
+ public String getName() {
+ return "3des-cbc";
+ }
+ }
+
+ public TripleDESCBC() {
+ super(8, 24, "DESede", "DESede/CBC/NoPadding");
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/compression/Compression.java b/src/main/java/net/schmizz/sshj/transport/compression/Compression.java
new file mode 100644
index 00000000..fa8a31e4
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/compression/Compression.java
@@ -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 compress or uncompress. Once the object has been initialized, only one of
+ * compress or uncompress 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;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/compression/DelayedZlibCompression.java b/src/main/java/net/schmizz/sshj/transport/compression/DelayedZlibCompression.java
new file mode 100644
index 00000000..30991e63
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/compression/DelayedZlibCompression.java
@@ -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 {
+ public Compression create() {
+ return new DelayedZlibCompression();
+ }
+
+ public String getName() {
+ return "zlib@openssh.com";
+ }
+ }
+
+ @Override
+ public boolean isDelayed() {
+ return true;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/compression/NoneCompression.java b/src/main/java/net/schmizz/sshj/transport/compression/NoneCompression.java
new file mode 100644
index 00000000..cb1e138e
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/compression/NoneCompression.java
@@ -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 Compression. */
+public abstract class NoneCompression implements Compression {
+
+ /** Named factory for the no-op Compression */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+ public Compression create() {
+ return null;
+ }
+
+ public String getName() {
+ return "none";
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/compression/ZlibCompression.java b/src/main/java/net/schmizz/sshj/transport/compression/ZlibCompression.java
new file mode 100644
index 00000000..ac1dcbea
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/compression/ZlibCompression.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.jcraft.jzlib.JZlib;
+import com.jcraft.jzlib.ZStream;
+import net.schmizz.sshj.common.DisconnectReason;
+import net.schmizz.sshj.common.SSHPacket;
+import net.schmizz.sshj.common.SSHRuntimeException;
+import net.schmizz.sshj.transport.TransportException;
+
+/** ZLib based Compression. */
+public class ZlibCompression implements Compression {
+
+ /** Named factory for the ZLib Compression. */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+ public Compression create() {
+ return new ZlibCompression();
+ }
+
+ public String getName() {
+ return "zlib";
+ }
+ }
+
+ static private final int BUF_SIZE = 4096;
+
+ private ZStream stream;
+ private final byte[] tmpbuf = new byte[BUF_SIZE];
+
+ /** Create a new instance of a ZLib base compression */
+ public ZlibCompression() {
+ }
+
+ public void compress(SSHPacket buffer) {
+ stream.next_in = buffer.array();
+ stream.next_in_index = buffer.rpos();
+ stream.avail_in = buffer.available();
+ buffer.wpos(buffer.rpos());
+ do {
+ stream.next_out = tmpbuf;
+ stream.next_out_index = 0;
+ stream.avail_out = BUF_SIZE;
+ int status = stream.deflate(JZlib.Z_PARTIAL_FLUSH);
+ switch (status) {
+ case JZlib.Z_OK:
+ buffer.putRawBytes(tmpbuf, 0, BUF_SIZE - stream.avail_out);
+ break;
+ default:
+ throw new SSHRuntimeException("compress: deflate returned " + status);
+ }
+ } while (stream.avail_out == 0);
+ }
+
+ public void init(Type type, int level) {
+ stream = new ZStream();
+ if (type == Type.Deflater)
+ stream.deflateInit(level);
+ else
+ stream.inflateInit();
+ }
+
+ public boolean isDelayed() {
+ return false;
+ }
+
+ public void uncompress(SSHPacket from, SSHPacket to) throws TransportException {
+ stream.next_in = from.array();
+ stream.next_in_index = from.rpos();
+ stream.avail_in = from.available();
+
+ while (true) {
+ stream.next_out = tmpbuf;
+ stream.next_out_index = 0;
+ stream.avail_out = BUF_SIZE;
+ int status = stream.inflate(JZlib.Z_PARTIAL_FLUSH);
+ switch (status) {
+ case JZlib.Z_OK:
+ to.putRawBytes(tmpbuf, 0, BUF_SIZE - stream.avail_out);
+ break;
+ case JZlib.Z_BUF_ERROR:
+ return; // wtf.. but this works *head spins*
+ default:
+ throw new TransportException(DisconnectReason.COMPRESSION_ERROR, "uncompress: inflate returned "
+ + status);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/digest/BaseDigest.java b/src/main/java/net/schmizz/sshj/transport/digest/BaseDigest.java
new file mode 100644
index 00000000..42cdebd7
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/digest/BaseDigest.java
@@ -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.digest;
+
+import net.schmizz.sshj.common.SSHRuntimeException;
+import net.schmizz.sshj.common.SecurityUtils;
+
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+
+/** Base class for Digest algorithms based on the JCE provider. */
+public class BaseDigest implements Digest {
+
+ private final String algorithm;
+ private final int bsize;
+ private MessageDigest md;
+
+ /**
+ * Create a new digest using the given algorithm and block size. The initialization and creation of the underlying
+ * {@link MessageDigest} object will be done in the {@link #init()} method.
+ *
+ * @param algorithm the JCE algorithm to use for this digest
+ * @param bsize the block size of this digest
+ */
+ public BaseDigest(String algorithm, int bsize) {
+ this.algorithm = algorithm;
+ this.bsize = bsize;
+ }
+
+ public byte[] digest() {
+ return md.digest();
+ }
+
+ public int getBlockSize() {
+ return bsize;
+ }
+
+ public void init() {
+ try {
+ md = SecurityUtils.getMessageDigest(algorithm);
+ } catch (GeneralSecurityException e) {
+ throw new SSHRuntimeException(e);
+ }
+ }
+
+ public void update(byte[] foo) {
+ update(foo, 0, foo.length);
+ }
+
+ public void update(byte[] foo, int start, int len) {
+ md.update(foo, start, len);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/digest/Digest.java b/src/main/java/net/schmizz/sshj/transport/digest/Digest.java
new file mode 100644
index 00000000..5541d3c3
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/digest/Digest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.digest;
+
+/** Interface used to compute digests, based on algorithms such as MD5 or SHA1. */
+public interface Digest {
+
+ byte[] digest();
+
+ int getBlockSize();
+
+ void init();
+
+ void update(byte[] foo);
+
+ void update(byte[] foo, int start, int len);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/digest/MD5.java b/src/main/java/net/schmizz/sshj/transport/digest/MD5.java
new file mode 100644
index 00000000..2738fbf2
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/digest/MD5.java
@@ -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.
+ *
+ * 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.digest;
+
+/** MD5 Digest. */
+public class MD5 extends BaseDigest {
+
+ /** Named factory for MD5 digest */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public Digest create() {
+ return new MD5();
+ }
+
+ public String getName() {
+ return "md5";
+ }
+ }
+
+ /** Create a new instance of a MD5 digest */
+ public MD5() {
+ super("MD5", 16);
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/digest/SHA1.java b/src/main/java/net/schmizz/sshj/transport/digest/SHA1.java
new file mode 100644
index 00000000..07917171
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/digest/SHA1.java
@@ -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.
+ *
+ * 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.digest;
+
+/** SHA1 Digest. */
+public class SHA1 extends BaseDigest {
+
+ /** Named factory for SHA1 digest */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public Digest create() {
+ return new SHA1();
+ }
+
+ public String getName() {
+ return "sha1";
+ }
+ }
+
+ /** Create a new instance of a SHA1 digest */
+ public SHA1() {
+ super("SHA-1", 20);
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/kex/AbstractDHG.java b/src/main/java/net/schmizz/sshj/transport/kex/AbstractDHG.java
new file mode 100644
index 00000000..24e6e46e
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/kex/AbstractDHG.java
@@ -0,0 +1,144 @@
+/*
+ * 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.kex;
+
+import net.schmizz.sshj.common.Buffer;
+import net.schmizz.sshj.common.ByteArrayUtils;
+import net.schmizz.sshj.common.DisconnectReason;
+import net.schmizz.sshj.common.Factory;
+import net.schmizz.sshj.common.KeyType;
+import net.schmizz.sshj.common.Message;
+import net.schmizz.sshj.common.SSHPacket;
+import net.schmizz.sshj.signature.Signature;
+import net.schmizz.sshj.transport.Transport;
+import net.schmizz.sshj.transport.TransportException;
+import net.schmizz.sshj.transport.digest.Digest;
+import net.schmizz.sshj.transport.digest.SHA1;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.PublicKey;
+
+/**
+ * Base class for DHG key exchange algorithms. Implementations will only have to configure the required data on the
+ * {@link DH} class in the
+ */
+public abstract class AbstractDHG implements KeyExchange {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private Transport trans;
+
+ private final Digest sha = new SHA1();
+ private final DH dh = new DH();
+
+ private byte[] V_S;
+ private byte[] V_C;
+ private byte[] I_S;
+ private byte[] I_C;
+
+ private byte[] e;
+ private byte[] f;
+ private byte[] K;
+ private byte[] H;
+ private PublicKey hostKey;
+
+ public byte[] getH() {
+ return ByteArrayUtils.copyOf(H);
+ }
+
+ public Digest getHash() {
+ return sha;
+ }
+
+ public PublicKey getHostKey() {
+ return hostKey;
+ }
+
+ public byte[] getK() {
+ return ByteArrayUtils.copyOf(K);
+ }
+
+ public void init(Transport trans, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C) throws TransportException {
+ this.trans = trans;
+ this.V_S = ByteArrayUtils.copyOf(V_S);
+ this.V_C = ByteArrayUtils.copyOf(V_C);
+ this.I_S = ByteArrayUtils.copyOf(I_S);
+ this.I_C = ByteArrayUtils.copyOf(I_C);
+ sha.init();
+ initDH(dh);
+ e = dh.getE();
+
+ log.info("Sending SSH_MSG_KEXDH_INIT");
+ trans.write(new SSHPacket(Message.KEXDH_INIT).putMPInt(e));
+ }
+
+ public boolean next(Message msg, SSHPacket packet) throws TransportException {
+ if (msg != Message.KEXDH_31)
+ throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, "Unxpected packet: " + msg);
+
+ log.info("Received SSH_MSG_KEXDH_REPLY");
+ final byte[] K_S = packet.readBytes();
+ f = packet.readMPIntAsBytes();
+ final byte[] sig = packet.readBytes(); // signature sent by server
+ dh.setF(f);
+ K = dh.getK();
+
+ hostKey = new Buffer.PlainBuffer(K_S).readPublicKey();
+
+ final Buffer.PlainBuffer buf = new Buffer.PlainBuffer() // our hash
+ .putString(V_C) //
+ .putString(V_S) //
+ .putString(I_C) //
+ .putString(I_S) //
+ .putString(K_S) //
+ .putMPInt(e) //
+ .putMPInt(f) //
+ .putMPInt(K); //
+ sha.update(buf.array(), 0, buf.available());
+ H = sha.digest();
+
+ Signature signature = Factory.Named.Util.create(trans.getConfig().getSignatureFactories(), KeyType.fromKey(hostKey).toString());
+ signature.init(hostKey, null);
+ signature.update(H, 0, H.length);
+ if (!signature.verify(sig))
+ throw new TransportException(DisconnectReason.KEY_EXCHANGE_FAILED, "KeyExchange signature verification failed");
+ return true;
+ }
+
+ protected abstract void initDH(DH dh);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/kex/DH.java b/src/main/java/net/schmizz/sshj/transport/kex/DH.java
new file mode 100644
index 00000000..d83e98b0
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/kex/DH.java
@@ -0,0 +1,133 @@
+/*
+ * 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.kex;
+
+import net.schmizz.sshj.common.ByteArrayUtils;
+import net.schmizz.sshj.common.SSHRuntimeException;
+import net.schmizz.sshj.common.SecurityUtils;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+
+/** Diffie-Hellman key generator. */
+public class DH {
+
+ private BigInteger p;
+ private BigInteger g;
+ private BigInteger e; // my public key
+ private byte[] e_array;
+ private BigInteger f; // your public key
+ private BigInteger K; // shared secret key
+ private byte[] K_array;
+ private final KeyPairGenerator myKpairGen;
+ private final KeyAgreement myKeyAgree;
+
+ public DH() {
+ try {
+ myKpairGen = SecurityUtils.getKeyPairGenerator("DH");
+ myKeyAgree = SecurityUtils.getKeyAgreement("DH");
+ } catch (GeneralSecurityException e) {
+ throw new SSHRuntimeException(e);
+ }
+
+ }
+
+ public byte[] getE() {
+ if (e == null) {
+ DHParameterSpec dhSkipParamSpec = new DHParameterSpec(p, g);
+ KeyPair myKpair;
+ try {
+ myKpairGen.initialize(dhSkipParamSpec);
+ myKpair = myKpairGen.generateKeyPair();
+ myKeyAgree.init(myKpair.getPrivate());
+ } catch (GeneralSecurityException e) {
+ throw new SSHRuntimeException(e);
+ }
+ e = ((javax.crypto.interfaces.DHPublicKey) myKpair.getPublic()).getY();
+ e_array = e.toByteArray();
+ }
+ return ByteArrayUtils.copyOf(e_array);
+ }
+
+ public byte[] getK() {
+ if (K == null) {
+ try {
+ KeyFactory myKeyFac = SecurityUtils.getKeyFactory("DH");
+ DHPublicKeySpec keySpec = new DHPublicKeySpec(f, p, g);
+ PublicKey yourPubKey = myKeyFac.generatePublic(keySpec);
+ myKeyAgree.doPhase(yourPubKey, true);
+ } catch (GeneralSecurityException e) {
+ throw new SSHRuntimeException(e);
+ }
+ byte[] mySharedSecret = myKeyAgree.generateSecret();
+ K = new BigInteger(mySharedSecret);
+ K_array = mySharedSecret;
+ }
+ return ByteArrayUtils.copyOf(K_array);
+ }
+
+ public void setF(byte[] f) {
+ setF(new BigInteger(f));
+ }
+
+ public void setG(byte[] g) {
+ setG(new BigInteger(g));
+ }
+
+ public void setP(byte[] p) {
+ setP(new BigInteger(p));
+ }
+
+ void setF(BigInteger f) {
+ this.f = f;
+ }
+
+ void setG(BigInteger g) {
+ this.g = g;
+ }
+
+ void setP(BigInteger p) {
+ this.p = p;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/kex/DHG1.java b/src/main/java/net/schmizz/sshj/transport/kex/DHG1.java
new file mode 100644
index 00000000..15c7872b
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/kex/DHG1.java
@@ -0,0 +1,64 @@
+/*
+ * 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.kex;
+
+/**
+ * Diffie-Hellman key exchange with SHA-1 and Oakley Group 2 [RFC2409] (1024-bit MODP Group).
+ *
+ * @see RFC 4253
+ */
+public class DHG1 extends AbstractDHG {
+
+ /** Named factory for DHG1 key exchange */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public KeyExchange create() {
+ return new DHG1();
+ }
+
+ public String getName() {
+ return "diffie-hellman-group1-sha1";
+ }
+
+ }
+
+ @Override
+ protected void initDH(DH dh) {
+ dh.setG(DHGroupData.getG());
+ dh.setP(DHGroupData.getP1());
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/kex/DHG14.java b/src/main/java/net/schmizz/sshj/transport/kex/DHG14.java
new file mode 100644
index 00000000..9eee9d5e
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/kex/DHG14.java
@@ -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.
+ *
+ * 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.kex;
+
+/**
+ * Diffie-Hellman key exchange with SHA-1 and Oakley Group 14 [RFC3526] (2048-bit MODP Group).
+ *
+ * DHG14 does not work with the default JCE implementation provided by Sun because it does not support 2048 bits
+ * encryption. It requires BouncyCastle to be used.
+ */
+public class DHG14 extends AbstractDHG {
+
+ /** Named factory for DHG14 key exchange */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public KeyExchange create() {
+ return new DHG14();
+ }
+
+ public String getName() {
+ return "diffie-hellman-group14-sha1";
+ }
+
+ }
+
+ @Override
+ protected void initDH(DH dh) {
+ dh.setG(DHGroupData.getG());
+ dh.setP(DHGroupData.getP14());
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/kex/DHGroupData.java b/src/main/java/net/schmizz/sshj/transport/kex/DHGroupData.java
new file mode 100644
index 00000000..c4cfa649
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/kex/DHGroupData.java
@@ -0,0 +1,104 @@
+/*
+ * 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.kex;
+
+/** Simple class holding the data for DH group key exchanges. */
+public final class DHGroupData {
+
+ public static byte[] getG() {
+ final byte[] G = {2};
+ return G;
+ }
+
+ public static byte[] getP1() {
+ final byte[] P_1 = {(byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xC9, (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21, (byte) 0x68,
+ (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62, (byte) 0x8B, (byte) 0x80, (byte) 0xDC,
+ (byte) 0x1C, (byte) 0xD1, (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A, (byte) 0x67,
+ (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B, (byte) 0xBE, (byte) 0xA6, (byte) 0x3B, (byte) 0x13,
+ (byte) 0x9B, (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79, (byte) 0x8E, (byte) 0x34,
+ (byte) 0x04, (byte) 0xDD, (byte) 0xEF, (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD, (byte) 0x3A,
+ (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A, (byte) 0x6D, (byte) 0xF2, (byte) 0x5F,
+ (byte) 0x14, (byte) 0x37, (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D, (byte) 0x51,
+ (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85, (byte) 0xB5, (byte) 0x76, (byte) 0x62, (byte) 0x5E,
+ (byte) 0x7E, (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9, (byte) 0xA6, (byte) 0x37,
+ (byte) 0xED, (byte) 0x6B, (byte) 0x0B, (byte) 0xFF, (byte) 0x5C, (byte) 0xB6, (byte) 0xF4, (byte) 0x06,
+ (byte) 0xB7, (byte) 0xED, (byte) 0xEE, (byte) 0x38, (byte) 0x6B, (byte) 0xFB, (byte) 0x5A, (byte) 0x89,
+ (byte) 0x9F, (byte) 0xA5, (byte) 0xAE, (byte) 0x9F, (byte) 0x24, (byte) 0x11, (byte) 0x7C, (byte) 0x4B,
+ (byte) 0x1F, (byte) 0xE6, (byte) 0x49, (byte) 0x28, (byte) 0x66, (byte) 0x51, (byte) 0xEC, (byte) 0xE6,
+ (byte) 0x53, (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF};
+ return P_1;
+ }
+
+ public static byte[] getP14() {
+ final byte[] P_14 = {(byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC9, (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21,
+ (byte) 0x68, (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62, (byte) 0x8B, (byte) 0x80,
+ (byte) 0xDC, (byte) 0x1C, (byte) 0xD1, (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A,
+ (byte) 0x67, (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B, (byte) 0xBE, (byte) 0xA6, (byte) 0x3B,
+ (byte) 0x13, (byte) 0x9B, (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79, (byte) 0x8E,
+ (byte) 0x34, (byte) 0x04, (byte) 0xDD, (byte) 0xEF, (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD,
+ (byte) 0x3A, (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A, (byte) 0x6D, (byte) 0xF2,
+ (byte) 0x5F, (byte) 0x14, (byte) 0x37, (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D,
+ (byte) 0x51, (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85, (byte) 0xB5, (byte) 0x76, (byte) 0x62,
+ (byte) 0x5E, (byte) 0x7E, (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9, (byte) 0xA6,
+ (byte) 0x37, (byte) 0xED, (byte) 0x6B, (byte) 0x0B, (byte) 0xFF, (byte) 0x5C, (byte) 0xB6, (byte) 0xF4,
+ (byte) 0x06, (byte) 0xB7, (byte) 0xED, (byte) 0xEE, (byte) 0x38, (byte) 0x6B, (byte) 0xFB, (byte) 0x5A,
+ (byte) 0x89, (byte) 0x9F, (byte) 0xA5, (byte) 0xAE, (byte) 0x9F, (byte) 0x24, (byte) 0x11, (byte) 0x7C,
+ (byte) 0x4B, (byte) 0x1F, (byte) 0xE6, (byte) 0x49, (byte) 0x28, (byte) 0x66, (byte) 0x51, (byte) 0xEC,
+ (byte) 0xE4, (byte) 0x5B, (byte) 0x3D, (byte) 0xC2, (byte) 0x00, (byte) 0x7C, (byte) 0xB8, (byte) 0xA1,
+ (byte) 0x63, (byte) 0xBF, (byte) 0x05, (byte) 0x98, (byte) 0xDA, (byte) 0x48, (byte) 0x36, (byte) 0x1C,
+ (byte) 0x55, (byte) 0xD3, (byte) 0x9A, (byte) 0x69, (byte) 0x16, (byte) 0x3F, (byte) 0xA8, (byte) 0xFD,
+ (byte) 0x24, (byte) 0xCF, (byte) 0x5F, (byte) 0x83, (byte) 0x65, (byte) 0x5D, (byte) 0x23, (byte) 0xDC,
+ (byte) 0xA3, (byte) 0xAD, (byte) 0x96, (byte) 0x1C, (byte) 0x62, (byte) 0xF3, (byte) 0x56, (byte) 0x20,
+ (byte) 0x85, (byte) 0x52, (byte) 0xBB, (byte) 0x9E, (byte) 0xD5, (byte) 0x29, (byte) 0x07, (byte) 0x70,
+ (byte) 0x96, (byte) 0x96, (byte) 0x6D, (byte) 0x67, (byte) 0x0C, (byte) 0x35, (byte) 0x4E, (byte) 0x4A,
+ (byte) 0xBC, (byte) 0x98, (byte) 0x04, (byte) 0xF1, (byte) 0x74, (byte) 0x6C, (byte) 0x08, (byte) 0xCA,
+ (byte) 0x18, (byte) 0x21, (byte) 0x7C, (byte) 0x32, (byte) 0x90, (byte) 0x5E, (byte) 0x46, (byte) 0x2E,
+ (byte) 0x36, (byte) 0xCE, (byte) 0x3B, (byte) 0xE3, (byte) 0x9E, (byte) 0x77, (byte) 0x2C, (byte) 0x18,
+ (byte) 0x0E, (byte) 0x86, (byte) 0x03, (byte) 0x9B, (byte) 0x27, (byte) 0x83, (byte) 0xA2, (byte) 0xEC,
+ (byte) 0x07, (byte) 0xA2, (byte) 0x8F, (byte) 0xB5, (byte) 0xC5, (byte) 0x5D, (byte) 0xF0, (byte) 0x6F,
+ (byte) 0x4C, (byte) 0x52, (byte) 0xC9, (byte) 0xDE, (byte) 0x2B, (byte) 0xCB, (byte) 0xF6, (byte) 0x95,
+ (byte) 0x58, (byte) 0x17, (byte) 0x18, (byte) 0x39, (byte) 0x95, (byte) 0x49, (byte) 0x7C, (byte) 0xEA,
+ (byte) 0x95, (byte) 0x6A, (byte) 0xE5, (byte) 0x15, (byte) 0xD2, (byte) 0x26, (byte) 0x18, (byte) 0x98,
+ (byte) 0xFA, (byte) 0x05, (byte) 0x10, (byte) 0x15, (byte) 0x72, (byte) 0x8E, (byte) 0x5A, (byte) 0x8A,
+ (byte) 0xAC, (byte) 0xAA, (byte) 0x68, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
+ return P_14;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/kex/KeyExchange.java b/src/main/java/net/schmizz/sshj/transport/kex/KeyExchange.java
new file mode 100644
index 00000000..33b7a94c
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/kex/KeyExchange.java
@@ -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.kex;
+
+import net.schmizz.sshj.common.Message;
+import net.schmizz.sshj.common.SSHPacket;
+import net.schmizz.sshj.transport.Transport;
+import net.schmizz.sshj.transport.TransportException;
+import net.schmizz.sshj.transport.digest.Digest;
+
+import java.security.PublicKey;
+
+/** Key exchange algorithm. */
+public interface KeyExchange {
+
+ /**
+ * Retrieves the computed H parameter
+ *
+ * @return
+ */
+ byte[] getH();
+
+ /**
+ * The message digest used by this key exchange algorithm.
+ *
+ * @return the message digest
+ */
+ Digest getHash();
+
+ PublicKey getHostKey();
+
+ /** Retrieves the computed K parameter */
+ byte[] getK();
+
+ /**
+ * Initialize the key exchange algorithm.
+ *
+ * @param V_S the server identification string
+ * @param V_C the client identification string
+ * @param I_S the server key init packet
+ * @param I_C the client key init packet
+ */
+ void init(Transport trans, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C) throws TransportException;
+
+ /**
+ * Process the next packet
+ *
+ * @param buffer the packet
+ *
+ * @return a boolean indicating if the processing is complete or if more packets are to be received
+ */
+ boolean next(Message msg, SSHPacket buffer) throws TransportException;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java b/src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java
new file mode 100644
index 00000000..94b695ab
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/mac/BaseMAC.java
@@ -0,0 +1,117 @@
+/*
+ * 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.mac;
+
+import net.schmizz.sshj.common.SSHRuntimeException;
+import net.schmizz.sshj.common.SecurityUtils;
+
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.GeneralSecurityException;
+
+/** Base class for MAC implementations based on the JCE provider. */
+public class BaseMAC implements MAC {
+
+ private final String algorithm;
+ private final int defbsize;
+ private final int bsize;
+ private final byte[] tmp;
+ private javax.crypto.Mac mac;
+
+ public BaseMAC(String algorithm, int bsize, int defbsize) {
+ this.algorithm = algorithm;
+ this.bsize = bsize;
+ this.defbsize = defbsize;
+ tmp = new byte[defbsize];
+ }
+
+ public byte[] doFinal() {
+ return mac.doFinal();
+ }
+
+ public byte[] doFinal(byte[] input) {
+ return mac.doFinal(input);
+ }
+
+ public void doFinal(byte[] buf, int offset) {
+ try {
+ if (bsize != defbsize) {
+ mac.doFinal(tmp, 0);
+ System.arraycopy(tmp, 0, buf, offset, bsize);
+ } else
+ mac.doFinal(buf, offset);
+ } catch (ShortBufferException e) {
+ throw new SSHRuntimeException(e);
+ }
+ }
+
+ public int getBlockSize() {
+ return bsize;
+ }
+
+ public void init(byte[] key) {
+ if (key.length > defbsize) {
+ byte[] tmp = new byte[defbsize];
+ System.arraycopy(key, 0, tmp, 0, defbsize);
+ key = tmp;
+ }
+
+ SecretKeySpec skey = new SecretKeySpec(key, algorithm);
+ try {
+ mac = SecurityUtils.getMAC(algorithm);
+ mac.init(skey);
+ } catch (GeneralSecurityException e) {
+ throw new SSHRuntimeException(e);
+ }
+ }
+
+ public void update(byte foo[], int s, int l) {
+ mac.update(foo, s, l);
+ }
+
+ public void update(byte[] foo) {
+ mac.update(foo, 0, foo.length);
+ }
+
+ public void update(long i) {
+ tmp[0] = (byte) (i >>> 24);
+ tmp[1] = (byte) (i >>> 16);
+ tmp[2] = (byte) (i >>> 8);
+ tmp[3] = (byte) i;
+ update(tmp, 0, 4);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/mac/HMACMD5.java b/src/main/java/net/schmizz/sshj/transport/mac/HMACMD5.java
new file mode 100644
index 00000000..cae47575
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/mac/HMACMD5.java
@@ -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.mac;
+
+/** HMAC-MD5 MAC. */
+public class HMACMD5 extends BaseMAC {
+
+ /** Named factory for the HMACMD5 MAC */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public MAC create() {
+ return new HMACMD5();
+ }
+
+ public String getName() {
+ return "hmac-md5";
+ }
+ }
+
+ public HMACMD5() {
+ super("HmacMD5", 16, 16);
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/mac/HMACMD596.java b/src/main/java/net/schmizz/sshj/transport/mac/HMACMD596.java
new file mode 100644
index 00000000..0a726645
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/mac/HMACMD596.java
@@ -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.
+ *
+ * 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.mac;
+
+/** HMAC-MD5-96 MAC */
+public class HMACMD596 extends BaseMAC {
+
+ /** Named factory for the HMAC-MD5-96 MAC */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public MAC create() {
+ return new HMACMD596();
+ }
+
+ public String getName() {
+ return "hmac-md5-96";
+ }
+ }
+
+ public HMACMD596() {
+ super("HmacMD5", 12, 16);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA1.java b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA1.java
new file mode 100644
index 00000000..a29abb6e
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA1.java
@@ -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.mac;
+
+/** HMAC-SHA1 MAC */
+public class HMACSHA1 extends BaseMAC {
+
+ /** Named factory for the HMAC-SHA1 MAC */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public MAC create() {
+ return new HMACSHA1();
+ }
+
+ public String getName() {
+ return "hmac-sha1";
+ }
+ }
+
+ public HMACSHA1() {
+ super("HmacSHA1", 20, 20);
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA196.java b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA196.java
new file mode 100644
index 00000000..72d1dcd9
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA196.java
@@ -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.mac;
+
+/** HMAC-SHA1-96 MAC */
+public class HMACSHA196 extends BaseMAC {
+
+ /** Named factory for the HMAC-SHA1-96 MAC */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public MAC create() {
+ return new HMACSHA196();
+ }
+
+ public String getName() {
+ return "hmac-sha1-96";
+ }
+ }
+
+ public HMACSHA196() {
+ super("HmacSHA1", 12, 20);
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/mac/MAC.java b/src/main/java/net/schmizz/sshj/transport/mac/MAC.java
new file mode 100644
index 00000000..30e8c605
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/mac/MAC.java
@@ -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.mac;
+
+/** Message Authentication Code for use in SSH. It usually wraps a javax.crypto.Mac class. */
+public interface MAC {
+
+ byte[] doFinal();
+
+ byte[] doFinal(byte[] input);
+
+ void doFinal(byte[] buf, int offset);
+
+ int getBlockSize();
+
+ void init(byte[] key);
+
+ void update(byte[] foo);
+
+ void update(byte[] foo, int start, int len);
+
+ void update(long foo);
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/random/BouncyCastleRandom.java b/src/main/java/net/schmizz/sshj/transport/random/BouncyCastleRandom.java
new file mode 100644
index 00000000..e41bcc6c
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/random/BouncyCastleRandom.java
@@ -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.transport.random;
+
+import org.bouncycastle.crypto.prng.RandomGenerator;
+import org.bouncycastle.crypto.prng.VMPCRandomGenerator;
+
+import java.security.SecureRandom;
+
+/**
+ * BouncyCastle Random. This pseudo random number generator uses the a very fast PRNG from BouncyCastle.
+ * The JRE random will be used when creating a new generator to add some random data to the seed.
+ */
+public class BouncyCastleRandom implements Random {
+
+ /** Named factory for the BouncyCastle Random */
+ public static class Factory implements net.schmizz.sshj.common.Factory {
+
+ public Random create() {
+ return new BouncyCastleRandom();
+ }
+
+ }
+
+ private final RandomGenerator random;
+
+ public BouncyCastleRandom() {
+ random = new VMPCRandomGenerator();
+ byte[] seed = new SecureRandom().generateSeed(8);
+ random.addSeedMaterial(seed);
+ }
+
+ public void fill(byte[] bytes, int start, int len) {
+ random.nextBytes(bytes, start, len);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/random/JCERandom.java b/src/main/java/net/schmizz/sshj/transport/random/JCERandom.java
new file mode 100644
index 00000000..44327245
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/random/JCERandom.java
@@ -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.random;
+
+import java.security.SecureRandom;
+
+/** A {@link Random} implementation using the built-in {@link SecureRandom} PRNG. */
+public class JCERandom implements Random {
+
+ /** Named factory for the JCE {@link Random} */
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public Random create() {
+ return new JCERandom();
+ }
+
+ public String getName() {
+ return "default";
+ }
+
+ }
+
+ private byte[] tmp = new byte[16];
+ private SecureRandom random = null;
+
+ public JCERandom() {
+ random = new SecureRandom();
+ }
+
+ /**
+ * Fill the given byte-array with random bytes from this PRNG.
+ *
+ * @param foo the byte-array
+ * @param start the offset to start at
+ * @param len the number of bytes to fill
+ */
+ public synchronized void fill(byte[] foo, int start, int len) {
+ if (start == 0 && len == foo.length)
+ random.nextBytes(foo);
+ else
+ synchronized (this) {
+ if (len > tmp.length)
+ tmp = new byte[len];
+ random.nextBytes(tmp);
+ System.arraycopy(tmp, 0, foo, start, len);
+ }
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/random/Random.java b/src/main/java/net/schmizz/sshj/transport/random/Random.java
new file mode 100644
index 00000000..5967bbd1
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/random/Random.java
@@ -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.
+ *
+ * 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.random;
+
+/** A pseudo random number generator. */
+public interface Random {
+
+ /**
+ * Fill part of bytes with random values.
+ *
+ * @param bytes byte array to be filled.
+ * @param start index to start filling at.
+ * @param len length of segment to fill.
+ */
+ void fill(byte[] bytes, int start, int len);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/random/SingletonRandomFactory.java b/src/main/java/net/schmizz/sshj/transport/random/SingletonRandomFactory.java
new file mode 100644
index 00000000..c7d2d210
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/random/SingletonRandomFactory.java
@@ -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.random;
+
+import net.schmizz.sshj.common.Factory;
+
+/** A random factory wrapper that uses a single random instance. The underlying random instance has to be thread safe. */
+public class SingletonRandomFactory implements Random, Factory {
+ private final Random random;
+
+ public SingletonRandomFactory(Factory factory) {
+ random = factory.create();
+ }
+
+ public Random create() {
+ return this;
+ }
+
+ public void fill(byte[] bytes, int start, int len) {
+ random.fill(bytes, start, len);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/verification/ConsoleHostKeyVerifier.java b/src/main/java/net/schmizz/sshj/transport/verification/ConsoleHostKeyVerifier.java
new file mode 100644
index 00000000..5d5e15fa
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/verification/ConsoleHostKeyVerifier.java
@@ -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.transport.verification;
+
+import net.schmizz.sshj.common.KeyType;
+import net.schmizz.sshj.common.SecurityUtils;
+
+import java.io.Console;
+import java.io.File;
+import java.io.IOException;
+import java.security.PublicKey;
+
+public class ConsoleHostKeyVerifier extends OpenSSHKnownHosts {
+
+ private static final String YES = "yes";
+ private static final String NO = "no";
+
+ private final Console console;
+
+ public ConsoleHostKeyVerifier(File khFile, Console console) throws IOException {
+ super(khFile);
+ this.console = console;
+ }
+
+ @Override
+ protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
+ console.printf("The authenticity of host '%s' can't be established.\n" +
+ "%s key fingerprint is %s.\n", hostname, KeyType.fromKey(key), SecurityUtils.getFingerprint(key));
+ String response = console.readLine("Are you sure you want to continue connecting (yes/no)? ");
+ while (!(response.equalsIgnoreCase(YES) || response.equalsIgnoreCase(NO))) {
+ response = console.readLine("Please explicitly enter yes/no: ");
+ }
+ if (response.equalsIgnoreCase(YES)) {
+ entries().add(new Entry(hostname, key));
+ try {
+ write();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean hostKeyChangedAction(Entry entry, String hostname, PublicKey key) throws IOException {
+ final KeyType type = KeyType.fromKey(key);
+ final String fp = SecurityUtils.getFingerprint(key);
+ final String path = khFile.getAbsolutePath();
+ console.printf(
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
+ "@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @\n" +
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" +
+ "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
+ "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
+ "It is also possible that the host key has just been changed.\n" +
+ "The fingerprint for the %s key sent by the remote host is\n" +
+ "%s.\n" +
+ "Please contact your system administrator or" +
+ "add correct host key in %s to get rid of this message.\n", type, fp, path);
+ return false;
+ }
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/verification/HostKeyVerifier.java b/src/main/java/net/schmizz/sshj/transport/verification/HostKeyVerifier.java
new file mode 100644
index 00000000..59866d54
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/verification/HostKeyVerifier.java
@@ -0,0 +1,38 @@
+/*
+ * 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.transport.verification;
+
+import java.security.PublicKey;
+
+/** Host key verification interface. */
+public interface HostKeyVerifier {
+
+ /**
+ * This callback is invoked when the server's host key needs to be verified. The return value indicates to the
+ * caller whether the SSH connection should proceed.
+ *
+ * Note: host key verification is the basis for security in SSH, therefore exercise due caution in
+ * implementing!
+ *
+ * @param hostname remote hostname
+ * @param port remote port
+ * @param key host key of server
+ *
+ * @return {@code true} if key is acceptable, {@code false} otherwise
+ */
+ boolean verify(String hostname, int port, PublicKey key);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/verification/InsecureAccepter.java b/src/main/java/net/schmizz/sshj/transport/verification/InsecureAccepter.java
new file mode 100644
index 00000000..1df74b45
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/verification/InsecureAccepter.java
@@ -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.transport.verification;
+
+import java.security.PublicKey;
+
+public final class InsecureAccepter implements HostKeyVerifier {
+
+ public boolean verify(String hostname, int port, PublicKey key) {
+ return true;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java
new file mode 100644
index 00000000..c848526c
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java
@@ -0,0 +1,267 @@
+/*
+ * 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.verification;
+
+import net.schmizz.sshj.common.Base64;
+import net.schmizz.sshj.common.Buffer;
+import net.schmizz.sshj.common.ByteArrayUtils;
+import net.schmizz.sshj.common.IOUtils;
+import net.schmizz.sshj.common.KeyType;
+import net.schmizz.sshj.common.SSHException;
+import net.schmizz.sshj.transport.mac.HMACSHA1;
+import net.schmizz.sshj.transport.mac.MAC;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+// TODO: allow modifications to known_hosts e.g. adding entries
+
+/**
+ * A {@link HostKeyVerifier} implementation for a {@code known_hosts} file i.e. in the format used by OpenSSH.
+ *
+ * Hashed hostnames are correctly handled.
+ *
+ * @see Hashed hostnames spec
+ */
+public class OpenSSHKnownHosts implements HostKeyVerifier {
+
+ private static final String LS = System.getProperty("line.separator");
+
+ /** Represents a single line */
+ public static class Entry {
+
+ private final MAC sha1 = new HMACSHA1();
+
+ private final List hosts;
+ private final KeyType type;
+
+ private PublicKey key;
+ private String sKey;
+
+ /** Construct an entry from the hostname and public key */
+ public Entry(String host, PublicKey key) {
+ this.key = key;
+ this.hosts = Arrays.asList(host);
+ type = KeyType.fromKey(key);
+ }
+
+ /**
+ * Construct an entry from a string containing the line
+ *
+ * @param line the line from a known_hosts file
+ *
+ * @throws SSHException if it could not be parsed for any reason
+ */
+ public Entry(String line) throws SSHException {
+ String[] parts = line.split(" ");
+ if (parts.length != 3)
+ throw new SSHException("Line parts not 3: " + line);
+ hosts = Arrays.asList(parts[0].split(","));
+ type = KeyType.fromString(parts[1]);
+ if (type == KeyType.UNKNOWN)
+ throw new SSHException("Unknown key type: " + parts[1]);
+ sKey = parts[2];
+ }
+
+ /** Checks whether this entry is applicable to some {@code hostname} */
+ public boolean appliesTo(String hostname) throws IOException {
+ if (!hosts.isEmpty() && hosts.get(0).startsWith("|1|")) { // Hashed hostname
+ final String[] splitted = hosts.get(0).split("\\|");
+ if (splitted.length != 4)
+ return false;
+
+ final byte[] salt = Base64.decode(splitted[2]);
+ if (salt.length != 20)
+ return false;
+ sha1.init(salt);
+
+ final byte[] host = Base64.decode(splitted[3]);
+ if (ByteArrayUtils.equals(host, sha1.doFinal(hostname.getBytes())))
+ return true;
+ } else
+ // Un-hashed, possibly comma-delimited
+ for (String host : hosts)
+ if (host.equals(hostname))
+ return true;
+ return false;
+ }
+
+ /**
+ * Returns the public host key represented in this entry.
+ *
+ * The key is cached so repeated calls to this method may be made without concern.
+ *
+ * @return the host key
+ */
+ public PublicKey getKey() {
+ if (key == null) {
+ byte[] decoded;
+ try {
+ decoded = Base64.decode(sKey);
+ } catch (IOException e) {
+ return null;
+ }
+ key = new Buffer.PlainBuffer(decoded).readPublicKey();
+ }
+ return key;
+ }
+
+ public KeyType getType() {
+ return type;
+ }
+
+ public String getLine() {
+ StringBuilder line = new StringBuilder();
+ for (String host : hosts) {
+ if (line.length() > 0)
+ line.append(",");
+ line.append(host);
+ }
+ line.append(" ").append(type.toString());
+ line.append(" ").append(getKeyString());
+ return line.toString();
+ }
+
+ private String getKeyString() {
+ if (sKey == null) {
+ final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(key);
+ sKey = Base64.encodeBytes(buf.array(), buf.rpos(), buf.available());
+ }
+ return sKey;
+ }
+
+ @Override
+ public String toString() {
+ return "Entry{hostnames=" + hosts + "; type=" + type + "; key=" + getKey() + "}";
+ }
+
+ }
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ protected final File khFile;
+ protected final List entries = new ArrayList();
+
+ /**
+ * Constructs a {@code KnownHosts} object from a file location
+ *
+ * @param khFile the file location
+ *
+ * @throws IOException if there is an error reading the file
+ */
+ public OpenSSHKnownHosts(File khFile) throws IOException {
+ this.khFile = khFile;
+ if (khFile.exists()) {
+ BufferedReader br = new BufferedReader(new FileReader(khFile));
+ String line;
+ try {
+ // Read in the file, storing each line as an entry
+ while ((line = br.readLine()) != null)
+ try {
+ entries.add(new Entry(line));
+ } catch (SSHException ignore) {
+ log.debug("Bad line ({}): {} ", ignore.toString(), line);
+ }
+ } finally {
+ IOUtils.closeQuietly(br);
+ }
+ }
+ }
+
+ /**
+ * Checks whether the specified host is known per the contents of the {@code known_hosts} file.
+ *
+ * @return {@code true} on successful verification or {@code false} on failure
+ */
+ public boolean verify(String hostname, int port, PublicKey key) {
+ KeyType type = KeyType.fromKey(key);
+ if (type == KeyType.UNKNOWN)
+ return false;
+
+ if (port != 22)
+ hostname = "[" + hostname + "]:" + port;
+
+ for (Entry e : entries)
+ try {
+ if (e.getType() == type && e.appliesTo(hostname))
+ if (key.equals(e.getKey()))
+ return true;
+ else {
+ return hostKeyChangedAction(e, hostname, key);
+ }
+ } catch (IOException ioe) {
+ log.error("Error with {}: {}", e, ioe);
+ return false;
+ }
+ return hostKeyUnverifiableAction(hostname, key);
+ }
+
+ protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
+ return false;
+ }
+
+ protected boolean hostKeyChangedAction(Entry entry, String hostname, PublicKey key) throws IOException {
+ log.warn("Host key for `{}` has changed!", hostname);
+ return false;
+ }
+
+ public List entries() {
+ return entries;
+ }
+
+ public void write() throws IOException {
+ BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(khFile));
+ for (Entry entry : entries)
+ bos.write((entry.getLine() + LS).getBytes());
+ bos.close();
+ }
+
+ public static File detectSSHDir() {
+ final File sshDir = new File(System.getProperty("user.home"), ".ssh");
+ return sshDir.exists() ? sshDir : null;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/AuthParams.java b/src/main/java/net/schmizz/sshj/userauth/AuthParams.java
new file mode 100644
index 00000000..38f4946b
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/AuthParams.java
@@ -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.userauth;
+
+import net.schmizz.sshj.transport.Transport;
+
+/** The parameters available to authentication method */
+public interface AuthParams {
+
+ /** All userauth requests need to include the name of the next service being requested */
+ String getNextServiceName();
+
+ /**
+ * Retrieve the transport which will allow sending packets; retrieving information like the session-id, remote
+ * host/port etc. which is needed by some method.
+ */
+ Transport getTransport();
+
+ /** All userauth requests need to include the username */
+ String getUsername();
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/schmizz/sshj/userauth/UserAuth.java b/src/main/java/net/schmizz/sshj/userauth/UserAuth.java
new file mode 100644
index 00000000..321e9f28
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/UserAuth.java
@@ -0,0 +1,99 @@
+/*
+ * 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.userauth;
+
+import net.schmizz.sshj.Service;
+import net.schmizz.sshj.transport.TransportException;
+import net.schmizz.sshj.userauth.method.AuthMethod;
+
+import java.util.Deque;
+
+/**
+ * User authentication API
+ *
+ * @see rfc4252
+ */
+public interface UserAuth {
+
+ /**
+ * Attempt to authenticate {@code username} using each of {@link methods} in order. {@code nextService} is the
+ * {@link net.schmizz.sshj.Service} that will be enabled on successful authentication.
+ *
+ * Authentication fails if there are no method available, i.e. if all the method failed or there were method
+ * available but could not be attempted because the server did not allow them. In this case, a {@code
+ * UserAuthException} is thrown with its cause as the last authentication failure. Other {@code UserAuthException}'s
+ * which may have been ignored may be accessed via {@link #getSavedExceptions()}.
+ *
+ * Further attempts may also be made by catching {@code UserAuthException} and retrying with this method.
+ *
+ * @param username the user to authenticate
+ * @param nextService the service to set on successful authentication
+ * @param methods the {@link AuthMethod}'s to try
+ *
+ * @throws UserAuthException in case of authentication failure
+ * @throws TransportException if there was a transport-layer error
+ */
+ void authenticate(String username, Service nextService, Iterable methods) throws UserAuthException,
+ TransportException;
+
+ /**
+ * Returns the authentication banner (if any). In some cases this is available even before the first authentication
+ * request has been made.
+ *
+ * @return the banner, or {@code null} if none was received
+ */
+ String getBanner();
+
+ /** Returns saved exceptions that might have been ignored because there were more authentication method available. */
+ Deque getSavedExceptions();
+
+ /** Returns the {@code timeout} for a method to successfully authenticate before it is abandoned. */
+ int getTimeout();
+
+ /**
+ * Returns whether authentication was partially successful. Some server's may be configured to require multiple
+ * authentications; and this value will be {@code true} if at least one of the method supplied succeeded.
+ */
+ boolean hadPartialSuccess();
+
+ /**
+ * Set the {@code timeout} for any method to successfully authenticate before it is abandoned.
+ *
+ * @param timeout the timeout in seconds
+ */
+ void setTimeout(int timeout);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/UserAuthException.java b/src/main/java/net/schmizz/sshj/userauth/UserAuthException.java
new file mode 100644
index 00000000..50926bc9
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/UserAuthException.java
@@ -0,0 +1,88 @@
+/*
+ * 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.userauth;
+
+import net.schmizz.concurrent.ExceptionChainer;
+import net.schmizz.sshj.common.DisconnectReason;
+import net.schmizz.sshj.common.SSHException;
+
+/** User authentication exception */
+public class UserAuthException extends SSHException {
+
+ public static final ExceptionChainer chainer = new ExceptionChainer() {
+
+ public UserAuthException chain(Throwable t) {
+ if (t instanceof UserAuthException)
+ return (UserAuthException) t;
+ else
+ return new UserAuthException(t);
+ }
+
+ };
+
+ public UserAuthException() {
+ super();
+ }
+
+ public UserAuthException(DisconnectReason code) {
+ super(code);
+ }
+
+ public UserAuthException(DisconnectReason code, String message) {
+ super(code, message);
+ }
+
+ public UserAuthException(DisconnectReason code, String message, Throwable cause) {
+ super(code, message, cause);
+ }
+
+ public UserAuthException(DisconnectReason code, Throwable cause) {
+ super(code, cause);
+ }
+
+ public UserAuthException(String message) {
+ super(message);
+ }
+
+ public UserAuthException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UserAuthException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/UserAuthProtocol.java b/src/main/java/net/schmizz/sshj/userauth/UserAuthProtocol.java
new file mode 100644
index 00000000..46cc03af
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/UserAuthProtocol.java
@@ -0,0 +1,250 @@
+/*
+ * 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.userauth;
+
+import net.schmizz.concurrent.Event;
+import net.schmizz.sshj.AbstractService;
+import net.schmizz.sshj.Service;
+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 net.schmizz.sshj.userauth.method.AuthMethod;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/** {@link UserAuth} implementation. */
+public class UserAuthProtocol extends AbstractService implements UserAuth, AuthParams {
+
+ private final Set allowed = new HashSet();
+
+ private final Deque savedEx = new ArrayDeque();
+
+ private final Event result = new Event("userauth result",
+ UserAuthException.chainer);
+
+ private String username;
+ private AuthMethod currentMethod;
+ private Service nextService;
+
+ private boolean firstAttempt = true;
+
+ private volatile String banner;
+ private volatile boolean partialSuccess;
+
+ public UserAuthProtocol(Transport trans) {
+ super("ssh-userauth", trans);
+ }
+
+ // synchronized for mutual exclusion; ensure one authenticate() ever in progress
+
+ public synchronized void authenticate(String username, Service nextService, Iterable methods)
+ throws UserAuthException, TransportException {
+ clearState();
+
+ this.username = username;
+ this.nextService = nextService;
+
+ // Request "ssh-userauth" service (if not already active)
+ request();
+
+ if (firstAttempt) { // Assume all allowed
+ for (AuthMethod meth : methods)
+ allowed.add(meth.getName());
+ firstAttempt = false;
+ }
+
+ try {
+
+ for (AuthMethod meth : methods)
+
+ if (allowed.contains(meth.getName())) {
+
+ log.info("Trying `{}` auth...", meth.getName());
+
+ boolean success = false;
+ try {
+ success = tryWith(meth);
+ } catch (UserAuthException e) {
+ // Give other method a shot
+ saveException(e);
+ }
+
+ if (success) {
+ log.info("`{}` auth successful", meth.getName());
+ return;
+ } else
+ log.info("`{}` auth failed", meth.getName());
+
+ } else
+ saveException(currentMethod.getName() + " auth not allowed by server");
+
+ } finally {
+ currentMethod = null;
+ }
+
+ log.debug("Had {} saved exception(s)", savedEx.size());
+ throw new UserAuthException("Exhausted available authentication methods", savedEx.peek());
+ }
+
+ public String getBanner() {
+ return banner;
+ }
+
+ public String getNextServiceName() {
+ return nextService.getName();
+ }
+
+ public Transport getTransport() {
+ return trans;
+ }
+
+ /**
+ * Returns the exceptions that occured during authentication process but were ignored because more method were
+ * available for trying.
+ *
+ * @return deque of saved exceptions
+ */
+ public Deque getSavedExceptions() {
+ return savedEx;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public boolean hadPartialSuccess() {
+ return partialSuccess;
+ }
+
+ @Override
+ public void handle(Message msg, SSHPacket buf) throws SSHException {
+ if (!msg.in(50, 80)) // ssh-userauth packets have message numbers between 50-80
+ throw new TransportException(DisconnectReason.PROTOCOL_ERROR);
+
+ switch (msg) {
+
+ case USERAUTH_BANNER:
+ gotBanner(buf);
+ break;
+
+ case USERAUTH_SUCCESS:
+ gotSuccess();
+ break;
+
+ case USERAUTH_FAILURE:
+ gotFailure(buf);
+ break;
+
+ default:
+ gotUnknown(msg, buf);
+
+ }
+ }
+
+ @Override
+ public void notifyError(SSHException error) {
+ super.notifyError(error);
+ result.error(error);
+ }
+
+ private void clearState() {
+ allowed.clear();
+ savedEx.clear();
+ banner = null;
+ }
+
+ private void gotBanner(SSHPacket buf) {
+ banner = buf.readString();
+ }
+
+ private void gotFailure(SSHPacket buf) throws UserAuthException, TransportException {
+ allowed.clear();
+ allowed.addAll(Arrays.asList(buf.readString().split(",")));
+ partialSuccess |= buf.readBoolean();
+ if (allowed.contains(currentMethod.getName()) && currentMethod.shouldRetry())
+ currentMethod.request();
+ else {
+ saveException(currentMethod.getName() + " auth failed");
+ result.set(false);
+ }
+ }
+
+ private void gotSuccess() {
+ trans.setAuthenticated(); // So it can put delayed compression into force if applicable
+ trans.setService(nextService); // We aren't in charge anymore, next service is
+ result.set(true);
+ }
+
+ private void gotUnknown(Message msg, SSHPacket buf) throws SSHException {
+ if (currentMethod == null || result == null) {
+ trans.sendUnimplemented();
+ return;
+ }
+
+ log.debug("Asking {} method to handle {} packet", currentMethod.getName(), msg);
+ try {
+ currentMethod.handle(msg, buf);
+ } catch (UserAuthException e) {
+ result.error(e);
+ }
+ }
+
+ private void saveException(String msg) {
+ saveException(new UserAuthException(msg));
+ }
+
+ private void saveException(UserAuthException e) {
+ log.error("Saving for later - {}", e.toString());
+ savedEx.push(e);
+ }
+
+ private boolean tryWith(AuthMethod meth) throws UserAuthException, TransportException {
+ currentMethod = meth;
+ result.clear();
+ meth.init(this);
+ meth.request();
+ return result.get(timeout, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java
new file mode 100644
index 00000000..c9e76308
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.userauth.keyprovider;
+
+import net.schmizz.sshj.userauth.password.PasswordFinder;
+
+import java.io.File;
+
+/** A file key provider is initialized with a location of */
+public interface FileKeyProvider extends KeyProvider {
+
+ enum Format {
+ PKCS8, OpenSSH, Unknown
+ }
+
+ void init(File location);
+
+ void init(File location, PasswordFinder pwdf);
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyPairWrapper.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyPairWrapper.java
new file mode 100644
index 00000000..40018973
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyPairWrapper.java
@@ -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.
+ *
+ * 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.userauth.keyprovider;
+
+import net.schmizz.sshj.common.KeyType;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/** A {@link KeyProvider} wrapper around {@link java.security.KeyPair} */
+public class KeyPairWrapper implements KeyProvider {
+
+ private final KeyPair kp;
+ private final KeyType type;
+
+ public KeyPairWrapper(KeyPair kp) {
+ this.kp = kp;
+ type = KeyType.fromKey(kp.getPublic());
+ }
+
+ public KeyPairWrapper(PublicKey publicKey, PrivateKey privateKey) {
+ this(new KeyPair(publicKey, privateKey));
+ }
+
+ public PrivateKey getPrivate() {
+ return kp.getPrivate();
+ }
+
+ public PublicKey getPublic() {
+ return kp.getPublic();
+ }
+
+ public KeyType getType() {
+ return type;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProvider.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProvider.java
new file mode 100644
index 00000000..10533acb
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProvider.java
@@ -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.userauth.keyprovider;
+
+import net.schmizz.sshj.common.KeyType;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/** A KeyProvider is a container for a public-private keypair. */
+public interface KeyProvider {
+
+ /** Returns the private key. */
+ PrivateKey getPrivate() throws IOException;
+
+ /** Returns the public key. */
+ PublicKey getPublic() throws IOException;
+
+ /** Returns the {@link KeyType}. */
+ KeyType getType() throws IOException;
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java
new file mode 100644
index 00000000..de3e2639
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java
@@ -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.userauth.keyprovider;
+
+import net.schmizz.sshj.common.IOUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class KeyProviderUtil {
+
+ /**
+ * Attempts to detect how a key file is encoded.
+ *
+ * Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
+ *
+ * @param location
+ *
+ * @return name of the key file format
+ *
+ * @throws java.io.IOException
+ */
+ public static FileKeyProvider.Format detectKeyFileFormat(File location) throws IOException {
+ BufferedReader br = new BufferedReader(new FileReader(location));
+ String firstLine = br.readLine();
+ IOUtils.closeQuietly(br);
+ if (firstLine == null)
+ throw new IOException("Empty file");
+ if (firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----"))
+ if (new File(location + ".pub").exists())
+ // Can delay asking for password since have unencrypted pubkey
+ return FileKeyProvider.Format.OpenSSH;
+ else
+ // More general
+ return FileKeyProvider.Format.PKCS8;
+ /*
+ * TODO: Tectia, PuTTY (.ppk) ...
+ */
+ return FileKeyProvider.Format.OpenSSH;
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java
new file mode 100644
index 00000000..2fb47113
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java
@@ -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.userauth.keyprovider;
+
+import net.schmizz.sshj.common.Base64;
+import net.schmizz.sshj.common.Buffer;
+import net.schmizz.sshj.common.KeyType;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.security.PublicKey;
+
+/**
+ * Represents an OpenSSH identity that consists of a PKCS8-encoded private key file and an unencrypted public key file
+ * of the same name with the {@code ".pub"} extension. This allows to delay requesting of the passphrase until the
+ * private key is requested.
+ *
+ * @see PKCS8KeyFile
+ */
+public class OpenSSHKeyFile extends PKCS8KeyFile {
+
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named {
+
+ public FileKeyProvider create() {
+ return new OpenSSHKeyFile();
+ }
+
+ public String getName() {
+ return "OpenSSH";
+ }
+ }
+
+ private PublicKey pubKey;
+
+ @Override
+ public PublicKey getPublic() throws IOException {
+ return pubKey != null ? pubKey : super.getPublic();
+ }
+
+ @Override
+ public void init(File location) {
+ File f = new File(location + ".pub");
+ if (f.exists())
+ try {
+ BufferedReader br = new BufferedReader(new FileReader(f));
+ String keydata = br.readLine();
+ if (keydata != null) {
+ String[] parts = keydata.split(" ");
+ assert parts.length >= 2;
+ type = KeyType.fromString(parts[0]);
+ pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
+ }
+ br.close();
+ } catch (IOException e) {
+ // let super provide both public & private key
+ log.warn("Error reading public key file: {}", e.toString());
+ }
+ super.init(location);
+ }
+
+}
diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java
new file mode 100644
index 00000000..e93bd671
--- /dev/null
+++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java
@@ -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.userauth.keyprovider;
+
+import net.schmizz.sshj.common.IOUtils;
+import net.schmizz.sshj.common.KeyType;
+import net.schmizz.sshj.userauth.password.PasswordFinder;
+import net.schmizz.sshj.userauth.password.PasswordUtils;
+import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
+import net.schmizz.sshj.userauth.password.Resource;
+import org.bouncycastle.openssl.EncryptionException;
+import org.bouncycastle.openssl.PEMReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */
+public class PKCS8KeyFile implements FileKeyProvider {
+
+ public static class Factory implements net.schmizz.sshj.common.Factory.Named