knipknap / exscript

A Python module making Telnet and SSH easy
MIT License
367 stars 136 forks source link

Exscript ssh2.interact() seems to be broken under linux #217

Open mpenning opened 2 years ago

mpenning commented 2 years ago

I can establish normal SSH2 sessions with Exscript. However, when I use conn.interact(), I get exscript tracebacks... fancy super-useful tracebacks courtesy of loguru

$ python testme_exscript.py
Interacting...
2021-12-26 15:51:45.849 | ERROR    | __main__:<module>:144 - An error has been caught in function '<module>', process 'MainProcess' (66656), thread 'MainThread' (140387668304256):
Traceback (most recent call last):

> File "testme_exscript.py", line 144, in <module>
    main()
    └ <function main at 0x7fae8a198ea0>

  File "testme_exscript.py", line 134, in main
    conn.interact(key_handlers={}, handle_window_size=False)
    │    └ <function SSH2.interact at 0x7fae87ad1950>
    └ <Exscript.protocols.ssh2.SSH2 object at 0x7fae8a1b8ac8>

  File "/opt/virtual_env/py37_test/lib/python3.7/site-packages/Exscript/protocols/ssh2.py", line 449, in interact
    return self._open_shell(self.shell, key_handlers, handle_window_size)
           │    │           │    │      │             └ False
           │    │           │    │      └ {}
           │    │           │    └ <paramiko.Channel 0 (open) window=2097099 -> <paramiko.Transport at 0x8a1b8fd0 (cipher aes128-ctr, 128 bits) (active; 1 open ...
           │    │           └ <Exscript.protocols.ssh2.SSH2 object at 0x7fae8a1b8ac8>
           │    └ <function Protocol._open_shell at 0x7fae87abeea0>
           └ <Exscript.protocols.ssh2.SSH2 object at 0x7fae8a1b8ac8>
  File "/opt/virtual_env/py37_test/lib/python3.7/site-packages/Exscript/protocols/protocol.py", line 1231, in _open_shell
    return self._open_posix_shell(channel, key_handlers, handle_window_size)
           │    │                 │        │             └ False
           │    │                 │        └ {}
           │    │                 └ <paramiko.Channel 0 (open) window=2097099 -> <paramiko.Transport at 0x8a1b8fd0 (cipher aes128-ctr, 128 bits) (active; 1 open ...
           │    └ <function Protocol._open_posix_shell at 0x7fae87abed90>
           └ <Exscript.protocols.ssh2.SSH2 object at 0x7fae8a1b8ac8>
  File "/opt/virtual_env/py37_test/lib/python3.7/site-packages/Exscript/protocols/protocol.py", line 1145, in _open_posix_shell
    with os.fdopen(sys.stdin.fileno(), 'r', 0) as stdin:
         │  │      │   │     └ <method 'fileno' of '_io.TextIOWrapper' objects>
         │  │      │   └ <_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>
         │  │      └ <module 'sys' (built-in)>
         │  └ <function fdopen at 0x7fae8cdac730>
         └ <module 'os' from '/opt/virtual_env/py37_test/lib/python3.7/os.py'>
  File "/opt/virtual_env/py37_test/lib/python3.7/os.py", line 1026, in fdopen
    return io.open(fd, *args, **kwargs)
           │  │    │    │       └ {}
           │  │    │    └ ('r', 0)
           │  │    └ 0
           │  └ <built-in function open>
           └ <module 'io' from '/opt/virtual_env/py37_test/lib/python3.7/io.py'>

ValueError: can't have unbuffered text I/O

Sadly, I don't know enough linux / exscript details to contribute to fixing the problem, but interact() is definitely broken in exscript 2.6.3.

Background:

cetfor commented 2 years ago

With libtelnet being deprecated as of 3.11 and removal slated for 3.13 I was looking for an alternative package. Exscript is suggested in the PEP which lead me to this issue.

I modified my local Protocol.py in Exscript and was able to get interactive working with two changes.

Change 1 on line 1145: set mode to rb instead of r https://github.com/knipknap/exscript/blob/a20e83ae3a78ea7e5ba25f07c1d9de4e9b961e83/Exscript/protocols/protocol.py#L1145

Change 2 on line 1145: the first change will now require a bytes object instead of str, so instead of sending data send bytes(data, 'utf-8') https://github.com/knipknap/exscript/blob/a20e83ae3a78ea7e5ba25f07c1d9de4e9b961e83/Exscript/protocols/protocol.py#L1199

My script now works with this:

import sys
from Exscript import Account
from Exscript.protocols import Telnet

account = Account('username', 'password')
conn = Telnet(stdout=sys.stdout)
conn.set_prompt('/ #')
conn.connect('<ip_address>')
conn.authenticate(account)
conn.interact() # blocking
conn.close()

I'm not confident this won't cause issues else where, but I figured I would share a possible solution.