O cliente Openssh está bloqueando para sempre se stderr for redirecionado para stdout

O cliente Openssh está bloqueando para sempre se stderr for redirecionado para stdout

Parece que pode haver um problema no openssh. No bashshell, se eu redirecionar stderrpara stdoutele, ele bloqueará para sempre, então tenho que 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

O mesmo comando sem o redirecionamento funciona perfeitamente:

$ 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

Por que isso está acontecendo? Existe uma solução alternativa?

Responder1

Quando ssh -festiver "indo para segundo plano" após ter se conectado e autenticado no host, ele continuará mantendo identificadores abertos em seu stdin, stdout e stderr originais, portanto, se esses identificadores foram conectados via pipes a outros processos (como seu stdout + stderr estão no seu exemplo para cat -A), terá o efeito de manter esses processos ativos, mesmo que eles não sejam mais necessários.

sshdaemoniza-se chamando odaemon(3)função de biblioteca, mas ela a chama com noclose = 1, impedindo-a de redirecionar o stdin/stderr/stdout de /dev/null.

Isso foi parcialmente corrigido em versões recentes openssh(para o processo de controle mestre - o stdin e o stdout em2010, o stderr em2016; para o processo de sessão - o stdout em2017), mas se você precisar executar um ssh mais antigo ou precisar que ele pare de se apegar ao stderr também, a única "solução" pode ser usar um LD_PRELOADhack que substitua a daemon(3)função por um wrapper que chame o original com 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
...

Responder2

Por que isso está acontecendo?

É catque precisa ser finalizado antes que o shell chegue ao echo. Está “bloqueando para sempre” porque as catvidas.

Existe uma solução alternativa?

No Bash eu usaria a substituição de processos para executar catque não bloqueia. Com catfora do caminho, é fácil apenas echo OKse estiver realmente OK (com &&ou $?). Exemplo:

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

Agora catfunciona antes e depois de sshir para segundo plano, mas o script continua assim que ssho faz (ou assim que sshfalha sem ir para segundo plano).

informação relacionada