[couverture de Linux Magazine 117]

Config::Model - Créer un éditeur graphique de configuration avec Perl (1ere partie)

Article publié dans Linux Magazine 117, juin 2009.

Copyright © 2008 - Dominique Dumont

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau

La configuration d'une application est très souvent le premier obstacle que doit franchir un utilisateur avant de pouvoir utiliser une application. Le plus souvent, l'utilisateur est dirigé vers un fichier qu'il doit éditer avec « son éditeur favori ». Peu d'applications proposent une interface plus conviviale. Pour combler cette lacune, cet article décrit comment créer un éditeur de configuration d'une manière simple et maintenable. Dans la première partie de cet article, nous allons spécifier le modèle de sshd_config, c'est-à-dire sa structure et ses contraintes. Ce modèle permettra à Config::Model de générer l'interface graphique. Nous verrons dans une seconde partie comment lire et écrire les données de sshd_config pour les charger dans l'interface.

Introduction

Quand on lance pour la première fois un logiciel, il est courant de voir un message du genre : « Ce logiciel n'est pas configuré. Veuillez lire la documentation et éditer /etc/machin.conf ». Dans le meilleur des cas, le fichier de configuration en question va contenir des explications sous forme de commentaires, dans d'autres cas, la documentation est dans un fichier séparé (e.g. /usr/share/doc/machin/README) ou dans une page de manuel.

À charge pour l'utilisateur de lire la documentation, déterminer les informations à ajouter, comprendre la syntaxe du fichier de configuration, ajouter les informations, sauvegarder le fichier et enfin lancer le logiciel.

Chaque étape peut poser un problème :

Pour l'utilisateur novice, l'idéal est d'avoir un wizard [WIZARD] pour le guider. Ce wizard va guider l'utilisateur à travers les étapes importantes tout en fournissant les explications nécessaires. La validation des données et l'écriture du fichier sera prise en charge par le wizard.

Le wizard est moins adapté pour un utilisateur sachant à l'avance quelle information il doit modifier. L'idéal pour cet utilisateur est un éditeur interactif qui prendrait en charge aussi la validation des données et l'écriture du fichier.

Malheureusement, peu de projets fournissent de tels outils car leur écriture est assez longue et rébarbative. De plus chaque changement dans la structure ou le contenu des fichiers de configuration peut entraîner des modifications importantes de ces outils.

Pour remédier à cette situation, Config::Model propose un environnement où les développeurs de projet peuvent créer un éditeur de configuration qui fournira :

Bien sûr, le développeur de projet devra fournir certaines informations pour que Config::Model puisse créer un éditeur de configuration :

Toutes ces données fournies par le développeur de projet seront dans une structure de données ce qui permettra une évolution facile durant la vie du projet. En d'autres termes, cette description est à la configuration du projet ce que la DTD est à un document XML.

Cet article va :

État des lieux

Configuration et format de stockage

Dans les systèmes UNIX, on trouve dans le répertoire /etc énormément de fichiers de configuration. Ces fichiers ont des syntaxes variées :

D'autres syntaxes ont été définies pour permettre de stocker des données structurées. Les plus connues sont XML, JSON et YAML.

Le choix d'une syntaxe pour la configuration d'une application est un compromis entre la facilité d'édition et la complexité des données à traiter.

Outils de configuration de quelques projets populaires

L'édition de fichier de configuration est souvent considérée par les utilisateurs novices comme un répulsif : trop de documentation à lire et trop de possibilités.

Certains projet ont essayés de s'attaquer à ce problème en proposant des interfaces plus conviviales à leur utilisateurs :

D'autres projets ont une approche plus générale et fournissent une interface cohérente pour configurer un ensemble d'application. On peut citer Webmin et les system-config-* de Red Hat. Ces projets utilisent une approche similaire à KDE : un framework et beaucoup de code dédié qui mélange le traitement des fichiers de configuration, la logique de validation et la présentation. Je n'ose pas imaginer les efforts requis pour suivre l'évolution de tous les spécifications des projets gérés et la course pour adapter le code à chaque nouvelle version des projets gérés.

Architecture de Config::Model

Un des principes fondateur de Config::Model est de séparer les parties présentation, validation et gestion des données persistantes en trois parties distinctes.

La figure ci-dessous présente :

Pour être plus précis, le modèle de configuration va décrire :

Structure d'un modèle

