Installation de Prosody + bonus Shibboleth + bonus Jappix Mini

Monday, November 18, 2013

Il est pratique de pouvoir faire de l’instant messaging avec ses collègues. J’ai utilisé ejabberd quelques temps mais sa configuration est .. euh .. lourde. Prosody est un logiciel en plein développement et on le trouve en RPM dans le dépôt EPEL ! D’autre part, on va chercher une solution pour simplifier la gestion du roster en se basant sur un SP Shibboleth. Enfin, en bonus, on utilisera le client Jappix Mini.

Introduction

C’est le genre d’outils que j’apprécie beaucoup, simple et très utile. Pour demander un truc au collègue qui est jamais dans son bureau ou pour remonter des bugs à un développeur, suffit de laisser un pti message. Et je vous parle pas du combo téléphone + jabber pour copier coller des lignes de commande ou des URLs !!

Dans cet article, on va installer rapidement le service et on va se concentrer sur la création de comptes. En effet, qu’est ce que c’est relou d’ajouter un par un ses contacts Jabber … J’aimerais peupler d’avance le roster des utilisateurs. Du coup, j’ai fais un pti script PHP qui récup des infos de la fédération d’identité et les “injecte” dans Prosody.

Enfin, en bonus, on verra comment extraire et utiliser Jappix Mini avec notre serveur Prosody. Il s’agit d’un petit client écrit en Javascript, issu du projet open source français Jappix (les devs sont très sympas au passage :D). Jappix Mini s’exécute coté client et à besoin du mot de passe de l’utilisateur, comment contourner le problème ?

Installation de Prosody

Cette étape va être rapide, il suffit d’installer le paquet prosody :

yum install epel-release -y
yum install prosody -y

On ajoute d’autres paquets qui n’ont rien à voir avec le service Jabber :

yum install httpd php hg-git

On va ajouter quelques modules nécessaires par la suite :

cd /tmp/
hg clone https://code.google.com/p/prosody-modules/
cp prosody-modules/mod_group_bookmarks/mod_group_bookmarks.lua /usr/lib64/prosody/modules/

On pense à démarrer automatiquement les démons :

chkconfig prosody on
chkconfig httpd on

Et à ouvrir le pare-feu :

vim /etc/sysconfig/iptables
-A INPUT -p tcp -m state --state NEW -m tcp --dport 5222 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 5280 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
service iptables restart

Le port 5222 pour les clients lourds XMPP, le port 80 (ou 443 sans RP) pour l’interface de création des comptes et le port 5280 pour le client web Jappix via la passerelle BOSH (Un module qui fait HTTP vers XMPP en gros).

Configuration Apache

Je suis derrière un reverse proxy qui s’occupe du SSL et de l’auth fédération, j’ai donc pas grand chose à configurer vu que je vais utiliser le document root par défaut. On lance le démon quand même :

service httpd start

Pourquoi installer Apache ? Il ne sert pas directement pour Prosody mais va nous être utile pour le script de création des comptes et le serveur BOSH.

Configuration de Prosody

Pour démarrer son serveur Jabber, il y a peu de chose à configurer. Tout se trouve dans le fichier prosody.cfg.lua :

vim /etc/prosody/prosody.cfg.lua

On s’attaque à la partie globale, les modules à activer, les admins, etc :

admins = { "<uid_admin>@<fqdn_serveur-jabber>" }
modules_enabled = { ...
  "admin_telnet";
  "groups";
  "group_bookmarks";
  "vcard";
  "bosh";
}

Pour expliciter ces choix de module : Le module groups va nous permettre d’ajouter automatiquement une liste de JID dans le roster d’un utilisateur. C’est avec ce module qu’on va automatiser l’ajout de contacts dans son roster. Group_bookmarks c’est le même principe que groups mais pour créer des salons de discussion. Vcard permet d’avoir des infos supplémentaires pour un compte utilisateur. Admin_telnet permet de recharger la conf sans redémarrer le service et donc déconnecter tout le monde, ça va nous être utile dans la procédure de création d’utilisateur. Enfin, le module BOSH va servir pour le client web Jappix-mini

Par défaut, le TLS est activé mais utilise des certificats auto-signés. On va utiliser le même certificat que celui de la partie web. On laisse le choix du SSL au client même si c’est-pas-bien car ça va bloquer avec le serveur BOSH et je n’ai pas cherché pourquoi :

