Der Openssh-Client blockiert dauerhaft, wenn stderr auf stdout umgeleitet wird

Der Openssh-Client blockiert dauerhaft, wenn stderr auf stdout umgeleitet wird

Es scheint, dass da ein Problem vorliegt openssh. Wenn ich in der bashShell zu umleite, stderrwird stdoutes dauerhaft blockiert, also muss ich KeyboardInterrupt:

$ ssh -fTNF './config' -MS './sockd5/ctrl_socket' -i './keys/id_rsa' -l 'root' -p '22' 'example.com' 2>&1 | cat -A; echo OK
ControlSocket ./sockd5/ctrl_socket already exists, disabling multiplexing^M$
^C

Derselbe Befehl ohne Umleitung funktioniert einwandfrei:

$ ssh -fTNF './config' -MS './sockd/ctrl_socket' -i './keys/id_rsa' -l 'root' -p '22' 'example.com' | cat -A; echo OK
ControlSocket ./sockd/ctrl_socket already exists, disabling multiplexing
OK

Warum passiert das? Gibt es eine Problemumgehung?

Antwort1

Wenn ssh -fes „in den Hintergrund geht“, nachdem eine Verbindung zum Host hergestellt und eine Authentifizierung durchgeführt wurde, hält es weiterhin Handles zu seinem ursprünglichen stdin, stdout und stderr offen. Wenn diese Handles also über Pipes mit anderen Prozessen verbunden waren (wie stdout + stderr in Ihrem Beispiel mit cat -A), hat dies den Effekt, dass diese Prozesse am Leben bleiben, auch wenn sie nicht mehr benötigt werden.

sshdämonisiert sich selbst durch den Aufruf desdaemon(3)Bibliotheksfunktion, ruft diese jedoch mit auf noclose = 1und verhindert so die Umleitung von stdin/stderr/stdout von /dev/null.

Dies wurde in neueren Versionen teilweise behoben openssh(für den Master-Steuerungsprozess -- die Standardeingabe- und Standardausgabedateien in2010, der Standardfehler in2016; für den Sitzungsprozess -- die Standardausgabe in2017), aber wenn Sie ein älteres SSH ausführen müssen oder es auch nicht mehr an stderr hängen bleiben soll, besteht die einzige „Lösung“ möglicherweise darin, einen LD_PRELOADHack zu verwenden, der die Funktion mit einem Wrapper überschreibt daemon(3), der das Original mit aufruft noclose = 0.

$ cat daemon-force-close.c
#define _GNU_SOURCE
#include <unistd.h>
#include <dlfcn.h>
#include <err.h>

int daemon(int nochdir, int noclose){
        static int (*orig)(int, int);
        if(!orig && !(*(void**)&orig = dlsym(RTLD_NEXT, "daemon")))
                errx(1, "%s", dlerror());
        return orig(nochdir, 0);
}

$ cc -shared -Wall -O2 daemon-force-close.c -ldl -o daemon-force-close.so

$ LD_PRELOAD=./daemon-force-close.so \
   ssh -Nf dummy@localhost -MS ./ctlsock 2>&1 | cat -A
dummy@localhost's password:
$
[no Ctrl-C needed]
$ ssh -S ~/w/c/ctlsock dummy@localhost
Last login: Tue May 28 21:04:26 2019 from ::1
...

Antwort2

Warum passiert das?

Es catmuss beendet werden, bevor die Shell dorthin gelangt echo. Es „blockiert für immer“, weil das catLeben.

Gibt es eine Problemumgehung?

In Bash würde ich Prozesssubstitution verwenden, um einen Prozess auszuführen cat, der nicht blockiert. Mit catout of the way ist es dann einfach, echo OKnur zu verwenden, wenn es wirklich OK ist (mit &&oder $?). Beispiel:

ssh -f … > >(cat -A) 2>&1 && echo OK; echo "The script goes on."

Funktioniert jetzt catvor und nach sshdem Wechsel in den Hintergrund, aber das Skript wird fortgesetzt, sobald sshdies geschieht (oder sobald sshes fehlschlägt, ohne in den Hintergrund zu wechseln).

verwandte Informationen