mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +03:00
import local
This commit is contained in:
202
LICENSE
Normal file
202
LICENSE
Normal 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
8
NOTICE
Normal 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
9
README
Normal 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
27
buildfile
Normal 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
|
||||
48
src/main/java/examples/Exec.java
Normal file
48
src/main/java/examples/Exec.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
55
src/main/java/examples/LocalPF.java
Normal file
55
src/main/java/examples/LocalPF.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
56
src/main/java/examples/MTTest.java
Normal file
56
src/main/java/examples/MTTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/main/java/examples/RemotePF.java
Normal file
62
src/main/java/examples/RemotePF.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
76
src/main/java/examples/RudimentaryPTY.java
Normal file
76
src/main/java/examples/RudimentaryPTY.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
43
src/main/java/examples/SCPDownload.java
Normal file
43
src/main/java/examples/SCPDownload.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
46
src/main/java/examples/SCPUpload.java
Normal file
46
src/main/java/examples/SCPUpload.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/main/java/examples/SFTPDownload.java
Normal file
42
src/main/java/examples/SFTPDownload.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
40
src/main/java/examples/SFTPUpload.java
Normal file
40
src/main/java/examples/SFTPUpload.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
72
src/main/java/examples/X11.java
Normal file
72
src/main/java/examples/X11.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/main/java/net/schmizz/concurrent/Event.java
Normal file
84
src/main/java/net/schmizz/concurrent/Event.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
40
src/main/java/net/schmizz/concurrent/ExceptionChainer.java
Normal file
40
src/main/java/net/schmizz/concurrent/ExceptionChainer.java
Normal 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<SomeException> chainer = new ExceptionChainer<SomeException>()
|
||||
* {
|
||||
* 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);
|
||||
|
||||
}
|
||||
204
src/main/java/net/schmizz/concurrent/Future.java
Normal file
204
src/main/java/net/schmizz/concurrent/Future.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
32
src/main/java/net/schmizz/concurrent/FutureUtils.java
Normal file
32
src/main/java/net/schmizz/concurrent/FutureUtils.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
83
src/main/java/net/schmizz/sshj/AbstractService.java
Normal file
83
src/main/java/net/schmizz/sshj/AbstractService.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
145
src/main/java/net/schmizz/sshj/Config.java
Normal file
145
src/main/java/net/schmizz/sshj/Config.java
Normal 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);
|
||||
}
|
||||
152
src/main/java/net/schmizz/sshj/ConfigImpl.java
Normal file
152
src/main/java/net/schmizz/sshj/ConfigImpl.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
168
src/main/java/net/schmizz/sshj/DefaultConfig.java
Normal file
168
src/main/java/net/schmizz/sshj/DefaultConfig.java
Normal 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
642
src/main/java/net/schmizz/sshj/SSHClient.java
Normal file
642
src/main/java/net/schmizz/sshj/SSHClient.java
Normal 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("hostname");
|
||||
* try
|
||||
* {
|
||||
* client.authPassword("username", "password");
|
||||
* client.startSession().exec("true");
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
50
src/main/java/net/schmizz/sshj/Service.java
Normal file
50
src/main/java/net/schmizz/sshj/Service.java
Normal 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;
|
||||
|
||||
}
|
||||
206
src/main/java/net/schmizz/sshj/SocketClient.java
Normal file
206
src/main/java/net/schmizz/sshj/SocketClient.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
1705
src/main/java/net/schmizz/sshj/common/Base64.java
Normal file
1705
src/main/java/net/schmizz/sshj/common/Base64.java
Normal file
File diff suppressed because it is too large
Load Diff
531
src/main/java/net/schmizz/sshj/common/Buffer.java
Normal file
531
src/main/java/net/schmizz/sshj/common/Buffer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
146
src/main/java/net/schmizz/sshj/common/ByteArrayUtils.java
Normal file
146
src/main/java/net/schmizz/sshj/common/ByteArrayUtils.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
56
src/main/java/net/schmizz/sshj/common/DisconnectReason.java
Normal file
56
src/main/java/net/schmizz/sshj/common/DisconnectReason.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
41
src/main/java/net/schmizz/sshj/common/ErrorNotifiable.java
Normal file
41
src/main/java/net/schmizz/sshj/common/ErrorNotifiable.java
Normal 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);
|
||||
|
||||
}
|
||||
119
src/main/java/net/schmizz/sshj/common/Factory.java
Normal file
119
src/main/java/net/schmizz/sshj/common/Factory.java
Normal 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();
|
||||
|
||||
}
|
||||
58
src/main/java/net/schmizz/sshj/common/IOUtils.java
Normal file
58
src/main/java/net/schmizz/sshj/common/IOUtils.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
74
src/main/java/net/schmizz/sshj/common/KeyType.java
Normal file
74
src/main/java/net/schmizz/sshj/common/KeyType.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
106
src/main/java/net/schmizz/sshj/common/Message.java
Normal file
106
src/main/java/net/schmizz/sshj/common/Message.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
122
src/main/java/net/schmizz/sshj/common/SSHException.java
Normal file
122
src/main/java/net/schmizz/sshj/common/SSHException.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
96
src/main/java/net/schmizz/sshj/common/SSHPacket.java
Normal file
96
src/main/java/net/schmizz/sshj/common/SSHPacket.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
35
src/main/java/net/schmizz/sshj/common/SSHPacketHandler.java
Normal file
35
src/main/java/net/schmizz/sshj/common/SSHPacketHandler.java
Normal 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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
277
src/main/java/net/schmizz/sshj/common/SecurityUtils.java
Normal file
277
src/main/java/net/schmizz/sshj/common/SecurityUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
128
src/main/java/net/schmizz/sshj/common/StreamCopier.java
Normal file
128
src/main/java/net/schmizz/sshj/common/StreamCopier.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
150
src/main/java/net/schmizz/sshj/connection/Connection.java
Normal file
150
src/main/java/net/schmizz/sshj/connection/Connection.java
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + " >";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
148
src/main/java/net/schmizz/sshj/connection/channel/Channel.java
Normal file
148
src/main/java/net/schmizz/sshj/connection/channel/Channel.java
Normal 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);
|
||||
|
||||
}
|
||||
@@ -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() + " >";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() + " >";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
113
src/main/java/net/schmizz/sshj/connection/channel/Window.java
Normal file
113
src/main/java/net/schmizz/sshj/connection/channel/Window.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2010 Shikhar Bhushan
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file may incorporate work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel.direct;
|
||||
|
||||
import net.schmizz.sshj.common.Buffer;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/** Various modes for a psuedo-terminal. They are meant to have integer parameters. */
|
||||
public enum PTYMode {
|
||||
/**
|
||||
* Interrupt character; 255 if none. Similarly for the other characters. Not all of these characters are supported
|
||||
* on all systems.
|
||||
*/
|
||||
VINTR(1),
|
||||
/** The quit character (sends SIGQUIT signal on POSIX systems). */
|
||||
VQUIT(2),
|
||||
/** Erase the character to left of the cursor. */
|
||||
VERASE(3),
|
||||
/** Kill the current input line. */
|
||||
VKILL(4),
|
||||
/** End-of-file character (sends EOF from the terminal). */
|
||||
VEOF(5),
|
||||
/** End-of-line character in addition to carriage return and/or linefeed. */
|
||||
VEOL(6),
|
||||
/** Additional end-of-line character. */
|
||||
VEOL2(7),
|
||||
/** Continues paused output (normally control-Q). */
|
||||
VSTART(8),
|
||||
/** Pauses output (normally control-S). */
|
||||
VSTOP(9),
|
||||
/** Suspends the current program. */
|
||||
VSUSP(10),
|
||||
/** Another suspend character. */
|
||||
VDSUSP(11),
|
||||
/** Reprints the current input line. */
|
||||
VREPRINT(12),
|
||||
/** Erases a word left of cursor. */
|
||||
VWERASE(13),
|
||||
/** Enter the next character typed literally, even if it is a special character. */
|
||||
VLNEXT(14),
|
||||
/** Character to flush output. */
|
||||
VFLUSH(15),
|
||||
/** Switch to a different shell layer. */
|
||||
VSWTCH(16),
|
||||
/** Prints system status line (load, command, pid, etc). */
|
||||
VSTATUS(17),
|
||||
/** Toggles the flushing of terminal output. */
|
||||
VDISCARD(18),
|
||||
/** The ignore parity flag. The parameter SHOULD be 0 if this flag is FALSE, and 1 if it is TRUE. */
|
||||
IGNPAR(30),
|
||||
/** Mark parity and framing errors. */
|
||||
PARMRK(31),
|
||||
/** Enable checking of parity errors. */
|
||||
INPCK(32),
|
||||
/** Strip 8th bit off characters. */
|
||||
ISTRIP(33),
|
||||
/** Map NL into CR on input. */
|
||||
INLCR(34),
|
||||
/** Ignore CR on input. */
|
||||
IGNCR(35),
|
||||
/** Map CR to NL on input. */
|
||||
ICRNL(36),
|
||||
/** Translate uppercase characters to lowercase. */
|
||||
IUCLC(37),
|
||||
/** Enable output flow control. */
|
||||
IXON(38),
|
||||
/** Any char will restart after stop. */
|
||||
IXANY(39),
|
||||
/** Enable input flow control. */
|
||||
IXOFF(40),
|
||||
/** Ring bell on input queue full. */
|
||||
IMAXBEL(41),
|
||||
/** Enable signals INTR, QUIT, [D]SUSP. */
|
||||
ISIG(50),
|
||||
/** Canonicalize input lines. */
|
||||
ICANON(51),
|
||||
/** Enable input and output of uppercase characters by preceding their lowercase equivalents with "\". */
|
||||
XCASE(52),
|
||||
/** Enable echoing. */
|
||||
ECHO(53),
|
||||
/** Visually erase chars. */
|
||||
ECHOE(54),
|
||||
/** Kill character discards current line. */
|
||||
ECHOK(55),
|
||||
/** Echo NL even if ECHO is off. */
|
||||
ECHONL(56),
|
||||
/** Don't flush after interrupt. */
|
||||
NOFLSH(57),
|
||||
/** Stop background jobs from output. */
|
||||
TOSTOP(58),
|
||||
/** Enable extensions. */
|
||||
IEXTEN(59),
|
||||
/** Echo control characters as ˆ(Char). */
|
||||
ECHOCTL(60),
|
||||
/** Visual erase for line kill. */
|
||||
ECHOKE(61),
|
||||
/** Retype pending input. */
|
||||
PENDIN(62),
|
||||
/** Enable output processing. */
|
||||
OPOST(70),
|
||||
/** Convert lowercase to uppercase. */
|
||||
OLCUC(71),
|
||||
/** Map NL to CR-NL. */
|
||||
ONLCR(72),
|
||||
/** Translate carriage return to newline (output). */
|
||||
OCRNL(73),
|
||||
/** Translate newline to carriage return-newline (output). */
|
||||
ONOCR(74),
|
||||
/** Newline performs a carriage return (output). */
|
||||
ONLRET(75),
|
||||
/** 7 bit mode. */
|
||||
CS7(90),
|
||||
/** 8 bit mode. */
|
||||
CS8(91),
|
||||
/** Parity enable. */
|
||||
PARENB(92),
|
||||
/** Odd parity, else even. */
|
||||
PARODD(93),
|
||||
/** Specifies the input baud rate in bits per second. */
|
||||
TTY_OP_ISPEED(128),
|
||||
/** Specifies the output baud rate in bits per second. */
|
||||
TTY_OP_OSPEED(129);
|
||||
|
||||
public static byte[] encode(Map<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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 "" means that connections are to be accepted on all protocol
|
||||
* families supported by the SSH implementation.
|
||||
* <p/>
|
||||
* o "0.0.0.0" means to listen on all IPv4 addresses.
|
||||
* <p/>
|
||||
* o "::" means to listen on all IPv6 addresses.
|
||||
* <p/>
|
||||
* o "localhost" means to listen on all protocol families supported by
|
||||
* the SSH implementation on loopback addresses only ([RFC3330] and
|
||||
* [RFC3513]).
|
||||
* <p/>
|
||||
* o "127.0.0.1" and "::1" 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() + "`");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
229
src/main/java/net/schmizz/sshj/sftp/FileAttributes.java
Normal file
229
src/main/java/net/schmizz/sshj/sftp/FileAttributes.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
97
src/main/java/net/schmizz/sshj/sftp/FileMode.java
Normal file
97
src/main/java/net/schmizz/sshj/sftp/FileMode.java
Normal 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) + "]";
|
||||
}
|
||||
|
||||
}
|
||||
60
src/main/java/net/schmizz/sshj/sftp/OpenMode.java
Normal file
60
src/main/java/net/schmizz/sshj/sftp/OpenMode.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
103
src/main/java/net/schmizz/sshj/sftp/PacketReader.java
Normal file
103
src/main/java/net/schmizz/sshj/sftp/PacketReader.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
71
src/main/java/net/schmizz/sshj/sftp/PacketType.java
Normal file
71
src/main/java/net/schmizz/sshj/sftp/PacketType.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
71
src/main/java/net/schmizz/sshj/sftp/PathComponents.java
Normal file
71
src/main/java/net/schmizz/sshj/sftp/PathComponents.java
Normal 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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
59
src/main/java/net/schmizz/sshj/sftp/PathHelper.java
Normal file
59
src/main/java/net/schmizz/sshj/sftp/PathHelper.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
272
src/main/java/net/schmizz/sshj/sftp/RandomAccessRemoteFile.java
Normal file
272
src/main/java/net/schmizz/sshj/sftp/RandomAccessRemoteFile.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
60
src/main/java/net/schmizz/sshj/sftp/RemoteDirectory.java
Normal file
60
src/main/java/net/schmizz/sshj/sftp/RemoteDirectory.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
175
src/main/java/net/schmizz/sshj/sftp/RemoteFile.java
Normal file
175
src/main/java/net/schmizz/sshj/sftp/RemoteFile.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
57
src/main/java/net/schmizz/sshj/sftp/RemoteResource.java
Normal file
57
src/main/java/net/schmizz/sshj/sftp/RemoteResource.java
Normal 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 + "}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
80
src/main/java/net/schmizz/sshj/sftp/RemoteResourceInfo.java
Normal file
80
src/main/java/net/schmizz/sshj/sftp/RemoteResourceInfo.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
52
src/main/java/net/schmizz/sshj/sftp/Request.java
Normal file
52
src/main/java/net/schmizz/sshj/sftp/Request.java
Normal 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 + "}";
|
||||
}
|
||||
|
||||
}
|
||||
26
src/main/java/net/schmizz/sshj/sftp/Requester.java
Normal file
26
src/main/java/net/schmizz/sshj/sftp/Requester.java
Normal 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;
|
||||
|
||||
}
|
||||
90
src/main/java/net/schmizz/sshj/sftp/Response.java
Normal file
90
src/main/java/net/schmizz/sshj/sftp/Response.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
175
src/main/java/net/schmizz/sshj/sftp/SFTPClient.java
Normal file
175
src/main/java/net/schmizz/sshj/sftp/SFTPClient.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
215
src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java
Normal file
215
src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
80
src/main/java/net/schmizz/sshj/sftp/SFTPException.java
Normal file
80
src/main/java/net/schmizz/sshj/sftp/SFTPException.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
251
src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java
Normal file
251
src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
66
src/main/java/net/schmizz/sshj/sftp/SFTPPacket.java
Normal file
66
src/main/java/net/schmizz/sshj/sftp/SFTPPacket.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
150
src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java
Normal file
150
src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
100
src/main/java/net/schmizz/sshj/signature/AbstractSignature.java
Normal file
100
src/main/java/net/schmizz/sshj/signature/AbstractSignature.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
89
src/main/java/net/schmizz/sshj/signature/Signature.java
Normal file
89
src/main/java/net/schmizz/sshj/signature/Signature.java
Normal 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);
|
||||
|
||||
}
|
||||
125
src/main/java/net/schmizz/sshj/signature/SignatureDSA.java
Normal file
125
src/main/java/net/schmizz/sshj/signature/SignatureDSA.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
80
src/main/java/net/schmizz/sshj/signature/SignatureRSA.java
Normal file
80
src/main/java/net/schmizz/sshj/signature/SignatureRSA.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
91
src/main/java/net/schmizz/sshj/transport/Converter.java
Normal file
91
src/main/java/net/schmizz/sshj/transport/Converter.java
Normal 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();
|
||||
|
||||
}
|
||||
205
src/main/java/net/schmizz/sshj/transport/Decoder.java
Normal file
205
src/main/java/net/schmizz/sshj/transport/Decoder.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
167
src/main/java/net/schmizz/sshj/transport/Encoder.java
Normal file
167
src/main/java/net/schmizz/sshj/transport/Encoder.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
98
src/main/java/net/schmizz/sshj/transport/Heartbeater.java
Normal file
98
src/main/java/net/schmizz/sshj/transport/Heartbeater.java
Normal 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");
|
||||
}
|
||||
}
|
||||
365
src/main/java/net/schmizz/sshj/transport/KeyExchanger.java
Normal file
365
src/main/java/net/schmizz/sshj/transport/KeyExchanger.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + //
|
||||
" ]");
|
||||
}
|
||||
|
||||
}
|
||||
176
src/main/java/net/schmizz/sshj/transport/Proposal.java
Normal file
176
src/main/java/net/schmizz/sshj/transport/Proposal.java
Normal 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(","));
|
||||
}
|
||||
|
||||
}
|
||||
86
src/main/java/net/schmizz/sshj/transport/Reader.java
Normal file
86
src/main/java/net/schmizz/sshj/transport/Reader.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
190
src/main/java/net/schmizz/sshj/transport/Transport.java
Normal file
190
src/main/java/net/schmizz/sshj/transport/Transport.java
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
548
src/main/java/net/schmizz/sshj/transport/TransportProtocol.java
Normal file
548
src/main/java/net/schmizz/sshj/transport/TransportProtocol.java
Normal 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
Reference in New Issue
Block a user