ssl = {
        key = "/etc/prosody/<fqdn_serveur-jabber>.key";
        certificate = "/etc/prosody/<fqdn_serveur-jabber>.org.crt";
}
c2s_require_encryption = false
s2s_require_encryption = false

On crée un VirtualHost et quelques options :

VirtualHost "<fqdn_serveur-jabber>"
  enabled = true
  groups_file = "/var/www/html/groups/prosody_groups.ini"
  group_bookmarks_file = "/var/www/html/groups/prosody_rooms.ini"

On active la partie MUC, c’est à dire les salons de discussion :

Component "conference.<fqdn_serveur-jabber>" "muc"
        name = "My Confrooms"

On démarre le service et on fonce voir les logs :

service prosody start
tail -f /var/log/messages
Sep 12 17:08:33 chat prosody[7066]: Successfully daemonized to PID 7066

Script prosodycmd

On va avoir besoin de ce pti script qui facilite la communication telnet avec le serveur. On crée le script :

cd /var/www/html/
vim prosodycmd

Avec le contenu suivant :

#!/bin/sh

## Issue Prosody Console commands
##
## Requires the mod_admin_telnet module to be enabled.
##
## No arguments drops into a telnet console
##
## Example: prosodycmd 'server:version()' 'server:uptime()' 'c2s:show()' 's2s:show()'

PORT=5582

if [ $# -gt 0 ]; then
  (
    for cmd in "$@"; do
      echo "$cmd"
    done
    echo "quit"
  ) | nc -w1 127.0.0.1 $PORT \
    | tr -d '\x00\x0d' \
    | sed -e '1,/^| http:/ d' \
          -e '/^$/ d' \
          -e 's/^| //' \
          -e 's/^OK: //' \
          -e '/^See you!/ d'
else
  echo "Prosody Console: type 'quit' or 'exit' to return."
  telnet 127.0.0.1 $PORT
fi

Et on modifie les droits :

chmod a+x prosodycmd

Test de Prosody

On va créer l’utilisateur admin déclaré plus haut dans le fichier de conf :

prosodyctl register <uid_admin> <fqdn_serveur-jabber> <passwd_admin>

Puis avec un client Jabber, on se connecte avec le JID <uid_admin>@<fqdn_serveur-jabber>. En phase de test, j’utilise le client web dispo sur jwchat.org qui est très léger. A ce niveau, ça devrait marcher et vous allez pouvoir voir votre beau roster … tout vide. Attaquons nous à la création de comptes et au peuplage des groupes.

Mini application de création de comptes

L’idée ici est d’automatiser la création des comptes, enfin la déléguer aux utilisateurs eux mêmes. Tout comme la fédération d’identité, ce système repose sur la confiance car c’est l’utilisateur qui va choisir les groupes auxquels il appartient.

Le script registration.php est “caché” derrière une authentification Shibboleth, on récupère ainsi des infos fiables qui vont nous permettre de créer le compte. Une fois authentifié, l’utilisateur aura un formulaire où il définira son mot de passe et s’abonnera aux groupes qui l’intéresse.

Une fois le formulaire validé, le script PHP va ajouter l’utilisateur dans chaque groupe (un fichier par groupe), créer le compte Jabber en appelant prosodyctl, générer le fichier de groupes global et enfin recharger la conf avec prosodycmd.

L’arborescence

Dans le DocumentRoot Apache, on a :

.
├── groups
│   ├── <name_grp1>_group.txt
│   ├── <name_grp1>_room.txt
│   ├── <name_grp2>_group.txt
│   ├── <name_grp2>_room.txt
│   ├── prosody_groups.ini
│   ├── prosody_rooms.ini
│   └── static_group.txt
├── index.php
├── prosodycmd
└── registration.php

L’index sert à rediriger les users vers registration.php. Le script prosodycmd est appelé par registration.php à la fin de la procédure pour recharger la conf. Et le dossier groups contient les fichiers INI nécessaires à prosody et les fichiers TXT qui servent à la cuisine interne du script.

Chaque fichier TXT de groupes et de salons possède une entête. Format de fichier de groupe privé :

[<name_group>]
<jid_user1>=<name_user1>
<jid_user2>=<name_user2>

Pour les salons, on a le même principe :

[<name_room>@<fqdn_muc_server>]
<jid_user1>=<name_user1>
<jid_user2>=<name_user2>

A noter que l’on peut aussi avoir des groupes publiques, c’est à dire qu’ils seront affichés même si l’utilisateur n’en est pas membre. Le format change, il y a un + devant le nom du groupe, par exemple :

[+Support]
<jid_support1>=<name_support1>
<jid_support2>=<name_support2>

Je déclare les groupes publiques dans le fichier static_group.txt, très pratique pour mettre les contacts de support.

Le script registration.php

La première version, pas parfaite, mais c’est pour montrer le principe, à chacun de “customiser” ;-) Il manque un affichage plus propre des résultats du script, il manque la gestion des homonymes !!, il va manquer la partie modification d’un compte existant. On verra si le service “prend”.

