Passer plusieurs fichiers via stdin (via ssh)

15

J'ai un programme sur un hôte distant, dont j'ai besoin d'automatiser l'exécution. La commande exécute ce programme, sur la même machine, ressemble à ceci:

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

Dans ce cas, file1.txtet file2.txtsont utilisés pour des choses entièrement différentes dans le programme, donc je ne peux pas simplement catles faire ensemble. Cependant, dans mon cas, le file1.txtet file2.txtque je souhaite transmettre au programme n'existent que sur mon appareil, pas sur l'hôte où j'ai besoin d'exécuter le programme. Je sais que je peux alimenter au moins un fichier via SSH en le passant par stdin:

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

mais, comme je ne suis pas autorisé à stocker des fichiers sur l'hôte, j'ai également besoin d'un moyen d'y accéder file2.txt. Je pense que cela pourrait être possible grâce à l'abus des variables d'environnement et à l'utilisation créative de catet sedensemble, mais je ne connais pas suffisamment les outils pour comprendre comment je les utiliserais pour y parvenir. Est-ce faisable et comment?

Green Cloak Guy
la source
2
catet sedne sont pas la solution ici.
Slyx
Je serais probablement capable de monter, mais étant donné les procurations et les contraintes de sécurité, je ne suis pas sûr de pouvoir m'en tirer.
Green Cloak Guy
Avez-vous l'autorisation sur la machine distante de monter un dossier ssh?
Slyx
1
si vous pouvez ouvrir une session ssh de l'ordinateur distant vers votre ordinateur local, il n'y a donc pas de problème au niveau du réseau pour monter un dossier SSH.
Slyx
Pouvez-vous faire des expéditions? Quels systèmes et coques avez-vous au niveau local et distant?
mosvy

Réponses:

18

