Passando più file tramite stdin (su ssh)


15

Ho un programma su un host remoto, di cui devo automatizzare l'esecuzione. Il comando esegue quel programma, sulla stessa macchina, assomiglia a questo:

/path/to/program -a file1.txt -b file2.txt

In questo caso, file1.txte file2.txtsono utilizzati per cose completamente diverse all'interno del programma, quindi non posso semplicemente catfarli insieme. Tuttavia, nel mio caso, il file1.txte file2.txtche voglio passare al programma esistono solo sul mio dispositivo, non sull'host in cui devo eseguire il programma. So che posso alimentare almeno un file tramite SSH passandolo attraverso stdin:

cat file1.txt | ssh host.name /path/to/program -a /dev/stdin -b file2.txt

ma, dal momento che non mi è permesso archiviare file sull'host, ho bisogno di un modo per ottenere anche file2.txtlì. Sto pensando che potrebbe essere possibile attraverso l'abuso delle variabili di ambiente e l'uso creativo di cate sedinsieme, ma non conosco gli strumenti abbastanza bene per capire come li userei per raggiungere questo obiettivo. È fattibile e come?


2
cate sednon sono la soluzione qui.
Slyx,

Probabilmente sarei in grado di montare, ma dati i proxy e i vincoli di sicurezza non sono sicuro che sarei in grado di cavarmela.
Green Cloak Guy,

Hai l'autorizzazione nel computer remoto per montare una cartella ssh?
Slyx,

1
se puoi aprire una sessione ssh dalla macchina remota a quella locale, quindi non c'è nessun problema a livello di rete per montare una cartella SSH.
Slyx,

Puoi fare spedizioni? Quali sistemi e shell hai a livello locale e remoto?
mosvy,

Risposte:


18

Se i file forniti come argomenti al tuo programma sono file di testo e sei in grado di controllarne il contenuto (conosci una riga che non si trova al loro interno), puoi usare più documenti qui:

{
    echo "cat /dev/fd/3 3<<'EOT' /dev/fd/4 4<<'EOT' /dev/fd/5 5<<'EOT'"
    cat file1
    echo EOT
    cat file2
    echo EOT
    cat file3
    echo EOT
} | ssh user@host sh

Ecco catun comando di esempio che accetta i nomi dei file come argomenti. Potrebbe essere invece:

echo "/path/to/prog -a /dev/fd/3 3<<'EOT' -b /dev/fd/4 4<<'EOT'

Sostituisci ciascuno EOTcon qualcosa che non si verifica in ciascuno dei file, rispettivamente.


1
Questo approccio è abbastanza intelligente! Se i file non sono di testo, potresti codificarli / decodificarli con qualcosa come base64 o anche un buon vecchio uuencode ... Molto bello!
filbranden,

3
Si noti che diverse (la maggior parte) implementazioni sh implementano qui i documenti utilizzando file temporanei, quindi potrebbe non funzionare per l'OP se non sono autorizzati a memorizzare file sull'host remoto.
Stéphane Chazelas il

2
dash(il /bin/shdebian, ubuntu, ecc.), busybox shil /bin/shda FreeBSD e yashnon usano file temporanei per i documenti qui. L'ho provato con un chroot di sola lettura. Naturalmente, i /dev/fd/file devono essere disponibili - solo su un sistema FreeBSD di serie /dev/fd/0-2, quindi fdescfsdevono essere montati /dev/fd.
mosvy

1
Sono un fan dei separatori di informazioni ASCII , progettati per delimitare i campi senza ostacoli. U+001Cè "Separatore di informazioni quattro" (separatore di file, FS) ed è ideale per questo caso, sebbene i file binari possano farne uso per coincidenza. Pertanto, suggerirei EOT="$(printf $'\x1c')"in shell avanzate oppure EOT="$(awk 'BEGIN{printf"%c",28}')"per compatibilità. Dovrai quotarlo quando lo metti in posizione, come echo "$EOT$EOT$EOT"(triplicato per ridurre le probabilità di abbinare un file binario).
Adam Katz,

Se hai intenzione di usare tty, perché non usare il ttycomando? A meno che non mi manchi qualcosa - che suppongo sia possibile?
Pryftan,

14

Forse non esattamente quello che vuoi ... Ma forse potresti inviare un tarball attraverso la pipe aperta da ssh?

L'hai detto tu:

Non sono autorizzato a archiviare file sull'host.

È possibile che tu non abbia una home directory scrivibile o un'altra posizione comoda per archiviare i file a lungo termine, ma direi che è improbabile che tu non abbia una posizione scrivibile, anche se un temporaneo tmpfssarà reso disponibile solo per il tuo connessione particolare.

