Partage de droits et de fichiers

Introduction

Dans ce TP on cherche à mettre en place un répertoire partagé dans lequel plusieurs utilisateurs peuvent lire et modifier des fichiers. Pour des raisons de sécurité, on souhaite préciser pour chaque fichier la liste des utilisateurs qui ont le droit de le lire et / ou de le modifier.

La classification des droits de fichier en utilisateur / groupe / autre adoptée par les Unix traditionnels n'est malheureusement pas suffisamment flexible pour cela. En particulier, la création de groupes supplémentaires (ou bien d'utilisateurs fictifs partagés par un groupe de personnes) nécessite d'être root, ce qui n'est pas notre cas.

Nous proposons ici une méthode alternative. Un utilisateur (non root) particulier joue le rôle du Maître qui administre un répertoire. Le répertoire et les fichiers sont à son nom, et lui seul a les droits en lecture et en écriture (droits 700 sur le répertoire et 600 sur les fichiers). Le Maître met alors à disposition des autres utilisateurs un utilitaire nommé op qui leur permet de copier des fichiers du répertoire du Maître vers un répertoire leur appartenant (ou dans l'autre sens en cas de droit en écriture). Le programme op sera setuid c'est à dire que, quel que soit l'utilisateur qui le lance, le programme tournera avec les droits du Maître.

Le but de ce TP est de programmer op en C.

Exercices

Programme setuid

1) Faites un programme op setuid (chmod +s op) qui prend en argument une commande get, put ou del suivie d'un nom de fichier:

Dans un premier temps, tous les utilisateurs peuvent faire get, put et del sur tous les fichiers du répertoire Maître. Par contre, op ne doit pas permettre à un utilisateur de modifier un fichier du Maître en dehors du répertoire dédié. (Attention donc aux / dans les noms de fichiers, aux liens symboliques, etc.) Faites tester votre programme par vos petits camarades.

Attention!
Un programme setuid bogué à votre nom présente de gros risques de sécurité.
Afin d'éviter tout problème, pensez à tester votre programme avant de faire le chmod +s et à effacer de votre compte tous les fichiers setuid à la fin du TP.

Correction) op1.c.

La solution proposée n'est pas satisfaisante car les opérations sur le répertoire local sont effectuées avec l'identité du Maître. En particulier:

2) Corrigez ces problèmes en vous assurant que les opérations dans le répertoire Maître sont effectuées avec l'identité du Maître, et les opérations dans le répertoire courant avec l'identité de l'utilisateur. On utilisera pour cela les fonctions setuid(2) et getuid(2), ainsi que setgid(2) et getgid(2).

Correction) op2.c.

Fichier de configuration

On suppose maintenant l'existence dans le répertoire Maître d'un fichier de configuration user.conf pour chaque utilisateur de login user. Celui-ci contient, sur chaque ligne, un nom de fichier, précédé d'une lettre R ou W indiquant que l'utilisateur a le droit de lecture ou de lecture / écriture / création / destruction sur le fichier.

3) Modifiez op pour qu'il consulte le fichier de configuration et vérifie que l'utilisateur a bien le droit d'effectuer l'opération demandée. On rappelle que getpwuid(3) permet d'obtenir de nombreuses informations concernant un utilisateur (son login, par exemple) à partir de son uid. Vous pouvez, si vous le souhaitez, gérer les caractères * et ? dans les fichiers de configurations en vous aidant de la fonction fnmatch(3).

Pour des raisons de sécurité, le fichier user.conf ne doit pas être modifiable avec put. De plus, il ne doit être lisible avec get que par user lui-même (ou pas du tout).

Correction) op3.c.

Verrous

Que se passe-t-il si plusieurs utilisateurs accèdent en même temps à un même fichier du répertoire Maître?

4) Modifiez op pour assurer l'atomicité de chaque opération get, put ou del. On pourra pour cela poser un verrou coopératif sur le fichier du répertoire Maître en cours de lecture ou de modification grâce à la fonction lockf(3). Attention aux race-conditions!

Correction) op4.c.

Garantir l'atomicité d'un get ou d'un put n'est souvent pas suffisante. Un utilisateur peut souhaiter verrouiller un fichier Maître pendant la durée de plusieurs opérations (par exemple, le temps d'obtenir une copie d'un fichier par get, de la modifier puis d'envoyer la nouvelle version par put).

5) Ajoutez à op des fonctions lock et unlock qui verrouillent un fichier Maître spécifié par l'utilisateur (à condition qu'il ait le droit d'écriture). Tant que le verrou existe, aucun autre utilisateur ne peut effectuer d'opération sur le fichier. Ceci ne peut pas se programmer avec lockf(3) (pourquoi?). On propose de programmer un verrou sur un fichier file en créant un fichier nommé file.lock contenant l'uid de l'utilisateur qui l'a verrouillé. Avant chaque opération sur file, op doit donc vérifier l'existence et le contenu de ce fichier.

Correction) op5.c.

Extensions possibles

Voici quelques idées d'extensions (indépendantes les unes des autres).

a) Ajoutez une commande ls qui liste les fichiers existants du répertoire Maître sur lesquels l'utilisateur a les droits de lecture.

b) Ajoutez des nouveaux types de droits. Par exemple, le droit d'ajouter des données à la fin d'un fichier mais pas de modifier celles qui existent.

c) Ajoutez la génération d'un fichier de log chez le Maître. À chaque appel, op devra ajouter une ligne indiquant quel utilisateur a modifié quel fichier et à quelle date et heure. Le fichier doit également (surtout!) consigner les tentatives qui échouent pour cause de droits insuffisants. Attention aux problèmes d'atomicité lors de l'accès au log!

d) Gérez les sous-répertoires dans le répertoire du Maître.

e) Créez un utilitaire qui affiche, pour chaque fichier, la liste des utilisateurs qui peuvent y accéder. Cet outil devra donc lire tous les fichiers de configuration. Pour des raisons de sécurité, seul le Maître pourra l'utiliser (il ne doit donc pas être setuid). Il peut être judicieux de le programmer en Perl.

f) Ajoutez une directive include toto.conf permettant d'importer dans un fichier de configuration le contenu d'un autre fichier. Ceci permet en particulier de gérer sans peine des groupes d'utilisateurs: un fichier groupe.conf est créé pour chaque groupe et on place dans la configuration d'un utilisateur un include groupe.conf pour chaque groupe auquel il appartient. Assurez-vous que votre commande include est récursive afin de permettre les méta-groupes, méta-méta-groupes, etc.

g) Étendez op pour qu'il accepte une succession d'ordres en ligne de commande. Ceci permet une optimisation intéressante: le fichier de configuration de l'utilisateur ne sera lu qu'une seule fois pour toute la séquence d'ordres au lieu d'une fois pour chaque ordre.

h) Proposez une commande pour permettre à certains utilisateurs de modifier les droits d'autres utilisateurs. Attention toutefois à tous les problèmes de sécurité que cela peut impliquer!


Antoine Miné