Si les fichiers donnés comme arguments à votre programme sont des fichiers texte, et que vous êtes capable de contrôler leur contenu (vous connaissez une ligne qui ne s'y trouve pas), vous pouvez utiliser plusieurs documents ici:

{
    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

Voici catun exemple de commande qui prend les noms de fichiers comme arguments. Ce pourrait être à la place:

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

Remplacez chacun EOTpar quelque chose qui ne se produit pas dans chacun des fichiers, respectivement.

mosvy
la source
1
Cette approche est assez intelligente! Si les fichiers ne sont pas du texte, vous pouvez les encoder / décoder avec quelque chose comme base64 ou même un bon vieux uuencode ... Très bien!
filbranden
3
Notez que plusieurs (la plupart) implémentations sh implémentent ici des documents à l'aide de fichiers temporaires, donc cela peut ne pas fonctionner pour l'OP s'ils ne sont pas autorisés à stocker des fichiers sur l'hôte distant.
Stéphane Chazelas
2
dash(le /bin/shdebian, ubuntu, etc.), busybox shle /bin/shde FreeBSD et yashne pas utiliser les fichiers temporaires pour ici-documents. Je l'ai testé avec un chroot en lecture seule. Bien sûr, les /dev/fd/fichiers doivent être disponibles - sur un système FreeBSD d'origine seulement /dev/fd/0-2, ils fdescfsdoivent donc être montés /dev/fd.
mosvy
1
Je suis un fan des séparateurs d'informations ASCII , qui sont conçus pour délimiter les champs sans obstruction. U+001Cest "Information Separator Four" (séparateur de fichiers, FS) et est idéal dans ce cas, bien que les fichiers binaires puissent en faire usage par hasard. Par conséquent, je suggère EOT="$(printf $'\x1c')"dans des shells avancés ou bien EOT="$(awk 'BEGIN{printf"%c",28}')"pour des raisons de compatibilité. Vous devrez le citer lorsque vous le mettrez en place, tel que echo "$EOT$EOT$EOT"(triplé pour réduire les chances de faire correspondre un fichier binaire).
Adam Katz
Si vous allez utiliser le tty, pourquoi ne pas utiliser la ttycommande? À moins que je manque quelque chose - ce qui, je suppose, est possible?
Pryftan
14

Peut-être pas exactement ce que vous voulez ... Mais pensez peut-être à envoyer une archive tar à travers le tuyau ouvert par ssh?

Vous avez dit que:

Je ne suis pas autorisé à stocker des fichiers sur l'hôte.

Il est possible que vous ne disposiez pas d'un répertoire de base accessible en écriture ou d'un autre emplacement pratique pour stocker des fichiers à long terme, mais je dirais qu'il est peu probable que vous ne disposiez pas d'un emplacement accessible en écriture, même si un temporaire tmpfsqui ne sera disponible que pour votre connexion particulière.

De nombreux programmes (et même des routines libc) nécessitent une écriture /tmp, il est donc très probable que l'un d'entre eux soit disponible.

Ensuite, vous pouvez utiliser un script qui décompactera l'archive tar dans un répertoire temporaire, exécutera votre programme et nettoiera via la connexion ssh.

Quelque chose comme:

$ 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
  '

Cela peut nécessiter des précautions supplémentaires avec les chemins de fichiers et il y a quelques cas d'angle à considérer (testez-les), mais l'approche générale devrait fonctionner.

Si aucun répertoire accessible en écriture n'est disponible, une approche possible serait de modifier programpour prendre une archive tar comme entrée unique et décompresser son contenu en mémoire. Par exemple, si programc'est un script Python, alors utiliser le tarfilemodule intégré accomplirait facilement quelque chose comme ça.

filbranden
la source
3
Même sans avoir la peine de le tarballer - j'ai plus ou moins décidé de simplement écrire un fichier /tmp/et de l'utiliser directement.
Green Cloak Guy
2
L'approche tarball présente certains avantages, car vous utilisez une seule connexion ssh (pas besoin de tester la réussite / l'échec) et plusieurs exécutions simultanées ne sont pas susceptibles de s'exécuter les unes dans les autres (utiliser et / ou supprimer des fichiers dans / tmp destinés à des exécutions distinctes .) Mais je pense que c'est le meilleur que vous obtiendrez de ssh tel qu'il existe actuellement. Bonne chance!
filbranden
3
@GreenCloakGuy Donc, après tout, vous pouvez très bien stocker des fichiers sur le serveur. Veuillez modifier la question en conséquence.
mosvy
1
@mosvy peut , oui, mais ne veut pas . La question se pose, dans un "est-il possible de le faire sans stocker de fichiers", dont la réponse semble être "peut-être, mais pas dans mon cas"
Green Cloak Guy
5

Si vous pouvez définir un écouteur TCP (il peut s'agir d'un port supérieur), vous pouvez utiliser une deuxième session SSH pour établir la deuxième source d'entrée avec nc.

Exemple:

Il y a ce script sur le serveur ( ~/script.bash):

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

Et il y a ces deux fichiers localement:

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

Maintenant, démarrez d'abord la deuxième source ( $servc'est le serveur):

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

Et exécutez la commande appropriée:

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

la source
2

Il a été établi dans un commentaire qui /tmpest accessible en écriture, donc il suffit de copier sur l' un des fichiers au préalable:

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

Cela nettoie également le fichier copié après une exécution réussie (remplacez-le &&par a ;si vous souhaitez le supprimer indépendamment de la réussite, mais notez ensuite que vous perdrez la valeur de sortie).


Si ce n'est pas acceptable, je proposerais de bricoler avec /path/to/programou un wrapper pour cela qui peut séparer les deux fichiers d'un seul flux d'entrée tel que:

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

Cela utilise le séparateur d'informations ASCII quatre (séparateur de fichiers, FS) et l'a triplé afin que nous minimisions les chances qu'un fichier binaire contienne par hasard cette chaîne. Vous diviseriez tweaked_programensuite l'entrée en fonction du séparateur, puis opéreriez sur les deux fichiers enregistrés en tant que variables.

Bien sûr, si vous utilisez un langage qui possède des bibliothèques pour gérer les tarballs, une approche plus sûre et plus propre serait simplement de canaliser tardans un tel code sshcomme ceci:

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

Et vous tweaked_programdécompressez et ouvrez l'archive, enregistrez chaque fichier dans une variable différente, puis exécutez la programlogique d'origine sur les variables.

Adam Katz
la source
1

