Celui d'OpenBSD, je précise. C'est qu'il m'en a donné à baver, ce sale bestiaux. Sécurité par l'obscurité bonjour, si je ne peux point relater la malheureuse aventure arrivée, je peux cependant noter toutes les astuces diaboliques qui sont contenus dans si peu de code, et ce qui m'amena à m'y confronter. Code que par ailleurs je n'ai toujours pas entièrement compris, et c'est bien la première fois que ça m'arrive, j'ai même déjà dû maîtriser du code indien, c'est dire (et c'est rusé, un Indien). Tout d'abord, le mode de fonctionnement de passwd : cette commande permet de modifier le mot de passe utilisateur. Ou pas tout à fait. En réalité, l'opération se déroule en deux temps : en premier lieu, changement de /etc/passwd, puis de /etc/master.passwd par le programme passwd lui-même ; ensuite, changement des bases de données /etc/pwd.db et /etc/spwd.db par pwd_mkdb. Ce sont ces bases qui sont utilisées par login pour authentifier les utilisateurs. En toute rigueur, si passwd détecte que le mot de passe est local (il gère aussi les bases kerberos et le système YP), pwd_mkdb est lancé, mais même dans la documentation il n'est pas bien clair qu'il ne faille ajouter l'option "-l" :

     -l      Causes the password to be updated only in the local password
             file.  When changing only the local password, pwd_mkdb(8) is used
             to update the password databases.

Il me sembla avoir expérimenté une fois où la base de donnée ne fut pas mise à jour, c'est même cette fois-là que je découvris l'existence de pwd_mkdb (ce n'est pas très naturel, pour le linuxien). Ce dernier a des options d'appel totalement différentes du premier : on doit notamment spécifier quel est le fichier master.passwd à utiliser, et quel répertoire on doit utiliser. Ainsi, on peut reconstruire une base d'utilisateurs locaux depuis un ramdisk, par exemple (dans le makefile, on parle d'exécution depuis... une disquette). Cette précision a son importance : pwd_mkdb est lié de manière statique, en l'occurrence avec la bibliothèque libutil, non celle du système que l'on compile, mais manifestement celle du système sur lequel on compile (la différence est de taille, surtout lorsqu'on développe dedans) ; de plus, lors d'une modification de la bibliothèque libutil de son arbre de développement, une recompilation depuis la racine n'a pas pour conséquence de gérer la dépendance et de recompiler pwd_mkdb (donc la lier avec la bibliothèque modifiée), ce qui peut avoir de fâcheuses conséquences (tout autant que de sérieuses prises de tête lors d'un développement intensif).

Il faut dire que libutil concentre beaucoup de choses hétérogènes, dont passwd.o, hérité du fichier C du même nom. Dedans se cachent des fonctions que l'on suppose communes à passwd autant qu'à pwd_mkdb ; le premier est ainsi lié dynamiquement avec la bibliothèque -- et en utilise presque toutes ses fonctions (il me semble bien qu'au moins une n'est utilisée par personne). Mais l'on aurait tort de penser cela : la fonction pw_mkdb (remarquez qu'il manque le "d", assez pour devenir fou lorsque l'on fait des recherches) contenue dans cette bibliothèque libutil est ainsi responsable du fork qui appelle pwd_mkdb. A vrai dire, peu de fonctions sont réellement partagées, et elles ne font guère plus de quelques lignes.

Si pwd_mkdb a été prévu dès l'origine, avec son option "-d" pour pouvoir travailler dans des répertoires différents de /etc (ce qui est fort pratique en embarqué pour générer une base à partir des fichiers de mots de passe texte construits à la main, la base étant en binaire), cela est presque de même pour passwd. "Presque" car voilà : il existe une fonction de changement des répertoires à chaud, utilisée par pwd_mkdb, qui fait que lorsque l'on cherche master.passwd dans /toto, on part de la macro /etc/master.passwd pour arriver à /toto/master.passwd. Ces macros sont nombreuses : certaines sont en dur, d'autres dans un fichier d'include ; là encore, problème du build d'OpenBSD : les includes considérés ne sont pas ceux de l'arbre de développement, mais ceux du système d'accueil de la compilation. Autre problème : passwd fait appel dans son code assez souvent aux fonctions de relocalisation, mais il n'y a pas d'option existante comme sur pwd_mkdb : ceci est fâcheux, il faut donc entrer dans le code, et se battre avec des valeurs en dur, qui ont tendance à changer lors de la compilation (réécriture des répertoires rapidement exposés juste avant : il en existe plusieurs mécanismes).

Tout ceci est déjà bien complexe à gérer, mais c'était sans compter sur l'intervention d'un dernier farceur : ptmp. De base, ce fichier est prévu pour être dans /etc. Mais il peut être relocalisé en fonction du code de passwd, ou plutôt celui de la libutil, et ce à chaud, contrairement à ce que laisser penser sa macro (qui est bien "/etc/ptmp" : la changer n'a des impacts que limités). Lorsque /etc n'est pas accessible en écriture (cas courant dans la problématique embarqué), voilà donc ce qui se passe, de base (extrait de man passwd) :