En pré-requis, il est nécessaire d’autoriser apache a utiliser la commande prosodyctl en passant par un sudo :

visudo
apache  ALL= NOPASSWD: /usr/bin/prosodyctl

Il sera nécessaire de commenter la ligne :

#Defaults    requiretty

Le code :

<?php
        $server_name = "<fqdn_serveur-jabber>";
        $user_surname = $_SERVER['<attribut-SN>'];
        $user_givenname = $_SERVER['<attribut-GIVENNAME>'];

        $script_path = "/var/www/html";

        $id = strtolower($user_givenname).".".strtolower($user_surname);
        $jid = strtolower($user_givenname).".".strtolower($user_surname)."@".$server_name;
        $nickname = $user_givenname." ".$user_surname;
//
// Template
//
       echo("<html><head><title>Cr&eacute;ation de compte Jabber</title>");

        echo("<style>
                body {
                        background-color: #ffffff;
                        color: #00294A;
                        font-family: Verdana, Arial, Geneva, Helvetica, sans-serif;
                        font-style: normal;
                        width: 800px;
                        margin-left: auto;
                        margin-right: auto;
                        margin-top: 0px;
                }
                fieldset {
                        background-color: #f5f5f5;
                        color: #00294A;
                        margin: 10px 0px 20px 0px;
                        border-top: 1px solid #329FBB;
                        border-left: 1px solid #329FBB;
                        border-right: 0px;
                        border-bottom: 0px;
                        border-radius: 5px;
                }
                a {
                    background-color: rgba(240, 240, 240, 0.9);
                    border: 1px solid rgba(190, 190, 190, 0.9);
                    border-radius: 0.5em 0.5em 0.5em 0.5em;
                    box-shadow: 0 1px 1px rgba(255, 255, 255, 0.9), 0 1px 1px rgba(255, 255, 255, 0.9) inset;
                    color: #555555;
                    cursor: pointer;
                    font-weight: bold;
                        font-size: 0.8em;
                    padding: 0.4em;
                    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9);
                    width: auto;
                        text-decoration:none;
                }
 
        </style>");

        echo("</head><body>");