Config::Model part du principe que les données de configuration sont structurées en arbre. Ceci permet une bonne correspondance entre la structure des données écrites dans les fichiers de configuration et la structure du modèle de la configuration.

Chaque nœud de l'arbre est une instance d'une classe de configuration. Chacune de ces classes va avoir des éléments qui peuvent être :

Chaque élément d'une classe de configuration va avoir des propriétés :

Pour plus de détails, voir la documentation de Config::Model::Node.

Propriétés des feuilles

Chaque feuille de l'arbre représente une donnée de configuration. Les éléments de type leaf doivent être déclarés avec un type (value_type). Ce type peut être :

Comment écrire le modèle

Un modèle de configuration peut être passé à Config::Model de trois manières :

Config::Model appliqué à OpenSSH

Comme j'en vois qui baillent au fond, voici ce que ça donne appliqué à la configuration du daemon sshd de OpenSSH. On va avoir (sous Debian) :

Si on dresse le bilan, que faut-il écrire pour avoir un outil de gestion de la configuration de sshd ?

On va maintenant voir plus en détails comment spécifier les différentes parties du modèle de configuration.

Déclaration du modèle de sshd_config

Installation de l'éditeur de modèle

Pour se simplifier la vie, la déclaration du modèle de configuration de sshd_config sera faite avec l'éditeur graphique de modèle fournit par Config::Model::Itself et Config::Model::TkUI.

Au moment de la rédaction de cet article, ces modules ne sont disponibles que sur CPAN et sur Debian/Sid.

Sur Debian (en version unstable), vous pouvez installer l'éditeur de modèle et ses dépendances avec aptitude :

 # aptitude install libconfig-model-itself-perl

Pour les autres systèmes, le plus simple est d'utiliser la commande cpan pour installer Config::Model::Itself v0.203 ou une version ultérieure. Les autres modules seront installés automatiquement par le jeu des dépendances.

  $ cpan Config::Model::Itself

Pour lancer l'éditeur graphique du modèle Sshd, lancez cette commande :

 $ config-model-edit -model Sshd

Déclaration de la classe Sshd

L'arbre de configuration de sshd_config ressemble à un râteau car tous les éléments de configuration sont au même niveau. La classe qui va représenter le nœud racine de cette arbre est Sshd.

En utilisant la commande config-model-edit -model Sshd, on lance l'éditeur graphique de modèle. Il faut en premier créer la classe de configuration de la racine de l'arbre :

config-model-edit aura créé pour vous une arborescence de développement d'un modèle :

  $ tree lib
    lib
    `-- Config
        `-- Model
            `-- models
                `-- Sshd.pl

Et Sshd.pl contient le squelette du modèle Sshd :

  [
     {
       'name' => 'Sshd'
     }
  ] ;

La page de manuel de sshd_config fournit la liste des éléments qui devront être spécifiés dans la classe de configuration Sshd. Je vais expliquer la création du modèle en choisissant les éléments de façon à couvrir les possibilités de Config::Model. Les éléments restant ne seront pas détaillés, mais seront rajoutés au modèle réel disponible sur SourceForge et sur le CPAN.

Les bases avec AllowTcpForwarding

Voici l'extrait de la page de manuel de sshd_config qui spécifie ce paramètre : «Specifies whether TCP forwarding is permitted. The default is "yes". Note that disabling TCP forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders.»

AllowTcpForwarding est donc une simple valeur de type boolean. Pour la créer avec config-model-edit, il faut ajouter un élément dans la classe Sshd :

Vous noterez sur l'éditeur un petit panneau qui indique une erreur. C'est normal, il faut impérativement renseigner le type de l'élément AllowTcpForwarding et le mettre à leaf.

Voilà, c'est tout pour le début, on verra plus tard comment renseigner l'aide en ligne. On peut tout de suite avoir un aperçu de l'interface de configuration de sshd_config en cliquant sur le menu Model->Test. On obtient une nouvelle fenêtre. En cliquant sur AllowTcpForwarding dans cette nouvelle fenêtre, on obtient :

On peut aussi regarder le code généré par l'éditeur du modèle (légèrement réorganisé pour le rendre plus clair) :

 [
   {
     'name' => 'Sshd',
     'element' => [
                    'AllowTcpForwarding',
                    {
                      'value_type' => 'boolean',
                      'built_in' => '1',
                      'type' => 'leaf'
                    }
                  ]
    }
 ] ;

