Show me the codez!
J'ai envoyé sur svn.fperrin.net/scripts quelques scripts, principalement pour mon propre usage. Toujours pour mon propre usage, voici un peu de documentation sur le fonctionnement de ces scripts.
1. backup.pl
1.1. Présentation
Un script pour organiser mes sauvegardes. Il utilise la technique
décrite dans Easy Automated Snapshot-Style Backups with Linux and
Rsync. L'idée est d'utiliser rsync
pour faire le gros du travail de
copie. Cependant, on aimerait quand même garder plusieurs générations
de sauvegardes. Pour éviter de multiplier l'espace nécessaire par
autant de générations conservées, on utilise des liens durs pour ne
conserver à chaque fois que les fichiers modifiés.
La première sauvegarde se fait avec un cp -a
tout simple. Pour faire
les sauvegardes suivantes, la dernière sauvegarde en date est copiée
en utilisant cp -l
, puis mise à jour par rapport aux données
courante en utilisant rsync
. Les fichiers qui n'ont pas été modifiés
entre la dernière sauvegarde et maintenant ne seront pas touchés par
rsync
.
Lorsqu'on a beaucoup de gros fichiers qui ne sont pas modifiés (films, musique et photos dans une moindre mesure), cette technique est particulièrement intéressante. Dans mon cas, la première sauvegarde fait 88G ; les sauvegardes suivantes font une dizaine de giga-octets chacune.
Il y a quelques différence entre la méthode décrite dans l'article
plus haut et ma mise en œuvre. Elles viennent du fait que j'ai écrit
ce script pour faire des backups de mon portable vers un disque
externe : mon portable n'est pas en route en permanence, et le script
ne tourne pas en tant que root
. En conséquent, le script n'est pas
prévu pour être mis dans un cron ; il ne fait pas de rotation des
sauvegardes ; il ne remonte pas en lecture seule les sauvegardes quand
le travail est fini ; le bug sur les permissions et le propriétaire
qui est mentionné n'est pas un problème pour moi. Il y a vaguement un
chmod -w
qui est fait sur la cible, mais cela reste symbolique.
Par contre, ce script va construire un chemin de destination différent par rapport à ce qui est proposé dans l'article, à partir du nom d'hôte de la machine, du chemin source, et de la date.
1.2. Utilisation
Synopsis : backup.pl /home/fred /media/extern/backup
.
Ceci copiera /home/fred
dans
/media/extern/backup/$(hostname)/home_fred/$date
, en utilisant la
dernière sauvegarde en date si elle existe. Le script fera un cp -a
simple si l'on lance le script pour la première fois.
Le premier chemin doit être un chemin absolu, afin de pouvoir
construire le chemin de destination sans ambiguïté. La racine des
sauvegardes (dans l'exemple au dessus, /media/extern/backup
) doit
exister, les autres dossiers seront créés au besoin.
2. rmdups.pl
Il arrive que le même fichier se retrouve au même endroit, après quelques copies de fichiers. En utilisant des liens durs, on peut ne conserver qu'une seule copie, sous plusieurs noms. Ce script va parcourir les dossiers donnés en arguments. Si deux fichiers différents ont le même contenu, le deuxième est supprimé puis recréé comme un lien vers le premier.
Pour savoir si deux fichiers ont le même contenu, on calcule le MD5 de chacun.
Ce script fait plusieurs suppositions :
- Deux fichiers ayant le même MD5 sont identiques. Lorsqu'il s'agit de fichiers personnels, c'est probablement excessif ; s'il s'agit d'un dossier accessible par plusieurs personnes, la perte de données est à une collision MD5 de distance. D'un autre côté, si une personne a accès en écriture à vos dossiers personnels, elle a sûrement des moyens plus directs pour effacer des fichiers.
- Le couple (
$st->dev, $st->ino
) identifie de façon unique un i-node. Ceci n'est pas forcément vrai sur une partition NFS.
Le script maintient deux tableaux. Le premier, %file
, est une
association (MD5 déjà vu -> inode du fichier). Le deuxième,
%seen
, conserve la liste des i-nodes déjà vus.
La fonction action
qui est passée à File::Find::find
vérifie
l'invariant de sortie suivant :
Parmi les fichiers déjà vus, tous les fichiers qui ont le même MD5 sont des liens vers le même i-node. Ces MD5 sont dans le tableau
%file
. L'ensemble des clefs du tableau%seen
est égal à l'ensemble des valeurs du tableau%file
.
Cet invariant est clairement vrai lorsqu'on atteint le premier fichier.
Lorsqu'on atteint un fichier quelconque, supposons l'invariant vrai.
Montrons qu'il est encore vrai lorsque action
retourne.
Si le fichier fait référence à un i-node qui est dans %seen
, alors
il a le même MD5 qu'un fichier déjà vu. Étant donnée la deuxième
supposition, c'est déjà un lien vers ce fichier. De plus, ce MD5 est
bien déjà dans le tableau %file
. Aucun des deux tableaux n'est
modifié. L'invariant est donc toujours vrai.
Sinon : si le fichier a un MD5 qui n'est pas dans le tableau %file
,
alors aucun des fichiers déjà vu n'a le même MD5, d'après l'invariant
appliqué à l'appel précédent. Son MD5 est ajouté au tableau des MD5,
son i-node est ajouté au tableau %seen
. L'invariant de sortie est
donc encore vérifié.
Dernier cas, l'i-node du fichier n'est pas dans %seen
mais son MD5
est dans le tableau %file
. On remplace ce fichier par un lien dur
vers l'i-node qui est dans %file
. Ni le MD5 ni le numéro d'i-node ne
sont ajoutés dans les deux tableaux, l'invariant est toujours valide.
Ainsi, à la fin du programme, les fichiers qui ont le même MD5 ont tous été remplacés par un lien vers le même i-node.
3. ssh.pl
3.1. Fonctionnement
Un petit script pour voler des mots de passe SSH. Appelé sans
argument, le script se place dans $HOME/.ssh
, ajoute un alias vers
lui-même dans les fichiers .rc
de l'utilisateur pour les shells les
plus courants.
Appelé avec arguments, il invoque /usr/bin/ssh
tout en écoutant le
mot de passe entré par l'utilisateur et l'envoie à travers le réseau
chez moi.
Le but de cet exercice était de voir les mécanismes utilisés par
ssh(1)
pour se protéger. Réussir à voler le mot de passe a été
étonnamment difficile. SSH réouvre lui-même /dev/tty
pour afficher
l'invite Password:
et lire la réponse. Il est donc impossible de lui
fournir sur son stdin
le mot de passe qu'on aurait demandé
directement à partir du code Perl. Il n'est par ailleurs pas possible
d'écrire sur le /dev/tty
d'un autre processus (deuxième essai dans
le script, en commentaire).
La solution est donc de créer une paire de terminaux en utilisant
ptmx(4)
. On donne la moitié esclave à ssh(1)
et nous gardons la
moitié maître de notre côté. De cette façon, nous contrôlons le
terminal ouvert par ssh(1)
, et en particulier nous pouvons écouter
les mots de passe qui circulent ainsi.
La mise en œuvre de cette technique en Perl, en s'inspirant de
expect(1)
, a échoué (les terminaux ainsi obtenus peuvent être
ouverts en écriture d'après -w
, mais un sysopen ... O_RDWR
échoue
avec errno == EIO
, et je ne sais pas trop comment déboguer cela).
J'ai donc utilisé openpty(3)
dans un morceau de code C, qui est
compilé et installé comme alias pour ssh
lorsque ssh.pl
est appelé
sans argument. openpty(3)
fait ce travail d'ouvrir ptmx(4)
, de
faire la tambouille nécessaire pour préparer les terminaux et de les
ouvrir en lecture/écriture.
Le code que j'ai utilisé vient assez largement de Florent Bondoux, pour un programme permettant de colorer la sortie d'erreur d'un autre logiciel.
Pour une raison que je ne m'explique pas trop, il est également
nécessaire de rediriger le stdout
de ssh(1)
vers la paire de
terminaux que l'on a ainsi créé. J'aurais cru que laisser le stdout
du programme initial dans le fils créé aurait simplement marché, mais
ssh(1)
refuse de fonctionner ainsi.
3.2. Utilisation
Télécharger le script (éventuellement en changeant l'adresse IP de
destination) et l'exécuter sur la machine de la victime. L'un des buts
était de voir en combien de temps on pouvait installer un tel piège à
ssh
…
Du point de vue de la victime, l'utilisation de ssh.pl
doit être
complètement transparente.
3.3. À faire
Améliorations possibles :
- mieux cacher le binaire généré dans un recoin de
$HOME
; - être plus fourbe, et voler la commande
ssh
de plusieurs façons : en ajoutant un chemin dans$PATH
, en définissant une fonction, et peut-être faire cela en plusieurs endroit (devrait particulièrement efficace avecbash(1)
qui lit deux ou trois fichiers au démarrage, plus un.bash_logout
qui pourrait réinstaller le code voulu à chaque fermeture de session:-)
) ; - comprendre le problème avec la version Perl, et se débarrasser de la dépendance envers un compilateur ;
- dans la partie en C, le code compilé dans le cas où
USEUDP
est défini ne fonctionne pas.
4. ariactl.pl
Déplacé vers une page particulière, voir : une interface web pour aria2, écrite en Perl.