//
// Affichage
//

        // Affichage par défaut
        if(!isset($_GET['confirm']) OR $_GET['confirm'] != "true") {
                echo "<fieldset><legend>Cr&eacute;ation de compte Jabber</legend>";
                echo "<form action=\"registration.php?confirm=true\" method=post> ";
                echo "<p><table>";
                echo "<tr><td><b>Nickname</b> : </td><td>$nickname</td></tr>";
                echo "<tr><td><b>JabberId</b> : </td><td>$jid</td></tr>";
                echo "<tr><td><b>Password</b> : </td><td><input type=password name=\"pass1\"/></td></tr>";
                echo "<tr><td>&nbsp;</td><td><input type=password name=\"pass2\"></input></td></tr>";
                echo "<tr><td valign=top><b>Groupes</b> :</td><td>";

                echo "<input type=checkbox name=\"groups[]\" value=\"<name_grp1>\"><Name_Groupe1><br/>";
                echo "<input type=checkbox name=\"groups[]\" value=\"<name_grp2>\"><Name_Groupe2><br/>";

                echo"</td></tr>";
                echo "</table></p>";
                echo "</form>";
                echo "<a href=\"#\" onClick=\"document.forms[0].submit();\" class=\"button\">Cr&eacute;er le compte</a>";
                echo "</fieldset>";
}
        // Traitement de la creation
        else{
                //print_r($_POST);

                $groups = $_POST['groups'];
                $pass1 = $_POST['pass1'];
                $pass2 = $_POST['pass2'];
                $user = $jid."=".$nickname;

                // On check les passwd
                echo "Verification password ... ";
                if($pass1 != $pass2 OR $pass1 == "") {
                        echo "FAIL<br/>";
                        exit(0);
                }else{
                        echo "OK<br/>";
                }

                // on peuple les fichiers de groupes et de salons
                foreach($groups as $group) {
                        // Test si deja dedans ?
                        $output = system("grep $jid $script_path/groups/$group\_group.txt > /dev/null", $returnVal);
                        if($returnVal == 0) {
                                echo "Deja dans le groupe $group ... SKIP<br/>";

                        }else{
                                echo "Ajout dans le groupe $group ... ";
                                $output = system("echo $user >> $script_path/groups/$group\_group.txt", $returnVal);
                                if($returnVal > 0) {
                                        echo "FAIL<br/>".$output;
                                        exit(0);
                                }else{
                                        echo "OK<br/>";
                                }
                                echo "Ajout dans le salon $group ... ";
                                $output = system("echo $user >> $script_path/groups/$group\_room.txt", $returnVal);
                                if($returnVal > 0) {
                                        echo "FAIL<br/>".$output;
                                        exit(0);
                                }else{
                                        echo "OK<br/>";
                                }
                        }
                }

                // On genere le fichier INI contenant tous les groupes
                echo "Creation fichier des groupes ... ";
                $output = system("cat $script_path/groups/*_group.txt > $script_path/groups/prosody_groups.ini", $returnVal);
                if($returnVal > 0) {
                        echo "FAIL<br/>".$output;
                        exit(0);
                }else{
                        echo "OK<br/>";
                }
                echo "Creation fichier des salons ... ";
                $output = system("cat $script_path/groups/*_room.txt > $script_path/groups/prosody_rooms.ini", $returnVal);
                if($returnVal > 0) {
                        echo "FAIL<br/>".$output;
                        exit(0);
                }else{
                        echo "OK<br/>";
                }

                // On cree le compte JID avec le password
                echo "Creation du compte $jid ... ";
                $output = system("/usr/bin/sudo /usr/bin/prosodyctl register $id $server_name $pass1", $returnVal);
                if($returnVal > 0) {
                        echo "FAIL<br/>".$output;
                        exit(0);
                }else{
                        echo "OK<br/>";
                }

                // On reload la conf
                echo "Rechargement de la conf ... ";
                $output = system("$script_path/prosodycmd 'module:reload(\"groups\")' > /dev/null", $returnVal);
                if($returnVal > 0) {
                        echo "FAIL<br/>".$output;
                        exit(0);
                }else{
                        echo "OK<br/>";
                }

        }
        echo("</body></html>");
?>

Soyez indulgent, je ne suis pas un développeur !!! :D

Le fonctionnement est simple, le script est protégé par Shibboleth, donc une fois l’utilisateur connecté, on calcul son JID. Il clique sur les groupes dont il souhaite être membre et valide.

Le client Jappix Mini

J’ai utilisé longtemps JWChat mais il ne semble plus maintenu et n’est pas spécialement sexy. Jappix est un moteur de réseau social basé sur le protocole XMPP, il s’agit d’un projet open source français. La partie qui m’intéressait dans ce logiciel était son mini client à la facebook, écrit en javascript.

Coup de chance pour nous, les développeurs de Jappix proposent un script pour extraire la partie jappix-mini ! Le ticket GitHub en rapport : https://github.com/jappix/jappix/issues/192

Extraction du client web Jappix Mini

Donc le script extrac_mini.sh se trouve ici : https://github.com/jappix/jappix/blob/master/extract_mini.sh

Pour récupérer le mini client, il faut télécharger les sources du projet Jappix, les décompresser, récupérer le script extrac_mini.sh et le placer à la racine de Jappix. Il sera peut être nécessaire de modifier l’entête de certains fichiers à propos de leur licence pour que le script s’exécute. Une fois ce problème réglé, vous trouverez le mini client dans le dossier mini/.

Intégrer Jappix Mini dans ses pages PHP

Le top de la classe est d’avoir son service d’instant messaging et de présence disponible sur ces sites PHP, une interface unifiée en quelque sorte. Dans cet exemple, on suppose que la page où intégrer Jappix Mini est derrière une authentification fédération. Ainsi on peut récupérer les attributs qui vont nous permettre de calculer le JID, ces attributs sont le SN et le GivenName, veillez à ce que l’IdP les expose.

Par contre, on ne peut pas récupérer le mot de passe par la fédération ! De plus, le mot de passe du service Jabber ne sera pas forcément le même que celui du référentiel. Pour contourner cette contrainte, j’ai modifié un peu le code pour qu’un prompt demande le mot de passe Jabber avant de connecter le mini client.

