- Le “trop-long-j’ai-pas-lu” :
- Pourquoi ?
- Quelle forme pour ces services ?
- Quelques commentaires, bonus et perspectives pour rendre ça un peu sympa
Cet article n’a pas encore été relu, un peu d’indulgence :)
Le “trop-long-j’ai-pas-lu” :
Sur un système unix possédant netcat1 vous pouvez faire
nc bebou.netlib.re 2222
puis taper h
ou help
et avoir la liste des commandes disponibles.
Pourquoi ?
Les outils existants
J’avais développé quelques services que des proches ou moi trouvions utiles,
notamment de quoi conjuguer, récupérer des
synonymes, faire une recherche youtube et certainement d’autres
à l’avenir. Ces outils sont généralement des scripts shell de scraping assez
simples, utilisés en local.
Ils se substituent à des usages inadéquats de sites web ou de
recherches Google. Remplacer “ouvrir firefox -> taper ‘conjugaison manger’ ->
cliquer sur le premier lien qui a pas l’air de vouloir me montrer mille popups
de cookies ou de newsletters -> scroller à travers la page pour trouver la
ligne que je veux -> faire un copier-coller” par “ouvrir un terminal -> taper ‘c
manger je présent’ -> faire un copier-coller” ou tout autre utilisation
davantage automatisée semblait opportun. On économise du temps, de la
frustration, des aller-retour clavier/souris, du texte à taper, de la bande
passante, des cycles cpu, de la ram et on s’offre en prime une meilleure
interopérabilité avec le reste du système. Selon quelle est la source des
données on se permet également de travailler en mode hors-ligne.
Qu’est-ce que “service” convoque comme images dans ma tête ?
J’ai eu plusieurs conversations avec des membres de Katzele sur le sens que l’on met derrière le mot “service” numérique. Intuitivement, en me nourrissant davantage des exemples que je trouve autour de moi que d’une réflexion proprement théorique sur le sujet, je conçois le service comme le rapport majoritaire qui existe entre les personnes détenant du matériel et des compétences techniques et les autres, souhaitant faire appel à ce matériel et ces compétences là. La première personne, généralement une entreprise, une association, un état, va à l’aide d’ingénieur·es répondre au besoin en créant un “service”. Il y a mille façons de les créer, en impliquant plus ou moins les personnes qui sont en demande par exemple, ce qui donnera mille formes différentes de services mais j’ai le sentiment que dans l’ensemble quelques caractéristiques restent assez constantes. Premièrement le cadre technique dans lesquels ils s’inscrivent est, en 2024 et ce depuis des années, presque toujours le web. Deuxièmement ils semblent vouloir, parfois volontairement, tenir au à distance les personnes utilisateur·ices du fonctionnement interne du service. La première pose des questions de soutenabilité pour des raisons mille fois évoquées sur ce blog bien que l’on puisse déjà drastiquement optimiser cette variable en faisant du web “sobre” et pour peu que l’on y évolue avec un outillage radicalement différent de celui majoritaire. La seconde va à l’encontre de l’utopie technique rêvée à Katzele dans laquelle la ligne de démarcation entre celleux qui détiennent et savent manipuler les outils et celleux qui ne peuvent que les consommer ou en consommer leurs fruits serait floutée ou à minima moins épaisse, dans la limite de ce que la complexité des outils nous permet d’envisager.
C’est au gré de ces pensées que je n’ai durant ces dernières années jamais sérieusement entrepris d’interfacer mes outils via du web. Je privilégiais le fait de faire des ateliers, des formations, des démos et des articles associés pour distribuer le code localement sur les machines des personnes intéressées, propager les idées et inciter les personnes à faire leurs propres versions quand elles le peuvent.
Évidemment la viabilité de cette stratégie est certainement corrélée à la complexité des idées et du code en question. Plus l’outil est complexe plus il sera intéressant pour moi d’investir une unique fois du temps dans une interface web et dire aux personnes “va simplement sur telle url” plutôt que de dupliquer l’outil ou ses variantes n fois sur n machines de n copaines. De la même manière, la pertinence de la critique est corrélée à l’écart qu’il existe entre la simplicité du service rendu et la complexité de son implémentation technique. C’est pour cela que les services implémentés jusque là sont simples et requiert peu d’interactions avec l’utilisateur·ice. C’est aussi parce que je n’ai pas l’envie ni le temps de faire des choses compliquées.
Mais l’idée fera tout de même son bout de chemin
Et pourtant, pour de multiples raisons que je n’ai pas audité en profondeur, les personnes sont généralement bien plus enthousiasmées par l’idée d’avoir un service web qu’une version locale, indépendante d’un service tiers et à l’interface plus sobre. J’en prends pour exemple un atelier sur l’ensemble de systèmes que j’utilise pour consulter des vidéos youtube2 de plus de deux heures pendant lesquelles les échanges avaient été fréquents et de qualité. Plusieurs jours après au gré du hasard je retombe sur l’une des personnes ayant assisté à l’atelier qui partage son enthousiasme à une tierce personne autour de l’interface web sobre de recherche youtuube et uniquement de cet aspect là. Je n’ai pas de mal à comprendre que c’était peut-être pour elle le seul outil qui se substituait directement à l’interface youtube classique sans pour autant changer ses habitudes mais j’avoue m’être dit “oh un peu dommage 😞”.
Alors fort de cette expérience et pour éviter de tomber dans le sophisme de la solution parfaite3 je me suis dit que j’allais regarder ce qu’il était possible de faire entre l’interface web simple (formulaire html + cgi) et l’outil en ligne de commande en local.
Quelle forme pour ces services ?
L’objectif est de minimiser au moins :
- La dépendance à une connection internet
- nombre de dépendances
- l’instabilité de ces dépendances
- nombre de lignes de codes de l’outil et des dépendances
- nombre de personnes dans les projets de ces dépendances4
En permettant :
- l’interopérabilité de l’outil avec des interfaces textuelles et cli
Communiquer sur internet
Sur internet les machines communiquent entre elles via des socket.
Un moyen simple de créer des sockets est d’utiliser un outil fait pour qui
pourra les ouvrir ou écouter dessus. netcat-openbsd
en est un exemple.
Il a l’avantage d’être relativement simple, ne faisant “que” 2340 lignes de C,
dont le fichier principal a été édité par une vingtaine de personne depuis
25 ans. Sans faire de revue plus détaillée5 j’en conclu que c’est un logiciel
suffisamment stable fonctionnement et techniquement pour assurer une bonne
soutenabilité. La documentation est raisonnable et présente même quelques
exemples6. Par exemple, pour communiquer à un serveur web, nc permet de faire
passer tel quel un header HTTP et de recevoir la réponse :
$ nc bebou.netlib.re 80
GET / HTTP/1.1
Host: arthur.bebou.netlib.re
HTTP/1.1 200 OK
Server: nginx/1.22.1
Date: Mon, 10 Jun 2024 12:15:14 GMT
Content-Type: text/html
Content-Length: 5940
Last-Modified: Wed, 05 Jun 2024 17:41:29 GMT
Connection: keep-alive
ETag: "6660a349-1734"
Accept-Ranges: bytes
<!DOCTYPE html>
<html lang="fr">
<head>
<title>Unix et environnement ?</title>
<link rel=stylesheet href="/style.css" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="Un blog au sujet de la culture Unix redirigée vers la diminution de l'impact environnementale du numérique" />
<meta name="author" content="Arthur Pons" />
<link rel="icon" href="/favicon.png" />
[...]
Au passage on voit que c’est côté serveur web qu’est géré la partie
“virtual-hosting” qui, sur la base de la valeur du Host
renverra tel site
plutôt qu’un autre.
Cet exemple qui paraîtra trivial pour les personnes ayant commencé à faire de l’informatique il y a plus de vingt an et un peu magique pour la plupart des autres7 met quelque chose en lumière : Il n’y a rien de magique dans la façon dont les machines parlent entre elles via des protocoles. D’ailleurs n’importe quoi peut s’échanger entre deux sockets. Le manuel nous enseigne que (traduit de l’anglais) :
Il est assez aisé de construire un modèle client/serveur très simple avec nc. Dans une console écoutez sur un port donné. Par exemple :
$ nc -l 1234
nc attend une connection sur le port 1234. Dans une seconde console (ou une seconde machine) connectez vous à la machine et le port correspondant :
$ nc -N 127.0.0.1 1234
Il devrait dorénavant y avoir une connection établie entre les deux ports. Ce qui est tapé dans la seconde console sera concaténé à la première et vice-versa.
Admettons que l’on souhaite que ce que l’on tape dans la première console soit
traité d’une manière ou d’une autre et nous revienne. Plusieurs versions de
netcat proposent une option -e
qui passe les données reçues à un script tier
pour traitement. Ce n’est pas le cas de netcat-openbsd, il faudra donc trouver
un subterfuge. Ca tombe bien, il nous est donné dans le manuel :
Il n’existe pas d’option
-c
ou-e
dans ce netcat mais vous pouvez toujours exécuter une commande après qu’une connection ait été établie en redirigeant des descripteurs de fichiers. Attention, ouvrir un port et laisser n’importe qui exécuter des commandes arbitraires sur votre serveur est DANGEREUX. Si vous avez réellement besoin de le faire voici un exemple :Côté serveur :
$ rm -f /tmp/f; mkfifo /tmp/f $ cat /tmp/f | /bin/sh -i 2>&1 | nc -l 127.0.0.1 1234 > /tmp/f
Côté client :
$ nc host.example.com 1234 $ (shell prompt from host.example.com)
L’example donne carrément un shell interactif à toute personne qui se connecte. C’est effectivement très dangeureux mais en plus ce n’est pas ce que l’on cherche à faire.
Exécuter des commandes à distance
Le serveur est déjà équipé des commandes conjuguer
, synonyme
etc. On
cherche, en se basant sur ce que l’on a appris précédemment, à implémenter le
minimum nécessaire sur le serveur pour rendre ces commandes disponibles à
distance de manière non authentifiée et sécurisée. L’idée est que la
construction de services sous cette forme ne nécessite presque rien de plus que
de faire fonctionner les commandes associées en local contrairement à une appli
web ou même un cgi.
Si l’on reprend l’exemple
cat /tmp/f | /bin/sh -i 2>&1 | nc -l 127.0.0.1 1234 > /tmp/f
on peut modifier le filtre /bin/sh -i 2>&1
par autre chose. Si l’on veut un
service qui modifie toutes les armes
par des fleurs
on pourra écrire :
$ cat /tmp/f | sed 's/armes/fleurs/g' | nc -l 127.0.0.1 1234 > /tmp/f[^8]
Ce qui donnera côté client
$ nc bebou.netlib.re 1234
Ils prirent les armes
Ah, ça ne fonctionne pas. En effet sed, comme pleins d’autres commandes pour
des raisons de performance, ne traite pas les lignes une à une mais en stocke
quelques une avant de les faire passer à la moulinette. Il constitue un
“buffer”, on dit parfois en franglais qu’il “buffer” son entrée. Pour résoudre
ce problème deux solutions. Soit l’outil intègre une option pour ne pas
bufferiser (--unbuffered
pour GNU sed) soit on utilise la commande stdbuf
qui dira à la commande qui lui est passée en argument qu’il ne faut rien
bufferiser :
$ cat /tmp/f | stdbuf -o0 sed 's/armes/fleurs/g' | nc -l 127.0.0.1 1234& > /tmp/f
$ nc bebou.netlib.re 1234
Ils prirent les armes
Ils prirent les fleurs
Super ! Nous en savons plus sur comment traiter les données qui arrivent dans le socket du serveur mais nous n’avons toujours pas créé le système que nous voulions. Pour ce faire reprenons l’exemple qui était donné dans le manuel en retirant l’aspect interactif et en ajoutant stdbuf :
$ cat /tmp/f | stdbuf -o0 sh | nc -l 127.0.0.1 1234& > /tmp/f
$ nc bebou.netlib.re 1234
date
lun. 10 juin 2024 16:04:52 CEST
Avec un peu de securité
Sans pour autant récupérer un shell, nous pouvons exécuter des commandes.
Pour des raisons évidentes de sécurité nous voulons restreindre ces commandes à
celles que nous mettons à disposition, par exemple conjuguer
. Pour cela nous
pouvons utiliser grep pour filtrer les entrées avant de les passer à sh
:
$ cat /tmp/f | stdbuf -o0 grep -E '^conjuguer' | stdbuf -o0 sh | nc -l 127.0.0.1 1234& > /tmp/f
$ nc bebou.netlib.re 1234
ls
conjuguer manger
Indicatif Présent je mange
Indicatif Présent tu manges
Indicatif Présent il mange
Indicatif Présent nous mangeons
Indicatif Présent vous mangez
C’est mieux, on ne peut plus exécuter d’autres commandes que conjuguer. A moins que…
$ nc bebou.netlib.re 1234
conjuguer;date
lun. 10 juin 2024 16:05:17 CEST
A mince. Le grep n’a pas retiré la ligne puisqu’elle commence bien par
“conjuguer”. Le shell a ensuite tenté d’exécuter conjuguer
sans argument (en
retournant certainement une erreur) puis a compris le point virgule comme
délimitant une nouvelle commande, en l’occurrence date
, qu’il a exécuté.
Le sh
du serveur sur lequel tout cela tourne est dash. J’ai donc consulté sont
manuel pour connaître les caractères spéciaux :
Lexical Structure The shell reads input in terms of lines from a file and breaks it up into words at whitespace (blanks and tabs), and at certain sequences of characters that are special to the shell called “operators”. There are two types of operators: control operators and redirection operators (their meaning is discussed later). Following is a list of operators:
Control operators: & && ( ) ; ;; | || <newline> Redirection operators: < > >| << >> <& >& <<- <>
et
Reserved Words Reserved words are words that have special meaning to the shell and are recognized at the beginning of a line and after a control operator. The following are reserved words:
! elif fi while case else for then { } do done until if esac
Une manière un peu bourrine mais je pense, du moins j’espère, assez fiable est de supprimer toutes les occurrences de ces mots et caractères. Nous aurions donc à minima pour les opérateurs et autres caractères importants :
$ cat /tmp/f |
stdbuf -o0 tr -d '$`&(;|<>' |
stdbuf -o0 grep -E '^conjuguer' |
stdbuf -o0 sh |
nc -l 127.0.0.1 1234& > /tmp/f
Je n’ai pas la certitude que cela empêche toute commande dangereuse d’arriver à
la ligne sh
. La bonne approche serait certainement de créer une commande
spéciale qui parse son entrée et sur la base de certains critères exécute
certaines commandes générées par nos soins plutôt que de tenter d’assainir les
commandes entrées par l’utilisateur·ice. En attendant le service étant utilisé
par très peu de personnes et la survie du serveur n’étant pas absolument
primordiale8 j’ai décidé que c’était suffisant. Je vais soumettre l’exercice
à des personnes motivées pour tenter de trouver des failles.
Finalement on pourra y ajouter des logs à coup de tee
:
$ cat /tmp/f |
stdbuf -o0 tee prefilter.log |
stdbuf -o0 tr -d '$`&(;|<>' |
stdbuf -o0 grep -E '^conjuguer' |
stdbuf -o0 tee postfilter.log |
stdbuf -o0 sh |
nc -l 127.0.0.1 1234& > /tmp/f
On peut ajouter des commandes disponibles en les installant sur la machine et en
ajoutant des alternatives à la regex du grep : ^(conjuguer|synonyme)
. On peut
également maintenir un fichier des commandes autorisées, une commande par ligne,
et faire :
stdbuf -o0 grep -wf authorized
Quelques commentaires, bonus et perspectives pour rendre ça un peu sympa
socat
Dans les faits et pour plusieurs raisons que j’ai déjà oublié j’utilise côté
serveur socat
. Les commandes vues précédemment sont les mêmes, à la suite d’un
cat
, mais existent dans un script séparé qui est renseigné dans la commande
socat qui lance le serveur :
socat -d -d -lf /var/log/socatlog tcp-listen:2222,fork exec:script
TODO : éclaircir pourquoi j’ai finalement choisi socat. En tout cas cet outil score probablement un peu moins bien dans les critères vus ici (32k lignes par ex) mais est plus polyvalent.
Une commande help
Une commande help
est une bonne idée ne serait-ce que pour lister les
commandes dispos. Pour qu’elle s’affiche au lancement d’une session on peut
ajouter un
echo "help pour la liste des commandes"
au début du script. Cela à pour inconvénient d’ajouter cette ligne dans les résultats de commandes lancées non interactivement. Exemple :
$ nc bebou.netlib.re 2222
help pour la liste des commandes
help
h[elp]
cette aide
s[ynonyme] mot
la liste des synonymes de "mot"
[...]
s table
tableau 14
répertoire 14
pupitre 13
liste 11
catalogue 11
[...]
mais si l’on passe la commande directement dans stdin (-N
dira à nc d’envoyer
un caractère de fin de fichier à la fin afin de terminer la connexion et
récupérer la main) :
$ echo "s table" | nc -N bebou.netlib.re 2222
help ou h pour obtenir de l'aide
tableau 14
répertoire 14
pupitre 13
liste 11
catalogue 11
[...]
$
On voit que l’on se traine l’aide. Il y a peut-être un moyen d’y remédier côté
serveur mais en attendant je script mes usages non interactifs de façon à
supprimer la première ligne de résultat. Par exemple, le script zsh que
j’utilise pour la conjugaison (noter le sed '1d'
qui supprime la première
ligne) :
read ?"Verbe (et filtres) : " verbe
echo "conjuguer -c $verbe" | nc -N bebou.netlib.re 2222 | sed '1d'
read -k1
Des scripts pour un peu plus d’interactivité
En jouant avec le service de synonyme je me suis rendu compte qu’il était assez naturel de vouloir sélectionner l’un des synonymes proposer pour voir ses synonymes et ainsi de suite, à la manière de la navigation dans un document avec des liens hypertexte. Il aurait été dommage de perdre totalement cette fonctionnalité là. Pour cela j’ai opté de ne pas complexifier la commande côté serveur mais de l’implémenter côté client :
read ?"Mot : " mot
while [ -n "$mot" ]
do
oldmot=$mot
echo $oldmot | cut -f1
mot=$(echo "synonyme $mot" | nc -N bebou.netlib.re 2222 | sed '1d' | fzy)
done
read -k1
Ce script donne ce genre de session de navigation :
Mot : table
table
>
tableau 14
répertoire 14
> pupitre 13
liste 11
Puis après sélection de pupitre :
Mot : table
table
pupitre
>
> lutrin 50
console 20
chaire 20
table 13
bureau 4
Une fois la navigation terminée on peut en sortir en appuyant sur “echap”. Pour les personnes initié·es voici mon menu zenu correspondant :
_conjuger
_synonyme
## pre
## react
;; (c)
read ?"Verbe (et filtres) : " verbe
echo "conjuguer -c $verbe" | nc -N bebou.netlib.re 2222 | sed '1d'
read -k1
;; (s)
read ?"Mot : " mot
while [ -n "$mot" ]
do
oldmot=$mot
echo $oldmot | cut -f1
mot=$(echo "synonyme $mot" | nc -N bebou.netlib.re 2222 | sed '1d' | fzy)
done
read -k1
Favoriser le local
Malgré l’existence de ces services que je compte bien continuer à maintenir je maintiens ma volonté que les personnes autour de moi les installent en local. Pour favoriser cela je devrais :
- En faire des paquets debian
- Mieux les documenter (c’est un peu le foutoir là)
- Publier des menus zenu directement dans les dépôts git et/ou dans les paquets debian
- Intégrer quelque chose au service qui permette à minima de visibiliser que c’est possible ?
-
sur debian faire
sudo apt install netcat-openbsd
si vous ne l’avez pas déjà. En réalité tout outil permettant de se connecter à un port sur une machine distante.socat
fonctionne,telnet
aussi etc. ↩ -
article plus très à jour par rapport à ce que je fais aujourd’hui mais passons ↩
-
surtout quand on parle de sobriété dans le numérique, lol. ↩
-
en écrivant ces lignes je me rends compte qu’ils n’ont rien d’évident. Notamment celle-ci. L’intuition première est que peu de personne = projet simple et facile à maintenir, pleins de personnes = projet complexe et difficile à maintenir. Je pense que cette intuition n’est pas totalement fausse mais il faut envisager d’autres paramètres. Une personne peut, si elle est très productive, construire des logiciels qui ne sont raisonnablement maintenu par la suite que par une équipe de personne (eg Fabrice Bellard avec qemu et ffmpeg). Il est également possible qu’un outil soit créé et maintenu par une seule personne sans être pour autant documenté ou partagée. A moins que cet outil soit très simple sa maintenance posera problème. D’une certaine manière l’existence d’une équipe, et donc de plus de monde, autour d’un outil peut offrir de la résilience en cas de perte d’une personne. Ça n’est probablement qu’en combinant les variables du nombre de personnes actives pour maintenir l’outil, le nombre de personnes connaissant bien son fonctionnement et la documentation associée que l’on parviendrait à dégager un avis pertinent sur le rapport entre personnes impliquées et soutenabilité. Il faudrait également y inclure des variables économiques telles que la pérennité des financements du projet s’ils existent, la popularité des technologies utilisées etc. Bref c’est bien plus compliqué que ce que ces cinq critères de base laissent penser. ↩
-
un jour peut-être ↩
-
oui oui, si vous avez l’habitude des manuels des outils GNU ça paraît fou. ↩
-
je dois avouer que je n’étais pas très bon élève lors de mes études mais je n’ai pas souvenir que l’on m’ait enseigné des protocoles aussi fondamentaux qu’HTTP et SMTP en me faisant expérimenter de la sorte. Tout conservait une aura un peu mystérieuse. On m’a pourtant enseigné TCP dans les détails. ↩
-
il ne serait pas très long de le réinstaller tel quel d’autant plus que tout est backupé ↩