19
mars
'12

Vodstok, un outil de partage volontaire

Publié le 19 mars 2012

Durant le meeting du mois de Janvier, j'avais présenté un de mes outils nommé Vodstok (pour Voluntary Distributed Storage Kit), mais il n'était pas finalisé. J'ai pris un peu de temps parmi le peu disponible pour tenter de finaliser le code et pouvoir le mettre à disposition de tous. Cet outil permet à tout un chacun de pouvoir uploader et télécharger des fichier stockés sur Internet, dans un nuage de serveurs de stockage mis à disposition par des volontaires.

Stockage distribué volontaire

Des personnes utilisant Vodstok l'installent sur leurs serveurs web (aucune base de données requise, compatible PHP4/5) et décident ainsi de partager une portion de leur espace de stockage aux utilisateurs de Vodstok. Chaque propriétaire de serveur peut ainsi s'ajouter à un réseau maillé de serveurs, et l'ensemble constitue une plate-forme de stockage distribué accessible en HTTP.

La force de ce système est d'une part sa résistance aux coupures: si un serveur tombe, le système est toujours fonctionnel, bien que les données du serveur perdu soient considérées comme perdues (Vodstok n'assure pas de redondance actuellement). Mais il est toujours possible de s'en servir, et en comptant sur l'effet Streisand, cela devrait a priori favoriser ce système. D'autre part, Vodstok se base sur le protocole HTTP, qui est universel et permet même de passer au travers de proxies, ce qui évite d'avoir une configuration de ports particulière, comme c'est le cas avec BitTorrent par exemple.

Vodstok intègre un client en ligne de commande écrit en Python, qui permet d'assurer un stockage sécurisé (AES256) et une répartition d'un fichier sur l'ensemble des serveurs. Bien sûr ce client peut être modifié et les usages dérivés sont nombreux, la seule limite reste votre imagination. Pour faire simple, un serveur sur lequel vodstok est déployé fait office de système de stockage seul, il n'y a pas d'autre logique particulière. On peut le considérer comme un "bout de disque dur" qui ne regarde pas ce qui y est stocké ni comment s'en servent les utilisateurs.

Comment partager une partie de son espace de stockage web avec Vodstok

En premier lieu, il faut récupérer l'archive de Vodstok sur la page du projet, et copier le contenu du dossier www sur le serveur distant. Une rapide modification du fichier de configuration config.inc.php permet de paramétrer le bouzin:

<?php

define('QUOTA_MB', 1024); /* Je décide de partager 1Go d'espace disque */
define('MAX_ENDPOINTS',1500);
define('MAX_ENDPOINT_LEN',200);

/*
DO NOT MODIFY OR REMOVE THE FOLLOWING LINES
*/

define('QUOTA',QUOTA_MB*1024*1024);
define('CHUNK_DIR','chunks');
define('ENDPOINT_DIR','endpoints');

?>

On chmod 777 les dossiers chunks et endpoints, et c'est tout, Vodstok est paré à fonctionner côté serveur. Reste à voir comment s'en servir côté client.

Pour le client, rien de bien compliqué: le dossier client contient le code source de l'outil, qui est pour l'instant seulement en ligne de commande. On le copie dans un dossier accessible à l'utilisateur, et on peut le lancer:

# chmod +x /opt/vodstok-1.2 && cd /usr/bin/local && ln -s /opt/vodstok-1.2/vodstok vodstok
$ vodstok
Usage: vodstok [options] [VDS URL]

Options:
  -h, --help            show this help message and exit
  -l, --list-endpoints  List saved endpoints
  -a ENDPOINT, --add-endpoint=ENDPOINT
                        Add an existing endpoint
  -r ENDPOINT, --del-endpoint=ENDPOINT
                        Remove an existing endpoint
  -p ENDPOINT, --publish=ENDPOINT
                        Publish an existing endpoint
  -w DIRECTORY, --write=DIRECTORY
                        Set destination directory
  -s, --size            Displays distributed storage capacity
  -v, --version         Show version
  -u, --update          Update endpoints

Première étape, ajouter le point de stockage web nouvellement créé, ainsi que d'autres déjà existant:

