jline/jline3

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

Alexandre01Dev opened this issue · 2 comments

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

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.