Par la suite, pour alléger l'article, seuls le nom de l'élément et ses paramètres seront extraits du code généré. C'est-à-dire seul le contenu du array ref (entre [ et ]) sera montré.

Un élément de type liste avec AcceptEnv

Extrait de sshd_config :

 Specifies what environment variables sent by the client will be copied
 into the session's environ(7). Variables are specified by name, which
 may contain the wildcard characters '*' and '?'. Multiple environment
 variables may be separated by whitespace or spread across multiple
 AcceptEnv directives.

Cet élément est donc une liste de variables d'environnement. En terme de modèle, on va utiliser un élément de type list. Le contenu de cette liste (cargo) sera de type leaf et uniline.

Sur l'éditeur, il faudra répéter des actions similaires à celles utilisées avec l'élément précédent pour :

Vous devriez obtenir ceci dans l'éditeur du modèle :

Dans l'éditeur, les flèches vertes indiquent des valeurs changées par rapport aux valeurs par défaut. Dans l'image précédente, les flèches vertes correspondent aux informations stockées dans le code du modèle :

  'AcceptEnv' => {
                   'type'  => 'list',
                   'cargo' => { 'type' => 'leaf',
                                'value_type' => 'uniline',
                              }
                 },

Pour alléger la suite de l'article, l'utilisation de l'éditeur sera moins détaillée. Chaque nouvel élément devra être ajouté à la classe Sshd (clic droit sur element). Ensuite les informations données avec le modèle (i.e. la structure de donnée Perl) devront être reportée dans l'éditeur :

Banner

Se transforme en monstre vert quand trop de pirates tentent de se connecter. Euh, non, cet élément spécifie un nom de fichier dont le contenu doit être affiché par sshd lors de la connexion. Pour le modèle, c'est juste une valeur de type uniline.

  'Banner' => {
                'type'       => 'leaf',
                'value_type' => 'uniline',
              }

Une liste à choix multiples avec Ciphers

D'après la page de manuel de sshd_config, cet élément est une liste d'algorithmes de chiffrement choisis parmi des choix possibles. C'est une feuille de l'arbre de configuration avec un type check_list et des choix validés par défaut. Dans l'éditeur, il faudra renseigner les choix disponibles :

Cette méthode étant vite pénible, vous pouvez aussi faire un copier-coller à partir de la liste fournie par la documentation de sshd_config dans le champ du bouton set all et cliquer sur ce bouton.

Pour finir, on obtient ce modèle :

 'Ciphers' =>
 {
   'type' => 'check_list',
   'choice' => [
                 '3des-cbc', 'aes128-cbc', 'aes192-cbc',
                 'aes256-cbc', 'aes128-ctr', 'aes192-ctr',
                 'aes256-ctr', 'arcfour128', 'arcfour256',
                 'arcfour', 'blowfish-cbc', 'cast128-cbc'
               ],
 }

Fournir l'aide intégrée avec GatewayPorts

Cet élément peut prendre 3 valeurs : yes, no et clientspecified. C'est donc un type énuméré.

Pour faciliter la vie de l'utilisateur final, on va pouvoir aussi :

On obtient ceci dans l'éditeur :

Et ce modèle :

  'GatewayPorts' =>
  {
    'type'        => 'leaf',
    'value_type'  => 'enum',
    'description' => 'Specifies whether remote hosts [...]',

    'help'        => {
                       'yes' => 'force remote port forwardings [...]',
                       'clientspecified' => 'allow the client to [...]',
                       'no' => 'No port forwarding',
                     },

    'built_in'    => 'no',
    'choice'      => [ 'yes', 'clientspecified', 'no']
  }

Copier/coller avec GSSApiAuthentication et GSSAPIKeyExchange

Ces deux éléments sont des booléens avec des valeurs par défaut à 0. Le plus simple est d'abord de définir le modèle de GSSApiAuthentication :

 'GSSApiAuthentication' => {
                             'type' => 'leaf' ,
                             'value_type' => 'boolean',
                             'built_in' => '0',
                           },

Puis de le copier dans GSSAPIKeyExchange :

Et voilà. Il ne reste plus qu'à renseigner les descriptions de chaque élément.

Un entier et une limite avec ServerKeyBits

Ce paramètre est un entier avec une valeur par défaut à 768 et une valeur minimale de 512 :

  'ServerKeyBits' =>
      {
         'type'        => 'leaf',
         'value_type'  => 'integer',
         'min'         => '512',
         'built_in'    => '768',
         'description' => 'Defines the number of [...]',
      }