DIAGNOSTICS
     Attempting lock password file, please wait or press ^C to abort

     The password file is currently locked by another process; passwd will
     keep trying to lock the password file until it succeeds or you hit the
     interrupt character (control-C by default).  If passwd is interrupted
     while trying to gain the lock the password changed will be lost.

     If the process holding the lock was prematurely terminated the lock file
     may be stale and passwd will wait forever trying to lock the password
     file.  To determine whether a live process is actually holding the lock,
     the admin may run the following:

           $ fstat /etc/ptmp

     If no process is listed, it is safe to remove the /etc/ptmp file to clear
     the error.

Et en effet, cette erreur apparaît dès que l'on a son / en lecture seule, ce que j'impose toujours dans des systèmes embarqués (par paranoïa naturelle). Malheur à celui qui eu son passwd testé avec un système de fichier monté en lecture-écriture pour cause de développement en cours (en l'occurrence, moi, on l'aura compris), avec de fait un bug non détecté. Et à vrai dire, avant de comprendre tout cela, il fallut pas mal de temps (dont deux remontées du même bug ou presque, avant de se rendre compte de l'erreur dans le test), et bien tâtonner : car il ne m'était pas venu à l'idée qu'un fichier servant de mutex puisse non pas être mis dans /var/run, comme dans tout système civilisé, mais dans /etc/ ; sans compter le fait que l'opération n'est pas atomique, mais on exclura l'idée de deux utilisateurs assez pervers pour lancer deux passwd quasiment en même temps (pourtant ipcs confirme l'existence d'un vrai système de sémaphores). Il y avait en réalité un autre mécanisme, caché : cela aurait été trop simple, sinon. En l'occurrence, passwd lançait tout seul pwd_mkdb, sans que l'option "-l" ne soit spécifié. Grâce à un système de liens, cela n'est pas bien grave, mais voilà : pwd_mkdb ne se lance qu'à condition de pouvoir ouvrir... /etc/ptmp. Car en réalité, passwd appelle le programme de construction de base à l'aide d'option de relocalisation "-d", et l'occurrence une mauvaise, en ce basant sur un autre système, qui au final donnait un "/etc" fatal. Croyant tout d'abord que c'était l'appel manuel de pwd_mkdb (on n'est jamais trop paranoïaque, et puis l'option "-l" n'est pas bien claire) qui échouait, et ayant découvert que la bibliothèque liée n'était pas la bonne, voilà comment on croît résoudre un bug alors qu'en fait on s'attaque à autre chose, et un mélange avec une partition en read-write pour cause de besoin de développement (au hasard, remplacer passwd, libutil.so et pwd_mkdb), puis oubliée dans cet état, amène à un drame (croire que c'est bon, puisque l'on a plus l'erreur d'avant, alors que ça ne l'est pas, puisqu'un bug en cache un autre).

"Errare humanum est, perseverare diabolicum"   (oui mais c'est passwd qui est diabolique, moi j'exorcise, j'le jure !)

J'eus donc l'idée, lors du développement, de retirer le fork de pwd_mkdb appelé depuis le code même de passwd, puisque l'exécutant de toute façon juste ensuite moi-même : le fichier de lock ptmp n'était alors plus supprimé ! De fait, deux exécutions d'affilée ne donnent pas la même erreur, et la seconde exécution donne lieu à une attente sur le fichier de lock présent alors qu'il ne devrait pas (plus) l'être (cf le man ci-dessus ; du coup, on peut croire être retombé dans la première configuration d'erreur, à tort). Ou comment brouiller encore plus les pistes. Et c'est ainsi que je perdis définitivement mon latin... Et pour finir : une erreur du pwd_mkdb forké, même inutilement, entraîne l'abandon de la modification du master.passwd (ce qui n'a pourtant pas grand rapport...), encore un comportement erratique de passwd (la fonction pw_error peut être ainsi appelé si l'édition du fichier échoue : c'est-à-dire si un appel via fork par sh de /bin/vi échoue... Tout dans la simplicité), de telle sorte que l'appel manuel "pour plus de sécurité" de pwd_mkdb travaillait sur un fichier non modifié, et regénérait donc la même base...

Que l'on se rassure, comme dans tous les contes, l'histoire se termine bien : une valeur en dur de répertoire inscriptible pour notre fichier de lock signalé à la main en des endroits stratégiques, et notamment dans le fork de la lib (oui, en dur...) résolut les problèmes. Ce n'est pas beau (euphémisme), mais ça marche. Et à vrai dire, dans une horreur diabolique pareille, cela ne se voit presque pas.