import local

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

202
LICENSE Normal file
View File

@@ -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.

8
NOTICE Normal file
View File

@@ -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

9
README Normal file
View File

@@ -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

27
buildfile Normal file
View File

@@ -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

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.concurrent;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/*
* Syntactic sugar around Future
*/
/**
* A kind of {@link Future} that caters to boolean values.
* <p/>
* An event can be set, cleared, or awaited, similar to Python's {@code threading.event}. The key difference is that a
* waiter may be delivered an exception of parameterized type {@code T}. Furthermore, an event {@link #isSet()} when it
* is not {@code null} i.e. it can be either {@code true} or {@code false} when set.
*
* @see Future
*/
public class Event<T extends Exception> extends Future<Boolean, T> {
/**
* Creates this event with given {@code name} and exception {@code chainer}. Allocates a new {@link
* java.util.concurrent.locks.Lock Lock} object for this event.
*
* @param name name of this event
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
*/
public Event(String name, ExceptionChainer<T> chainer) {
super(name, chainer);
}
/**
* Creates this event with given {@code name}, exception {@code chainer}, and associated {@code lock}.
*
* @param name name of this event
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
* @param lock lock to use
*/
public Event(String name, ExceptionChainer<T> chainer, ReentrantLock lock) {
super(name, chainer, lock);
}
/** Sets this event to be {@code true}. Short for {@code set(true)}. */
public void set() {
super.set(true);
}
/**
* Await this event to have a definite {@code true} or {@code false} value.
*
* @throws T if another thread meanwhile informs this event of an error
*/
public void await() throws T {
super.get();
}
/**
* Await this event to have a definite {@code true} or {@code false} value, for {@code timeout} seconds.
*
* @param timeout timeout in seconds
* @param unit
*
* @throws T if another thread meanwhile informs this event of an error, or timeout expires
*/
public void await(long timeout, TimeUnit unit) throws T {
super.get(timeout, unit);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.concurrent;
/**
* Chains an exception to desired type. For example: </p>
* <p/>
* <pre>
* ExceptionChainer&lt;SomeException&gt; chainer = new ExceptionChainer&lt;SomeException&gt;()
* {
* public SomeException chain(Throwable t)
* {
* if (t instanceof SomeException)
* return (SomeException) t;
* else
* return new SomeExcepion(t);
* }
* };
* </pre>
*
* @param <Z> Throwable type
*/
public interface ExceptionChainer<Z extends Throwable> {
Z chain(Throwable t);
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.concurrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Represents future data of the parameterized type {@code V} and allows waiting on it. An exception may also be
* delivered to a waiter, and will be of the parameterized type {@code T}.
* <p/>
* For atomic operations on a future, e.g. checking if a value is set and if it is not then setting it - in other words,
* Compare-And-Set type operations - the associated lock for the future should be acquired while doing so.
*/
public class Future<V, T extends Exception> {
private final Logger log;
private final ExceptionChainer<T> chainer;
private final ReentrantLock lock;
private final Condition cond;
private V val;
private T pendingEx;
/**
* Creates this future with given {@code name} and exception {@code chainer}. Allocates a new {@link
* java.util.concurrent.locks.Lock lock} object for this future.
*
* @param name name of this future
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
*/
public Future(String name, ExceptionChainer<T> chainer) {
this(name, chainer, null);
}
/**
* Creates this future with given {@code name}, exception {@code chainer}, and associated {@code lock}.
*
* @param name name of this future
* @param chainer {@link ExceptionChainer} that will be used for chaining exceptions
* @param lock lock to use
*/
public Future(String name, ExceptionChainer<T> chainer, ReentrantLock lock) {
this.log = LoggerFactory.getLogger("<< " + name + " >>");
this.chainer = chainer;
this.lock = lock == null ? new ReentrantLock() : lock;
this.cond = this.lock.newCondition();
}
/**
* Set this future's value to {@code val}. Any waiters will be delivered this value.
*
* @param val the value
*/
public void set(V val) {
lock();
try {
log.debug("Setting to `{}`", val);
this.val = val;
cond.signalAll();
} finally {
unlock();
}
}
/**
* Queues error that will be thrown in any waiting thread or any thread that attempts to wait on this future
* hereafter.
*
* @param e the error
*/
public void error(Exception e) {
lock();
try {
pendingEx = chainer.chain(e);
cond.signalAll();
} finally {
unlock();
}
}
/** Clears this future by setting its value and queued exception to {@code null}. */
public void clear() {
lock();
try {
pendingEx = null;
set(null);
} finally {
unlock();
}
}
/**
* Wait indefinitely for this future's value to be set.
*
* @return the value
*
* @throws T in case another thread informs the future of an error meanwhile
*/
public V get() throws T {
return get(0, TimeUnit.SECONDS);
}
/**
* Wait for {@code timeout} seconds for this future's value to be set.
*
* @param timeout the timeout in seconds
* @param unit
*
* @return the value
*
* @throws T in case another thread informs the future of an error meanwhile, or the timeout expires
*/
public V get(long timeout, TimeUnit unit) throws T {
lock();
try {
if (pendingEx != null)
throw pendingEx;
if (val != null)
return val;
log.debug("Awaiting");
while (val == null && pendingEx == null)
if (timeout == 0)
cond.await();
else if (!cond.await(timeout, unit))
throw chainer.chain(new TimeoutException("Timeout expired"));
if (pendingEx != null) {
log.error("Woke to: {}", pendingEx.toString());
throw pendingEx;
}
return val;
} catch (InterruptedException ie) {
throw chainer.chain(ie);
} finally {
unlock();
}
}
/** @return Whether this future has a value set, and no error waiting to pop. */
public boolean isSet() {
lock();
try {
return pendingEx == null && val != null;
} finally {
unlock();
}
}
/** @return Whether this future currently has an error set. */
public boolean hasError() {
lock();
try {
return pendingEx != null;
} finally {
unlock();
}
}
/** @return Whether this future has threads waiting on it. */
public boolean hasWaiters() {
lock();
try {
return lock.hasWaiters(cond);
} finally {
unlock();
}
}
/**
* Lock using the associated lock. Use as part of a {@code try-finally} construct in conjunction with {@link
* #unlock()}.
*/
public void lock() {
lock.lock();
}
/**
* Unlock using the associated lock. Use as part of a {@code try-finally} construct in conjunction with {@link
* #lock()}.
*/
public void unlock() {
lock.unlock();
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.concurrent;
import java.util.Collection;
public class FutureUtils {
public static void alertAll(Exception x, Future... futures) {
for (Future f : futures)
f.error(x);
}
public static void alertAll(Exception x, Collection<? extends Future> futures) {
for (Future f : futures)
f.error(x);
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** An abstract class for {@link Service} that implements common or default functionality. */
public abstract class AbstractService implements Service {
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
/** Assigned name of this service */
protected final String name;
/** Transport layer */
protected final Transport trans;
/** Timeout for blocking operations */
protected int timeout;
public AbstractService(String name, Transport trans) {
this.name = name;
this.trans = trans;
timeout = trans.getTimeout();
}
public String getName() {
return name;
}
public int getTimeout() {
return this.timeout;
}
public void handle(Message msg, SSHPacket buf) throws SSHException {
trans.sendUnimplemented();
}
public void notifyError(SSHException error) {
log.debug("Was notified of {}", error.toString());
}
public void notifyUnimplemented(long seqNum) throws SSHException {
throw new SSHException(DisconnectReason.PROTOCOL_ERROR, "Unexpected: SSH_MSG_UNIMPLEMENTED");
}
public void request() throws TransportException {
final Service active = trans.getService();
if (!equals(active))
if (name.equals(active.getName()))
trans.setService(this);
else
trans.reqService(this);
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void notifyDisconnect() throws SSHException {
log.debug("Was notified of disconnect");
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.signature.Signature;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.Compression;
import net.schmizz.sshj.transport.kex.KeyExchange;
import net.schmizz.sshj.transport.mac.MAC;
import net.schmizz.sshj.transport.random.Random;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import java.util.List;
/**
* Holds configuration information and factories. Acts a container for factories of {@link KeyExchange}, {@link Cipher},
* {@link Compression}, {@link MAC}, {@link Signature}, {@link Random}, and {@link FileKeyProvider}.
*/
public interface Config {
/**
* Retrieve the list of named factories for {@code Cipher}.
*
* @return a list of named {@code Cipher} factories
*/
List<Factory.Named<Cipher>> getCipherFactories();
/**
* Retrieve the list of named factories for {@code Compression}.
*
* @return a list of named {@code Compression} factories
*/
List<Factory.Named<Compression>> getCompressionFactories();
/**
* Retrieve the list of named factories for {@code FileKeyProvider}.
*
* @return a list of named {@code FileKeyProvider} factories
*/
List<Factory.Named<FileKeyProvider>> getFileKeyProviderFactories();
/**
* Retrieve the list of named factories for <code>KeyExchange</code>.
*
* @return a list of named <code>KeyExchange</code> factories
*/
List<Factory.Named<KeyExchange>> getKeyExchangeFactories();
/**
* Retrieve the list of named factories for <code>MAC</code>.
*
* @return a list of named <code>MAC</code> factories
*/
List<Factory.Named<MAC>> getMACFactories();
/**
* Retrieve the {@link net.schmizz.sshj.transport.random.Random} factory.
*
* @return the {@link net.schmizz.sshj.transport.random.Random} factory
*/
Factory<Random> getRandomFactory();
/**
* Retrieve the list of named factories for {@link net.schmizz.sshj.signature.Signature}
*
* @return a list of named {@link net.schmizz.sshj.signature.Signature} factories
*/
List<Factory.Named<Signature>> getSignatureFactories();
/**
* Returns the software version information for identification during SSH connection initialization. For example,
* {@code "NET_3_0"}.
*/
String getVersion();
/**
* Set the named factories for {@link net.schmizz.sshj.transport.cipher.Cipher}.
*
* @param cipherFactories a list of named factories
*/
void setCipherFactories(List<Factory.Named<Cipher>> cipherFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.transport.compression.Compression}.
*
* @param compressionFactories a list of named factories
*/
void setCompressionFactories(List<Factory.Named<Compression>> compressionFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.userauth.keyprovider.FileKeyProvider}.
*
* @param fileKeyProviderFactories a list of named factories
*/
void setFileKeyProviderFactories(List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.transport.kex.KeyExchange}.
*
* @param kexFactories a list of named factories
*/
void setKeyExchangeFactories(List<Factory.Named<KeyExchange>> kexFactories);
/**
* Set the named factories for {@link net.schmizz.sshj.transport.mac.MAC}.
*
* @param macFactories a list of named factories
*/
void setMACFactories(List<Factory.Named<MAC>> macFactories);
/**
* Set the factory for {@link net.schmizz.sshj.transport.random.Random}.
*
* @param randomFactory the factory
*/
void setRandomFactory(Factory<Random> randomFactory);
/**
* Set the named factories for {@link net.schmizz.sshj.signature.Signature}.
*
* @param signatureFactories a list of named factories
*/
void setSignatureFactories(List<Factory.Named<Signature>> signatureFactories);
/**
* Set the software version information for identification during SSH connection initialization. For example, {@code
* "NET_3_0"}.
*
* @param version software version info
*/
void setVersion(String version);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacketHandler;
import net.schmizz.sshj.transport.TransportException;
/** Represents a service running on top of the SSH {@link net.schmizz.sshj.transport.Transport transport layer}. */
public interface Service extends SSHPacketHandler, ErrorNotifiable {
/** @return The assigned name for this SSH service. */
String getName();
/**
* Notifies this service that a {@code SSH_MSG_UNIMPLEMENTED} was received for packet with given sequence number.
* Meant to be invoked as a callback by the transport layer.
*
* @param seqNum sequence number of the packet which the server claims is unimplemented
*
* @throws SSHException if the packet is unexpected and may represent a disruption
*/
void notifyUnimplemented(long seqNum) throws SSHException;
/**
* Request and install this service with the associated transport. Implementations should aim to make this method
* idempotent by first checking the {@link net.schmizz.sshj.transport.Transport#getService() currently active
* service}.
*
* @throws TransportException if there is an error sending the service request
*/
void request() throws TransportException;
void notifyDisconnect() throws SSHException;
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.common;
/** Disconnect error codes */
public enum DisconnectReason {
UNKNOWN(0),
HOST_NOT_ALLOWED_TO_CONNECT(1),
PROTOCOL_ERROR(2),
KEY_EXCHANGE_FAILED(3),
HOST_AUTHENTICATION_FAILED(4),
RESERVED(4),
MAC_ERROR(5),
COMPRESSION_ERROR(6),
SERVICE_NOT_AVAILABLE(7),
PROTOCOL_VERSION_NOT_SUPPORTED(8),
HOST_KEY_NOT_VERIFIABLE(9),
CONNECTION_LOST(10),
BY_APPLICATION(11),
TOO_MANY_CONNECTIONS(12),
AUTH_CANCELLED_BY_USER(13),
NO_MORE_AUTH_METHODS_AVAILABLE(14),
ILLEGAL_USER_NAME(15);
public static DisconnectReason fromInt(int code) {
for (DisconnectReason dc : values())
if (dc.code == code)
return dc;
return UNKNOWN;
}
private final int code;
private DisconnectReason(int code) {
this.code = code;
}
public int toInt() {
return code;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.common;
import java.util.Collection;
/** API for classes that are capable of being notified on an error so they can cleanup. */
public interface ErrorNotifiable {
/** Utility functions. */
class Util {
/** Notify all {@code notifiables} of given {@code error}. */
public static void alertAll(SSHException error, ErrorNotifiable... notifiables) {
for (ErrorNotifiable notifiable : notifiables)
notifiable.notifyError(error);
}
/** Notify all {@code notifiables} of given {@code error}. */
public static void alertAll(SSHException error, Collection<? extends ErrorNotifiable> notifiables) {
for (ErrorNotifiable notifiable : notifiables)
notifiable.notifyError(error);
}
}
/** Notifies this object of an {@code error}. */
void notifyError(SSHException error);
}

View File

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

View File

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

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.common;
import java.security.Key;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public enum KeyType {
/** SSH identifier for RSA keys */
RSA("ssh-rsa", new KeyChecker() {
public boolean isMyType(Key key) {
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
}
}),
/** SSH identifier for DSA keys */
DSA("ssh-dss", new KeyChecker() {
public boolean isMyType(Key key) {
return (key instanceof DSAPublicKey || key instanceof DSAPrivateKey);
}
}),
/** Unrecognized */
UNKNOWN("unknown", null);
private static interface KeyChecker {
boolean isMyType(Key key);
}
private final String sType;
private final KeyChecker checker;
private KeyType(String type, KeyChecker checker) {
this.sType = type;
this.checker = checker;
}
public static KeyType fromKey(Key key) {
for (KeyType kt : values())
if (kt.checker != null && kt.checker.isMyType((key)))
return kt;
return UNKNOWN;
}
public static KeyType fromString(String sType) {
for (KeyType kt : values())
if (kt.sType.equals(sType))
return kt;
return UNKNOWN;
}
@Override
public String toString() {
return sType;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.common;
/** SSH message identifiers */
public enum Message {
DISCONNECT(1),
IGNORE(2),
UNIMPLEMENTED(3),
DEBUG(4),
SERVICE_REQUEST(5),
SERVICE_ACCEPT(6),
KEXINIT(20),
NEWKEYS(21),
KEXDH_INIT(30),
/** { KEXDH_REPLY, KEXDH_GEX_GROUP } */
KEXDH_31(31),
KEX_DH_GEX_INIT(32),
KEX_DH_GEX_REPLY(33),
KEX_DH_GEX_REQUEST(34),
USERAUTH_REQUEST(50),
USERAUTH_FAILURE(51),
USERAUTH_SUCCESS(52),
USERAUTH_BANNER(53),
/** { USERAUTH_PASSWD_CHANGREQ, USERAUTH_PK_OK, USERAUTH_INFO_REQUEST } */
USERAUTH_60(60),
USERAUTH_INFO_RESPONSE(61),
GLOBAL_REQUEST(80),
REQUEST_SUCCESS(81),
REQUEST_FAILURE(82),
CHANNEL_OPEN(90),
CHANNEL_OPEN_CONFIRMATION(91),
CHANNEL_OPEN_FAILURE(92),
CHANNEL_WINDOW_ADJUST(93),
CHANNEL_DATA(94),
CHANNEL_EXTENDED_DATA(95),
CHANNEL_EOF(96),
CHANNEL_CLOSE(97),
CHANNEL_REQUEST(98),
CHANNEL_SUCCESS(99),
CHANNEL_FAILURE(100);
private final byte b;
private static final Message[] commands = new Message[256];
static {
for (Message c : Message.values())
if (commands[c.toByte()] == null)
commands[c.toByte()] = c;
}
public static Message fromByte(byte b) {
return commands[b];
}
Message(int b) {
this.b = (byte) b;
}
public boolean geq(int num) {
return b >= num;
}
public boolean gt(int num) {
return b > num;
}
public boolean in(int x, int y) {
return b >= x && b <= y;
}
public boolean leq(int num) {
return b <= num;
}
public boolean lt(int num) {
return b < num;
}
public byte toByte() {
return b;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.common;
/**
* An interface for classes to which packet handling may be delegated. Chains of such delegations may be used, e.g.
* {@code packet decoder -> (SSHPacketHandler) transport layer -> (SSHPacketHandler) connection layer ->
* (SSHPacketHandler) channel}.
*/
public interface SSHPacketHandler {
/**
* Delegate handling of some SSH packet to this object.
*
* @param msg the SSH {@link Message message identifier}
* @param buf {@link SSHPacket} containing rest of the request
*
* @throws SSHException if there is a non-recoverable error
*/
void handle(Message msg, SSHPacket buf) throws SSHException;
}

View File

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

View File

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

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
public class StreamCopier extends Thread {
private static final Logger LOG = LoggerFactory.getLogger(StreamCopier.class);
public interface ErrorCallback {
void onError(IOException ioe);
}
public static ErrorCallback closeOnErrorCallback(final Closeable... toClose) {
final Closeable[] closeables = Arrays.copyOf(toClose, toClose.length);
return new ErrorCallback() {
public void onError(IOException ioe) {
IOUtils.closeQuietly(closeables);
}
};
}
public static long copy(InputStream in, OutputStream out, int bufSize, boolean keepFlushing) throws IOException {
long count = 0;
final long startTime = System.currentTimeMillis();
final byte[] buf = new byte[bufSize];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
count += read;
if (keepFlushing)
out.flush();
}
if (!keepFlushing)
out.flush();
final double sizeKiB = count / 1024.0;
final double timeSeconds = (System.currentTimeMillis() - startTime) / 1000.0;
LOG.info(sizeKiB + " KiB transferred in {} seconds ({} KiB/s)", timeSeconds, (sizeKiB / timeSeconds));
return count;
}
public static String copyStreamToString(InputStream stream) throws IOException {
final StringBuilder sb = new StringBuilder();
int read;
while ((read = stream.read()) != -1)
sb.append((char) read);
return sb.toString();
}
private final Logger log;
private final InputStream in;
private final OutputStream out;
private int bufSize = 1;
private boolean keepFlushing = true;
private ErrorCallback errCB = new ErrorCallback() {
public void onError(IOException ioe) {
}
}; // Default null cb
public StreamCopier(String name, InputStream in, OutputStream out) {
this.in = in;
this.out = out;
setName("streamCopier");
log = LoggerFactory.getLogger(name);
}
public StreamCopier bufSize(int size) {
bufSize = size;
return this;
}
public StreamCopier keepFlushing(boolean choice) {
keepFlushing = choice;
return this;
}
public StreamCopier daemon(boolean choice) {
setDaemon(choice);
return this;
}
public StreamCopier errorCallback(ErrorCallback errCB) {
this.errCB = errCB;
return this;
}
@Override
public void run() {
try {
log.debug("Wil pipe from {} to {}", in, out);
copy(in, out, bufSize, keepFlushing);
log.debug("EOF on {}", in);
} catch (IOException ioe) {
log.error("In pipe from {} to {}: " + ioe.toString(), in, out);
errCB.onError(ioe);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection;
import net.schmizz.concurrent.Future;
import net.schmizz.concurrent.FutureUtils;
import net.schmizz.sshj.AbstractService;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.ErrorNotifiable;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.channel.Channel;
import net.schmizz.sshj.connection.channel.OpenFailException;
import net.schmizz.sshj.connection.channel.OpenFailException.Reason;
import net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener;
import net.schmizz.sshj.transport.Transport;
import net.schmizz.sshj.transport.TransportException;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/** {@link Connection} implementation. */
public class ConnectionProtocol extends AbstractService implements Connection {
private final Object internalSynchronizer = new Object();
private final AtomicInteger nextID = new AtomicInteger();
private final Map<Integer, Channel> channels = new ConcurrentHashMap<Integer, Channel>();
private final Map<String, ForwardedChannelOpener> openers = new ConcurrentHashMap<String, ForwardedChannelOpener>();
private final Queue<Future<SSHPacket, ConnectionException>> globalReqFutures = new LinkedList<Future<SSHPacket, ConnectionException>>();
private int windowSize = 2048 * 1024;
private int maxPacketSize = 32 * 1024;
/**
* Create with an associated {@link Transport}.
*
* @param trans transport layer
*/
public ConnectionProtocol(Transport trans) {
super("ssh-connection", trans);
}
public void attach(Channel chan) {
log.info("Attaching `{}` channel (#{})", chan.getType(), chan.getID());
channels.put(chan.getID(), chan);
}
public Channel get(int id) {
return channels.get(id);
}
public ForwardedChannelOpener get(String chanType) {
return openers.get(chanType);
}
public void forget(Channel chan) {
log.info("Forgetting `{}` channel (#{})", chan.getType(), chan.getID());
channels.remove(chan.getID());
synchronized (internalSynchronizer) {
if (channels.isEmpty())
internalSynchronizer.notifyAll();
}
}
public void forget(ForwardedChannelOpener opener) {
log.info("Forgetting opener for `{}` channels: {}", opener.getChannelType(), opener);
openers.remove(opener.getChannelType());
}
public void attach(ForwardedChannelOpener opener) {
log.info("Attaching opener for `{}` channels: {}", opener.getChannelType(), opener);
openers.put(opener.getChannelType(), opener);
}
private Channel getChannel(SSHPacket buffer) throws ConnectionException {
int recipient = buffer.readInt();
Channel channel = get(recipient);
if (channel != null)
return channel;
else {
buffer.rpos(buffer.rpos() - 5);
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Received " + buffer.readMessageID()
+ " on unknown channel #" + recipient);
}
}
@Override
public void handle(Message msg, SSHPacket buf) throws SSHException {
if (msg.in(91, 100))
getChannel(buf).handle(msg, buf);
else if (msg.in(80, 90))
switch (msg) {
case REQUEST_SUCCESS:
gotGlobalReqResponse(buf);
break;
case REQUEST_FAILURE:
gotGlobalReqResponse(null);
break;
case CHANNEL_OPEN:
gotChannelOpen(buf);
break;
default:
super.handle(msg, buf);
}
else
super.handle(msg, buf);
}
@Override
public void notifyError(SSHException error) {
super.notifyError(error);
synchronized (globalReqFutures) {
FutureUtils.alertAll(error, globalReqFutures);
globalReqFutures.clear();
}
ErrorNotifiable.Util.alertAll(error, channels.values());
channels.clear();
}
public int getMaxPacketSize() {
return maxPacketSize;
}
public Transport getTransport() {
return trans;
}
public void setMaxPacketSize(int maxPacketSize) {
this.maxPacketSize = maxPacketSize;
}
public int getWindowSize() {
return windowSize;
}
public void setWindowSize(int windowSize) {
this.windowSize = windowSize;
}
public void join() throws InterruptedException {
synchronized (internalSynchronizer) {
while (!channels.isEmpty())
internalSynchronizer.wait();
}
}
public int nextID() {
return nextID.getAndIncrement();
}
public Future<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
Buffer.PlainBuffer specifics) throws TransportException {
synchronized (globalReqFutures) {
log.info("Making global request for `{}`", name);
trans.write(new SSHPacket(Message.GLOBAL_REQUEST) //
.putString(name) //
.putBoolean(wantReply) //
.putBuffer(specifics)); //
Future<SSHPacket, ConnectionException> future = null;
if (wantReply) {
future = new Future<SSHPacket, ConnectionException>("global req for " + name, ConnectionException.chainer);
globalReqFutures.add(future);
}
return future;
}
}
private void gotGlobalReqResponse(SSHPacket response) throws ConnectionException {
synchronized (globalReqFutures) {
Future<SSHPacket, ConnectionException> gr = globalReqFutures.poll();
if (gr == null)
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR,
"Got a global request response when none was requested");
else if (response == null)
gr.error(new ConnectionException("Global request [" + gr + "] failed"));
else
gr.set(response);
}
}
private void gotChannelOpen(SSHPacket buf) throws ConnectionException, TransportException {
final String type = buf.readString();
log.debug("Received CHANNEL_OPEN for `{}` channel", type);
if (openers.containsKey(type))
openers.get(type).handleOpen(buf);
else {
log.warn("No opener found for `{}` CHANNEL_OPEN request -- rejecting", type);
sendOpenFailure(buf.readInt(), OpenFailException.Reason.UNKNOWN_CHANNEL_TYPE, "");
}
}
public void sendOpenFailure(int recipient, Reason reason, String message) throws TransportException {
trans.write(new SSHPacket(Message.CHANNEL_OPEN_FAILURE) //
.putInt(recipient) //
.putInt(reason.getCode()) //
.putString(message));
}
@Override
public void notifyDisconnect() throws SSHException {
super.notifyDisconnect();
// wh'about them futures?
for (Channel chan : channels.values())
chan.close();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.TransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class Window {
protected final Logger log;
protected final Object lock = new Object();
protected final int maxPacketSize;
protected int size;
public Window(int chanID, String kindOfWindow, int initialWinSize, int maxPacketSize) {
log = LoggerFactory.getLogger("<< chan#" + chanID + " / " + kindOfWindow + " >>");
size = initialWinSize;
this.maxPacketSize = maxPacketSize;
}
public void expand(int inc) {
synchronized (lock) {
log.debug("Increasing by {} up to {}", inc, size);
size += inc;
lock.notifyAll();
}
}
public int getMaxPacketSize() {
return maxPacketSize;
}
public int getSize() {
return size;
}
public void consume(int dec) {
synchronized (lock) {
log.debug("Consuming by " + dec + " down to " + size);
size -= dec;
if (size < 0)
throw new SSHRuntimeException("Window consumed to below 0");
}
}
@Override
public String toString() {
return "[winSize=" + size + "]";
}
/** Controls how much data we can send before an adjustment notification from remote end is required. */
public static final class Remote extends Window {
public Remote(int chanID, int initialWinSize, int maxPacketSize) {
super(chanID, "remote win", initialWinSize, maxPacketSize);
}
public void waitAndConsume(int howMuch) throws ConnectionException {
synchronized (lock) {
while (size < howMuch) {
log.debug("Waiting, need window space for {} bytes", howMuch);
try {
lock.wait();
} catch (InterruptedException ie) {
throw new ConnectionException(ie);
}
}
consume(howMuch);
}
}
}
/** Controls how much data remote end can send before an adjustment notification from us is required. */
public static final class Local extends Window {
private final int initialSize;
private final int threshold;
public Local(int chanID, int initialWinSize, int maxPacketSize) {
super(chanID, "local win", initialWinSize, maxPacketSize);
this.initialSize = initialWinSize;
threshold = Math.min(maxPacketSize * 20, initialSize / 4);
}
public int neededAdjustment() throws TransportException {
synchronized (lock) {
return (size - threshold <= 0) ? (initialSize - size) : 0;
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel.direct;
import net.schmizz.sshj.common.SSHException;
/** A factory interface for creating SSH {@link Session session channels}. */
public interface SessionFactory {
/**
* Opens a {@code session} channel. The returned {@link Session} instance allows {@link Session#exec(String)
* executing a remote command}, {@link Session#startSubsystem(String) starting a subsystem}, or {@link
* Session#startShell() starting a shell}.
*
* @return the opened {@code session} channel
*
* @throws SSHException
* @see {@link Session}
*/
Session startSession() throws SSHException;
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel.forwarded;
import net.schmizz.sshj.connection.channel.Channel;
import java.io.IOException;
/** A connect listener is just that: it listens for new forwarded channels and can be delegated charge of them. */
public interface ConnectListener {
/**
* Notify this listener of a new forwarded channel. An implementation should firstly {@link
* net.schmizz.sshj.connection.channel.Channel.Forwarded#confirm() confirm} or {@link
* net.schmizz.sshj.connection.channel.Channel.Forwarded#reject() reject} that channel.
*
* @param chan the {@link net.schmizz.sshj.connection.channel.Channel.Forwarded forwarded channel}
*
* @throws java.io.IOException
*/
void gotConnect(Channel.Forwarded chan) throws IOException;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel.forwarded;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.TransportException;
/** Takes care of handling {@code SSH_MSG_CHANNEL_OPEN} requests for forwarded channels of a specific type. */
public interface ForwardedChannelOpener {
/** Returns the name of the channel type this opener can next. */
String getChannelType();
/**
* Delegates a {@code SSH_MSG_CHANNEL_OPEN} request for the channel type claimed by this opener.
*
* @param buf {@link net.schmizz.sshj.common.SSHPacket} containg the request except for the message identifier and
* channel type field
*/
void handleOpen(SSHPacket buf) throws ConnectionException, TransportException;
}

View File

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

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel.forwarded;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.common.StreamCopier.ErrorCallback;
import net.schmizz.sshj.connection.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
/**
* A {@link net.schmizz.sshj.connection.channel.forwarded.ConnectListener} that forwards what is received over the
* channel to a socket and vice-versa.
*/
public class SocketForwardingConnectListener implements ConnectListener {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final SocketAddress addr;
/** Create with a {@link SocketAddress} this listener will forward to. */
public SocketForwardingConnectListener(SocketAddress addr) {
this.addr = addr;
}
/** On connect, confirm the channel and start forwarding. */
public void gotConnect(Channel.Forwarded chan) throws IOException {
log.info("New connection from " + chan.getOriginatorIP() + ":" + chan.getOriginatorPort());
final Socket sock = new Socket();
sock.setSendBufferSize(chan.getLocalMaxPacketSize());
sock.setReceiveBufferSize(chan.getRemoteMaxPacketSize());
sock.connect(addr);
// ok so far -- could connect, let's confirm the channel
chan.confirm();
final ErrorCallback closer = StreamCopier.closeOnErrorCallback(chan, new Closeable() {
public void close() throws IOException {
sock.close();
}
});
new StreamCopier("soc2chan", sock.getInputStream(), chan.getOutputStream())
.bufSize(chan.getRemoteMaxPacketSize())
.errorCallback(closer)
.daemon(true)
.start();
new StreamCopier("chan2soc", chan.getInputStream(), sock.getOutputStream())
.bufSize(chan.getLocalMaxPacketSize())
.errorCallback(closer)
.daemon(true)
.start();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.connection.channel.forwarded;
import net.schmizz.sshj.common.SSHPacket;
import net.schmizz.sshj.connection.Connection;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.transport.TransportException;
/**
* Handles forwarded {@code x11} channels. The actual request to forward X11 should be made from the specific {@link
* net.schmizz.sshj.connection.channel.direct.Session}.
*/
public class X11Forwarder extends AbstractForwardedChannelOpener {
/** An {@code x11} forwarded channel. */
public static class X11Channel extends AbstractForwardedChannel {
public static final String TYPE = "x11";
public X11Channel(Connection conn, int recipient, int remoteWinSize, int remoteMaxPacketSize, String origIP,
int origPort) {
super(TYPE, conn, recipient, remoteWinSize, remoteMaxPacketSize, origIP, origPort);
}
}
private final ConnectListener listener;
/**
* Creates and registers itself with {@code conn}.
*
* @param conn connection layer
* @param listener listener which will be delegated {@link X11Channel}'s to next
*/
public X11Forwarder(Connection conn, ConnectListener listener) {
super(X11Channel.TYPE, conn);
this.listener = listener;
}
/** Internal API */
public void handleOpen(SSHPacket buf) throws ConnectionException, TransportException {
callListener(listener, new X11Channel(conn, buf.readInt(), buf.readInt(), buf.readInt(), buf.readString(), buf
.readInt()));
}
/** Stop handling {@code x11} channel open requests. De-registers itself with connection layer. */
public void stop() {
conn.forget(this);
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.xfer.FilePermission;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public final class FileAttributes {
public static enum Flag {
SIZE(0x00000001), UIDGID(0x00000002), MODE(0x00000004), ACMODTIME(0x00000008), EXTENDED(0x80000000);
private final int flag;
private Flag(int flag) {
this.flag = flag;
}
public boolean isSet(int mask) {
return (mask & flag) == flag;
}
public int get() {
return flag;
}
}
private final FileMode mode;
private final int mask;
private final long size;
private final int uid;
private final int gid;
private final long atime;
private final long mtime;
private final Map<String, String> ext = new HashMap<String, String>();
public FileAttributes() {
size = atime = mtime = uid = gid = mask = 0;
mode = new FileMode(0);
}
public FileAttributes(int mask, long size, int uid, int gid, FileMode mode, long atime, long mtime,
Map<String, String> ext) {
this.mask = mask;
this.size = size;
this.uid = uid;
this.gid = gid;
this.mode = mode;
this.atime = atime;
this.mtime = mtime;
this.ext.putAll(ext);
}
public boolean has(Flag flag) {
return flag.isSet(mask);
}
public long getSize() {
return size;
}
public int getUID() {
return uid;
}
public int getGID() {
return gid;
}
public FileMode getMode() {
return mode;
}
public Set<FilePermission> getPermissions() {
return mode.getPermissions();
}
public FileMode.Type getType() {
return mode.getType();
}
public long getAtime() {
return atime;
}
public long getMtime() {
return mtime;
}
public String getExtended(String type) {
return ext.get(type);
}
public byte[] toBytes() {
Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
buf.putInt(mask);
if (has(Flag.SIZE))
buf.putUINT64(size);
if (has(Flag.UIDGID)) {
buf.putInt(uid);
buf.putInt(gid);
}
if (has(Flag.MODE))
buf.putInt(mode.getMask());
if (has(Flag.ACMODTIME)) {
buf.putInt(atime);
buf.putInt(mtime);
}
if (has(Flag.EXTENDED)) {
buf.putInt(ext.size());
for (Entry<String, String> entry : ext.entrySet()) {
buf.putString(entry.getKey());
buf.putString(entry.getValue());
}
}
return buf.getCompactData();
}
public static class Builder {
private int mask;
private long size;
private long atime;
private long mtime;
private FileMode mode = new FileMode(0);
private int uid;
private int gid;
private final Map<String, String> ext = new HashMap<String, String>();
public Builder withSize(long size) {
mask |= Flag.SIZE.get();
this.size = size;
return this;
}
public Builder withAtimeMtime(long atime, long mtime) {
mask |= Flag.ACMODTIME.get();
this.atime = atime;
this.mtime = mtime;
return this;
}
public Builder withUIDGID(int uid, int gid) {
mask |= Flag.UIDGID.get();
this.uid = uid;
this.gid = gid;
return this;
}
public Builder withPermissions(Set<FilePermission> perms) {
mask |= Flag.MODE.get();
this.mode = new FileMode(FilePermission.toMask(perms));
return this;
}
public Builder withPermissions(int perms) {
mask |= Flag.MODE.get();
this.mode = new FileMode(perms);
return this;
}
public Builder withExtended(String type, String data) {
mask |= Flag.EXTENDED.get();
ext.put(type, data);
return this;
}
public Builder withExtended(Map<String, String> ext) {
mask |= Flag.EXTENDED.get();
this.ext.putAll(ext);
return this;
}
public FileAttributes build() {
return new FileAttributes(mask, size, uid, gid, mode, atime, mtime, ext);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
if (has(Flag.SIZE))
sb.append("size=").append(size).append(";");
if (has(Flag.UIDGID))
sb.append("uid=").append(size).append(",gid=").append(gid).append(";");
if (has(Flag.MODE))
sb.append("mode=").append(mode.toString()).append(";");
if (has(Flag.ACMODTIME))
sb.append("atime=").append(atime).append(",mtime=").append(mtime).append(";");
if (has(Flag.EXTENDED))
sb.append("ext=").append(ext);
sb.append("]");
return sb.toString();
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.xfer.FilePermission;
import java.util.Collections;
import java.util.Set;
public class FileMode {
public static enum Type {
/** block special */
BLOCK_SPECIAL(0060000),
/** character special */
CHAR_SPECIAL(0020000),
/** FIFO special */
FIFO_SPECIAL(0010000),
/** socket special */
SOCKET_SPECIAL(0140000),
/** regular */
REGULAR(0100000),
/** directory */
DIRECTORY(0040000),
/** symbolic link */
SYMKLINK(0120000),
/** unknown */
UNKNOWN(0);
private final int val;
private Type(int val) {
this.val = val;
}
public static Type fromMask(int mask) {
for (Type t : Type.values())
if (t.val == mask)
return t;
return UNKNOWN;
}
public static int toMask(Type t) {
return t.val;
}
}
private final int mask;
private final Type type;
private final Set<FilePermission> perms;
public FileMode(int mask) {
this.mask = mask;
this.type = Type.fromMask(getTypeMask());
this.perms = FilePermission.fromMask(getPermissionsMask());
}
public int getMask() {
return mask;
}
public int getTypeMask() {
return mask & 0770000;
}
public int getPermissionsMask() {
return mask & 07777;
}
public Type getType() {
return type;
}
public Set<FilePermission> getPermissions() {
return Collections.unmodifiableSet(perms);
}
@Override
public String toString() {
return "[mask=" + Integer.toOctalString(mask) + "]";
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import java.util.Set;
public enum OpenMode {
/** Open the file for reading. */
READ(0x00000001),
/**
* Open the file for writing. If both this and {@link OpenMode#READ} are specified, the file is opened for both
* reading and writing.
*/
WRITE(0x00000002),
/** Force all writes to append data at the end of the file. */
APPEND(0x00000004),
/**
* If this flag is specified, then a new file will be created if one does not already exist (if {@link
* OpenMode#TRUNC} is specified, the new file will be truncated to zero length if it previously exists).
*/
CREAT(0x00000008),
/**
* Forces an existing file with the same name to be truncated to zero length when creating a file by specifying
* {@link OpenMode#CREAT}. {@link OpenMode#CREAT} MUST also be specified if this flag is used.
*/
TRUNC(0x00000010),
/**
* Causes the request to fail if the named file already exists. {@link OpenMode#CREAT} MUST also be specified if
* this flag is used.
*/
EXCL(0x00000020);
private final int pflag;
private OpenMode(int pflag) {
this.pflag = pflag;
}
public static int toMask(Set<OpenMode> modes) {
int mask = 0;
for (OpenMode m : modes)
mask |= m.pflag;
return mask;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PacketReader extends Thread {
/** Logger */
private final Logger log = LoggerFactory.getLogger(getClass());
private final InputStream in;
private final Map<Long, Future<Response, SFTPException>> futures = new ConcurrentHashMap<Long, Future<Response, SFTPException>>();
private final SFTPPacket<Response> packet = new SFTPPacket<Response>();
private final byte[] lenBuf = new byte[4];
public PacketReader(InputStream in) {
this.in = in;
setName("sftp reader");
}
private void readIntoBuffer(byte[] buf, int off, int len) throws IOException {
int count = 0;
int read = 0;
while (count < len && ((read = in.read(buf, off + count, len - count)) != -1))
count += read;
if (read == -1)
throw new SFTPException("EOF while reading packet");
}
private int getPacketLength() throws IOException {
readIntoBuffer(lenBuf, 0, lenBuf.length);
return (int) (lenBuf[0] << 24 & 0xff000000L
| lenBuf[1] << 16 & 0x00ff0000L
| lenBuf[2] << 8 & 0x0000ff00L
| lenBuf[3] & 0x000000ffL
);
}
public SFTPPacket<Response> readPacket() throws IOException {
int len = getPacketLength();
packet.rpos(0);
packet.wpos(0);
packet.ensureCapacity(len);
readIntoBuffer(packet.array(), 0, len);
packet.wpos(len);
return packet;
}
@Override
public void run() {
try {
while (true) {
readPacket();
handle();
}
} catch (IOException e) {
for (Future<Response, SFTPException> future : futures.values())
future.error(e);
}
}
public void handle() throws SFTPException {
Response resp = new Response(packet);
Future<Response, SFTPException> future = futures.remove(resp.getRequestID());
log.debug("Received {} packet", resp.getType());
if (future == null)
throw new SFTPException("Received [" + resp.readType() + "] response for request-id " + resp.getRequestID()
+ ", no such request was made");
else
future.set(resp);
}
public void expectResponseTo(Request req) {
futures.put(req.getRequestID(), req.getResponseFuture());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
public enum PacketType {
UNKNOWN(0),
INIT(1),
VERSION(2),
OPEN(3),
CLOSE(4),
READ(5),
WRITE(6),
LSTAT(7),
FSTAT(8),
SETSTAT(9),
FSETSTAT(10),
OPENDIR(11),
READDIR(12),
REMOVE(13),
MKDIR(14),
RMDIR(15),
REALPATH(16),
STAT(17),
RENAME(18),
READLINK(19),
SYMLINK(20),
STATUS(101),
HANDLE(102),
DATA(103),
NAME(104),
ATTRS(105),
EXTENDED(200),
EXTENDED_REPLY(201);
private final byte b;
private static final PacketType[] cache = new PacketType[256];
static {
for (PacketType t : PacketType.values())
if (cache[t.toByte() & 0xff] == null)
cache[t.toByte() & 0xff] = t;
}
private PacketType(int b) {
this.b = (byte) b;
}
public static PacketType fromByte(byte b) {
return cache[b & 0xff];
}
public byte toByte() {
return b;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
class PathComponents {
public static String adjustForParent(String parent, String path) {
return (path.startsWith("/")) ? path // Absolute path, nothing to adjust
: (parent + (parent.endsWith("/") ? "" : "/") + path); // Relative path
}
private static String trimFinalSlash(String path) {
return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
}
private final String parent;
private final String name;
private final String path;
public PathComponents(String parent, String name) {
this.parent = parent;
this.name = name;
this.path = adjustForParent(parent, name);
}
public String getParent() {
return parent;
}
public String getName() {
return name;
}
public String getPath() {
return path;
}
@Override
public boolean equals(Object o) {
if (o instanceof PathComponents) {
final PathComponents that = (PathComponents) o;
return (trimFinalSlash(path).equals(trimFinalSlash(that.path)));
}
return false;
}
@Override
public int hashCode() {
return trimFinalSlash(path).hashCode();
}
@Override
public String toString() {
return "[parent=" + parent + "; name=" + name + "; path=" + path + "]";
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import java.io.IOException;
class PathHelper {
private final SFTPEngine sftp;
private String dotDir;
public PathHelper(SFTPEngine sftp) {
this.sftp = sftp;
}
public PathComponents getComponents(String path) throws IOException {
if (path.isEmpty() || path.equals("."))
return getComponents(getDotDir());
final int lastSlash = path.lastIndexOf("/");
if (lastSlash == -1)
if (path.equals(".."))
return getComponents(canon(path));
else
return new PathComponents(getDotDir(), path);
final String name = path.substring(lastSlash + 1);
if (name.equals(".") || name.equals(".."))
return getComponents(canon(path));
else {
final String parent = path.substring(0, lastSlash);
return new PathComponents(parent, name);
}
}
private String getDotDir() throws IOException {
return (dotDir != null) ? dotDir : (dotDir = canon("."));
}
private String canon(String path) throws IOException {
return sftp.canonicalize(path);
}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
public class RandomAccessRemoteFile implements DataInput, DataOutput {
private final byte[] singleByte = new byte[1];
private final RemoteFile rf;
private long fp;
public RandomAccessRemoteFile(RemoteFile rf) {
this.rf = rf;
}
public long getFilePointer() {
return fp;
}
public void seek(long fp) {
this.fp = fp;
}
public int read() throws IOException {
return read(singleByte, 0, 1) == -1 ? -1 : singleByte[0];
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
final int count = rf.read(fp, b, off, len);
fp += count;
return count;
}
public boolean readBoolean() throws IOException {
final int ch = read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
public byte readByte() throws IOException {
final int ch = this.read();
if (ch < 0)
throw new EOFException();
return (byte) (ch);
}
public char readChar() throws IOException {
final int ch1 = this.read();
final int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (char) ((ch1 << 8) + ch2);
}
public double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
public float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public void readFully(byte[] b, int off, int len) throws IOException {
int n = 0;
do {
int count = read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
} while (n < len);
}
public int readInt() throws IOException {
final int ch1 = read();
final int ch2 = read();
final int ch3 = read();
final int ch4 = read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
}
public String readLine() throws IOException {
StringBuffer input = new StringBuffer();
int c = -1;
boolean eol = false;
while (!eol)
switch (c = read()) {
case -1:
case '\n':
eol = true;
break;
case '\r':
eol = true;
long cur = getFilePointer();
if ((read()) != '\n')
seek(cur);
break;
default:
input.append((char) c);
break;
}
if ((c == -1) && (input.length() == 0))
return null;
return input.toString();
}
public long readLong() throws IOException {
return ((long) (readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
}
public short readShort() throws IOException {
final int ch1 = this.read();
final int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (short) ((ch1 << 8) + ch2);
}
public String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
public int readUnsignedByte() throws IOException {
final int ch = this.read();
if (ch < 0)
throw new EOFException();
return ch;
}
public int readUnsignedShort() throws IOException {
final int ch1 = this.read();
final int ch2 = this.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (ch1 << 8) + ch2;
}
public int skipBytes(int n) throws IOException {
if (n <= 0)
return 0;
final long pos = getFilePointer();
final long len = rf.length();
long newpos = pos + n;
if (newpos > len)
newpos = len;
seek(newpos);
/* return the actual number of bytes skipped */
return (int) (newpos - pos);
}
public void write(int i) throws IOException {
singleByte[0] = (byte) i;
write(singleByte);
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
rf.write(fp, b, off, len);
fp += (len - off);
}
public void writeBoolean(boolean v) throws IOException {
write(v ? 1 : 0);
}
public void writeByte(int v) throws IOException {
write(v);
}
public void writeBytes(String s) throws IOException {
final byte[] b = s.getBytes();
write(b, 0, b.length);
}
public void writeChar(int v) throws IOException {
write((v >>> 8) & 0xFF);
write(v & 0xFF);
}
public void writeChars(String s) throws IOException {
final int clen = s.length();
final int blen = 2 * clen;
final byte[] b = new byte[blen];
final char[] c = new char[clen];
s.getChars(0, clen, c, 0);
for (int i = 0, j = 0; i < clen; i++) {
b[j++] = (byte) (c[i] >>> 8);
b[j++] = (byte) c[i];
}
write(b, 0, blen);
}
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
public void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
public void writeInt(int v) throws IOException {
write((v >>> 24) & 0xFF);
write((v >>> 16) & 0xFF);
write((v >>> 8) & 0xFF);
write(v & 0xFF);
}
public void writeLong(long v) throws IOException {
write((int) (v >>> 56) & 0xFF);
write((int) (v >>> 48) & 0xFF);
write((int) (v >>> 40) & 0xFF);
write((int) (v >>> 32) & 0xFF);
write((int) (v >>> 24) & 0xFF);
write((int) (v >>> 16) & 0xFF);
write((int) (v >>> 8) & 0xFF);
write((int) v & 0xFF);
}
public void writeShort(int v) throws IOException {
write((v >>> 8) & 0xFF);
write(v & 0xFF);
}
public void writeUTF(String str) throws IOException {
final DataOutputStream dos = new DataOutputStream(rf.new RemoteFileOutputStream(fp));
try {
dos.writeUTF(str);
} finally {
dos.close();
}
fp += dos.size();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.sftp.Response.StatusCode;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class RemoteDirectory extends RemoteResource {
public RemoteDirectory(Requester requester, String path, String handle) {
super(requester, path, handle);
}
public List<RemoteResourceInfo> scan(RemoteResourceFilter filter) throws IOException {
List<RemoteResourceInfo> rri = new LinkedList<RemoteResourceInfo>();
loop:
for (; ;) {
Response res = requester.doRequest(newRequest(PacketType.READDIR));
switch (res.getType()) {
case NAME:
final int count = res.readInt();
for (int i = 0; i < count; i++) {
final String name = res.readString();
res.readString(); // long name - IGNORED - shdve never been in the protocol
final FileAttributes attrs = res.readFileAttributes();
RemoteResourceInfo inf = new RemoteResourceInfo(path, name, attrs);
if (!(name.equals(".") || name.equals("..")) && (filter == null || filter.accept(inf)))
rri.add(inf);
}
break loop;
case STATUS:
res.ensureStatusIs(StatusCode.EOF);
break loop;
default:
throw new SFTPException("Unexpected packet: " + res.getType());
}
}
return rri;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.sftp.Response.StatusCode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class RemoteFile extends RemoteResource {
public RemoteFile(Requester requester, String path, String handle) {
super(requester, path, handle);
}
public RemoteFileInputStream getInputStream() {
return new RemoteFileInputStream();
}
public RemoteFileOutputStream getOutputStream() {
return new RemoteFileOutputStream();
}
public FileAttributes fetchAttributes() throws IOException {
return requester.doRequest(newRequest(PacketType.FSTAT)) //
.ensurePacketTypeIs(PacketType.ATTRS) //
.readFileAttributes();
}
public long length() throws IOException {
return fetchAttributes().getSize();
}
public void setLength(long len) throws IOException {
setAttributes(new FileAttributes.Builder().withSize(len).build());
}
public int read(long fileOffset, byte[] to, int offset, int len) throws IOException {
Response res = requester.doRequest(newRequest(PacketType.READ).putUINT64(fileOffset).putInt(len));
switch (res.getType()) {
case DATA:
int recvLen = res.readInt();
System.arraycopy(res.array(), res.rpos(), to, offset, recvLen);
return recvLen;
case STATUS:
res.ensureStatusIs(StatusCode.EOF);
return -1;
default:
throw new SFTPException("Unexpected packet: " + res.getType());
}
}
public void write(long fileOffset, byte[] data, int off, int len) throws IOException {
requester.doRequest( //
newRequest(PacketType.WRITE) //
.putUINT64(fileOffset) //
.putInt(len - off) //
.putRawBytes(data, off, len) //
).ensureStatusPacketIsOK();
}
public void setAttributes(FileAttributes attrs) throws IOException {
requester.doRequest(newRequest(PacketType.FSETSTAT).putFileAttributes(attrs)).ensureStatusPacketIsOK();
}
public int getOutgoingPacketOverhead() {
return 1 + // packet type
4 + // request id
4 + // next length
handle.length() + // next
8 + // file offset
4 + // data length
4; // packet length
}
public class RemoteFileOutputStream extends OutputStream {
private final byte[] b = new byte[1];
private long fileOffset;
public RemoteFileOutputStream() {
this(0);
}
public RemoteFileOutputStream(long fileOffset) {
this.fileOffset = fileOffset;
}
@Override
public void write(int w) throws IOException {
b[0] = (byte) w;
write(b, 0, 1);
}
@Override
public void write(byte[] buf, int off, int len) throws IOException {
RemoteFile.this.write(fileOffset, buf, off, len);
fileOffset += len;
}
}
public class RemoteFileInputStream extends InputStream {
private final byte[] b = new byte[1];
private long fileOffset;
private long markPos;
private long readLimit;
public RemoteFileInputStream() {
this(0);
}
public RemoteFileInputStream(int fileOffset) {
this.fileOffset = fileOffset;
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readLimit) {
this.readLimit = readLimit;
markPos = fileOffset;
}
@Override
public void reset() throws IOException {
fileOffset = markPos;
}
@Override
public long skip(long n) throws IOException {
return (this.fileOffset = Math.min(fileOffset + n, length()));
}
@Override
public int read() throws IOException {
return read(b, 0, 1) == -1 ? -1 : b[0];
}
@Override
public int read(byte[] into, int off, int len) throws IOException {
int read = RemoteFile.this.read(fileOffset, into, off, len);
if (read != -1) {
fileOffset += read;
if (markPos != 0 && read > readLimit) // Invalidate mark position
markPos = 0;
}
return read;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
abstract class RemoteResource implements Closeable {
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final Requester requester;
protected final String path;
protected final String handle;
protected RemoteResource(Requester requester, String path, String handle) {
this.requester = requester;
this.path = path;
this.handle = handle;
}
public String getPath() {
return path;
}
protected Request newRequest(PacketType type) {
return requester.newRequest(type).putString(handle);
}
public void close() throws IOException {
log.info("Closing `{}`", this);
requester.doRequest(newRequest(PacketType.CLOSE)).ensureStatusPacketIsOK();
}
@Override
public String toString() {
return "RemoteResource{" + path + "}";
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
public interface RemoteResourceFilter {
boolean accept(RemoteResourceInfo resource);
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
public class RemoteResourceInfo {
private final PathComponents comps;
private final FileAttributes attrs;
public RemoteResourceInfo(String parent, String name, FileAttributes attrs) {
this.comps = new PathComponents(parent, name);
this.attrs = attrs;
}
public String getParent() {
return comps.getParent();
}
public String getPath() {
return comps.getPath();
}
public String getName() {
return comps.getName();
}
public FileAttributes getAttributes() {
return attrs;
}
public boolean isType(FileMode.Type type) {
return attrs.getType() == type;
}
public boolean isRegularFile() {
return isType(FileMode.Type.REGULAR);
}
public boolean isDirectory() {
return isType(FileMode.Type.DIRECTORY);
}
public boolean isSymlink() {
return isType(FileMode.Type.SYMKLINK);
}
@Override
public boolean equals(Object o) {
if (o instanceof RemoteResourceInfo) {
final RemoteResourceInfo that = (RemoteResourceInfo) o;
if (comps.equals(that.comps))
return true;
}
return false;
}
@Override
public int hashCode() {
return comps.hashCode();
}
@Override
public String toString() {
return "[" + attrs.getType() + "] " + getPath();
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.Future;
public class Request extends SFTPPacket<Request> {
private final PacketType type;
private final long reqID;
private final Future<Response, SFTPException> responseFuture;
public Request(PacketType type, long reqID) {
super();
this.reqID = reqID;
this.type = type;
responseFuture = new Future<Response, SFTPException>("sftp / " + reqID, SFTPException.chainer);
putByte(type.toByte());
putInt(reqID);
}
public long getRequestID() {
return reqID;
}
public PacketType getType() {
return type;
}
public Future<Response, SFTPException> getResponseFuture() {
return responseFuture;
}
@Override
public String toString() {
return "Request{" + reqID + ";" + type + "}";
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import java.io.IOException;
public interface Requester {
Request newRequest(PacketType type);
Response doRequest(Request req) throws IOException;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.common.Buffer;
public class Response extends SFTPPacket<Response> {
public static enum StatusCode {
UNKNOWN(-1),
OK(0),
EOF(1),
NO_SUCH_FILE(2),
PERMISSION_DENIED(3),
FAILURE(4),
BAD_MESSAGE(5),
NO_CONNECTION(6),
CONNECITON_LOST(7),
OP_UNSUPPORTED(8);
private final int code;
public static StatusCode fromInt(int code) {
for (StatusCode s : StatusCode.values())
if (s.code == code)
return s;
return UNKNOWN;
}
private StatusCode(int code) {
this.code = code;
}
}
private final PacketType type;
private final long reqID;
public Response(Buffer<Response> pk) {
super(pk);
this.type = readType();
this.reqID = readLong();
}
public long getRequestID() {
return reqID;
}
public PacketType getType() {
return type;
}
public StatusCode readStatusCode() {
return StatusCode.fromInt(readInt());
}
public Response ensurePacketTypeIs(PacketType pt) throws SFTPException {
if (getType() != pt)
if (getType() == PacketType.STATUS)
throw new SFTPException(readStatusCode(), readString());
else
throw new SFTPException("Unexpected packet " + getType());
return this;
}
public Response ensureStatusPacketIsOK() throws SFTPException {
return ensurePacketTypeIs(PacketType.STATUS).ensureStatusIs(StatusCode.OK);
}
public Response ensureStatusIs(StatusCode acceptable) throws SFTPException {
StatusCode sc = readStatusCode();
if (sc != acceptable)
throw new SFTPException(sc, readString());
return this;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import net.schmizz.sshj.xfer.FilePermission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
public class SFTPClient {
/** Logger */
protected final Logger log = LoggerFactory.getLogger(getClass());
private final SFTPEngine sftp;
private final SFTPFileTransfer xfer;
public SFTPClient(SessionFactory ssh) throws IOException {
this.sftp = new SFTPEngine(ssh).init();
this.xfer = new SFTPFileTransfer(sftp);
}
public SFTPEngine getSFTPEngine() {
return sftp;
}
public SFTPFileTransfer getFileTansfer() {
return xfer;
}
public List<RemoteResourceInfo> ls(String path) throws IOException {
return ls(path, null);
}
public List<RemoteResourceInfo> ls(String path, RemoteResourceFilter filter) throws IOException {
final RemoteDirectory dir = sftp.openDir(path);
try {
return dir.scan(filter);
} finally {
dir.close();
}
}
public RemoteFile open(String filename, Set<OpenMode> mode, FileAttributes attrs) throws IOException {
log.debug("Opening `{}`", filename);
return sftp.open(filename, mode, attrs);
}
public RemoteFile open(String filename, Set<OpenMode> mode) throws IOException {
return open(filename, mode, new FileAttributes());
}
public RemoteFile open(String filename) throws IOException {
return open(filename, EnumSet.of(OpenMode.READ));
}
public void mkdir(String dirname) throws IOException {
sftp.makeDir(dirname);
}
public void rename(String oldpath, String newpath) throws IOException {
sftp.rename(oldpath, newpath);
}
public void rm(String filename) throws IOException {
sftp.remove(filename);
}
public void rmdir(String dirname) throws IOException {
sftp.removeDir(dirname);
}
public void symlink(String linkpath, String targetpath) throws IOException {
sftp.symlink(linkpath, targetpath);
}
public int version() {
return sftp.getOperativeProtocolVersion();
}
public void setattr(String path, FileAttributes attrs) throws IOException {
sftp.setAttributes(path, attrs);
}
public int uid(String path) throws IOException {
return stat(path).getUID();
}
public int gid(String path) throws IOException {
return stat(path).getGID();
}
public long atime(String path) throws IOException {
return stat(path).getAtime();
}
public long mtime(String path) throws IOException {
return stat(path).getMtime();
}
public Set<FilePermission> perms(String path) throws IOException {
return stat(path).getPermissions();
}
public FileMode mode(String path) throws IOException {
return stat(path).getMode();
}
public FileMode.Type type(String path) throws IOException {
return stat(path).getType();
}
public String readlink(String path) throws IOException {
return sftp.readLink(path);
}
public FileAttributes stat(String path) throws IOException {
return sftp.stat(path);
}
public FileAttributes lstat(String path) throws IOException {
return sftp.lstat(path);
}
public void chown(String path, int uid) throws IOException {
setattr(path, new FileAttributes.Builder().withUIDGID(uid, gid(path)).build());
}
public void chmod(String path, int perms) throws IOException {
setattr(path, new FileAttributes.Builder().withPermissions(perms).build());
}
public void chgrp(String path, int gid) throws IOException {
setattr(path, new FileAttributes.Builder().withUIDGID(uid(path), gid).build());
}
public void truncate(String path, long size) throws IOException {
setattr(path, new FileAttributes.Builder().withSize(size).build());
}
public String canonicalize(String path) throws IOException {
return sftp.canonicalize(path);
}
public long size(String path) throws IOException {
return stat(path).getSize();
}
public void get(String source, String dest) throws IOException {
xfer.download(source, dest);
}
public void put(String source, String dest) throws IOException {
xfer.upload(source, dest);
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.connection.channel.direct.Session.Subsystem;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class SFTPEngine implements Requester {
/** Logger */
private final Logger log = LoggerFactory.getLogger(getClass());
public static final int PROTOCOL_VERSION = 3;
public static final int DEFAULT_TIMEOUT = 30;
private volatile int timeout = DEFAULT_TIMEOUT;
private final Subsystem sub;
private final PacketReader reader;
private final OutputStream out;
private long reqID;
private int negotiatedVersion;
private final Map<String, String> serverExtensions = new HashMap<String, String>();
public SFTPEngine(SessionFactory ssh) throws SSHException {
sub = ssh.startSession().startSubsystem("sftp");
out = sub.getOutputStream();
reader = new PacketReader(sub.getInputStream());
}
public Subsystem getSubsystem() {
return sub;
}
public SFTPEngine init() throws IOException {
transmit(new SFTPPacket<Request>(PacketType.INIT).putInt(PROTOCOL_VERSION));
final SFTPPacket<Response> response = reader.readPacket();
final PacketType type = response.readType();
if (type != PacketType.VERSION)
throw new SFTPException("Expected INIT packet, received: " + type);
negotiatedVersion = response.readInt();
log.info("Client version {}, server version {}", PROTOCOL_VERSION, negotiatedVersion);
if (negotiatedVersion < PROTOCOL_VERSION)
throw new SFTPException("Server reported protocol version: " + negotiatedVersion);
while (response.available() > 0)
serverExtensions.put(response.readString(), response.readString());
// Start reader thread
reader.start();
return this;
}
public int getOperativeProtocolVersion() {
return negotiatedVersion;
}
public synchronized Request newRequest(PacketType type) {
return new Request(type, reqID = reqID + 1 & 0xffffffffL);
}
public Response doRequest(Request req) throws IOException {
reader.expectResponseTo(req);
log.debug("Sending {}", req);
transmit(req);
return req.getResponseFuture().get(timeout, TimeUnit.SECONDS);
}
private synchronized void transmit(SFTPPacket<Request> payload) throws IOException {
final int len = payload.available();
out.write((len >>> 24) & 0xff);
out.write((len >>> 16) & 0xff);
out.write((len >>> 8) & 0xff);
out.write(len & 0xff);
out.write(payload.array(), 0, len);
out.flush();
}
public RemoteFile open(String path, Set<OpenMode> modes, FileAttributes fa) throws IOException {
final String handle = doRequest(
newRequest(PacketType.OPEN).putString(path).putInt(OpenMode.toMask(modes)).putFileAttributes(fa)
).ensurePacketTypeIs(PacketType.HANDLE).readString();
return new RemoteFile(this, path, handle);
}
public RemoteFile open(String filename, Set<OpenMode> modes) throws IOException {
return open(filename, modes, new FileAttributes());
}
public RemoteFile open(String filename) throws IOException {
return open(filename, EnumSet.of(OpenMode.READ));
}
public RemoteDirectory openDir(String path) throws IOException {
final String handle = doRequest(
newRequest(PacketType.OPENDIR).putString(path)
).ensurePacketTypeIs(PacketType.HANDLE).readString();
return new RemoteDirectory(this, path, handle);
}
public void setAttributes(String path, FileAttributes attrs) throws IOException {
doRequest(
newRequest(PacketType.SETSTAT).putString(path).putFileAttributes(attrs)
).ensureStatusPacketIsOK();
}
public String readLink(String path) throws IOException {
return readSingleName(
doRequest(
newRequest(PacketType.READLINK).putString(path)
));
}
public void makeDir(String path, FileAttributes attrs) throws IOException {
doRequest(
newRequest(PacketType.MKDIR).putString(path).putFileAttributes(attrs)
).ensureStatusPacketIsOK();
}
public void makeDir(String path) throws IOException {
makeDir(path, new FileAttributes());
}
public void symlink(String linkpath, String targetpath) throws IOException {
doRequest(
newRequest(PacketType.SYMLINK).putString(linkpath).putString(targetpath)
).ensureStatusPacketIsOK();
}
public void remove(String filename) throws IOException {
doRequest(
newRequest(PacketType.REMOVE).putString(filename)
).ensureStatusPacketIsOK();
}
public void removeDir(String path) throws IOException {
doRequest(
newRequest(PacketType.RMDIR).putString(path)
).ensureStatusIs(Response.StatusCode.OK);
}
private FileAttributes stat(PacketType pt, String path) throws IOException {
return doRequest(newRequest(pt).putString(path))
.ensurePacketTypeIs(PacketType.ATTRS)
.readFileAttributes();
}
public FileAttributes stat(String path) throws IOException {
return stat(PacketType.STAT, path);
}
public FileAttributes lstat(String path) throws IOException {
return stat(PacketType.LSTAT, path);
}
public void rename(String oldPath, String newPath) throws IOException {
doRequest(
newRequest(PacketType.RENAME).putString(oldPath).putString(newPath)
).ensureStatusPacketIsOK();
}
public String canonicalize(String path) throws IOException {
return readSingleName(
doRequest(
newRequest(PacketType.REALPATH).putString(path)
));
}
private static String readSingleName(Response res) throws IOException {
res.ensurePacketTypeIs(PacketType.NAME);
if (res.readInt() == 1)
return res.readString();
else
throw new SFTPException("Unexpected data in " + res.getType() + " packet");
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getTimeout() {
return timeout;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.concurrent.ExceptionChainer;
import net.schmizz.sshj.common.DisconnectReason;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.sftp.Response.StatusCode;
public class SFTPException extends SSHException {
public static final ExceptionChainer<SFTPException> chainer = new ExceptionChainer<SFTPException>() {
public SFTPException chain(Throwable t) {
if (t instanceof SFTPException)
return (SFTPException) t;
else
return new SFTPException(t);
}
};
public SFTPException() {
super();
}
public SFTPException(DisconnectReason code) {
super(code);
}
public SFTPException(DisconnectReason code, String message) {
super(code, message);
}
public SFTPException(DisconnectReason code, String message, Throwable cause) {
super(code, message, cause);
}
public SFTPException(DisconnectReason code, Throwable cause) {
super(code, cause);
}
public SFTPException(String message) {
super(message);
}
public SFTPException(String message, Throwable cause) {
super(message, cause);
}
public SFTPException(Throwable cause) {
super(cause);
}
private StatusCode sc;
public StatusCode getStatusCode() {
return (sc == null) ? StatusCode.UNKNOWN : sc;
}
public SFTPException(StatusCode sc, String msg) {
this(msg);
this.sc = sc;
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.sftp.Response.StatusCode;
import net.schmizz.sshj.xfer.AbstractFileTransfer;
import net.schmizz.sshj.xfer.FileTransfer;
import net.schmizz.sshj.xfer.FileTransferUtil;
import net.schmizz.sshj.xfer.ModeGetter;
import net.schmizz.sshj.xfer.ModeSetter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.EnumSet;
public class SFTPFileTransfer extends AbstractFileTransfer implements FileTransfer {
private final SFTPEngine sftp;
private final PathHelper pathHelper;
private volatile FileFilter uploadFilter = defaultLocalFilter;
private volatile RemoteResourceFilter downloadFilter = defaultRemoteFilter;
private static final FileFilter defaultLocalFilter = new FileFilter() {
public boolean accept(File pathName) {
return true;
}
};
private static final RemoteResourceFilter defaultRemoteFilter = new RemoteResourceFilter() {
public boolean accept(RemoteResourceInfo resource) {
return true;
}
};
public SFTPFileTransfer(SFTPEngine sftp) {
this.sftp = sftp;
this.pathHelper = new PathHelper(sftp);
}
public void upload(String source, String dest) throws IOException {
new Uploader(getModeGetter(), getUploadFilter()).upload(new File(source), dest);
}
public void download(String source, String dest) throws IOException {
PathComponents src = pathHelper.getComponents(source);
new Downloader(getModeSetter(), getDownloadFilter()).download(new RemoteResourceInfo(src.getParent(), src
.getName(), sftp.stat(source)), new File(dest));
}
public void setUploadFilter(FileFilter uploadFilter) {
this.uploadFilter = (this.uploadFilter == null) ? defaultLocalFilter : uploadFilter;
}
public void setDownloadFilter(RemoteResourceFilter downloadFilter) {
this.downloadFilter = (this.downloadFilter == null) ? defaultRemoteFilter : downloadFilter;
}
public FileFilter getUploadFilter() {
return uploadFilter;
}
public RemoteResourceFilter getDownloadFilter() {
return downloadFilter;
}
private class Downloader {
private final ModeSetter modeSetter;
private final RemoteResourceFilter filter;
Downloader(ModeSetter modeSetter, RemoteResourceFilter filter) {
this.modeSetter = modeSetter;
this.filter = filter;
}
private void setAttributes(RemoteResourceInfo remote, File local) throws IOException {
final FileAttributes attrs = remote.getAttributes();
modeSetter.setPermissions(local, attrs.getMode().getPermissionsMask());
if (modeSetter.preservesTimes() && attrs.has(FileAttributes.Flag.ACMODTIME)) {
modeSetter.setLastAccessedTime(local, attrs.getAtime());
modeSetter.setLastModifiedTime(local, attrs.getMtime());
}
}
private void downloadFile(RemoteResourceInfo remote, File local) throws IOException {
local = FileTransferUtil.getTargetFile(local, remote.getName());
setAttributes(remote, local);
RemoteFile rf = sftp.open(remote.getPath());
try {
final FileOutputStream fos = new FileOutputStream(local);
try {
StreamCopier.copy(rf.getInputStream(), fos, sftp.getSubsystem()
.getLocalMaxPacketSize(), false);
} finally {
fos.close();
}
} finally {
rf.close();
}
}
private void downloadDir(RemoteResourceInfo remote, File local) throws IOException {
local = FileTransferUtil.getTargetDirectory(local, remote.getName());
setAttributes(remote, local);
final RemoteDirectory rd = sftp.openDir(remote.getPath());
for (RemoteResourceInfo rri : rd.scan(filter))
download(rri, new File(local.getPath(), rri.getName()));
rd.close();
}
void download(RemoteResourceInfo remote, File local) throws IOException {
log.info("Downloading [{}] to [{}]", remote, local);
if (remote.isDirectory())
downloadDir(remote, local);
else if (remote.isRegularFile())
downloadFile(remote, local);
else
throw new IOException(remote + " is not a regular file or directory");
}
}
private class Uploader {
private final ModeGetter modeGetter;
private final FileFilter filter;
Uploader(ModeGetter modeGetter, FileFilter filter) {
this.modeGetter = modeGetter;
this.filter = filter;
}
public FileAttributes getAttributes(File local) throws IOException {
FileAttributes.Builder builder = new FileAttributes.Builder().withPermissions(modeGetter
.getPermissions(local));
if (modeGetter.preservesTimes())
builder.withAtimeMtime(modeGetter.getLastAccessTime(local), modeGetter.getLastModifiedTime(local));
return builder.build();
}
// tread carefully
private void setAttributes(FileAttributes current, File local, String remote) throws IOException {
final FileAttributes attrs = getAttributes(local);
// TODO whoaaa.. simplify?
if (!(current != null
&& current.getMode().getPermissionsMask() == attrs.getMode().getPermissionsMask()
&& (!modeGetter.preservesTimes() || (attrs.getAtime() == current.getAtime() && attrs.getMtime() == current
.getMtime()))))
sftp.setAttributes(remote, attrs);
}
private String prepareDir(File local, String remote) throws IOException {
FileAttributes attrs;
try {
attrs = sftp.stat(remote);
} catch (SFTPException e) {
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
log.debug("probeDir: {} does not exist, creating", remote);
sftp.makeDir(remote, getAttributes(local));
return remote;
} else
throw e;
}
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY)
if (pathHelper.getComponents(remote).getName().equals(local.getName())) {
log.debug("probeDir: {} already exists", remote);
setAttributes(attrs, local, remote);
return remote;
} else {
log.debug("probeDir: {} already exists, path adjusted for {}", remote, local.getName());
return prepareDir(local, PathComponents.adjustForParent(remote, local.getName()));
}
else
throw new IOException(attrs.getMode().getType() + " file already exists at " + remote);
}
private String prepareFile(File local, String remote) throws IOException {
FileAttributes attrs;
try {
attrs = sftp.stat(remote);
} catch (SFTPException e) {
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
log.debug("probeFile: {} does not exist", remote);
return remote;
} else
throw e;
}
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY) {
log.debug("probeFile: {} was directory, path adjusted for {}", remote, local.getName());
remote = PathComponents.adjustForParent(remote, local.getName());
return remote;
} else {
log.debug("probeFile: {} is a {} file that will be replaced", remote, attrs.getMode().getType());
return remote;
}
}
private void uploadDir(File local, String remote) throws IOException {
final String adjusted = prepareDir(local, remote);
for (File f : local.listFiles(filter))
upload(f, adjusted);
}
private void uploadFile(File local, String remote) throws IOException {
final String adjusted = prepareFile(local, remote);
final RemoteFile rf = sftp.open(adjusted, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC),
getAttributes(local));
try {
final FileInputStream fis = new FileInputStream(local);
try {
StreamCopier.copy(fis, //
rf.getOutputStream(), sftp.getSubsystem().getRemoteMaxPacketSize()
- rf.getOutgoingPacketOverhead(), false);
} finally {
fis.close();
}
} finally {
rf.close();
}
}
void upload(File local, String remote) throws IOException {
log.info("Uploading [{}] to [{}]", local, remote);
if (local.isDirectory())
uploadDir(local, remote);
else if (local.isFile())
uploadFile(local, remote);
else
throw new IOException(local + " is not a file or directory");
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.common.Buffer;
public class SFTPPacket<T extends SFTPPacket<T>> extends Buffer<T> {
public SFTPPacket() {
super();
}
public SFTPPacket(Buffer<T> buf) {
super(buf);
}
public SFTPPacket(PacketType pt) {
super();
putByte(pt.toByte());
}
public FileAttributes readFileAttributes() {
final FileAttributes.Builder builder = new FileAttributes.Builder();
final int mask = readInt();
if (FileAttributes.Flag.SIZE.isSet(mask))
builder.withSize(readUINT64());
if (FileAttributes.Flag.UIDGID.isSet(mask))
builder.withUIDGID(readInt(), readInt());
if (FileAttributes.Flag.MODE.isSet(mask))
builder.withPermissions(readInt());
if (FileAttributes.Flag.ACMODTIME.isSet(mask))
builder.withAtimeMtime(readInt(), readInt());
if (FileAttributes.Flag.EXTENDED.isSet(mask)) {
final int extCount = readInt();
for (int i = 0; i < extCount; i++)
builder.withExtended(readString(), readString());
}
return builder.build();
}
public PacketType readType() {
return PacketType.fromByte(readByte());
}
public T putFileAttributes(FileAttributes fa) {
return putRawBytes(fa.toBytes());
}
public T putType(PacketType type) {
return putByte(type.toByte());
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2010 Shikhar Bhushan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.connection.channel.direct.SessionFactory;
import java.io.IOException;
import java.util.List;
import java.util.Set;
public class StatefulSFTPClient extends SFTPClient {
private String cwd;
public StatefulSFTPClient(SessionFactory ssh) throws IOException {
super(ssh);
this.cwd = getSFTPEngine().canonicalize(".");
log.info("Start dir = " + cwd);
}
private synchronized String cwdify(String path) {
return PathComponents.adjustForParent(cwd, path);
}
public synchronized void cd(String dirname) {
cwd = cwdify(dirname);
log.info("CWD = " + cwd);
}
public synchronized List<RemoteResourceInfo> ls() throws IOException {
return ls(cwd, null);
}
public synchronized List<RemoteResourceInfo> ls(RemoteResourceFilter filter) throws IOException {
return ls(cwd, filter);
}
public synchronized String pwd() throws IOException {
return super.canonicalize(cwd);
}
@Override
public List<RemoteResourceInfo> ls(String path) throws IOException {
return ls(path, null);
}
@Override
public List<RemoteResourceInfo> ls(String path, RemoteResourceFilter filter) throws IOException {
final RemoteDirectory dir = getSFTPEngine().openDir(cwdify(path));
try {
return dir.scan(filter);
} finally {
dir.close();
}
}
@Override
public RemoteFile open(String filename, Set<OpenMode> mode, FileAttributes attrs) throws IOException {
return super.open(cwdify(filename), mode, attrs);
}
@Override
public RemoteFile open(String filename, Set<OpenMode> mode) throws IOException {
return super.open(cwdify(filename), mode);
}
@Override
public RemoteFile open(String filename) throws IOException {
return super.open(cwdify(filename));
}
@Override
public void mkdir(String dirname) throws IOException {
super.mkdir(cwdify(dirname));
}
@Override
public void rename(String oldpath, String newpath) throws IOException {
super.rename(cwdify(oldpath), cwdify(newpath));
}
@Override
public void rm(String filename) throws IOException {
super.rm(cwdify(filename));
}
@Override
public void rmdir(String dirname) throws IOException {
super.rmdir(cwdify(dirname));
}
@Override
public void symlink(String linkpath, String targetpath) throws IOException {
super.symlink(cwdify(linkpath), cwdify(targetpath));
}
@Override
public void setattr(String path, FileAttributes attrs) throws IOException {
super.setattr(cwdify(path), attrs);
}
@Override
public String readlink(String path) throws IOException {
return super.readlink(cwdify(path));
}
@Override
public FileAttributes stat(String path) throws IOException {
return super.stat(cwdify(path));
}
@Override
public FileAttributes lstat(String path) throws IOException {
return super.lstat(cwdify(path));
}
@Override
public void truncate(String path, long size) throws IOException {
super.truncate(cwdify(path), size);
}
@Override
public String canonicalize(String path) throws IOException {
return super.canonicalize(cwdify(path));
}
@Override
public void get(String source, String dest) throws IOException {
super.get(cwdify(source), dest);
}
@Override
public void put(String source, String dest) throws IOException {
super.get(source, cwdify(dest));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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