Premièrement, on met les sources dans le répertoire du site PHP :

cd /var/www/<mon_site>
tar zxvf jappixmini.tar.gz
chown apache:apache jappixmini/ -R

On édite ensuite les scripts PHP où intégrer le mini client :

vim <ma_page>.php

Au début du script, on calcul les paramètres (noms de variables à adapter) :

<?php
        $server_name = "<fqdn_serveur-jabber>";
        $user_surname = $_SERVER['<attribut_SN>'];
        $user_givenname = $_SERVER['<attribut_GIVENNAME>'];

        $id = strtolower($user_givenname).".".strtolower($user_surname);
        $jid = strtolower($user_givenname).".".strtolower($user_surname)."@".$server_name;
        $nickname = $user_givenname." ".$user_surname;
?>

Ensuite, dans les headers HTML, on ajoute jquery, css et javascript (ne pas mettre la ligne jquery si déjà présent) :

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<link rel="stylesheet" href="jappixmini/css/mini.css" type="text/css" media="screen" />
<script type="text/javascript" src="jappixmini/js/mini.js"></script>
<script type="text/javascript">
        function connectJappixMini() {
                var passwd = prompt("Mot de passe pour <?php echo($jid); ?>");                 

                jQuery(document).ready(function() {
                        JAPPIX_STATIC="/jappixmini/";
                        HOST_BOSH="https://<fqdn_serveur-php>/http-bind/";
                        launchMini(true, false, "<?php echo($server_name); ?>", "<?php echo($id); ?>", passwd);
                });
        }
</script>

Enfin, dans le corps du document HTML, on ajoute une div en bas à droite :

<div id="jappix_mini">
                <div class="jm_position">
                        <div class="jm_starter" style="float: left;">
                                <a class="jm_pane jm_button jm_images" href="#" onClick="connectJappixMini();">
                                <span class="jm_counter jm_images">Chat</span>
                                </a>
                        </div>
                </div>
        </div>

Vous avez constaté que le BOSH serveur ici n’est pas le serveur Jabber … Je n’ai pas réussi à faire marcher le mini client sur un host différent du paramètre BOSH serveur, je ne l’explique pas. Bref, il faut configurer un ProxyPass sur votre serveur hébergeant les pages PHP :

vim /etc/httpd/conf/vhost/<mon_serveur-php>.conf
<Location /http-bind/>
        ProxyPass http://<fqdn_serveur-jabber>:5280/http-bind/
        ProxyPassReverse http://<fqdn_serveur-jabber>:5280/http-bind/

        Order allow,deny
        Allow from all
        Satisfy Any
</Location>

Par rapport au client lourd, on a pas l’affichage des groupes et des personnes offline mais seulement les personnes connectés. On peut utiliser le service de conférence, grâce au bouton “Join a chat” dans le coin supérieur droit du mini client. En bonus, ce client peut faire d’autres protocoles de communication tel que IRC.

Conclusion

Je trouve ça beaucoup moins fastidieux pour un utilisateur de passer par ce script, plutôt que devoir rentrer ses contacts un par un. Sachant que le contact doit valider l’ajout, c’est louuurd. Des utilisateurs d’un même établissement peuvent subscribe aux groupes qui les intéressent. Un groupe peut être une équipe, par exemple l’équipe réseau ou un comité pour les projets. On peut mettre tous les groupes en publiques pour que tout le monde puissent voir l’ensemble des utilisateurs, le système est suffisamment flexible.

Je dois améliorer l’interface, en particulier si un utilisateur veut se désabonner d’un groupe, écraser son password. Il y a une grosse faiblesse dans ma gestion des identités, je n’indique pas l’établissement d’origine dans le JID, en cas d’homonyme ou de personne ayant plusieurs comptes, il va y avoir conflit. J’avais essayé de faire quelque chose dans ce sens, c’est à dire construire le JID avec l’EPPN de l’agent mais ça devenait très très lourd pour l’utilisateur d’avoir un JID de la forme -@<fqdn_serveur-jabber>, d’autant plus que l’EPPN n’a pas le même format dans tous les établissements oO

Références

ArticleGeekboshextrac_mini.shfédération identitéinstant messagingjabberJappixJappix Miniprosodyprosodycmdprosodyctlshibbolethxmpp
Le contenu de ce site est sous licence Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)

Gamb

DIY

Haute dispo de prolo - épisode 2 : Proxmox deux noeuds HA

Vivons heureux, vivons cachés - épisode 1 : OpenVPN