Ziirish's Home :: Blog

Ziirish's Pub

 
 

Wait... What? oO Et oui, quand y'en a plus, y'en a encore ! Mon dernier post commence à dater un peu. Pourtant, depuis j'ai encore apporté quelques modifications à mon infrastructure "FruitWifi". Eh oui, je suis un éternel insatisfait...

Quelles sont donc ces nouveautés ? Face à l’engouement du service (une dizaine d'utilisateurs hebdomadaires), et n'ayant présenté les «CGU» qu'à 4 personnes, je me suis dit que ça serait pas bête de "forcer" les gens à passer par la case «CGU». D'autant plus qu'avec la mise en application de la loi HADOPI, en partageant ma connexion je me mets en délit de sécurisation !

Un autre service qui s'avérera plus utile pour les utilisateurs : la mise en place d'un antivirus analysant en temps réel les flux HTTP/HTTPS.

On commence par l'antivirus. Il s'agit de clamAV, couplé à Squid grâce au projet squidclamav .

Pour l'installation, je vous renvoie vers la page dédiée sur le site du projet car je n'ai rien à y ajouter.

Et ça juste marche :

squidclamav.png

squidclamav.png (direct link)

Bon, ça c'était facile :D Maintenant on passe à un truc un peu moins simple, mais ça reste abordable (la preuve, j'y suis arrivé tout seul !) Je parle bien entendu du "portail" des «CGU» de FruitWifi.

Pour ce faire, que faut-il ?

    Une base de données "quelque chose" qui vérifie qu'on a accepté les conditions La page des conditions

J'ai donc une base de données toute bête avec une petite table (code postgresql) :


CREATE TABLE users (
    ip         inet CONSTRAINT firstkey PRIMARY KEY,
    accept boolean default 'n',
    hash    char(32) default NULL,
    last      date default current_date
);

Ce qui va vérifier que les conditions ont bien été acceptées, c'est le proxy lui même, enfin... presque :)

On l'a déjà vu plus tôt, on peut dire à Squid de passer sa sortie à un filtre. Précédemment, nous avions placé squidGuard en tant que filtre, et juste un peu plus haut, squidclamav.

Voici donc le "filtre" écrit en Perl, parce que c'est bien :


#!/usr/bin/env perl

# proxy_redirector.pl

use strict;
use warnings;
use FileHandle;
use IPC::Open2;
use DBI;

my $portal = "http://redirect.ziirish.info/portail.php";
my $squidclamav = "/usr/local/bin/squidclamav";

my $dbname = 'toto';
my $dbuser = 'blah';
my $dbpass = 'blih';
my $dbhost = 'localhost';

my $network_filter = "192.168.10.[0-9]{1,3}";
my $allowed_urls = "^(https?:\/\\/)?(.*\.)?ziirish\.info";

$|=1;

# cette fonction vérifie l’obtention d’une adresse MAC, vérifie sa présence et redirige éventuellement
# l’utilisateur vers le portail captif
sub check_ip {

    # squid envoie les informations au redirecteur de cette façon
    # URL adresse-ip/fqdn ident method
    # soit par exemple :
    # http://www.google.fr/ 192.168.10.155/- - GET
    # ici, nous n’aurons besoin que de l’IP et de l'URL consultée

    my ($url,$ip,@rest) = (split(/ /, $_));
    my $ip_ori = $ip;
    $ip =~ s/([0-9.]+)/.*/$1/;

    my $ret = 0;

    # Seules les machines du réseau FruitWifi passent par le portail
    # De plus, certaines URLs ne nécessitent pas d'être "authentifié"
    if (!($ip =~ qr/^$network_filter$/)) {
        return 1;
    }
    if ($url =~ qr/$allowed_urls/i) {
        return 1;
    }

    # on vérifie que le client a accepté les conditions d'utilisation il y a moins 
    # de 12h. Sinon, on le redirige vers le portail.

    my $dbh = DBI->connect("DBI:Pg:dbname=$dbname;host=$dbhost",$dbuser,$dbpass, {'RaiseError' => 1});
    
    # Si vous n'utilisez pas de pooler de connexion, préférez les prepared statement qui gèrent leur propre pooler
    #my $sth = $dbh->prepare("SELECT accept,extract('epoch' from age(now(),last)) as time FROM users WHERE ip = '$ip'");
    #$sth->execute();
    #my $ref = $sth->fetchrow_hashref();
    
    my $ref = $dbh->selectrow_hashref("SELECT accept,extract('epoch' from age(now(),last)) as time FROM users WHERE ip = '$ip'");
    $dbh->disconnect();
    
    if (defined $ref && $ref->{'accept'} && $ref->{'time'} < (60*60*12)) {
        $ret = 1;
    } else {
        # On encode l'url pour éviter l'url poisoning
        $url =~ s/([^A-Za-z0-9.])/sprintf("%%%02X", ord($1))/seg;
        print "$portal?ip=$ip&url=$url $ip_ori @rest";
        $ret = 0;
    }
    return $ret;
}

