lib.ec.ssh.Paramiko: Fix URI parameters, stdin handling, and output deadlock #85

Merged
Jan Lindemann merged 3 commits from jan/fix/20260905-lib-ec-ssh-paramiko-fix-uri-parameters-stdin-handling-and-output-deadlock into master 2026-09-05 19:09:29 +02:00 AGit

This PR adds three independent fixes to the Paramiko SSH client, each addressing a defect that makes remote commands fail or hang:

lib.ec.ssh.Paramiko: Pass port and password

__client() connects using only the URI's hostname and username. The URI's port is ignored, so ssh://host:2222/... ends up connecting to port 22, and a password carried in the URI is never passed to paramiko, so URI-based password authentication cannot work. The Exec and AsyncSSH clients both honor the port and the password.

Pass the port and the password to connect(). The port argument is omitted entirely when the URI carries no port, because getaddrinfo() would interpret a None port as service port 0; without the argument, paramiko falls back to its default of 22.

lib.ec.ssh.Paramiko: Close remote stdin

_run_ssh() writes cmd_input to the remote stdin channel but never closes the write side. The channel stays open until the client process exits, so a remote command that reads stdin (cat, a login shell, ...) never sees EOF and blocks forever - including for cmd_input = None, which is supposed to mean non-interactive with stdin from /dev/null.

Call shutdown_write() on the channel after writing the input, or immediately when there is none, so the remote command gets EOF.

lib.ec.ssh.Paramiko: Avoid recv deadlock

_run_ssh() waits for the remote process's exit status before reading stdout and stderr. When a command produces more output than the channel's flow-control window can hold, the server stops sending, the remote process blocks on its write and never exits, and recv_exit_status() blocks forever.

Drain stdout and stderr to EOF first - the channels close when the process exits, so reading them also implies completion - and only then query the exit status.

This PR adds three independent fixes to the Paramiko SSH client, each addressing a defect that makes remote commands fail or hang: #### lib.ec.ssh.Paramiko: Pass port and password __client() connects using only the URI's hostname and username. The URI's port is ignored, so ssh://host:2222/... ends up connecting to port 22, and a password carried in the URI is never passed to paramiko, so URI-based password authentication cannot work. The Exec and AsyncSSH clients both honor the port and the password. Pass the port and the password to connect(). The port argument is omitted entirely when the URI carries no port, because getaddrinfo() would interpret a None port as service port 0; without the argument, paramiko falls back to its default of 22. #### lib.ec.ssh.Paramiko: Close remote stdin _run_ssh() writes cmd_input to the remote stdin channel but never closes the write side. The channel stays open until the client process exits, so a remote command that reads stdin (cat, a login shell, ...) never sees EOF and blocks forever - including for cmd_input = None, which is supposed to mean non-interactive with stdin from /dev/null. Call shutdown_write() on the channel after writing the input, or immediately when there is none, so the remote command gets EOF. #### lib.ec.ssh.Paramiko: Avoid recv deadlock _run_ssh() waits for the remote process's exit status before reading stdout and stderr. When a command produces more output than the channel's flow-control window can hold, the server stops sending, the remote process blocks on its write and never exits, and recv_exit_status() blocks forever. Drain stdout and stderr to EOF first - the channels close when the process exits, so reading them also implies completion - and only then query the exit status.
__client() connects using only the URI's hostname and username. The
URI's port is ignored, so ssh://host:2222/... ends up connecting to
port 22, and a password carried in the URI is never passed to
paramiko, so URI-based password authentication cannot work. The Exec
and AsyncSSH clients both honor the port and the password.

Pass the port and the password to connect(). The port argument is
omitted entirely when the URI carries no port, because
getaddrinfo() would interpret a None port as service port 0; without
the argument, paramiko falls back to its default of 22.

Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2
Signed-off-by: Jan Lindemann <jan@janware.com>
_run_ssh() writes cmd_input to the remote stdin channel but never
closes the write side. The channel stays open until the client
process exits, so a remote command that reads stdin (cat, a login
shell, ...) never sees EOF and blocks forever - including for
cmd_input = None, which is supposed to mean non-interactive with
stdin from /dev/null.

Call shutdown_write() on the channel after writing the input, or
immediately when there is none, so the remote command gets EOF.

Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2
Signed-off-by: Jan Lindemann <jan@janware.com>
lib.ec.ssh.Paramiko: Avoid recv deadlock
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m13s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m13s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 3m56s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m1s
CI / Packaging test (push) Successful in 0s
e6295f7d2b
_run_ssh() waits for the remote process's exit status before reading
stdout and stderr. When a command produces more output than the
channel's flow-control window can hold, the server stops sending, the
remote process blocks on its write and never exits, and
recv_exit_status() blocks forever.

Drain stdout and stderr to EOF first - the channels close when the
process exits, so reading them also implies completion - and only
then query the exit status.

Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2
Signed-off-by: Jan Lindemann <jan@janware.com>
Jan Lindemann scheduled this pull request to auto merge when all checks succeed 2026-09-05 19:01:00 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
janware/jw-pkg!85
No description provided.