第一次写文章,有点小紧张。下面进入正题。
最近有需求ssh远程连接,服务器是1.5版本的,在网上百度很多都是2.0版本。找的到的1.5版本基本都是英文网站(英语渣的我一头雾水),很多还是404根本点不开。更加严重的是java里有个ssh框架,惨啊,关键词搜索出来的还大多是这么一个东西。
然后呢,终于让我找到了这个网址http://www.pitman.co.za/projects/jssh/,进去里边左侧有Download,点进去,下载jssh-0.9.5.tar.gz解压就行了。解压后的文件大都是后缀为java的文件,我是要在我的项目里添加ssh1.5的功能,如果把这些文件都复制粘贴进来就太麻烦了,而且也显得很乱。所以我就将他们一起生成了一个jssh.jar,然后导入我的java项目中。最后我模仿解压文件里的SSHClient.java写了一个SSH1_5Client,下边是代码。
---------------------------分割线----------------------------
我回来更新了,之前我写SSH1_5Client的方法根本就行不通,只能执行一条语句,不能执行连续的指令。而且原作者写的也是一种交互式的程序,即在控制台中启动SSHClient.java的main方法后,可以说整个控制台就变成了ssh的客户端,由客户实时输入指令,控制台实时输出结果。而且在jssh代码中搜索我发现有System.exit语句。这个出现的本意是程序出现异常后就停止,但是我是要整合近我的项目中去的,如果一有异常就停止程序那就糟糕了。
之前贴的代码我删了,以下先贴上我最新改进的代码,替换原文件的两个文件,经过多轮测试后没发现有问题,如果有人找到问题欢迎提出,之后有空我也会回来详细说一下我的代码。
---------------------------分割线----------------------------
我回来了,一开始我想我还是稍微简单说明一下原作者的程序是怎么工作的吧。
1.首先是启动程序,程序的入口就是SSHClient的main方法。
2.接着需要用户键入密码,此时程序中存在以下五个相关的线程:
TimeoutThread:程序启动后立刻生成,设定为等待60秒,60秒后如果ssh未连接成功,则System.exit结束程序。
ReaderThread:程序启动后立刻生成,监听STDOUT_InputStream(封装的标准输出流),在控制台System.out输出所有结果。
WriterThread:连接成功后生成,从控制台System.in接收所有用户输入,发送到STDIN_OutputStream(封装的标准输入流)
KeepaliveThread:连接成功后生成(需要设置超时时间),定时循环发送MSG_IGNORE(封装的信息包)到PacketQueue(封装的信息包队列)。
ClientProtocolHandler:连接成功后生成,监听PacketQueue(封装的信息包队列),队列不空则发送信息包到SSHOutputStream(这个流才是真正的与远程进行信息交互的流,根据Socket生成的流封装),出现异常System.exit结束程序。
发送一次指令时的数据流向:
控制台输入--->STDIN_OutputStream--->PacketQueue--->SSHOutputStream--->Socket
控制台输出<---STDOUT_InputStream<---SSHInputStream<---Socket
---------------------------分割线----------------------------
下边就谈一下我是怎么改的:
1.System.exit出现的地方都改为抛出异常。
2.把ReaderThread、WriterThread俩线程去掉,输入输出当然不能再依赖控制台了。
3.KeepaliveThread小修改。
4.去掉TimeoutThread类,增加同名TimeoutThread类实现超时功能。
5.ClientProtocolHandler不再实现Runnable接口,也就是说去掉了ClientProtocolHandler线程,类保留了,并且大改造。
1、2点好改,删点代码加点代码就搞掂,第3点改成能结束的循环,然后第4点就利用Callable来实现,代码里有注释,我就不多说了。
第5点删掉线程,相当于删掉了PacketQueue的监听器。这样输入的命令永远停在PacketQueue里边,发不到远程那里。
所以我删掉了ClientProtocolHandler类中这一行。
private PacketQueue _clientqueue = new PacketQueue();
涉及到PacketQueue的入队的代码改成直接发送到SSHOutputStream,然后设计到多个命令的执行的execShell也做了修改。
总之改完后就是如下俩个类。
/* class SSHClient
*
* Copyright (C) 2002 R M Pitman <http://www.pitman.co.za>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jssh;
import java.net.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.io.*;
import java.math.BigInteger;
import de.mud.ssh.Cipher;
public class SSHClient {
/**
* Constructor
*/
public SSHClient(Options options_) {
_options = options_;
}
private class TimeoutThread<T> implements Callable<Exception> {
final ClientProtocolHandler _handler;
@Override
public Exception call() {
try {
_handler.exchangeIdStrings();
} catch (Exception e) {
e.printStackTrace();
return e;
}
return null;
}
TimeoutThread(ClientProtocolHandler _handler) {
this._handler = _handler;
}
}
private Exception connect(long timeout) {
ExecutorService exec = Executors.newFixedThreadPool(1);
Future<Exception> future = exec.submit(new TimeoutThread<>(_handler));
Exception exception = null;
try {
exception = future.get(timeout, TimeUnit.MILLISECONDS);
} catch (Exception e) {// get方法超时后会立刻抛出e.getMessage()=null的异常
exception = new Exception(" timeout in authentication.");
} finally {
if (future != null && !future.isCancelled()) {
future.cancel(true);
}
exec.shutdownNow();
}
return exception;
}
/**
* This method connects to the server and starts up the protocol handler.
*/
public void connect(String password) throws Exception {
/*
* Resolve the server address.
*/
InetAddress serverAddress = null;
String servername = _options.getHostname();
try {
serverAddress = InetAddress.getByName(servername);
} catch (UnknownHostException e) {
throw new Exception(" Unknown host " + servername);
}
System.out.print(" Connecting to " + servername + "...");
Socket socket = new Socket(serverAddress, _options.getPort());
System.out.println(" Connected");
/*
* Create an SSH protocol handler.
*/
_handler = new ClientProtocolHandler(socket, _options);
/**
* 超时强制退出,可以看一下源码里ClientProtocolHandler类的exchangeIdStrings方法,这个方法里有这么一句_instream.read();
* 这是socket连接后读取的第一个字符,但是有时候永远不会有东西读到,我使用来测试的机器大概20次就出现一次这种情况,那么这就永远阻塞在这里了。
* 所以我写了一个超时方法。
* 此外,下边还提供了close()对socket进行关闭,因为即使程序可以执行下去了,但是在connect(20000)方法里创建的线程仍然不屈不挠的在等那个缺席的字符。
* 虽然方法里有cancel()和shutdownNow()来结束线程,但是这俩方法只能停止非阻塞线程。
* 所以下边强制关闭socket,而在socket强制关闭后,那个阻塞的_instream.read();就会报Socketclosed的错误,然后才能结束。
* 至于Socketclosed的报错就不是我要关注的点了,可以忽视掉。
*/
Exception exception = connect(20000);
if (exception != null) {
throw exception;
}
_handler.receiveServerKey();
/*
* Check that the server's host key is in the known_hosts file (but skip the
* check if we are connecting to localhost).
*/
String server = socket.getInetAddress().getHostAddress();
if (server.equals("127.0.0.1") == false) {
check_host_key(_handler.getServerKeyPacket(), servername);
} else
debug("Forcing acceptance of host key for localhost");
/*
* Instantiate an object that reads true random bits from the /dev/urandom
* device. This works on Linux and any other OS that has /dev/random or
* /dev/urandom; on other OS's, such as Windoze, you'll have to supply your own
* object that implements the TrueRandom interface. Note that it is preferable
* to use /dev/random, but /dev/random blocks if it doesn't have the requested
* number of bits in its entropy pool, so it can cause unacceptably long delays.
*/
ITrueRandom trueRandom = new DevURandom();
_handler.sendSessionKey(trueRandom);
String userName = _options.getUser();
boolean authRequired = _handler.declareUser(userName);
if (authRequired) {
RSAPrivateKeyFile keyfile = null;
try {
keyfile = new RSAPrivateKeyFile(_options.getIdentityFile());
} catch (FileNotFoundException e) {
}
// If the private-key file exists, try RSA authentication
// first.
if (keyfile != null) {
RSAPrivateKey privateKey = null;
if (keyfile.getCipherType() != Cipher.SSH_CIPHER_NONE) {
privateKey = keyfile.getPrivateKey(RSAKeyPassphrase);
} else {
privateKey = keyfile.getPrivateKey();
}
if (_handler.authenticateUser(privateKey) == true) {
authRequired = false;
}
} else {
_handler.debug("unknown identity file " + _options.getIdentityFile());
}
// Either the private-key file doesn't exist, or RSA
// authentication failed.
if (authRequired) {
if (!_handler.authenticateUser(userName, password))
throw new SSHAuthFailedException();
}
}
/*
* Request compression, port-forwarding etc.
*/
_handler.preparatoryOperations();
return;
}
public String execCmd() throws IOException, SSHProtocolException {
String command = _options.getCommand();
if (command == null) {
return "";
}
_handler.execCmd(command);
_handler.getSTDOUT().enqueue(new SMSG_STDERR_DATA("\ndny_execCmd\n"));// 添加结束标志
StringBuffer sb = new StringBuffer();
String s;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(_handler.getSTDOUT()));) {
while ((s = reader.readLine()) != null) {
if (s.equals("dny_execCmd")) {
break;
}
sb.append(s + "\n");
}
}
return sb.toString().trim();
}
public String execShell(String[] commands, String[] end) throws IOException, SSHProtocolException {
if (_options.getCommand() != null) {
return "";
}
_handler.execShell(commands, end);
_handler.getSTDOUT().enqueue(new SMSG_STDERR_DATA("\ndny_execShell\n"));// 添加结束标志
StringBuffer sb = new StringBuffer();
String s;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(_handler.getSTDOUT()));) {
while ((s = reader.readLine()) != null) {
if (s.equals("dny_execShell")) {
break;
}
sb.append(s + "\n");
}
}
return sb.toString().trim();
}
public void close() {
if (_handler != null) {
try {
_handler.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Check if the received server key is in the known_hosts file.
*/
private void check_host_key(SMSG_PUBLIC_KEY packet_, String servername_) throws IOException, SSHSetupException {
BigInteger host_key_public_modulus = new BigInteger(1, packet_.getHostKeyPublicModulus());
BigInteger host_key_public_exponent = new BigInteger(1, packet_.getHostKeyPublicExponent());
// First check in the global known_hosts file.
KnownHostsFile globalKnownHosts = new KnownHostsFile("/etc/ssh/ssh_known_hosts");
int status = globalKnownHosts.check_host_key(servername_, host_key_public_modulus, host_key_public_exponent);
String user_known_hosts = System.getProperty("user.home") + "/.ssh/known_hosts";
KnownHostsFile userKnownHosts = new KnownHostsFile(user_known_hosts);
if (status == KnownHostsFile.HOST_KEY_NEW) {
// It wasn't there, so check the user known_hosts.
status = userKnownHosts.check_host_key(servername_, host_key_public_modulus, host_key_public_exponent);
}
/*
* If the server's host key was not found in either of the known_hosts files,
* add it to the user's known_hosts.
*/
if (status == KnownHostsFile.HOST_KEY_NEW) {
debug("Adding host key to " + user_known_hosts);
userKnownHosts.add_host_key(servername_, host_key_public_modulus, host_key_public_exponent);
} else if (status == KnownHostsFile.HOST_KEY_DIFFERS) {
throw new SSHSetupException("Server host key changed!");
}
}
private void debug(String string_) {
if (_options.getDebug())
System.err.println("debug: " + string_);
}
// ====================================================================
// INSTANCE VARIABLES
private Options _options;
private ClientProtocolHandler _handler;
private String RSAKeyPassphrase;
}
package jssh;
import java.io.*;
import java.net.*;
import java.util.*;
import java.security.*;
import de.mud.ssh.Cipher;
/**
* This class performs all the SSH client protocol handling on a specified
* network connection.
* <p>
*
* An example of how to use this protocol handler is provided in the
* {@link SSHClient SSHClient} class.
*/
public class ClientProtocolHandler implements IProtocolHandler, IPacketConstants {
public ClientProtocolHandler(Socket socket_, Options options_) throws IOException {
_socket = socket_;
_instream = new BufferedInputStream(socket_.getInputStream());
_outstream = new BufferedOutputStream(socket_.getOutputStream());
_options = options_;
}
/**
* Enqueues a packet to the SSH server.
*/
public void enqueueToRemote(Packet packet_) {
try {
_ssh_out.writePacket(packet_);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Enqueues a packet to the STDOUT.
*/
public void enqueueToStdout(Packet packet_) {
_stdout.enqueue(packet_);
}
/**
* Returns a reference to the InputStream object from which data must be read
* (for displaying on the screen).
*/
public STDOUT_InputStream getSTDOUT() {
return _stdout;
}
/**
* Returns a reference to the OutputStream object to which keyboard data must be
* written (for sending to the server).
*/
public STDIN_OutputStream getSTDIN() {
if (_stdin == null)
_stdin = new STDIN_OutputStream(this);
return _stdin;
}
/**
* This method exchanges version identification strings with the SSH server.
*
* @exception SSHSetupException if the server's version string is incompatible.
*/
public void exchangeIdStrings() throws IOException, SSHSetupException {
if (_phase != PHASE_EXCHANGE_ID_STRINGS) {
throw new RuntimeException("SSH protocol already in packet mode");
}
for (;;) {
int b = _instream.read();
if (b == '\n') {
debug("Remote version: " + _id_string_received.toString());
debug("Local version string: " + ID_STRING_SENT);
_outstream.write((ID_STRING_SENT + "\n").getBytes());
_outstream.flush();
// Here we should check that the server version is compatible.
if (_id_string_received.toString().startsWith("SSH-1") == false) {
throw new SSHSetupException("incompatible server version: " + _id_string_received.toString());
}
_phase = PHASE_RECEIVE_SERVER_KEY;
return;
}
_id_string_received.append((char) b);
}
}
/**
* Wait for the SSH_SMSG_PUBLIC_KEY packet from the server. This method must be
* called after <code>exchangeIdStrings()</code> has returned successfully.
*/
public void receiveServerKey() throws IOException, SSHSetupException, SSHProtocolException {
if (_phase != PHASE_RECEIVE_SERVER_KEY) {
throw new RuntimeException("SSH protocol not in packet mode yet");
}
_ssh_in = new SSHInputStream(_instream);
_ssh_out = new SSHOutputStream(_outstream);
debug("Waiting for server public key.");
Packet packet = readNextPacket();
if (packet.getPacketType() != SSH_SMSG_PUBLIC_KEY) {
throw new SSHProtocolException(
"Received packet type " + packet.getPacketType() + ", expected SSH_SMSG_PUBLIC_KEY");
}
_server_public_key_packet = (SMSG_PUBLIC_KEY) packet;
debug("Received server public key (" + (8 * _server_public_key_packet.getServerKeyPublicModulus().length)
+ " bits) and host key (" + (8 * _server_public_key_packet.getHostKeyPublicModulus().length)
+ " bits).");
_phase = PHASE_SEND_SESSION_KEY;
return;
}
/**
* Create and send an encrypted session key; this method must be called AFTER
* receiveServerKey() has returned successfully.
*
* @param trueRandom_ an object that implements the ITrueRandom interface and
* supplies truly random bits for the session key. Note that
* it is <strong>not</strong> sufficient to use a
* pseudo-random number generator (PRNG) seeded by the system
* clock; for an explanation, see
* <a href="http://www.ietf.org/rfc/rfc1750.txt">rfc1750
* (Randomness Recommendations for Security)</a>
* <p>
*
* On a Linux system (or any system that provides the
* /dev/random device), you can use the DevRandom class,
* which implements the ITrueRandom interface. On other
* operating systems you will have to provide your own source
* of true random bits. See
* <a href="http://www.cs.berkeley.edu/~daw/rnd/">Randomness
* for Crypto</a> for information on generating sufficiently
* random numbers.
*/
public void sendSessionKey(ITrueRandom trueRandom_) throws SSHProtocolException, SSHSetupException, IOException {
if (_phase != PHASE_SEND_SESSION_KEY) {
throw new RuntimeException("SSH protocol not in valid state for sending session key");
}
/*
* Send an SSH_CMSG_SESSION_KEY message in reply.
*/
CMSG_SESSION_KEY session_key_packet = new CMSG_SESSION_KEY(trueRandom_, _server_public_key_packet);
_session_id = session_key_packet.getSessionID(); // save it.
_ssh_out.writePacket(session_key_packet);
debug("Sent encrypted session key.");
/*
* Now we have exchanged session keys, enable encryption.
*/
String cipherName = session_key_packet.getCipherName();
Cipher sendCipher = Cipher.getInstance(cipherName);
if (sendCipher == null) {
throw new SSHSetupException(cipherName + " algorithm not supported");
}
debug("Encryption type: " + cipherName);
sendCipher.setKey(session_key_packet.getSessionKey());
_ssh_out.setCipher(sendCipher);
Cipher receiveCipher = Cipher.getInstance(cipherName);
receiveCipher.setKey(session_key_packet.getSessionKey());
_ssh_in.setCipher(receiveCipher);
Packet packet = readNextPacket();
if (packet.getPacketType() != SSH_SMSG_SUCCESS) {
throw new SSHProtocolException(
"Received packet type " + packet.getPacketType() + ", expected SSH_SMSG_SUCCESS");
}
debug("Received encrypted confirmation.");
_phase = PHASE_DECLARE_USER;
}
/**
* Declares the username to the server. This method must be called AFTER
* sendSessionKey() has returned successfully.
*
* @return false if the server is willing to accept the user without further
* authentication; true if authentication is required.
*/
public boolean declareUser(String username_) throws IOException, SSHProtocolException {
if (_phase != PHASE_DECLARE_USER) {
throw new RuntimeException("SSH protocol in invalid state for declaring user");
}
CMSG_USER user_packet = new CMSG_USER(username_);
_ssh_out.writePacket(user_packet);
Packet packet = readNextPacket();
if (packet.getPacketType() == SSH_SMSG_SUCCESS) {
// The server has accepted our login; a password is not required.
_phase = PHASE_PREPARATORY;
return false;
}
if (packet.getPacketType() != SSH_SMSG_FAILURE) {
throw new SSHProtocolException("Received packet type " + packet.getPacketType()
+ ", expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE");
}
_phase = PHASE_AUTHENTICATE;
return true;
}
/**
* Authenticate the user using username-password authentication. This method
* must be called after declareUser() has returned successfully with a return
* value of <code>true</code>.
*
* @param user_ the username.
* @param password_ the password string.
* @return true if the server accepted the username and password.
* @exception IOException if a network error occurred.
* @exception SSHProtocolException if a malformed/invalid SSH packet was
* received.
*/
public boolean authenticateUser(String user_, String password_) throws IOException, SSHProtocolException {
if (_phase != PHASE_AUTHENTICATE) {
throw new RuntimeException("SSH protocol in invalid state for user authentication");
}
/*
* The server has sent us an encrypted packet and we have succeeded in
* decrypting it.
*/
debug("Doing password authentication.");
/*
* The server sent us SSH_SMSG_FAILURE, which means that authentication is
* required.
*/
CMSG_AUTH_PASSWORD password_packet = new CMSG_AUTH_PASSWORD(password_);
_ssh_out.writePacket(password_packet);
Packet packet = readNextPacket();
if (packet.getPacketType() != SSH_SMSG_SUCCESS) {
debug("User authentication failure");
return false;
}
_phase = PHASE_PREPARATORY;
return true;
}
/**
* Authenticate the user using RSA authentication. This method must be called
* after declareUser() has returned successfully with a return value of
* <code>true</code>.
*
* @return true if the server authenticated us and authorized us to log in.
* @exception IOException if a network error occurs.
* @exception SSHProtocolException is a malformed/invalid SSH packet is
* received.
*/
public boolean authenticateUser(RSAPrivateKey privateKey_)
throws IOException, SSHProtocolException, SSHAuthFailedException {
if (_phase != PHASE_AUTHENTICATE) {
throw new RuntimeException("SSH protocol in invalid state for user authentication");
}
/*
* The server has sent us an encrypted packet and we have succeeded in
* decrypting it. Now declare the user name.
*/
debug("Trying RSA authentication with key '" + privateKey_.getComment() + "'");
/*
* The server sent us SSH_SMSG_FAILURE, which means that authentication is
* required.
*/
byte[] modulus = privateKey_.getModulus();
CMSG_AUTH_RSA auth_rsa = new CMSG_AUTH_RSA(modulus);
_ssh_out.writePacket(auth_rsa);
Packet packet = readNextPacket();
int packet_type = packet.getPacketType();
if (packet_type == SSH_SMSG_FAILURE) {
debug("Server refused our key");
return false;
} else if (packet_type != SSH_SMSG_AUTH_RSA_CHALLENGE) {
throw new SSHProtocolException("Received packet type " + packet_type
+ ", expected SSH_SMSG_AUTH_RSA_CHALLENGE or SSH_SMSG_FAILURE");
}
debug("Received RSA challenge from server.");
SMSG_AUTH_RSA_CHALLENGE challenge_packet = (SMSG_AUTH_RSA_CHALLENGE) packet;
byte[] challenge = challenge_packet.getChallenge();
/*
* Now we must decrypt the challenge using our private key, compute the MD5
* checksum of the challenge plus the session id, and send back the resulting 16
* bytes.
*/
byte[] serverRandomBytes = RSAAlgorithm.encrypt(challenge, privateKey_.getExponent(), privateKey_.getModulus());
serverRandomBytes = RSAAlgorithm.stripPKCSPadding(serverRandomBytes);
byte[] toBeHashed = SSHMisc.concatenate(serverRandomBytes, _session_id);
byte[] response_bytes;
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
response_bytes = md5.digest(toBeHashed);
} catch (NoSuchAlgorithmException e) {
// Should never happen
throw new SSHProtocolException("MD5 algorithm not supported");
}
CMSG_AUTH_RSA_RESPONSE response_packet = new CMSG_AUTH_RSA_RESPONSE(response_bytes);
_ssh_out.writePacket(response_packet);
packet = readNextPacket();
packet_type = packet.getPacketType();
if (packet_type == SSH_SMSG_FAILURE) {
debug("RSA authentication failed");
return false;
} else if (packet_type != SSH_SMSG_SUCCESS) {
throw new SSHProtocolException(
"Received packet type " + packet_type + ", expected SSH_MSG_SUCCESS or SSH_SMSG_FAILURE");
}
_phase = PHASE_PREPARATORY;
return true;
}
/**
* Performs preparatory operations such as requesting compression and
* port-forwarding. This method must be called after the user has been
* successfully authenticated.
*/
public void preparatoryOperations() throws IOException, SSHProtocolException, SSHSetupException {
if (_phase != PHASE_PREPARATORY) {
throw new RuntimeException("Protocol not in preparatory phase");
}
byte[] terminalModes = { CMSG_REQUEST_PTY.ICRNL, 1, CMSG_REQUEST_PTY.ISIG, 1, CMSG_REQUEST_PTY.ICANON, 1,
CMSG_REQUEST_PTY.ECHO, 1, CMSG_REQUEST_PTY.ECHOE, 1, CMSG_REQUEST_PTY.ECHOK, 1, CMSG_REQUEST_PTY.ECHONL,
1, CMSG_REQUEST_PTY.OPOST, 1, CMSG_REQUEST_PTY.ONLCR, 1, CMSG_REQUEST_PTY.CS8, 1,
CMSG_REQUEST_PTY.TTY_OP_END };
int columns = _options.getTerminalSize().width;
int rows = _options.getTerminalSize().height;
CMSG_REQUEST_PTY pty_packet = new CMSG_REQUEST_PTY(_options.getTerminalType(), rows, columns, terminalModes);
debug("Requesting pty.");
_ssh_out.writePacket(pty_packet);
Packet packet = readNextPacket();
if (packet.getPacketType() != SSH_SMSG_SUCCESS) {
throw new SSHProtocolException("server refused to allocate pseudo-tty");
}
/*
* Request the local port-forwardings that were specified by the user.
*/
Iterator<?> iter = _options.getLocalForwardings();
while (iter.hasNext()) {
PortForwarding pf = (PortForwarding) iter.next();
Channel channel = null;
try {
channel = new Channel(this, pf.getListenPort(), pf.getHostname(), pf.getHostPort());
} catch (java.net.BindException e) {
throw new java.net.BindException("Cannot bind to port " + pf.getListenPort() + "; " + e.getMessage());
}
channel.setDaemon(true);
channel.start();
}
/*
* Request the remote port-forwardings that were specified by the user.
*/
iter = _options.getRemoteForwardings();
while (iter.hasNext()) {
PortForwarding pf = (PortForwarding) iter.next();
debug("Requesting forwarding of remote port " + pf.getListenPort() + " to " + pf.getHostname() + ":"
+ pf.getHostPort());
CMSG_PORT_FORWARD_REQUEST forward_request = new CMSG_PORT_FORWARD_REQUEST(pf.getListenPort(),
pf.getHostname(), pf.getHostPort());
_ssh_out.writePacket(forward_request);
if (readNextPacket().getPacketType() != SSH_SMSG_SUCCESS) {
throw new SSHSetupException("Server refused to forward port " + "(listen-port=" + pf.getListenPort()
+ " hostname=" + pf.getHostname() + " host-port=" + pf.getHostPort() + ")");
}
}
/*
* Enable compression if it was requested by the user.
*/
if (_options.compressionEnabled()) {
debug("Requesting compression.");
CMSG_REQUEST_COMPRESSION compression_request = new CMSG_REQUEST_COMPRESSION(6);
_ssh_out.writePacket(compression_request);
if (readNextPacket().getPacketType() == SSH_SMSG_SUCCESS) {
_ssh_out.setCompression(6);
_ssh_in.setCompression();
}
}
_phase = PHASE_READY;
return;
}
/**
* Start an interactive session. This method blocks until the session is
* terminated. The method must be called after the preparatory phase has
* completed successfully.
*/
public void execShell(String[] commands, String[] end) throws IOException, SSHProtocolException {
if (_phase != PHASE_READY) {
throw new RuntimeException("Protocol not in correct state");
}
/*
* Request the server to start up an interactive shell for us.
*/
debug("Requesting shell.");
CMSG_EXEC_SHELL shell_packet = new CMSG_EXEC_SHELL();
_ssh_out.writePacket(shell_packet);
/*
* If the keepalive timeout is nonzero, start a thread to send SSH_MSG_IGNORE
* packets when the session has been idle for the specified time.
*/
int timeout = _options.getKeepaliveTimeout();
if (timeout != 0) {
_keepaliveThread = new KeepaliveThread(timeout);
_keepaliveThread.setDaemon(true);
_keepaliveThread.start();
}
/*
* Read packets from the network input stream, decrypt them and process them.
*/
debug("Entering interactive session.");
int index = 0;
for (;;) {
Packet packet = null;
try {
packet = readNextPacket();
} catch (EOFException e) {
break; // the server closed the connection.
}
if (packet instanceof IInteractivePacket == false) {
throw new SSHProtocolException(
"packet type " + packet.getPacketType() + " received in interactive mode");
}
/*
* All packets that implement the IInteractivePacket interface implement the
* processPacket() method.
*/
((IInteractivePacket) packet).processPacket(this);
if (packet.getPacketType() == SSH_SMSG_EXITSTATUS) {// 退出状态
SMSG_EXITSTATUS status_packet = (SMSG_EXITSTATUS) packet;
debug("Exit status " + status_packet.getExitStatus());
break;
}
String s = new String(packet.getData());
int i = 0;
for (; i < end.length; ++i) {
if (s.trim().endsWith(end[i])) {
break;
}
}
if (i < end.length) {
if (index < commands.length) {
_ssh_out.writePacket(new CMSG_STDIN_DATA((commands[index++] + "\n").getBytes()));
} else {
break;
}
}
} // end for loop
/*
* The server closed the network connection; terminate the thread.
*/
return;
}
/**
* Starts executing the given command, and enters interactive session mode. On
* UNIX, the command is run as "<shell> -c <command>" where
* <shell> is the user's login shell.
* <p>
*
* This method is an alternative to <code>execShell()</code>. It must be called
* after the preparatory operations have completed successfully.
*/
public void execCmd(String command_) throws IOException, SSHProtocolException {
if (_phase != PHASE_READY) {
throw new RuntimeException("Protocol not in correct state");
}
/*
* Request the server to start up an interactive shell for us.
*/
debug("Sending command: " + command_);
CMSG_EXEC_CMD cmd_packet = new CMSG_EXEC_CMD(command_);
_ssh_out.writePacket(cmd_packet);
/*
* Read packets from the network input stream, decrypt them and process them.
*/
debug("Entering interactive session.");
for (;;) {
Packet packet = null;
try {
packet = readNextPacket();
} catch (EOFException e) {
break; // the server closed the connection.
} catch (SocketException e) {
break; // the server closed the connection.
}
if (packet instanceof IInteractivePacket == false) {
throw new SSHProtocolException(
"packet type " + packet.getPacketType() + " received in interactive mode");
}
((IInteractivePacket) packet).processPacket(this);
if (packet.getPacketType() == SSH_SMSG_EXITSTATUS) {
SMSG_EXITSTATUS status_packet = (SMSG_EXITSTATUS) packet;
debug("Exit status " + status_packet.getExitStatus());
_ssh_out.writePacket(new CMSG_EXIT_CONFIRMATION());
}
}
}
/**
* Registers an open SSH channel (encrypted tunnel).
*/
public void registerOpenChannel(OpenChannel channel_) {
_channelManager.registerOpenChannel(channel_);
}
/**
* Deregisters an open SSH chanel.
*/
public void removeOpenChannel(OpenChannel channel_) {
_channelManager.removeOpenChannel(channel_);
}
/**
* Finds the open channel with the specified channel number.
*/
public OpenChannel findOpenChannel(int channel_number_) {
return _channelManager.findOpenChannel(channel_number_);
}
/**
* This method is called when a SSH_MSG_PORT_OPEN message is received; it
* returns true if the port open request matches one of the local port
* forwardings specified by the user.
*/
public boolean isPortOpenAllowed(String hostname_, int port_) {
// Delegate to the Options member variable.
return _options.isPortOpenAllowed(hostname_, port_);
}
/**
* Returns the SSH_SMSG_PUBLIC_KEY packet that was sent by the server.
*/
public SMSG_PUBLIC_KEY getServerKeyPacket() {
return _server_public_key_packet;
}
/**
* Private helper method to read the next SSH packet. DEBUG packets are logged
* but are not returned to the caller.
*/
private Packet readNextPacket() throws IOException, SSHProtocolException {
Packet packet = null;
while (true) {
packet = _ssh_in.readPacket();
// Reception of any SSH packet should reset the keepalive timer.
if (_keepaliveThread != null) {
_keepaliveThread.interrupt();
}
int packet_type = packet.getPacketType();
if (packet_type == SSH_MSG_DEBUG) {
debug(((MSG_DEBUG) packet).getMessage());
} else if (packet_type != SSH_MSG_IGNORE)
break;
}
return packet;
}
/**
* Display the specified debugging message.
*/
public void debug(String string_) {
if (_options.getDebug())
System.err.println("debug: " + string_ + "\r\n");
}
/**
* A nonstatic inner class which sends SSH_MSG_IGNORE packets to keep the
* session alive.
*/
private class KeepaliveThread extends Thread {
private int _seconds;
public KeepaliveThread(int seconds_) {
_seconds = seconds_;
}
public void run() {
for (;;) {
try {
Thread.sleep(_seconds * 1000L);
} catch (InterruptedException e) {
/*
* The sleep was interrupted by transmission or reception of a packet. Start a
* new sleep.
*/
continue;
}
if (_socket == null || _socket.isClosed() || !_socket.isConnected()) {
break;
}
/*
* If we get here, it means that we completed our sleep without being
* interrupted by reception of an SSH packet. So send a SSH_MSG_IGNORE packet to
* keep the session alive.
*/
try {
_ssh_out.writePacket(new MSG_IGNORE());
} catch (IOException e) {
e.printStackTrace();
break;
}
} // end for loop
}
}
public void close() throws IOException {
if (_socket != null) {
_socket.close();
}
}
// ====================================================================
// INSTANCE VARIABLES
/**
* This is the network socket connected to the remote server.
*/
private Socket _socket;
/**
* Encapsulates all the command-line options.
*/
private Options _options;
/**
* This is the input stream associated with the SSH network connection.
*/
private BufferedInputStream _instream;
/**
* This is the output stream associated with the SSH network connection.
*/
private BufferedOutputStream _outstream;
/**
* The public key packet received from the server is stored here for later use.
*/
private SMSG_PUBLIC_KEY _server_public_key_packet;
/**
* The session id (extracted from the SSH_CMSG_SESSION_KEY packet) is stored
* here for later use.
*/
private byte[] _session_id;
private SSHInputStream _ssh_in;
private SSHOutputStream _ssh_out;
private STDOUT_InputStream _stdout = new STDOUT_InputStream();
private STDIN_OutputStream _stdin = null;
private StringBuffer _id_string_received = new StringBuffer();
private static String ID_STRING_SENT = "SSH-1.5-JSSH 0.9.5 (2002/10/05)";
private ChannelManager _channelManager = new ChannelManager();
private KeepaliveThread _keepaliveThread = null;
private int _phase = PHASE_EXCHANGE_ID_STRINGS;
// The protocol must proceed through each of these phases in turn.
private static final int PHASE_EXCHANGE_ID_STRINGS = 1;
private static final int PHASE_RECEIVE_SERVER_KEY = 2;
private static final int PHASE_SEND_SESSION_KEY = 3;
private static final int PHASE_DECLARE_USER = 4;
private static final int PHASE_AUTHENTICATE = 5;
private static final int PHASE_PREPARATORY = 6;
private static final int PHASE_READY = 7;
}
使用方式如下
Options options = new Options();
options.setHostname(machineip);
options.setUser(username);
options.setPort(connect_port);
options.setTerminalType("xterm");
SSHClient client = new SSHClient(options);
try {
client.connect(password);
String[] commands = command.split(";");
if (commands.length == 1) {
options.setCommand(commands[0]);
result = client.execCmd();
} else {
result = client.execShell(commands, new String[] { ">", "]" });
}
} catch (Exception e) {
if (e instanceof java.net.ConnectException) {
throw new CommandException("Cannot connect to " + options.getHostname() + ": " + e.getMessage());
} else if (e instanceof IOException) {
throw new CommandException("IOException: " + e.getMessage());
} else if (e instanceof SSHSetupException) {
throw new CommandException("SSHSetupException: " + e.getMessage());
} else if (e instanceof SSHProtocolException) {
throw new CommandException("SSHProtocolException: " + e.getMessage());
} else if (e instanceof SSHAuthFailedException) {
throw new CommandException("Authentication failed");
} else {
throw e;
}
} finally {// 操作结束后,一定要关闭
client.close();
}
执行结果是之前的,不过我还是留着吧:
Connecting to 不给看... Connected
-------------
Note: The max number of VTY users is 5, and the current number
of VTY users on line is 1.
NOTICE:This is a private communication system.
Unauthorized access or use may lead to prosecution.
HRP_M<不给看>
16:42:27 2019/12/31
16:42:27 beijing Tue 2019/12/31
Time Zone : beijing add 08:00:00
HRP_M<不给看>
-------------
好了,以上就是我的方法,要是有更简单的方法也可以告诉我,就是那种有专门的封装好的jar包,跟ssh2一样简便就更好了,网上还有一大堆教程。