# cette fonction exécute squidclamav
sub squidclamav_exec {

    # on utilise open2() afin de manipuler l’entrée et la sortie standard
    my $pid = open2( *IN, *OUT, "$squidclamav" );

    OUT->autoflush();
    print OUT "$_";

    my $redir = <IN>;
    chomp $redir;

    # puis on mime la sortie de squidclamav
    if (! $redir eq "") {
        print "$redir\n";
    } else {
        print "\n";
    }

    # on ferme les descripteurs
    close(OUT);
    close(IN);

    # et on s’assure de ne pas laisser traîner de zombies derrière nous
    waitpid($pid, 0);
}

# boucle principale
while (<>) {

    # on passe d’abord par l’analyse de la MAC
    # si elle a conduit à une redirection on saute l’étape squidguard
    if (!check_ip($_)) { next; }

    squidclamav_exec($_);
}

Alors oui, c'est assez limite parce que ça tape en base à chaque requête... Mais disons que sur mon architecture qui n'autorise au maximum que 10 utilisateurs simultanés, "ça passe".

Si le Perl n'est pas votre seconde langue, et que vous trouvez que les commentaires sont trop légers (je vous crois pas là), petite explication über rapide :

Squid va passer à l\'entrée standard de notre filtre quelque chose comme : http://www.google.fr/ 192.168.10.155/- - GET, avec dans l'ordre : url ip/name - method

Pour éviter de boucler, on autorise le client à consulter les sites du domaine ziirish.info, et nous ne filtrons que sur les IPs du réseau FruitWifi. On découpe donc chaque ligne en trois parties : url, ip, reste.

Si une IP a accepté les conditions il y a moins de 12h, elle passe à la \"suite\" du traitement. Sinon, elle est redirigée vers le portail.

La "suite", c'est le passage de la ligne à squidclamav, qui lui même transmettra à squidGuard... Ouais, ça part loin !

Pour ça, on édite le fichier de configuration de squid pour y mettre :


url_rewrite_program /mon/prog/proxy_redirector.pl
url_rewrite_children 5

Le 5 est arbitraire, vous mettez ce que vous voulez en fonction de votre machine.

Puis on demande à squid de recharger sa configuration :


% squid -k reconfigure

Vous suivez toujours ? Allez, il ne nous reste plus qu'à faire le portail à proprement parler... On va faire ça en PHP, pour le fun :)