Molti programmi (e persino routine libc) richiedono una scrittura /tmp, quindi è molto probabile che uno sia disponibile per te.

Quindi è possibile utilizzare uno script che decomprimerà il tarball in una directory temporanea, eseguirà il programma e pulirà attraverso la connessione SSH.

Qualcosa di simile a:

$ tar cf - file1.txt file2.txt |
  ssh host.name '
      set -e
      tmpdir=$(mktemp -d -t tmp.XXXXXXXXXX)
      cleanup () { rm -rf "$tmpdir"; }
      trap cleanup EXIT
      cd "$tmpdir"
      tar xf -
      /path/to/program -a file1.txt -b file2.txt
  '

Questo potrebbe richiedere qualche attenzione in più con i percorsi dei file e ci sono alcuni casi angolari da considerare (test per loro), ma l'approccio generale dovrebbe funzionare.

Se non è disponibile alcuna directory scrivibile, un possibile approccio potrebbe essere quello di modificare programun tarball come input singolo e decomprimerne il contenuto in memoria. Ad esempio, se si programtratta di uno script Python, l'utilizzo del tarfilemodulo integrato comporterebbe facilmente qualcosa del genere.


3
Anche senza affrontare il problema di tarballarlo, ho deciso più o meno di scrivere un file /tmp/e usarlo direttamente.
Green Cloak Guy,

2
L'approccio tarball presenta alcuni vantaggi, in quanto si utilizza una singola connessione ssh (non è necessario eseguire test per esito positivo / negativo) e non è probabile che si verifichino più esecuzioni simultanee (utilizzare e / o eliminare i file in / tmp per esecuzioni separate .) Ma penso che sia il migliore che otterrai da ssh come esiste ora. In bocca al lupo!
filbranden,

3
@GreenCloakGuy Quindi, dopo tutto, puoi archiviare i file sul server bene. Modifica la domanda di conseguenza.
mosvy

1
@mosvy può , sì, ma non voglio . La domanda si pone, in un "è possibile farlo senza memorizzare alcun file", la cui risposta sembra essere "forse, ma non nel mio caso"
Green Cloak Guy

5

Se è possibile impostare un listener TCP (può essere una porta superiore), è possibile utilizzare una seconda sessione SSH per stabilire la seconda sorgente di input nc.

Esempio:

C'è questo script sul server ( ~/script.bash):

#!/usr/bin/env bash
cat "$1" | tr a 1
nc localhost "$2" | tr '[:lower:]' '[:upper:]'

E ci sono questi due file localmente:

$ cat a 
aaa
aaa
aaa
$ cat b
bbb
bbb
bbb

Ora prima avvia la seconda fonte ( $servè il server):

ssh "$serv" nc -l -p 2222 -q1 <b &

Ed esegui il comando corretto:

$ ssh "$serv" ./script.bash - 2222 <a
111
111
111
BBB
BBB
BBB
[1]+  Done                    ssh "$serv" nc -l -p 2222 -q1 < b

2

È stato stabilito in un commento che /tmpè scrivibile, quindi copia semplicemente prima uno dei file:

scp -p file2.txt host.name:/tmp/
ssh host.name "/path/to/program -a /dev/stdin -b /tmp/file2.txt && rm /tmp/file2.txt" < file1.txt