ClientAliveInterval et ClientAliveCountMax

ClientAliveInterval est documenté comme étant une valeur à double sens. Quand elle est nulle, la fonction de détection des clients inactifs est invalidée et ClientAliveCountMax ne sert à rien.

Pour faciliter la vie l'administrateur de Sshd, on peut choisir de créer un élément ClientAliveCheck et ne présenter les deux autres paramètres à l'administrateur que si ClientAliveCheck est vrai. Ça a pour avantage d'alléger l'interface de l'éditeur. Mais d'un autre coté, s'éloigner de la spécification de sshd_config en ajoutant un paramètre « artificiel » peut perturber les administrateurs chevronnés. Et oui, dès qu'il faut tenir compte de l'historique, il n'y a pas de solution idéale, juste des compromis.

On va courir le risque de déplaire aux administrateurs (ne faites surtout pas ça à la maison ou au boulot ! ;-) ), en créant le paramètre ClientAliveCheck. Dans le cadre de cet article, ce paramètre « artificiel » est ajouté dans un but pédagogique pour expliquer le mécanisme de warping (déformation) du modèle. Ce mécanisme, très utile pour des modèles plus compliqués comme Xorg, permet de masquer ou de faire apparaître des paramètres selon les besoins.

Qu'est ce que le warping ?

Dans certains cas, le coté statique d'un modèle de configuration ne suffit plus. Des valeurs par défaut, des choix ou des éléments de configuration peuvent changer en fonction d'une autre donnée de configuration.