<?php
    /* Quelques variables */
    $dbhost = 'localhost';
    $dbuser = 'blah';
    $dbpass = 'blih';
    $dbname = 'toto';
    $dbport = 5432;
    
    $version = 1.0;
    
    /* On vérifie que la page est appelée correctement, sinon on affiche une erreur */
    if (!isset($_GET['ip']) || !isset($_GET['url']) || empty($_GET['ip']) || empty($_GET['url'])) {
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
    <head>
        <title>FruitWifi CGU</title>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    </head>
    <body>
    <h2 style="text-align: center">Les paramètres fournis sont incorrects.</h2>
    </body>
</html>
<?php
        exit();
    }
    
    /* On récupère l'IP du client et l'URL visée */
    $ip = $_GET['ip'];
    $url = urldecode($_GET['url']);
    
    /* Si on a déjà un cookie, on vérifie son contenu */
    if (isset($_COOKIE['fruitwifi'])) {
        $ip_cli = $_COOKIE['fruitwifi']['ip'];
        $hash_cli = $_COOKIE['fruitwifi']['hash'];
        $ver_cli = $_COOKIE['fruitwifi']['version'];

        /* On vérifie qu'il n'y a pas de spoofing d'adresse */
        if ($ip_cli == $ip) {
            /* On se connecte à la base de données */
            $db = pg_connect("host=$dbhost port=$dbport user=$dbuser password=$dbpass dbname=$dbname") 
            or die("Could not connect to database");
            /* On récupère quelques données en base */
            $result = pg_query($db,"SELECT accept,hash FROM users where ip = '$ip'");
    
            $myrow = pg_fetch_assoc($result);
            

            if ($ver_cli == $version && $myrow && $myrow['hash'] == $hash_cli) {
                if ($myrow['accept'] = 'y') {
                    /* Si on a récupéré des données et que les conditions ont été accéptées, on génère un nouveau hash */
                    $new_hash = md5(uniqid());
                    /* On met à jour la base */
                    pg_query($db,"UPDATE users SET hash = '$new_hash', last = now() WHERE ip = '$ip'");
                    /* et le cookie */
                    setcookie('fruitwifi[ip]',$ip,time()+60*60*24*14);
                    setcookie('fruitwifi[hash]',$new_hash,time()+60*60*24*14);
                    setcookie('fruitwifi[version]',$version,time()+60*60*24*14);
                    pg_close($db);
                    /* et enfin, on redirige vers l'url initialement demandée */
                    header("Location: $url");
                    exit();
                }
            } elseif ($myrow) {
                /* Si les (nouvelles) conditions n'ont pas été lues ou que le cookie est faux, on met à jour la base */
                pg_query($db,"UPDATE users SET accept = 'n' WHERE ip = '$ip'");
            }
            pg_close($db);
        } 
    } elseif (isset($_POST['accept']) && !empty($_POST['accept']) && $_POST['accept'] == 1) {
        /* Si on accepte les conditions, on se connecte à la base */
        $db = pg_connect("host=$dbhost port=$dbport user=$dbuser password=$dbpass dbname=$dbname") 
        or die("Could not connect to database");
        /* On récupère des données en base */
        $result = pg_query($db,"SELECT accept,hash FROM users where ip = '$ip'");
        
        $myrow = pg_fetch_assoc($result);
        
        /* On génère un nouveau hash */
        $new_hash = md5(uniqid());
        
        if ($myrow) {
            /* Si le client existe déjà en base, on met à jour ses infos */
            pg_query($db,"UPDATE users SET hash = '$new_hash', last = now(), accept = 'y' WHERE ip = '$ip'");
        } else {
            /* Sinon, on ajoute un enregistrement */
            pg_query($db,"INSERT INTO users (ip,accept,hash) VALUES ('$ip','y','$new_hash')");
        }
        /* On génère un cookie */
        setcookie('fruitwifi[ip]',$ip,time()+60*60*24*14);
        setcookie('fruitwifi[hash]',$new_hash,time()+60*60*24*14);
        setcookie('fruitwifi[version]',$version,time()+60*60*24*14);
        pg_close($db);
        /* Puis on redirige vers l'url initialement demandée */
        header("Location: $url");
        exit ();
    }
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
    <head>
        <title>FruitWifi CGU</title>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    </head>
    <body>
        <h1 style="text-align: center">FruitWifi</h1>
        <h2 style="text-align: center">Le réseau sans fil, gratuit.</h2>
        <p>
        Oui, oui, vous avez bien vu, il n'y a pas d'Astérisque, c'est gratuit.
        </p>
        <p>
        Pardonnez-moi cette irruption. Je ne suis généralement pas fan des services intrusifs.<br/>
        Cependant, FruitWifi est désormais disponible depuis un mois, et vous êtes toutes
        les semaines une dizaine à utiliser ce service. Cependant, je n'ai présenté les «CGU» 
        qu'à 4 personnes. <br />
        Avec la récente mise en application de la loi HADOPI, il me semblait important 
        de vous les (re-)présenter à tous.<br />
        <br />
        Dans le pire des cas, vous aurez à faire à cette page toutes les 12h.<br />
        Pour poursuivre sur le site <em><?php echo $url; ?></em>, veuillez lire les 
        Conditions Générales d'Utilisation puis cliquer sur le boutton «J'accepte» 
        situé en bas de cette page.
        </p>
        <p>
        <strong>FruitWifi, qu'est-ce que c'est ?</strong>
        </p>
        <p>
        C'est un point d'accès (hotspot) Wifi que je mets gratuitement à votre disposition. Il vous sera possible
        à partir de ce dernier d'accéder au WEB (http et https), ainsi qu'à MSN. Cela devrait correspondre à
        95% de vos besoins.
        </p>
        <p>
        <strong>Mais pourquoi ?</strong>
        </p>
        <p>
        Je suis un fervent défenseur des logiciels Libres et Open Sources, et je suis convaincu que cette
        ouverture est bénéfique à tous.<br />
        J'avais le matériel pour mettre ce service en place, alors pourquoi s'en priver ?<br />
        J'avais envie d'apprendre à utiliser les différents outils mis en place pour réaliser cette infrastructure.
        </p>
        <p>
        <strong>Quelles-sont les conditions ?</strong>
        </p>
        <p>
        Je ne vous garantis pas 100% de disponibilité du service, des problèmes peuvent survenir à différents
        niveaux (connexion adsl, point d'accès, murs, etc.).<br />
        Ma responsabilité est engagée, c'est la raison pour laquelle je n'autorise qu'un accès au WEB et à MSN.<br />
        De plus, certains sites sont bloqués. Si je constate des débordements, je me réserve le droit de couper la
        connexion sans préavis.<br />
        À l'heure actuelle, 10 connexions simultanées sont possibles. J'ai dédié une partie de ma bande passante qui
        sera partagée entre les différents utilisateurs.<br />
        Comme sur n'importe quel point d'accès public, vous êtes responsable de la sécurité de vos données.<br />
        Préférez un accès https pour consulter vos e-mails ou pour vous identifier sur un site.<br />
        Afin d'économiser de la bande passante, toutes les requêtes http sont mises en cache (les images par
        exemple).<br />
        Il s'agit en quelque sorte d'un « jouet », des modifications sont donc possibles à tout moment.<br />
        </p>
        <p>
        <strong>HADOPI</strong>
        </p>
        <p>
        Comme vous le savez peut-être, la loi HADOPI a été mise en application il y a peu. <br />
        Cette loi sanctionne le <em>défaut de sécurisation</em> de la ligne ADSL. 
        Typiquement dans mon cas, en vous laissant accès à ma ligne, je me mets en situation de délit au regard de cette loi.<br />
        Ces infractions sont (vraisemblablement) constatées sur les réseaux de peer-to-peer et de torrent.<br/>
        C'est une des raisons pour lesquelles ces protocoles ne sont pas autorisés sur FruitWifi. Cependant, je suis bien placé pour savoir 
        qu'aucun système de sécurisation n'est infaillible. <br />
        Je compte donc sur chacun d'entre-vous pour respecter cet accès communautaire
        que je mets à votre disposition.
        </p>
        <p>
        <strong>Services</strong>
        </p>
        <p>
        Comme je l'ai précisé plus haut, ce service me sert également de terrain d\'expérimentations technologiques.<br />
        J'y ai donc mis en place un antivirus analysant en temps réél les flux HTTP.<br />
        Afin d'optimiser l'usage de la bande passante, un proxy basé sur Squid3 a été mis en place.<br />
        Ci-dessous, des statistiques sur 3 semaines d'utilisation :<br />
        <pre>