$ vodstok -a http://www.monserveur.com/vodstok/
$ vodstok -a http://virtualabs.fr/vodstok/
$ vodstok -a http://www.depotware-network.net/vodstock/
$ vodstok -a http://vodstok.zengeek.org/

Lors de chaque ajout de point de stockage (ou endpoint), vodstok vérifie que celui-ci est bien fonctionnel.

Seconde étape, publier votre point de stockage:

$ vodstok -p http://www.monserveur.com/vodstok/

Etape ultime, récupérer de nouveaux points de stockage grâce à la fonctionnalité de mise-à-jour intégrée:

$ vodstok -u

Comment échanger des fichiers grâce à Vodstok

Pour échanger des fichiers, vous n'avez pas obligatoirement besoin d'installer un point de partage (mais ça serait quand même 'achement bien, pour la bonne santé et l'espace de stockage du système). Il est tout de même conseillé d'ajouter au moins un point de stockage existant, et de mettre à jour la liste des points de stockage:

$ vodstok -a http://virtualabs.fr/vodstok/
$ vodstok -u

Une fois cela fait, il est alors très facile d'uploader un fichier sur le réseau vodstok:

$ vodstok britney-spears-chante-sous-la-douche.mp3
[+] Uploading ... 100.00%
[i] File successfully uploaded.
Share this link: vds://054B22B4D64746A94CCE72AB410516C7@www.depotware-network.net/vodstock/#5e23d232a24a332d3e64f02d9b9c9350

Et c'est identique pour le téléchargement:

$ vodstok vds://9AAFC5E52E29E833D969399E9EEF3D0F@virtualabs.fr/vodstok/#6edc5a91f3c29190229822f0119f9745
[+] Retrieving file summary
[+] Starting file download
[+] Downloading ... 100.00%
[i] File saved as britney-spears-chante-sous-la-douche.mp3

Source et contribution

J'ai mis le projet sur [GITHUB], et monté un repository Vodstok sur mon hébergement. N'hésitez pas à tester, jeter un oeil, et si vous pensez pouvoir apporter votre brique, à modifier le code et proposer vos améliorations.

Conclusion

Le projet vodstok est actuellement viable, mais toujours en phase beta. Je suis en train d'essayer d'améliorer le système et de faire en sorte que celui-ci soit fiable et efficace. Essayez-le, installez des points de stockage, et faites moi vos retours d'expérience par email (virtualabs -at- gmail -dot- com) ou directement via twitter !

`GITHUB] [https://github.com/virtualabs/Vodstok <https://github.com/virtualabs/Vodstok>`_ `HOMEPAGE] [http://virtualabs.fr/vodstok/ <http://virtualabs.fr/vodstok/>`_

24
févr.
'12

La face cachée des tickets RATP

Publié le 24 février 2012

Grand utilisateur de tickets de métro (émis par le STIF/RATP), je me suis posé la question un jour (oui, j'ai de ces idées pourries des fois) de savoir comment sont codées les informations sur ce support magnétique. L'idée m'a semblé intéressante à creuser, mais nécessitait un brin de bidouille électronique. L'occasion de tester mon nouveau fer à souder reçu à Noël, et de découvrir l'univers des bandes magnétiques et plus particulièrement celui des tickets de la RATP.

Les bandes magnétiques

J'ai fureté pendant des heures sur Internet, à la recherche de documentation sur la lecture des bandes magnétiques, ainsi que sur les normes de codage employées. J'ai déniché quelques papiers intéressants:

Ces trois ressources sont très intéressantes, le white paper du CCC étant celle qui m'a apporté le plus. J'en ai discuté au boulot avec Tixlegeek, et il m'a lui aussi soumis l'idée d'utiliser la carte son de mon ordinateur portable pour échantillonner les données stockées sur le ticket (à l'instar de ce qui est documenté dans le papier de Joseph Battaglia). Il s'agit là d'un hack archi-simple, vu qu'il suffit de trouver une tête de lecture d'un vieux lecteur cassette et de la souder sur un jack 3.5 puis de le brancher dans la prise micro. La carte son fera le reste.

En ce qui concerne le codage des données sur bande, celui-ci peut se faire de deux manières:

La première méthode est celle employée pour stocker des données audio, mais est peu robuste. La seconde permet de stocker de manière plus robuste, et indépendamment de la vitesse de lecture ou d'écriture, des données binaires. C'est celle qui est préférée dans la grande majorité des systèmes de stockage numériques sur bande.

Le codage de fréquence le plus classique a un joli surnom: F2F. Ou plus généralement connu sous le nom de Aiken Biphase. L'idée est de coder le 0 avec un signal de fréquence F, et le 1 avec un signal de fréquence 2F. L'avantage de ce codage, c'est que peu importe la vitesse de lecture (qui est variable selon les périphériques), le décodage reste fiable. Bon par contre ça complique un brin la phase de décodage. Patrick Gueulle explique très bien cela dans son livre "Carte magnétique et PC".

Traitement du signal

Le papier du CCC [2] présente deux programmes (nommés DAB et DMBS) visant à respectivement décoder le codage Aiken Biphase (F2F) et à extraire les données selon les normes ISO781X. Ces mêmes programmes ont été utilisés par K1wy dans [1]. Le souci c'est que ceux-ci ont été principalement conçus pour les cartes magnétiques rigides à trois pistes, pas forcément pour les tickets de métro. De plus, selon les réglages audio de l'ordinateur servant à la capture (Mic-Boost notamment). Il me fallait une solution plus malléable, car ces deux programmes sont écrits en C et pas forcément bien documentés.

J'ai trouvé plusieurs modules python permettant de réaliser la capture d'un signal sur l'entrée micro et le traitement de celui-ci:

A l'aide de ces modules, j'ai réalisé quelques scripts de capture de signal et de sauvegarde des données sous format PCM et WAV. l'avantage de pouvoir sauvegarder les captures audio est double: on peut facilement refaire des analyses et surtout on peut les lire avec des outils comme Audacity. J'ai donc réalisé un script de capture du signal, et un script de traitement visant à traiter le signal afin de mettre en évidence le codage fréquentiel.

J'ai réalisé les tests de capture à l'aide d'un micro, et utilisé pylab pour afficher l'allure du signal. Pour le coup, c'était assez efficace:

Il m'a fallut ensuite réaliser mon lecteur de bande magnétique à la manière du CCC, à l'aide d'un vieux lecteur de cassette audio, d'un fer à souder, d'un jack 3.5 et d'un cable IDE =). J'ai du sacrifier mon super lecteur CD/K7 de mes 18 ans (de toute façon je n'ai plus de K7), mais c'est pas grave.

Fabrication du lecteur de bande magnétique maison

Premièrement, j'ai démonté le lecteur pour pouvoir accéder à la partie électro-mécanique au niveau du lecteur de K7:

Une fois les têtes de lecture et d'écriture démontées, j'ai pu souder la tête de lecture (stéréo, mais on s'en moque) sur le jack 3,5mm. Bon la méthode de connection étant pas trop documentée, j'ai un peu galéré mais j'y suis arrivé. Pour information, si vous essayez de refaire le montage chez vous et que vous employez un jack 3,5mm stéréo, faites très attention au contact central: il délivre du 5V (cette tension est utile pour les microphones de type Electret). Pour ma part j'ai pas gazé car j'aurais du relier la sortie de la seconde bobine de lecture à la première, pour cumuler les tensions de sortie, alors que dans mon montage je l'ai mise à la masse (donc je n'utilise qu'une seule des deux bobines de lecture).

J'ai utilisé trois fils couplés d'un cable IDE, de manière à avoir de quoi manipuler la tête de lecture et l'éloigner de l'ordinateur portable (pour limiter les interférences). Bon au final ça n'a pas tellement aidé, le cable IDE faisant une merveilleuse antenne. Une meilleure solution serait d'utiliser un cable blindé à trois brins dont le blindage est mis à la masse. Une fois celui-ci connecté à mon ordinateur portable, j'ai pu balayer un ticket de métro et capturer avec mon précédent script le signal issu de la tête de lecture (signal brut).

Décodage du signal et analyse

A l'aide de ce lecteur de bande magnétique fait maison et de mes quelques scripts, j'ai codé un second script permettant de mettre en évidence les données codées sur la bande magnétique, et ça n'a pas été une paire de manche. J'ai réalisé trois décodages pour la forme:

Et voilà les données codées (Aiken Biphase, ou F2F) extraites des captures de signaux:

Le premier est issu d'un ticket neuf, et les deux suivants de deux tickets a priori validés à Saint Lazare (Paris). Il est flagrant de voir qu'un motif général est présent, motif que l'on retrouve en partie dans le ticket de métro neuf. Chose intrigante, le ticket de métro neuf possède moins d'information que les tickets compostés. J'en déduis donc que des données sont ajoutées lors de la validation, et qu'un ticket de métro neuf est codé d'une manière particulière.Si vous regardez bien, il y a de légères variations de période, mais cela est dû au fait que je scanne manuellement, et ma vitesse de balayage n'est pas constante.

J'ai donc entrepris de décoder les premiers bits significatifs communs aux trois tickets, et voici ce qui en ressort:

Ticket neuf:
11111111 00100 11111 11111 11111 0

Ticket #1:
11111111 00100 11111 00110 01001 0

Ticket #2:
11111111 00100 11111 00110 01110 1

Les espaces sont de mon fait ;). J'ai pu déduire le rôle de chacun (ou du moins tenter de deviner) à partir des informations décodées:

Si on regarde de plus près le codage, on peut voir que sur le premier ticket validé les troisième et quatrième séquences de 5 bits correspondent aux valeurs 69 et 6E. Ou plus précisemment aux valeurs 6 et 9, et 6 et 14. Cela ressemble bien aux codes de stations connus de Paris, tels que décrit sur Wikipédia. Cela signifie qu'un de mes tickets a été validé à Saint-Lazare même (code station 0609), l'autre à la station ayant pour code 0614 (inconnu sur Wikipédia oO). Le dernier bit que j'ai repéré semble être un bit de parité.

Le plus drôle dans l'histoire, c'est de voir que le ticket vierge n'a aucune valeur paramétrée, si ce n'est le type de ticket (première séquence de 5 bits). Donc facile à cloner.

Conclusion

La sécurité des tickets de métro ... heuu.... quelle sécurité ? Il est a priori aisé de dupliquer des tickets neufs (aucune limite dans le temps ni dans l'espace}, la seule limitation est technique (il faut un matériel particulier). De même, je n'ai pas détaillé ici le décodage complet mais la suite des bits stockés représentent très certainement la date de validation et l'heure de validation, mais je n'ai pas vraiment cherché à le décoder.

Heureusement qu'il y a le passe Navigo pour tous nous sauver (spéciale dédicace à Nono2357 ;).

17
févr.
'12

Jiwa v3, retour à la case départ

Publié le 17 février 2012

Je suis retourné sur Jiwa aujourd'hui. Je m'attendais à trouver ma plateforme de musique habituelle, mais je me suis rendu compte que cela faisait un bail que je n'y avais pas été. Le site a complètement changé de look et a été repris par *Allomusic* qui en a profité pour le PURifier à l'Hadopi. Vu que je m'étais bien intéressé à cette plateforme il y a de cela quelques années, je me suis demandé si les choses avaient changé. Et puis parce que cette satanée limitation de 30 secondes d'écoute m'a été insupportable.

Jiwa version 3

Comme en témoigne le code source de Jiwa, le site est passé en vesion 3:

<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# jiwavtrois: http://ogp.me/ns/fb/jiwavtrois#">

Jiwa avait fait de nombreux efforts il y a quelques années pour empêcher le téléchargement des fichiers MP3 et leur sauvegarde. J'avais d'ailleurs à l'époque rédigé quelques articles et développé une extension Firefox dédiée. Il semble donc raisonnable de penser que les protections mises en place dans le code sont identiques ou meilleures que les dernières implémentées.

J'ai donc repris mes anciens Proof of Concept, et je me suis attelé à la tâche. J'ai tout simplement réessayé mes outils, sans succès. J'ai donc sorti l'artillerie lourde (paros + firefox) et commencé à regarder les requêtes et là, ce fut la surprise. Une grosse suprise. Non seulement quelques détails ont changé, certes très minimes, mais j'ai pu retrouver des requêtes similaires à celles employées quelques années plus tôt sur Jiwa ... Comme si on avait fait un bond en arrière de quelques années.

C'est reparti comme en 40 !

Autant dans la version précédente de Jiwa, on pouvait toujours écouter sans limite, autant avec la version actuelle Jiwa ne sert que d'appât: les pages redirigent pour l'écoute vers le site d'Allomusic, qui bride énormément les contenus et force un poil la main pour amener le visiteur à prendre un compte Premium.

Il faut dire qu'à l'époque où je m'étais interessé à Jiwa, l'écoute en ligne était toujours possible, ce qui par ailleurs me satisfaisait pleinement. Mais dans la configuration actuelle, je trouve ça un brin décevant de se servir de l'ancienne image de Jiwa pour essayer de remonter l'image d'un autre service.

On s'éloigne quand même du modèle original de Jiwa, et même de celui de Deezer: il ne s'agit plus de streaming gratuit en ligne reposant sur de la pub mais d'une version lite qui vise à convertir les utilisateurs en comptes Premium. Certainement une preuve que le modèle économique qui se veut reposer sur la pub n'est pas viable, comme a pu l'expérimenter Deezer. Sans parler des tarifs faramineux demandés par les majors comme Universal Music.

Alors bon, je ne suis pas un fervent adepte de ce système, bon nombre de lecteurs le savent, et j'ai adapté mes précédents scripts pour prendre en charge la version actuelle de Jiwa. Plusieurs restrictions ont été mises en œuvres pour éviter le téléchargement:

Le détail amusant c'est que même si l'écoute est limitée à 30 secondes, le fichier MP3 complet est téléchargé. Le bridage par user-agent est même complètement inefficace, je soupçonne même le script distant de bugger quasi aléatoirement ...

iJaw nouvelle génération

Pour ceux qui seraient intéressés, je diffuse ci-après le code source de mon Proof of Concept amélioré. Ce bout de code python est un poil sale, mais je ne compte pas le maintenir. C'est une preuve de concept, juste présente pour démontrer ce que j'affirme dans ce billet.

"""
Crapy Jiwa download tool
"""

import re
import sys
import json
import time
from hashlib import md5
from random import choice
import urllib,httplib,urllib2

def filter(s):
        return s.replace('\n',' ')

def randomUA():
        rstr = ''.join([choice('0123456789') for i in range(32)])
        return 'Mozilla Firefox (%s)'%rstr

def getSongDetails(sid):
        r = urllib2.Request('http://www.jiwa.fr/song/%d/play/'%sid)
        resp = urllib2.urlopen(r).read()
        return json.loads(resp)

def lookupSongs(keywords):
        try:
                r = urllib2.Request('http://www.jiwa.fr/search?q=%s'%urllib.quote(keywords))
                resp = urllib2.urlopen(r).read()
                matches = re.findall('/track/.*-([0-9]+)\.html',resp)
                res = []
                for match in list(set(matches))[:5]:
                        res.append(getSongDetails(int(match)))
                return res
        except urllib2.Error,e:
                return None


def dlTrack(sid,filename):
        errors = 0
        while errors<3:
                token = getToken(sid)
                _t = token.split('=')
                url = 'http://m.jiwa.fm/play.php'
                l1 = 'gwqd29ydg7sqys_qsh0'
                l3 = _t[0]
                l4 = _t[1]
                l5 = _t[2]
                l6 = _t[3]
                l7 = md5(l1).hexdigest()
                l8 = md5(l3+l7+str(sid)).hexdigest()
                url = "%s?r=%s&s=%s&t=%s&m=%s&from=1" % (url,l4,str(sid),l8,l6)

                r = httplib.HTTPConnection('m.jiwa.fm:80')
                r.putrequest('GET',url)
                r.putheader('Host','m.jiwa.fm:80')
                r.putheader('User-Agent',randomUA())
                r.endheaders()
                token = r.getresponse().read()
                if token=='-1':
                        errors += 1
                        time.sleep(1)
                else:
                        f = open(filename,'wb')
                        f.write(token)
                        f.close()
                        return True
        return False

def getToken(sid):
        params = *
        's':str(sid),
        *
        p = urllib.urlencode(params)

        r = httplib.HTTPConnection('m.jiwa.fm:80')
        r.putrequest('POST','http://m.jiwa.fm/token.php?r=1')
        r.putheader('Host','m.jiwa.fm:80')
        r.putheader('User-Agent',randomUA())
        r.putheader('Content-Type','application/x-www-form-urlencoded')
        r.putheader('Content-Length',str(len(p)))
        r.endheaders()
        r.send(p)

        token = r.getresponse().read()
        return token


if __name__ == '__main__':

        print '[>] iJaw - A Jiwa search & download tool'
        print '[>] author: virtualabs (http://www.virtualabs.fr)'
        print '[>]'

        if len(sys.argv)>2:
                operator = sys.argv[1]
                if operator == 'search':
                        kw = ' '.join(sys.argv[2:])
                        print '[+] Looking for songs ...'
                        tracks = lookupSongs(kw)
                        if tracks:
                                if len(tracks)>0:
                                        print ''
                                        print 'Song ID  - Song Name [Artist, Album]'
                                        print ''
                                        for t in tracks:
                                                print '%s - %s [%s, %s]' % (t['songId'],filter(t['songName']),filter(t['artistName']),filter(t['albumName']))
                                        print ''
                                        print '%d songs found' % len(tracks)
                                else:
                                        print '[!] No songs found.'
                        else:
                                print '[!] No songs found.'
                elif operator == 'get':
                        tracks = [t for t in sys.argv[2:]]
                        for sid in tracks:
                                print '[+] getting track info ...'
                                _track = getSongDetails(int(sid))
                                filename = '%s_%s_%s.mp3' % (_track['artistName'].replace(' ','_'),_track['albumName'].replace(' ','_'),_track['songName'].replace(' ','_'))
                                print '[+] downloading track ...'
                                if dlTrack(sid,filter(filename)):
                                        print '[i] saved to %s.' % filter(filename)
                                else:
                                        print '[!] Unable to download song.'
                elif operator == 'dl':
                        kw = ' '.join(sys.argv[2:])
                        tracks = lookupSongs(kw)
                        if tracks:
                                for t in tracks:
                                        _track = getSongDetails(t['songId'])
                                        filename = '%s_%s_%s.mp3' % (_track['artistName'].replace(' ','_'),_track['albumName'].replace(' ','_'),_track['songName'].replace(' ','_'))
                                        print '[+] downloading song "%s [%s]" ...' % (_track['songName'],_track['artistName'])
                                        if dlTrack(int(t['songId']),filter(filename)):
                                                print '[i] saved to %s.' % filter(filename)
                                        else:
                                                print '[!] Failed to download song: %s' % _track['songName']
                                print '[+] Done.'
                        else:
                                print '[!] No songs found'
        else:
                print '[i] usage: %s [search|get|dl] [second arg]'
                print '[i]'
                print '[i] + you can look for songs:'
                print '[i]'
                print '[i]  $ ijaw.py search Madonna like a virgin'
                print '[i]  $ 12345 - Like a virgin [Madonna, ...]'
                print '[i]'
                print '[i] + and download a given song with its ID:'
                print '[i]  $ ijaw.py get 12345'
                print '[i]'
                print '[i] + or download all tracks found:'
                print '[i]'
                print '[i]  $ ijaw.py dl Madonna like a virgin'
                print '[i]'
                print '[i] + Have phun !'

Conclusion

Il est étonnant que le passage de Jiwa sous la houlette d'Allomusic n'ait pas introduit un nouveau système plus fiable de lecture de musique en ligne, et il est franchement dommage qu'une plateforme labellisée PUR force autant la main de l'utilisateur vers les comptes payants, en tentant de promouvoir l'écoute gratuite mais très limitée. OK, le modèle économique basé sur les pubs n'est pas forcément viable, mais on est pas QUE des vaches à lait. A bon entendeur ...



Les contenus disponibles sur ce blog sont publiés sous licence Creative Commons BY-NC-SA.
Vous pouvez réutiliser tout ou partie de ces contenus à condition de citer l'auteur et l'origine, vous ne pouvez en faire une utilisation commerciale, et enfin vous devez partager tout travail ou œuvre dérivée sous les mêmes conditions — c'est-à-dire avec la même licence d'utilisation Creative Commons.