Prenons par exemple la configuration de Xorg. En fonction de votre modèle de carte graphique (Ati ou Nvidia, les pilotes possibles changent :

Et chaque pilote a son propre jeu d'options disponibles (je vous passe les détails). En fonction du pilote choisi par l'utilisateur, les options et la structure du modèle changent complètement. C'est implémenté dans Config::Model avec le mécanisme de warping.

Application du warping sur ClientAlive

Comme indiqué au-dessus, on va introduire un nouveau paramètre booléen : ClientAliveCheck. Si celui-ci est vrai, les paramètres ClientAliveInterval et ClientAliveCountMax seront montrés à l'utilisateur. Ces paramètres seront masqués dans le cas contraire.

On va d'abord créer ClientAliveCheck qui est un simple booléen :

 'ClientAliveCheck' =>
  {
    'type' => 'leaf',
    'value_type' => 'boolean',
    'default' => '0',
  },

Ensuite, on va créer le premier paramètre ClientAliveInterval. Pour que le mécanisme de warping fonctionne, il faut indiquer :

En terme de modèle, ça se traduit en :

   'ClientAliveInterval',
   {
     'type'       => 'leaf',
     'value_type' => 'integer',
     'level'      => 'hidden',
     'min'        => '1',
     'warp' 
     => {
         'follow' => {
                       # précise où trouver la variable c_a_check
                       'c_a_check' => '- ClientAliveCheck'
                     },
         'rules' => [
                     # Cette variable n'est pas interpolée par Perl.
                     # Elle doit être définie dans le hash "follow" au dessus
                     '$c_a_check == 1',
                     {
                       # ClientAliveInterval redevient visible 
                       # quand ClientAliveCheck est vrai
                       'level' => 'normal'
                     }
                    ]
        },
   },

Le mécanisme de warping de Config::Model permet de déformer d'autres attributs des valeurs comme les valeurs par défaut, les limites minimales ou maximales, les choix possibles des types énumérés ...

Une ramification de l'arbre de configuration avec Match

Voici la spécification de ce paramètre de sshd_config :

 Introduces a conditional block. If all of the criteria on the Match
 line are satisfied, the keywords on the following lines override those
 set in the global section of the config file, until either another
 Match line or the end of the file. The arguments to Match are one or
 more criteria-pattern pairs.  The available criteria are User, Group,
 Host, and Address. Only a subset of keywords may be used on the lines
 following a Match keyword. Available keywords are AllowTcpForwarding,
 Banner, ForceCommand, GatewayPorts, GSSApiAuthentication,
 KbdInteractiveAuthentication, KerberosAuthentication,
 PasswordAuthentication, PermitOpen, RhostsRSAAuthentication,
 RSAAuthentication, X11DisplayOffset, X11Forwarding, and
 X11UseLocalHost.

Cet élément introduit un bloc conditionnel qui va contenir une série de paramètres. On va devoir modéliser ce bloc conditionnel avec 2 nouvelles classes de configuration :

On obtiendra cette structure dans les classes de configuration :

Pour créer cette structure, il faut rajouter ces deux nouvelles classes dans le modèle en suivant une méthode similaire à celle utilisée pour créer la classe racine Sshd :

Maintenant, il faut créer l'élément Match dans la classe Sshd pour relier Sshd à Sshd::MatchBlock. Vu que sshd_config peut contenir plusieur blocs Match, il faut créer un élément de type list. Cette liste va contenir les instances de la classe Sshd::MatchBlock et son cargo doit donc être de type node :

Voici ce que ça donne dans le code généré par l'éditeur :

 'Match' =>
 {
     'type' => 'list',
     'cargo' => {
                  'type' => 'node',
                  'config_class_name' => 'Sshd::MatchBlock'
                },
 }

Cet élément Match est intéressant car il permet de voir comment relier 2 classes de configuration avec une liaison multiple (1 <--> * ) entre Sdhd et Sshd::MatchBlock. Une liaison de type liste entre 2 classes est assez exceptionnelle. La plupart du temps cette liaison est faite avec un hash, ce qui est plus facile à exploiter.

Déclaration de la classe Sshd::MatchBlock

La nouvelle classe Sshd::MatchBlock va contenir quatre éléments de type leaf : User, Group, Host and Address et un élément de type node de classe Sshd::MatchElement>.

Chaque feuille de MatchBlock va contenir un des critères «Match» détaillés par la documentation de sshd_config. Chacun de ces critères est un motif (pattern). D'après la documentation de sshd_config, chaque connexion entrante va devoir satisfaire tous ces motifs pour que les surcharges de configuration contenues dans le bloc Match soient appliquées.

Les données de configuration du bloc Match sont stockées dans une instance de la classe Sshd::MatchElement.

Ça parait compliqué, mais voici le résultat contenu dans le fichier Sshd/MatchBlock.pl généré par l'éditeur du modèle :

 [
   {
      'name' => 'Sshd::MatchBlock',
      'element' 
       => [
           'User' =>  {
                       'type' => 'leaf',
                       'value_type' => 'uniline',
                       'description' => 'Define the User criteria [...]',
                      },
           'Group' => {
                       'type' => 'leaf',
                       'value_type' => 'uniline',
                       'description' => 'Define the Group criteria [...]',
                      },
           'Host' =>  {
                       'type' => 'leaf',
                       'value_type' => 'uniline',
                       'description' => 'Define the Host criteria [...]',
                      },
           'Address' =>
                      {
                       'type' => 'leaf',
                       'value_type' => 'uniline',
                       'description' => 'Define the Address criteria [...]'.
                      },
           'Elements' => 
                      {
                       'type' => 'node',
                       'config_class_name' => 'Sshd::MatchElement',
                       'description' => 'Defines the sshd_config parameters [...]',
                      }
          ]
  }
 ] ;

Il reste maintenant à déclarer la classe contenue par l'élement Elements.

Déclaration de la classe Sshd::MatchElement

On a vu que les blocs Match permettent de spécifier des paramètres sshd pour certaines connexions entrantes. Ces paramètres sont aussi disponibles en dehors de ces blocs. Mais il va falloir les dupliquer dans la classe Sshd::MatchElement.

Là, j'en vois qui grognent et se disent : « Quoi ? Dupliquer ce modèle ? Ça va pas la tête ?. La duplication complique trop la maintenance ! »

Effectivement On pourrait utiliser les mécanismes d'inclusion fournis par Config::Model pour éviter cette duplication. Mais il y a un os.

La plupart des paramètres de sshd_config ont une valeur par défaut (déclarés avec built_in dans le modèle). Mais cette valeur par défaut ne s'applique dans les bloc Match que si le paramètre correspondant n'est pas utilisé dans la partie principale de sshd_config. Sinon, c'est le paramètre spécifié dans la partie principale qui est en fait la valeur par défaut du même paramètre dans le bloc Match.

Vous êtes perdu ? Voici un exemple : D'après la documentation de sshd_config, la valeur par défaut de X11Forwarding est no.

Dans l'exemple suivant, la spécification de X11Forwarding est inutile car sa valeur par défaut est no :

 Match User toto
 X11Forwarding no

Alors que dans celui-ci, elle est nécessaire, car la valeur par défaut dans le bloc Match est yes

 X11Forwarding yes

 Match User toto
 X11Forwarding no

Il serait donc intéressant que l'éditeur de sshd_config indique ces valeurs par défaut de X11Forwarding:

Et bien, c'est possible avec le paramètre compute de Config::Model::Value. Ce paramètre permet d'aller chercher certaines valeurs (notez le pluriel) dans l'arbre de configuration, de faire quelques calculs (arithmétiques ou substitutions dans une chaîne de caractères) et d'utiliser le résultat comme valeur par défaut.

Dans notre cas, on aura une variable à aller chercher et un calcul très simple. Voici comment.

Comment copier les éléments dans se fatiguer

D'abord, il faut remplir la classe Sshd::MatchElement avec des éléments similaires à celle de Sshd. Le plus rapide est de copier certains éléments de Sshd avec la fonction « copier/coller » :

Spécifier une valeur par défaut «adaptable»

Prenons par exemple l'élément AllowTcpForwarding de Sshd::MatchElement. Sa valeur par défaut doit être celle de l'élément AllowTcpForwarding dans la classe Sshd. Ceux qui suivent se rappellent que la structure de l'arbre de configuration est :

  Sshd -> Sshd::MatchBlock -> Sshd::MatchElement

Donc, l'élément AllowTcpForwarding de Sshd::MatchElement doit « utiliser » le AllowTcpForwarding qui est trois niveaux plus haut (ou sous la racine). Pourquoi trois niveaux ? Parce qu'il faut « remonter » trois classes (Sshd::MatchElement, Sshd::MatchBlock et Sshd) avant de trouver AllowTcpForwarding. C'est matérialisé par le parcours rouge dans la figure ci-dessous :

En terme de modèle, on va utiliser le paramètre compute et spécifier :

Et voilà. Avec tous ces paramètres, Config::Model va

Enfin, voici le modèle généré de AllowTcpForwarding :

   'AllowTcpForwarding' => 
    {
      'type'       => 'leaf',
      'value_type' => 'boolean',
      'compute'    => {
                       'formula'        => '$main',
                       'variables'      => {
                                            'main' => '- - - &element'
                                           },
                       'allow_override' => '1'

                      },
      description  => "Specifies whether TCP [...]",
    },

Il reste à appliquer ce principe à (presque) tous les autres éléments de Sshd::MatchElement avec la fonction copier/coller. C'est là que la fonction &element utilisée dans la variable main est pratique car on n'a pas besoin d'ajuster le chemin spécifié dans cette variable pour chaque nouvel élément. Pour plus de détails sur le paramètre compute, vous pouvez consulter la documentation de Config::Model::ValueComputer.

Premiers essais

Ca y est ! Tous les paramètres de sshd_config sont entrés dans le modèle. Maintenant on peut avoir un aperçu de l'interface que verra l'administrateur pour configurer Sshd en cliquant sur le menu Model/test :

On peut aussi lancer ce tout nouvel éditeur de configuration avec la commande :

  config-edit -dev -model Sshd

L'option -dev est nécessaire, car nous sommes toujours en phase de développement et le modèle Sshd n'est pas encore installé.

Et là, on se rend compte d'un problème : il n'y a pas une seule flèche verte dans l'éditeur de configuration, aucune valeur spécifique au système hôte. Et oui, on a créé un beau modèle de configuration, mais l'outil est autiste :il ne peut encore ni lire ni écrire le fichier /etc/ssh/sshd_config.

Fondu au noir, générique de fin

Et là, je vous laisse en plein suspens : notre interface arrivera-t'elle à communiquer avec l'extérieur ?

Vous le saurez dans le prochain épisode où nous verrons comment utiliser Perl, Parse::RecDescent et l'API de Config::Model pour :

Remerciements

Liens

Les pages du projet :

[FRESHMEAT] http://freshmeat.net/projects/config_model/

[SOURCEFORGE] http://config-model.wiki.sourceforge.net/

[CPAN] http://search.cpan.org/~ddumont/

Les autres liens :

[WIZARD] http://en.wikipedia.org/wiki/Wizard_%28software%29

[OPENSSH] http://www.openssh.org/

[KDE] http://developer.kde.org/documentation/other/kcm_howto.html

[WEBMIN] http://www.webmin.com/

[CMDEVEL] http://lists.sourceforge.net/mailman/listinfo/config-model-devel

[CMUSERS] http://lists.sourceforge.net/mailman/listinfo/config-model-users

Auteur

Dominique Dumont (dominique.dumont@hp.com)

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]