diff --git a/src/main/kotlin/app/termora/SshClients.kt b/src/main/kotlin/app/termora/SshClients.kt index f378d62..7eecd11 100644 --- a/src/main/kotlin/app/termora/SshClients.kt +++ b/src/main/kotlin/app/termora/SshClients.kt @@ -3,7 +3,6 @@ package app.termora import app.termora.keyboardinteractive.TerminalUserInteraction import app.termora.keymgr.OhKeyPairKeyPairProvider import app.termora.terminal.TerminalSize -import app.termora.x11.ChannelX11 import app.termora.x11.X11ChannelFactory import com.formdev.flatlaf.FlatLaf import com.formdev.flatlaf.util.FontUtils @@ -80,7 +79,6 @@ import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference import javax.swing.* import kotlin.math.max -import kotlin.random.Random @Suppress("CascadeIf") object SshClients { @@ -111,6 +109,12 @@ object SshClients { env.putAll(host.options.envs()) val channel = session.createShellChannel(configuration, env) + if (host.options.enableX11Forwarding) { + if (channel is app.termora.x11.ChannelShell) { + channel.xForwarding = true + } + } + if (!channel.open().verify(timeout).await()) { throw SshException("Failed to open Shell") } @@ -563,7 +567,7 @@ object SshClients { ): ChannelShell { if (inCipher is CipherNone || outCipher is CipherNone) throw IllegalStateException("Interactive channels are not supported with none cipher") - val channel = MyChannelShell(ptyConfig, env) + val channel = app.termora.x11.ChannelShell(ptyConfig, env) val id = connectionService.registerChannel(channel) if (log.isDebugEnabled) { log.debug("createShellChannel({}) created id={} - PTY={}", this, id, ptyConfig) @@ -580,63 +584,6 @@ object SshClients { throw UnsupportedOperationException() } - private class MyChannelShell( - configHolder: PtyChannelConfigurationHolder?, - env: MutableMap? - ) : ChannelShell(configHolder, env) { - - override fun doOpenPty() { - val session = super.getSession() - val x11Host = CoreModuleProperties.X11_BIND_HOST.getOrNull(session) - val x11Port = CoreModuleProperties.X11_BASE_PORT.getOrNull(session) - - if (x11Port == null || x11Host == null) { - super.doOpenPty() - return - } - - val buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST) - buffer.putInt(super.getRecipient()) - buffer.putString("x11-req") - buffer.putBoolean(false) // want-reply - buffer.putBoolean(false) - buffer.putString("MIT-MAGIC-COOKIE-1") - buffer.putBytes(getFakedCookie()) - buffer.putInt(0) - - writePacket(buffer) - - super.doOpenPty() - } - - private fun getFakedCookie(): ByteArray { - val session = super.getSession() - var cookie = ChannelX11.X11_COOKIE_HEX.getOrNull(session) - if (cookie != null) { - return cookie as ByteArray - } - - synchronized(session) { - cookie = ChannelX11.X11_COOKIE_HEX.getOrNull(session) - if (cookie != null) { - return cookie as ByteArray - } - - val foo = Random.nextBytes(16) - ChannelX11.X11_COOKIE.set(session, foo) - - val bar = foo.copyOf(32) - for (i in 0..15) { - bar[2 * i] = ChannelX11.COOKIE_TABLE[(foo[i].toInt() ushr 4) and 0xf] - bar[2 * i + 1] = ChannelX11.COOKIE_TABLE[foo[i].toInt() and 0xf] - } - ChannelX11.X11_COOKIE_HEX.set(session, bar) - - return bar - } - } - } - private class MyIoConnector(private val sshClient: MyJGitSshClient, private val ioConnector: IoConnector) : IoConnector { override fun close(immediately: Boolean): CloseFuture { diff --git a/src/main/kotlin/app/termora/x11/ChannelShell.kt b/src/main/kotlin/app/termora/x11/ChannelShell.kt new file mode 100644 index 0000000..83efa9a --- /dev/null +++ b/src/main/kotlin/app/termora/x11/ChannelShell.kt @@ -0,0 +1,59 @@ +package app.termora.x11 + +import org.apache.sshd.client.channel.ChannelShell +import org.apache.sshd.common.SshConstants +import org.apache.sshd.common.channel.PtyChannelConfigurationHolder +import kotlin.random.Random + +class ChannelShell( + configHolder: PtyChannelConfigurationHolder?, + env: MutableMap? +) : ChannelShell(configHolder, env) { + + var xForwarding = false + + override fun doOpenPty() { + val session = super.getSession() + + if (xForwarding) { + val buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST) + buffer.putInt(super.getRecipient()) + buffer.putString("x11-req") + buffer.putBoolean(false) // want-reply + buffer.putBoolean(false) + buffer.putString("MIT-MAGIC-COOKIE-1") + buffer.putBytes(getFakedCookie()) + buffer.putInt(0) + writePacket(buffer) + } + + super.doOpenPty() + } + + private fun getFakedCookie(): ByteArray { + val session = super.getSession() + var cookie = ChannelX11.X11_COOKIE_HEX.getOrNull(session) + if (cookie != null) { + return cookie as ByteArray + } + + synchronized(session) { + cookie = ChannelX11.X11_COOKIE_HEX.getOrNull(session) + if (cookie != null) { + return cookie as ByteArray + } + + val foo = Random.nextBytes(16) + ChannelX11.X11_COOKIE.set(session, foo) + + val bar = foo.copyOf(32) + for (i in 0..15) { + bar[2 * i] = ChannelX11.COOKIE_TABLE[(foo[i].toInt() ushr 4) and 0xf] + bar[2 * i + 1] = ChannelX11.COOKIE_TABLE[foo[i].toInt() and 0xf] + } + ChannelX11.X11_COOKIE_HEX.set(session, bar) + + return bar + } + } +}