jline / jline3

JLine is a Java library for handling console input.
Other
1.49k stars 217 forks source link

SshChannelClosedException occurs when SSH client disconnects: Issue with PosixPtyTerminal and ChannelOutputStream #1060

Closed Alexandre01Dev closed 2 weeks ago

Alexandre01Dev commented 2 months ago

Hello, I am using JLINE3 with the MinaSSHD library. When creating the LineReaderImpl with the terminal generated by ShellFactoryImpl via the ShellParams consumer, and executing the readLine function, everything works fine except when the SSH client disconnects, whether the user disconnects from their terminal or is ejected by the java process, which causes an error that cannot be handled. Here it is:

org.apache.sshd.common.channel.exception.SshChannelClosedException: write(ChannelOutputStream[ChannelSession[id=0, recipient=0]-ServerSessionImpl[testuser@/217.136.124.54:8978]] SSH_MSG_CHANNEL_DATA) len=1 - channel already closed
        at org.apache.sshd.common.channel.ChannelOutputStream.write(ChannelOutputStream.java:146)
        at org.apache.sshd.common.channel.ChannelOutputStream.write(ChannelOutputStream.java:138)
        at org.jline.terminal.impl.PosixPtyTerminal.pumpOut(PosixPtyTerminal.java:227)
        at java.base/java.lang.Thread.run(Thread.java:1583)
java.io.IOError: java.io.IOException: Input/output error
        at org.jline.keymap.BindingReader.readCharacter(BindingReader.java:169)
        at org.jline.keymap.BindingReader.readBinding(BindingReader.java:109)
        at org.jline.keymap.BindingReader.readBinding(BindingReader.java:61)
        at org.jline.reader.impl.LineReaderImpl.doReadBinding(LineReaderImpl.java:974)
        at org.jline.reader.impl.LineReaderImpl.readBinding(LineReaderImpl.java:1007)
        at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:690)
        at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:512)
        at be.alexandre01.dreamnetwork.core.console.InputThread.run(InputThread.java:40)
        at be.alexandre01.dreamnetwork.core.ssh.SSHServerCore.lambda$init$13(SSHServerCore.java:264)
        at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.io.IOException: Input/output error
        at java.base/java.io.FileInputStream.read0(Native Method)
        at java.base/java.io.FileInputStream.read(FileInputStream.java:231)
        at java.base/java.io.FilterInputStream.read(FilterInputStream.java:71)
        at org.jline.terminal.impl.AbstractPty$1.read(AbstractPty.java:58)
        at org.jline.terminal.impl.AbstractPty$PtyInputStream.read(AbstractPty.java:124)
        at org.jline.terminal.impl.PosixPtyTerminal$InputStreamWrapper.read(PosixPtyTerminal.java:178)
        at org.jline.utils.NonBlockingInputStream.read(NonBlockingInputStream.java:62)
        at org.jline.utils.NonBlocking$NonBlockingInputStreamReader.read(NonBlocking.java:157)
        at org.jline.utils.NonBlockingReader.read(NonBlockingReader.java:56)
        at org.jline.keymap.BindingReader.readCharacter(BindingReader.java:159)
        ... 11 more

Here are the steps to reproduce the error:

        SshServer sshd = SshServer.setUpDefaultServer();
        sshd.setPort(2222);
        sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(Paths.get("hostkey.ser")));
        sshd.setPasswordAuthenticator((username, password, session) -> true);
        sshd.setCipherFactories(Arrays.asList(BuiltinCiphers.aes256ctr, BuiltinCiphers.aes192ctr));
        sshd.setKeyExchangeFactories(ServerBuilder.setUpDefaultKeyExchanges(false));

        sshd.setShellFactory(new ShellFactoryImpl(shellParams -> {
            LineReader reader = LineReaderBuilder.builder()
                    .terminal(shellParams.getTerminal())
                    .build();

            try {
                String line;
                reader.printAbove("Welcome to SSH Server");
                while ((line = reader.readLine("sshTest > ")) != null) {
                    System.out.println(line);
                }
            } catch (UserInterruptException e) {
                // Ignore
            } catch (EndOfFileException e) {
                // Ignore
            }catch (Exception e){
                // ignore OTHER EXCEPTIONS
            }
        }));
        try {
            // Start the server
            sshd.start();
            System.out.println("SSH Server started on port 2222");

            // Keep the server running
            Thread.sleep(Long.MAX_VALUE);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                sshd.stop();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

Thanks for looking into this issue. If you need more details or have any suggestions, please let me know. Looking forward to your feedback

Christian-Martins commented 1 month ago

Up !

Alexandre01Dev commented 1 month ago

I think I found a temporary solution on Unix. In the ShellFactoryImpl class (line 204), when we force the creation of an ExternalTerminal and not a PosixPtyTerminal, the bug is gone.

gnodet commented 2 weeks ago

I think this is perfectly normal for the stream to thrown an exception during the read when the stream is closed. Exceptions occurring while closing the resources can be safely ignored in this case.