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 avec bash(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.

Auteur : Frédéric Perrin

Date : mardi 24 août 2010, modifié le jeudi 23 décembre 2010

Sauf mention contraire, les textes de ce site sont sous licence Creative Common BY-SA.

Ce site est produit avec des logiciels libres 100% élevés au grain et en plein air.