Calamaris statistics
--------------------------------------------------------- -------------- ------
lines parsed:                                                      lines 202818
invalid lines:                                                     lines      2
parse time:                                                          sec     23
parse speed:                                                   lines/sec   8818
--------------------------------------------------------- -------------- ------
Proxy statistics
--------------------------------------------------------- -------------- ------
Total amount:                                                   requests 202818
unique hosts/users:                                                hosts     13
Total Bandwidth:                                                    Byte  6112M
Proxy efficiency (HIT [kB/sec] / DIRECT [kB/sec]):                factor   8.06
Average speed increase:                                                %  11.24
TCP response time of 100% requests:                                msec   3380
--------------------------------------------------------- -------------- ------
Cache statistics
--------------------------------------------------------- -------------- ------
Total amount cached:                                            requests  27277
Request hit rate:                                                      %  13.45
Bandwidth savings:                                                  Byte   705M
Bandwidth savings in Percent (Byte hit rate):                          %  11.53
Average cached object size:                                         Byte  27097
Average direct object size:                                         Byte  32301
Average object size:                                                Byte  31602
--------------------------------------------------------- -------------- ------
        </pre>
        On peut voir que 11,5% de bande passante ont été économisés, ce qui n'est 
        pas négligeable.<br />
        Pour ceux que ça intéresse, vous trouverez 
        <a href="http://blog.ziirish.info/post-36-un_jus_de_fruit_sans_pulpes.htm">ici</a>, 
        <a href="http://blog.ziirish.info/post-37-fruitwifi_(1_3).htm">ici</a>, 
        <a href="http://blog.ziirish.info/post-38-fruitwifi_(2_3).htm">ici</a>, et 
        <a href="http://blog.ziirish.info/post-39-fruitwifi_(3_3).htm">là</a> 
        les détails techniques de l'infrastructure.
        </p>
        <p>
        <strong>Des questions/remarques ?</strong>
        </p>
        <p>
        Vous pouvez me contacter par e-mail à l'adresse <a href="mailto:xxx@yahoo.fr">xxx@yahoo.fr</a>.
        </p>
        <br />
        <form method="POST" action="http://redirect.ziirish.info/portail.php?ip=<?php echo $_GET['ip']; ?>&url=<?php echo $_GET['url']; ?>">
        <input type="hidden" value="1" name="accept" />
        <center><input type="submit" value="J'accepte" /></center>
        </form>
    </body>
</html>

Tous les commentaires sont là... Je ne vois rien à ajouter. À part peut-être... Ça MARCHE !

Enfin, si, dans l'idée, qu'est-ce qu'on fait ? On commence par vérifier que nos paramètres sont bien passés, dans le cas contraire, on s'arrête là. Ensuite, si on est toujours là, on vérifie la présence d\'un cookie sur le poste client. S'il en a un, on vérifie qu'il est cohérent avec les données transmises. Puis on récupère quelques infos en base que l'on teste avant de mettre à jour le cookie, la base et de rediriger le client vers l'URL qu'il souhaitais initialement atteindre.

Si par contre il vient juste de valider les conditions, on mets à jour la base de données, et on le redirige.

Enfin, s'il n'est dans aucun de ces cas, c'est qu'il doit passer par la case «CGU».

Donc, si tout se passe "bien", lors de la première consultation, nous devons passer par le portail PHP, ensuite, si on accepte les conditions, notre filtre Perl va nous laisser passer pendant 12h. Au bout de ces 12 heures, on repasse par la case portail PHP, si les cookies ne sont pas effacés, cette étape est "invisible". Le cookie est conservé par défaut 14 jours. Ça permet d'avoir un truc pas trop intrusif de mon point de vue.