From ed0b9a16ef208944b208f5e2e91842fbfe3af1c6 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 24 Sep 2023 13:24:48 +0800 Subject: [PATCH] fix code --- .../ssh/engine/ganymed/GanymedSession.java | 52 +- .../extra/ssh/engine/jsch/JschSession.java | 3 +- .../ssh/engine/jsch/JschSessionPool.java | 131 ---- .../extra/ssh/engine/jsch/JschUtil.java | 574 ------------------ .../extra/ssh/engine/sshj/SshjSession.java | 145 +++++ 5 files changed, 180 insertions(+), 725 deletions(-) delete mode 100644 hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSessionPool.java delete mode 100644 hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschUtil.java create mode 100644 hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSession.java diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java index 69c523bb4..86474114d 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/ganymed/GanymedSession.java @@ -16,6 +16,7 @@ import ch.ethz.ssh2.Connection; import ch.ethz.ssh2.StreamGobbler; import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.io.IoUtil; +import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.extra.ssh.Connector; import org.dromara.hutool.extra.ssh.Session; @@ -65,7 +66,7 @@ public class GanymedSession implements Session { /** * 执行Shell命令(使用EXEC方式) *

- * 此方法单次发送一个命令到服务端,不读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,不读取环境变量,不会产生阻塞。 *

* * @param cmd 命令 @@ -73,24 +74,31 @@ public class GanymedSession implements Session { * @param errStream 错误信息输出到的位置 * @return 执行返回结果 */ - public String exec(final String cmd, final Charset charset, final OutputStream errStream) { - final String result; + public String exec(final String cmd, Charset charset, final OutputStream errStream) { + if (null == charset) { + charset = CharsetUtil.UTF_8; + } + + // 发送命令 try { this.raw.execCommand(cmd, charset.name()); - result = IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset); - - // 错误输出 - IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream); } catch (final IOException e) { throw new IORuntimeException(e); } - return result; + + // 错误输出 + if(null != errStream){ + IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream); + } + + // 结果输出 + return IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset); } /** * 执行Shell命令 *

- * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,可能产生阻塞。 *

* * @param cmd 命令 @@ -98,21 +106,27 @@ public class GanymedSession implements Session { * @param errStream 错误信息输出到的位置 * @return 执行返回结果 */ - public String execByShell(final String cmd, final Charset charset, final OutputStream errStream) { - final String result; + public String execByShell(final String cmd, Charset charset, final OutputStream errStream) { + if (null == charset) { + charset = CharsetUtil.UTF_8; + } + try { this.raw.requestDumbPTY(); - IoUtil.write(this.raw.getStdin(), charset, true, cmd); - - result = IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset); - if(null != errStream){ - // 错误输出 - IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream); - } } catch (final IOException e) { throw new IORuntimeException(e); } - return result; + + // 发送命令 + IoUtil.write(this.raw.getStdin(), charset, true, cmd); + + // 错误输出 + if (null != errStream) { + IoUtil.copy(new StreamGobbler(this.raw.getStderr()), errStream); + } + + // 结果输出 + return IoUtil.read(new StreamGobbler(this.raw.getStdout()), charset); } /** diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java index ab907a169..31677124c 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSession.java @@ -205,6 +205,7 @@ public class JschSession implements Session { final ChannelExec channel = (ChannelExec) createChannel(ChannelType.EXEC); channel.setCommand(ByteUtil.toBytes(cmd, charset)); channel.setInputStream(null); + channel.setErrStream(errStream); InputStream in = null; try { @@ -234,7 +235,7 @@ public class JschSession implements Session { * @return {@link ChannelExec} * @since 5.2.5 */ - public static String execByShell(final String cmd, final Charset charset) { + public String execByShell(final String cmd, final Charset charset) { final ChannelShell shell = openShell(); // 开始连接 shell.setPty(true); diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSessionPool.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSessionPool.java deleted file mode 100644 index 92a4df792..000000000 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschSessionPool.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.extra.ssh.engine.jsch; - -import org.dromara.hutool.core.cache.SimpleCache; -import org.dromara.hutool.core.text.StrUtil; -import com.jcraft.jsch.Session; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map.Entry; - -/** - * Jsch会话池 - * - * @author looly - */ -public enum JschSessionPool { - INSTANCE; - - /** - * SSH会话池,key:host,value:Session对象 - */ - private final SimpleCache cache = new SimpleCache<>(new HashMap<>()); - - /** - * 获取Session,不存在返回null - * - * @param key 键 - * @return Session - */ - public Session get(final String key) { - return cache.get(key); - } - - /** - * 获得一个SSH跳板机会话,重用已经使用的会话 - * - * @param sshHost 跳板机主机 - * @param sshPort 跳板机端口 - * @param sshUser 跳板机用户名 - * @param sshPass 跳板机密码 - * @return SSH会话 - */ - public Session getSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { - final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort); - return this.cache.get(key, Session::isConnected, ()-> JschUtil.openSession(sshHost, sshPort, sshUser, sshPass)); - } - - /** - * 获得一个SSH跳板机会话,重用已经使用的会话 - * - * @param sshHost 跳板机主机 - * @param sshPort 跳板机端口 - * @param sshUser 跳板机用户名 - * @param prvkey 跳板机私钥路径 - * @param passphrase 跳板机私钥密码 - * @return SSH会话 - */ - public Session getSession(final String sshHost, final int sshPort, final String sshUser, final String prvkey, final byte[] passphrase) { - final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort); - return this.cache.get(key, Session::isConnected, ()->JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase)); - } - - /** - * 加入Session - * - * @param key 键 - * @param session Session - */ - public void put(final String key, final Session session) { - this.cache.put(key, session); - } - - /** - * 关闭SSH连接会话 - * - * @param key 主机,格式为user@host:port - */ - public void close(final String key) { - final Session session = get(key); - if (session != null && session.isConnected()) { - session.disconnect(); - } - this.cache.remove(key); - } - - /** - * 移除指定Session - * - * @param session Session会话 - * @since 4.1.15 - */ - public void remove(final Session session) { - if (null != session) { - final Iterator> iterator = this.cache.iterator(); - Entry entry; - while (iterator.hasNext()) { - entry = iterator.next(); - if (session.equals(entry.getValue())) { - iterator.remove(); - break; - } - } - } - } - - /** - * 关闭所有SSH连接会话 - */ - public void closeAll() { - Session session; - for (final Entry entry : this.cache) { - session = entry.getValue(); - if (session != null && session.isConnected()) { - session.disconnect(); - } - } - cache.clear(); - } -} diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschUtil.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschUtil.java deleted file mode 100644 index 6f3aed9db..000000000 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/jsch/JschUtil.java +++ /dev/null @@ -1,574 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.extra.ssh.engine.jsch; - -import com.jcraft.jsch.*; -import org.dromara.hutool.core.io.IORuntimeException; -import org.dromara.hutool.core.io.IoUtil; -import org.dromara.hutool.core.lang.Assert; -import org.dromara.hutool.core.net.LocalPortGenerator; -import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.ByteUtil; -import org.dromara.hutool.core.util.CharsetUtil; -import org.dromara.hutool.extra.ssh.Connector; -import org.dromara.hutool.extra.ssh.SshException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.Charset; - -/** - * Jsch工具类
- * Jsch是Java Secure Channel的缩写。JSch是一个SSH2的纯Java实现。
- * 它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等。
- * - * @author Looly - * @since 4.0.0 - */ -public class JschUtil { - - /** - * 不使用SSH的值 - */ - public final static String SSH_NONE = "none"; - - /** - * 本地端口生成器 - */ - private static final LocalPortGenerator portGenerater = new LocalPortGenerator(10000); - - /** - * 生成一个本地端口,用于远程端口映射 - * - * @return 未被使用的本地端口 - */ - public static int generateLocalPort() { - return portGenerater.generate(); - } - - /** - * 获得一个SSH会话,重用已经使用的会话 - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名 - * @param sshPass 密码 - * @return SSH会话 - */ - public static Session getSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { - return JschSessionPool.INSTANCE.getSession(sshHost, sshPort, sshUser, sshPass); - } - - /** - * 获得一个SSH会话,重用已经使用的会话 - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名 - * @param privateKeyPath 私钥路径 - * @param passphrase 私钥密码 - * @return SSH会话 - */ - public static Session getSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase) { - return JschSessionPool.INSTANCE.getSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase); - } - - /** - * 打开一个新的SSH会话 - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名 - * @param sshPass 密码 - * @return SSH会话 - */ - public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { - return openSession(sshHost, sshPort, sshUser, sshPass, 0); - } - - /** - * 打开一个新的SSH会话 - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名 - * @param sshPass 密码 - * @param timeout Socket连接超时时长,单位毫秒 - * @return SSH会话 - * @since 5.3.3 - */ - public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass, final int timeout) { - final Session session = createSession(sshHost, sshPort, sshUser, sshPass); - try { - session.setTimeout(timeout); - session.connect(timeout); - } catch (final JSchException e) { - throw new SshException(e); - } - return session; - } - - /** - * 打开一个新的SSH会话 - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名 - * @param privateKeyPath 私钥的路径 - * @param passphrase 私钥文件的密码,可以为null - * @return SSH会话 - */ - public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase) { - final Session session = createSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase); - try { - session.connect(); - } catch (final JSchException e) { - throw new SshException(e); - } - return session; - } - - /** - * 打开一个新的SSH会话 - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名 - * @param privateKeyPath 私钥的路径 - * @param passphrase 私钥文件的密码,可以为null - * @param timeout 超时时间,单位毫秒 - * @return SSH会话 - */ - public static Session openSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase, final int timeout) { - final Session session = createSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase); - try { - session.setTimeout(timeout); - session.connect(timeout); - } catch (final JSchException e) { - throw new SshException(e); - } - return session; - } - - /** - * 新建一个新的SSH会话,此方法并不打开会话(既不调用connect方法) - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名,如果为null,默认root - * @param sshPass 密码 - * @return SSH会话 - * @since 4.5.2 - */ - public static Session createSession(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { - final JSch jsch = new JSch(); - final Session session = createSession(jsch, sshHost, sshPort, sshUser); - - if (StrUtil.isNotEmpty(sshPass)) { - session.setPassword(sshPass); - } - - return session; - } - - /** - * 新建一个新的SSH会话,此方法并不打开会话(既不调用connect方法) - * - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名,如果为null,默认root - * @param privateKeyPath 私钥的路径 - * @param passphrase 私钥文件的密码,可以为null - * @return SSH会话 - * @since 5.0.0 - */ - public static Session createSession(final String sshHost, final int sshPort, final String sshUser, final String privateKeyPath, final byte[] passphrase) { - Assert.notEmpty(privateKeyPath, "PrivateKey Path must be not empty!"); - - final JSch jsch = new JSch(); - try { - jsch.addIdentity(privateKeyPath, passphrase); - } catch (final JSchException e) { - throw new SshException(e); - } - - return createSession(jsch, sshHost, sshPort, sshUser); - } - - /** - * 创建一个SSH会话,重用已经使用的会话 - * - * @param jsch {@link JSch} - * @param sshHost 主机 - * @param sshPort 端口 - * @param sshUser 用户名,如果为null,默认root - * @return {@link Session} - * @since 5.0.3 - */ - public static Session createSession(JSch jsch, final String sshHost, final int sshPort, String sshUser) { - Assert.notEmpty(sshHost, "SSH Host must be not empty!"); - Assert.isTrue(sshPort > 0, "SSH port must be > 0"); - - // 默认root用户 - if (StrUtil.isEmpty(sshUser)) { - sshUser = "root"; - } - - if (null == jsch) { - jsch = new JSch(); - } - - final Session session; - try { - session = jsch.getSession(sshUser, sshHost, sshPort); - } catch (final JSchException e) { - throw new SshException(e); - } - - // 设置第一次登录的时候提示,可选值:(ask | yes | no) - session.setConfig("StrictHostKeyChecking", "no"); - - return session; - } - - /** - * 绑定端口到本地。 一个会话可绑定多个端口 - * - * @param session 需要绑定端口的SSH会话 - * @param remoteHost 远程主机 - * @param remotePort 远程端口 - * @param localPort 本地端口 - * @return 成功与否 - * @throws SshException 端口绑定失败异常 - */ - public static boolean bindPort(final Session session, final String remoteHost, final int remotePort, final int localPort) throws SshException { - return bindPort(session, remoteHost, remotePort, "127.0.0.1", localPort); - } - - /** - * 绑定端口到本地。 一个会话可绑定多个端口 - * - * @param session 需要绑定端口的SSH会话 - * @param remoteHost 远程主机 - * @param remotePort 远程端口 - * @param localHost 本地主机 - * @param localPort 本地端口 - * @return 成功与否 - * @throws SshException 端口绑定失败异常 - * @since 5.7.8 - */ - public static boolean bindPort(final Session session, final String remoteHost, final int remotePort, final String localHost, final int localPort) throws SshException { - if (session != null && session.isConnected()) { - try { - session.setPortForwardingL(localHost, localPort, remoteHost, remotePort); - } catch (final JSchException e) { - throw new SshException(e, "From [{}:{}] mapping to [{}:{}] error!", remoteHost, remotePort, localHost, localPort); - } - return true; - } - return false; - } - - - /** - * 绑定ssh服务端的serverPort端口, 到host主机的port端口上.
- * 即数据从ssh服务端的serverPort端口, 流经ssh客户端, 达到host:port上. - * - * @param session 与ssh服务端建立的会话 - * @param bindPort ssh服务端上要被绑定的端口 - * @param host 转发到的host - * @param port host上的端口 - * @return 成功与否 - * @throws SshException 端口绑定失败异常 - * @since 5.4.2 - */ - public static boolean bindRemotePort(final Session session, final int bindPort, final String host, final int port) throws SshException { - if (session != null && session.isConnected()) { - try { - session.setPortForwardingR(bindPort, host, port); - } catch (final JSchException e) { - throw new SshException(e, "From [{}] mapping to [{}] error!", bindPort, port); - } - return true; - } - return false; - } - - - /** - * 解除端口映射 - * - * @param session 需要解除端口映射的SSH会话 - * @param localPort 需要解除的本地端口 - */ - public static void unBindPort(final Session session, final int localPort) { - try { - session.delPortForwardingL(localPort); - } catch (final JSchException e) { - throw new SshException(e); - } - } - - /** - * 打开SSH会话,并绑定远程端口到本地的一个随机端口 - * - * @param sshConn SSH连接信息对象 - * @param remoteHost 远程主机 - * @param remotePort 远程端口 - * @return 映射后的本地端口 - * @throws SshException 连接异常 - */ - public static int openAndBindPortToLocal(final Connector sshConn, final String remoteHost, final int remotePort) throws SshException { - final Session session = openSession(sshConn.getHost(), sshConn.getPort(), sshConn.getUser(), sshConn.getPassword()); - final int localPort = generateLocalPort(); - bindPort(session, remoteHost, remotePort, localPort); - return localPort; - } - - /** - * 打开SFTP连接 - * - * @param session Session会话 - * @return {@link ChannelSftp} - * @since 4.0.3 - */ - public static ChannelSftp openSftp(final Session session) { - return openSftp(session, 0); - } - - /** - * 打开SFTP连接 - * - * @param session Session会话 - * @param timeout 连接超时时长,单位毫秒 - * @return {@link ChannelSftp} - * @since 5.3.3 - */ - public static ChannelSftp openSftp(final Session session, final int timeout) { - return (ChannelSftp) openChannel(session, ChannelType.SFTP, timeout); - } - - /** - * 创建Sftp - * - * @param sshHost 远程主机 - * @param sshPort 远程主机端口 - * @param sshUser 远程主机用户名 - * @param sshPass 远程主机密码 - * @return {@link Sftp} - * @since 4.0.3 - */ - public static Sftp createSftp(final String sshHost, final int sshPort, final String sshUser, final String sshPass) { - return new Sftp(sshHost, sshPort, sshUser, sshPass); - } - - /** - * 创建Sftp - * - * @param session SSH会话 - * @return {@link Sftp} - * @since 4.0.5 - */ - public static Sftp createSftp(final Session session) { - return new Sftp(session); - } - - /** - * 打开Shell连接 - * - * @param session Session会话 - * @return {@link ChannelShell} - * @since 4.0.3 - */ - public static ChannelShell openShell(final Session session) { - return (ChannelShell) openChannel(session, ChannelType.SHELL); - } - - /** - * 打开Channel连接 - * - * @param session Session会话 - * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType} - * @return {@link Channel} - * @since 4.5.2 - */ - public static Channel openChannel(final Session session, final ChannelType channelType) { - return openChannel(session, channelType, 0); - } - - /** - * 打开Channel连接 - * - * @param session Session会话 - * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType} - * @param timeout 连接超时时长,单位毫秒 - * @return {@link Channel} - * @since 5.3.3 - */ - public static Channel openChannel(final Session session, final ChannelType channelType, final int timeout) { - final Channel channel = createChannel(session, channelType); - try { - channel.connect(Math.max(timeout, 0)); - } catch (final JSchException e) { - throw new SshException(e); - } - return channel; - } - - /** - * 创建Channel连接 - * - * @param session Session会话 - * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType} - * @return {@link Channel} - * @since 4.5.2 - */ - public static Channel createChannel(final Session session, final ChannelType channelType) { - final Channel channel; - try { - if (!session.isConnected()) { - session.connect(); - } - channel = session.openChannel(channelType.getValue()); - } catch (final JSchException e) { - throw new SshException(e); - } - return channel; - } - - /** - * 执行Shell命令 - * - * @param session Session会话 - * @param cmd 命令 - * @param charset 发送和读取内容的编码 - * @return {@link ChannelExec} - * @since 4.0.3 - */ - public static String exec(final Session session, final String cmd, final Charset charset) { - return exec(session, cmd, charset, System.err); - } - - /** - * 执行Shell命令(使用EXEC方式) - *

- * 此方法单次发送一个命令到服务端,不读取环境变量,执行结束后自动关闭channel,不会产生阻塞。 - *

- * - * @param session Session会话 - * @param cmd 命令 - * @param charset 发送和读取内容的编码 - * @param errStream 错误信息输出到的位置 - * @return 执行结果内容 - * @since 4.3.1 - */ - public static String exec(final Session session, final String cmd, Charset charset, final OutputStream errStream) { - if (null == charset) { - charset = CharsetUtil.UTF_8; - } - final ChannelExec channel = (ChannelExec) createChannel(session, ChannelType.EXEC); - channel.setCommand(ByteUtil.toBytes(cmd, charset)); - channel.setInputStream(null); - channel.setErrStream(errStream); - InputStream in = null; - try { - channel.connect(); - in = channel.getInputStream(); - return IoUtil.read(in, charset); - } catch (final IOException e) { - throw new IORuntimeException(e); - } catch (final JSchException e) { - throw new SshException(e); - } finally { - IoUtil.closeQuietly(in); - close(channel); - } - } - - /** - * 执行Shell命令 - *

- * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭channel,不会产生阻塞。 - *

- * - * @param session Session会话 - * @param cmd 命令 - * @param charset 发送和读取内容的编码 - * @return {@link ChannelExec} - * @since 5.2.5 - */ - public static String execByShell(final Session session, final String cmd, final Charset charset) { - final ChannelShell shell = openShell(session); - // 开始连接 - shell.setPty(true); - OutputStream out = null; - InputStream in = null; - try { - out = shell.getOutputStream(); - in = shell.getInputStream(); - - out.write(ByteUtil.toBytes(cmd, charset)); - out.flush(); - - return IoUtil.read(in, charset); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - IoUtil.closeQuietly(out); - IoUtil.closeQuietly(in); - close(shell); - } - } - - /** - * 关闭SSH连接会话 - * - * @param session SSH会话 - */ - public static void close(final Session session) { - if (session != null && session.isConnected()) { - session.disconnect(); - } - JschSessionPool.INSTANCE.remove(session); - } - - /** - * 关闭会话通道 - * - * @param channel 会话通道 - * @since 4.0.3 - */ - public static void close(final Channel channel) { - if (channel != null && channel.isConnected()) { - channel.disconnect(); - } - } - - /** - * 关闭SSH连接会话 - * - * @param key 主机,格式为user@host:port - */ - public static void close(final String key) { - JschSessionPool.INSTANCE.close(key); - } - - /** - * 关闭所有SSH连接会话 - */ - public static void closeAll() { - JschSessionPool.INSTANCE.closeAll(); - } - -} diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSession.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSession.java new file mode 100644 index 000000000..99b6149f9 --- /dev/null +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/ssh/engine/sshj/SshjSession.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.extra.ssh.engine.sshj; + +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.transport.verification.PromiscuousVerifier; +import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.io.IoUtil; +import org.dromara.hutool.core.util.CharsetUtil; +import org.dromara.hutool.extra.ssh.Connector; +import org.dromara.hutool.extra.ssh.Session; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * 基于SSHJ(https://github.com/hierynomus/sshj)的Session封装 + * + * @author looly + */ +public class SshjSession implements Session { + + private SSHClient ssh; + private final net.schmizz.sshj.connection.channel.direct.Session raw; + + /** + * 构造 + * + * @param connector {@link Connector},保存连接和验证信息等 + */ + public SshjSession(final Connector connector) { + final SSHClient ssh = new SSHClient(); + ssh.addHostKeyVerifier(new PromiscuousVerifier()); + try { + ssh.connect(connector.getHost(), connector.getPort()); + ssh.authPassword(connector.getUser(), connector.getPassword()); + this.raw = ssh.startSession(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + + this.ssh = ssh; + } + + /** + * 构造 + * + * @param raw {@link net.schmizz.sshj.connection.channel.direct.Session} + */ + public SshjSession(final net.schmizz.sshj.connection.channel.direct.Session raw) { + this.raw = raw; + } + + @Override + public Object getRaw() { + return raw; + } + + @Override + public void close() throws IOException { + IoUtil.closeQuietly(this.raw); + IoUtil.closeQuietly(this.ssh); + } + + /** + * 执行Shell命令(使用EXEC方式) + *

+ * 此方法单次发送一个命令到服务端,不读取环境变量,不会产生阻塞。 + *

+ * + * @param cmd 命令 + * @param charset 发送和读取内容的编码 + * @param errStream 错误信息输出到的位置 + * @return 执行返回结果 + */ + public String exec(final String cmd, Charset charset, final OutputStream errStream) { + if (null == charset) { + charset = CharsetUtil.UTF_8; + } + + final net.schmizz.sshj.connection.channel.direct.Session.Command command; + + // 发送命令 + try { + command = this.raw.exec(cmd); + //command.join(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + + // 错误输出 + if (null != errStream) { + IoUtil.copy(command.getErrorStream(), errStream); + } + + // 结果输出 + return IoUtil.read(command.getInputStream(), charset); + } + + /** + * 执行Shell命令 + *

+ * 此方法单次发送一个命令到服务端,自动读取环境变量,可能产生阻塞。 + *

+ * + * @param cmd 命令 + * @param charset 发送和读取内容的编码 + * @param errStream 错误信息输出到的位置 + * @return 执行返回结果 + */ + public String execByShell(final String cmd, Charset charset, final OutputStream errStream) { + if (null == charset) { + charset = CharsetUtil.UTF_8; + } + + final net.schmizz.sshj.connection.channel.direct.Session.Shell shell; + try { + shell = this.raw.startShell(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + + // 发送命令 + IoUtil.write(shell.getOutputStream(), charset, true, cmd); + + // 错误输出 + if (null != errStream) { + IoUtil.copy(shell.getErrorStream(), errStream); + } + + // 结果输出 + return IoUtil.read(shell.getInputStream(), charset); + } +}