Questo pulisce anche il file copiato dopo una corretta esecuzione (cambiare &&in a ;se si desidera rimuoverlo indipendentemente dall'esito positivo, ma si noti che si perderà il valore di uscita).


Se questo non è accettabile, proporrei di armeggiare /path/to/programo un wrapper per esso che può separare i due file da un singolo flusso di input come:

awk 'FNR == 1 && NR > 1 { printf "%c%c%c", 28, 28, 28 } 1' file1.txt file2.txt \
  | ssh host.name /path/to/tweaked_program

Questo utilizza il separatore di informazioni ASCII quattro (separatore di file, FS) e lo ha triplicato in modo da ridurre al minimo la possibilità che un file binario contenga casualmente quella stringa. Il tuo tweaked_programsarebbe poi dividere l'ingresso dato il separatore e poi operare su due file salvati come variabili.

Naturalmente, se si utilizza un linguaggio che ha librerie per gestire tarball, un approccio più sicuro e più pulito sarebbe semplicemente quello di convogliare tartale codice attraverso sshquesto:

tar -zpc file1.txt file2.txt |ssh host.name /path/to/tweaked_program

E tweaked_programdecomprimere e aprire l'archivio, salvare ogni file in una variabile diversa e quindi eseguire la programlogica originale sulle variabili.


1

Se ti è consentito inoltrare le porte sshe hai accesso al wgetcomputer remoto e al busyboxcomputer locale, puoi fare qualcosa del tipo:

mkdir /tmp/test; cd /tmp/test
echo 1st_file > 1st_file
echo 2nd_file > 2nd_file

busybox httpd -f -p 127.0.0.1:11080 &
ssh USER@HOST -R 10080:127.0.0.1:11080 '
        cat <(wget -q -O- http://localhost:10080/1st_file) \
            <(wget -q -O- http://localhost:10080/2nd_file)
'
kill $!

(usando catcome programma di esempio che accetta due argomenti di file).

È -Ressenziale solo la capacità di inoltrare le porte tramite - invece di http, è possibile utilizzare altri metodi, ad es. se netcatsupporta le opzioni -de -N:

nc -Nl localhost 11001 < 1st_file &
nc -Nl localhost 11002 < 2nd_file &
ssh USER@HOST -R 10001:localhost:11001 -R 10002:localhost:11002 '
        cat <(nc -d localhost 10001) <(nc -d localhost 10002)

Ci possono essere modi per sostituire le <(...)sostituzioni di processo se la shell di accesso sul computer remoto non è simile a ksh o bash.

Tutto sommato, questo non è troppo grande: una migliore conoscenza dell'esatto sistema / shell / config / autorizzazioni (che non hai fornito) potrebbe consentire soluzioni più intelligenti.


-1

AGGIORNAMENTO: Questo non funziona davvero, ssh ha un'idea molto precisa di cosa significhino stdin e stdout / stderr e non consente davvero a usurping stderr di leggerlo. Eliminerò questa risposta tra qualche giorno poiché non funziona. Grazie per l'interessantissima discussione !!!


Purtroppo non c'è un ottimo modo per farlo direttamente, dal momento che il sshcliente passerà solo i tre descrittori di file ( stdin, stdoute stderr) per il server e non ha disposizioni per passare descrittori di file aggiuntivi (che sarebbe utile per questo particolare caso d'uso .)

(Si noti inoltre che il protocollo SSH prevede disposizioni per il passaggio di descrittori di file aggiuntivi, solo il sshclient non implementa un modo per utilizzare quella funzione. Teoricamente, l'estensione del client con una patch sarebbe sufficiente per esporre questa funzione.)

Un modo bizzarro per realizzare ciò che stai cercando è usare il descrittore di file 2 (il stderrdescrittore di file) per passare il secondo file. Qualcosa di simile a:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/stderr \
      <file1.txt 2<file2.txt

Questo dovrebbe funzionare, potrebbe causare un problema se si programtenta di scrivere su stderr. È possibile aggirare il problema riagganciando i descrittori di file sull'estremità remota prima di eseguire il programma. Puoi passare file2.txtal descrittore di file 3 e riaprire stderr per rispecchiare stdout:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt

Non l'ho mai provato (digitando su un telefono) ma almeno in teoria mi aspetto che funzioni. Fammi sapere se per qualche motivo non lo è. Saluti!
filbranden,

Non funzionerà affatto. Nemmeno con un ssh hackerato. AFAIK stderr non utilizza un canale separato, ma un tipo speciale di messaggio. Ma sì, essere in grado di accedere ai canali ssh come pipe o socket di dominio unix senza nome (invece di dover inoltrare socket) sarebbe fantastico, semplicemente non sembra possibile in alcun tipo o forma.
mosvy,

@mosvy, fd 0, 1 e 2 sul comando remoto saranno 3 differenti pipe. I dati viaggiano attraverso 3 diversi canali multiplexati nella connessione ssh, ma quelli sono unidirezionali (da client a server per fd 0 e da server a client per 1 e 2), quindi anche su sistemi in cui le pipe sono bidirezionali, che ha vinto ' non funziona. Su Linux, openening / dev / stderr in modalità sola lettura dà all'altra estremità della pipe, quindi non funzionerebbe nemmeno.
Stéphane Chazelas,

Si noti che l'ultimo presuppone che la shell di accesso dell'utente su remote.name sia simile 3<&2a Bourne ( è un operatore di shell Bourne e sshd esegue la shell di accesso dell'utente per interpretare il comando inviato dal client)
Stéphane Chazelas

2
@ StéphaneChazelas No, un singolo canale ssh viene utilizzato per stdin / stdout / stderr e i dati stderr vengono trasferiti tramite SSH_MSG_CHANNEL_EXTENDED_DATAmessaggi con il tipo impostato su SSH_EXTENDED_DATA_STDERR. Puoi leggere tutto qui
mosvy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.