Si vous êtes autorisé à transférer des ports ssh, et que vous avez accès à wgetsur la machine distante et à busyboxla machine locale, vous pouvez faire quelque chose comme:

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 $!

(en utilisant catcomme exemple de programme qui prend deux arguments de fichier).

Seule la possibilité de transférer des ports via -Rest essentielle - au lieu de faire http, vous pouvez utiliser d'autres méthodes, par exemple. si votre netcatprend en charge les options -det -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)

Il peut y avoir des moyens de remplacer les <(...)substitutions de processus si le shell de connexion sur la machine distante n'est pas de type ksh ou bash.

Dans l'ensemble, ce n'est pas trop grand - une meilleure connaissance du système / shells / config / permissions exact (que vous n'avez pas fourni) pourrait permettre des solutions plus intelligentes.

mosvy
la source
-1

MISE À JOUR: Cela ne fonctionne pas vraiment, ssh a une idée très précise de ce que stdin et stdout / stderr signifient et ne permet pas vraiment d'usurper stderr pour en lire. Je supprimerai cette réponse dans quelques jours car cela ne fonctionne pas. Merci pour la discussion très intéressante !!!


Malheureusement, il n'y a pas un excellent moyen de le faire directement, car le sshclient ne transmettra que les trois descripteurs de fichiers ( stdin, stdoutet stderr) au serveur et il n'a pas de dispositions pour transmettre des descripteurs de fichiers supplémentaires (ce qui serait utile pour ce cas d'utilisation particulier). .)

(Notez également que le protocole SSH a des dispositions pour passer des descripteurs de fichiers supplémentaires, seul le sshclient n'implémente pas un moyen d'utiliser cette fonctionnalité. Théoriquement, étendre le client avec un correctif serait suffisant pour exposer cette fonctionnalité.)

Une manière hacky d'accomplir ce que vous cherchez est d'utiliser le descripteur de fichier 2 (le stderrdescripteur de fichier) pour passer le deuxième fichier. Quelque chose comme:

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

Cela devrait fonctionner, cela pourrait provoquer un problème si vous programessayez d'écrire sur stderr. Vous pouvez contourner cela en jonglant avec les descripteurs de fichiers sur l'extrémité distante avant d'exécuter le programme. Vous pouvez passer file2.txtau descripteur de fichier 3 et rouvrir stderr pour mettre en miroir stdout:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt
filbranden
la source
Je n'ai pas vraiment testé cela (taper sur un téléphone) mais au moins en théorie, je m'attendrais à ce que cela fonctionne. Faites-moi savoir si ce n'est pas le cas pour une raison quelconque. À votre santé!
filbranden
Ça ne marchera pas du tout. Pas même avec un ssh piraté. AFAIK stderr n'utilise pas un canal séparé, mais un type particulier de message. Mais oui, pouvoir accéder aux canaux ssh en tant que canaux ou sockets de domaine Unix sans nom (au lieu d'avoir à faire des transferts de socket) serait formidable, cela ne semble tout simplement pas possible sous quelque forme que ce soit.
mosvy
@mosvy, fd 0, 1 et 2 sur la commande à distance auront 3 canaux différents. Les données transitent par 3 canaux différents multiplexés dans la connexion ssh, mais ceux-ci sont unidirectionnels (du client au serveur pour fd 0 et du serveur au client pour 1 et 2), donc même sur les systèmes où les tuyaux sont bidirectionnels, cela a gagné '' t travailler. Sous Linux, ouvrir / dev / stderr en mode lecture seule donne à l'autre bout du tube, donc ça ne marcherait jamais non plus.
Stéphane Chazelas
Notez que le dernier suppose que le shell de connexion de l'utilisateur sur remote.name est de type Bourne ( 3<&2est un opérateur de shell Bourne, et sshd exécute le shell de connexion de l'utilisateur pour interpréter la commande envoyée par le client)
Stéphane Chazelas
2
@ StéphaneChazelas Non, un seul canal ssh est utilisé pour stdin / stdout / stderr, et les données stderr sont transférées via des SSH_MSG_CHANNEL_EXTENDED_DATAmessages de type défini sur SSH_EXTENDED_DATA_STDERR. Vous pouvez lire le tout ici
mosvy