PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Python a le don d’Ubiquité : Multiprocessing

dimanche 2 février 2014 à 17:41

Ceci est un post invité de Foxmask posté sous licence creative common 3.0 unported.

Tout récemment j’ai voulu donner un coup de fouet à mon script de traitement de Trigger Happy (que je fais tourner sur ma “raspberry pi” parce que chuis un w4rl0rdZ:P) que j’estimais être trop long dans ses traitements de données.

Actuellement avec Trigger Happy, j’ai 30 sources de données (essentiellement des flux rss), que je parcours, et quand un nouvel item arrive, je l’envoi à pétaouchnock (Evernote ;) Le tout prend 7min, soit 14secondes par source. La loose totale.

Voici le corps du délit :

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import datetime
import time
 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_th.settings")
from django_th.services import default_provider
from django_th.models import TriggerService
from django.utils.log import getLogger
 
# create logger
logger = getLogger('django_th.trigger_happy')
 
def go():
    """
        run the main process
    """
    trigger = TriggerService.objects.filter(status=True)
    if trigger:
        for service in trigger:
[...]
    else:
        print "No trigger set by any user"
 
 
def main():
    default_provider.load_services()
    # let's go
    go()
 
if __name__ == "__main__":
 
    main()

Mais avant que je ne me penche sur le code du script pour l’améliorer, je me suis dit que plutôt que de chercher à corriger un problème, autant chercher la source de celui-ci d’abord (normal hein).

Un HTOP m’a révélé :

  1. que le CPU était à 100% tout le temps, que le script tourne ou pas
  2. quye la raison était double : rabittmq et celery…

Une fois shootés ces derniers, tout va pour le mieux :P
Je ne dis pas que ceux ci sont de la merde, mais que, pour mon cas, la crontab se suffit à elle-même.

Donc une fois désinstallés c’est 2 (sur)consommateurs de ressources, je relance le script pour tomber à un temps de traitement à 5mn

2014-02-02 14:40:51,693 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Sam et Max nothing new
2014-02-02 14:40:53,865 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de Numerama nothing new
2014-02-02 14:40:56,013 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de La Ferme du Web nothing new
2014-02-02 14:41:01,005 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Paulds nothing new
2014-02-02 14:41:20,098 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux d'un Odieux Connard nothing new
2014-02-02 14:41:22,142 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Strict minimum nothing new
2014-02-02 14:41:25,868 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Humeurs Illustrées nothing new
2014-02-02 14:41:33,497 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux du Journalisme Total nothing new
2014-02-02 14:41:35,658 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Kernel Panic nothing new
2014-02-02 14:41:44,897 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de AngularJS nothing new
2014-02-02 14:41:49,016 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Odeon nothing new
2014-02-02 14:41:54,186 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Nicolargo nothing new
2014-02-02 14:42:12,525 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de JEEK nothing new
2014-02-02 14:42:21,349 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Recher nothing new
2014-02-02 14:42:31,266 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Un blog d'adminsys Libres nothing new
2014-02-02 14:42:35,824 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Le bloc-notes de Gee nothing new
2014-02-02 14:42:36,647 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de mere code (atom) nothing new
2014-02-02 14:42:39,616 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Alex Mac Caw nothing new
2014-02-02 14:42:42,985 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Yearofmoo Articles nothing new
2014-02-02 14:43:42,732 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Novapost nothing new
2014-02-02 14:43:46,722 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Les Numériques nothing new
2014-02-02 14:43:58,303 INFO fire 5142 date 2014-02-02 13:00:00 >= date triggered 2014-02-02 09:02:36 title Test du Quechua Phone 5, le smartphone des montagnards ?
2014-02-02 14:44:08,010 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Frandroid = 1 new data
2014-02-02 14:44:17,624 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de TechCrunch Mobile nothing new
2014-02-02 14:44:20,339 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux Django annonces nothing new
2014-02-02 14:44:20,744 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - La Hyène - Python nothing new
2014-02-02 14:44:24,237 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de PCInpact nothing new
2014-02-02 14:44:29,055 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - PointGPhone nothing new
2014-02-02 14:44:31,299 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HumanCoders Python nothing new
2014-02-02 14:44:52,751 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HauteFeuille Lab (python) nothing new
2014-02-02 14:44:58,850 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Python Plone Planet nothing new

Comme je suis un éternel insatisfait de bibi, j’ai cherché des moyens un peu partout, jusqu’à ce que Sam me souffle une suggestion ;)

A présent donc une version modifiée pour exploiter le multiprocessing est la suivante :

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import datetime
import time
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "th.settings")
 
from django_th.services import default_provider
from django_th.models import TriggerService
from django.utils.log import getLogger
 
# create logger
logger = getLogger('django_th.trigger_happy')
 
def go(service):
    """
        run the main process
    """
    [...]
 
 
def main():
    default_provider.load_services()
    # let's go
    trigger = TriggerService.objects.filter(status=True)
    if trigger:
        from multiprocessing import Pool
        pool = Pool(processes=5)
        result = pool.map(go, trigger)
    else:
        print "No trigger set by any user"
 
if __name__ == "__main__":
 
    main()

fait tomber le temps de traitement à … 1min …:

$ date && ./fire.sh && date 
dimanche 2 février 2014, 14:58:38 (UTC+0100)
2014-02-02 14:58:48,221 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de La Ferme du Web nothing new
2014-02-02 14:58:48,243 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Humeurs Illustrées nothing new
2014-02-02 14:58:48,256 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Kernel Panic nothing new
2014-02-02 14:58:48,283 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Sam et Max nothing new
2014-02-02 14:58:48,907 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux d'un Odieux Connard nothing new
2014-02-02 14:58:49,267 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Paulds nothing new
2014-02-02 14:58:49,446 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux du Journalisme Total nothing new
2014-02-02 14:58:49,713 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de Numerama nothing new
2014-02-02 14:58:49,847 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Strict minimum nothing new
2014-02-02 14:58:50,209 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Odeon nothing new
2014-02-02 14:58:50,353 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de AngularJS nothing new
2014-02-02 14:58:50,830 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Un blog d'adminsys Libres nothing new
2014-02-02 14:58:51,338 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de JEEK nothing new
2014-02-02 14:58:51,396 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Nicolargo nothing new
2014-02-02 14:58:51,476 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Yearofmoo Articles nothing new
2014-02-02 14:58:51,735 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Le bloc-notes de Gee nothing new
2014-02-02 14:58:52,148 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de mere code (atom) nothing new
2014-02-02 14:58:52,640 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Recher nothing new
2014-02-02 14:58:52,971 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Alex Mac Caw nothing new
2014-02-02 14:58:53,416 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Les Numériques nothing new
2014-02-02 14:58:53,474 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de TechCrunch Mobile nothing new
2014-02-02 14:58:53,870 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Novapost nothing new
2014-02-02 14:58:54,072 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - PointGPhone nothing new
2014-02-02 14:58:54,316 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux Django annonces nothing new
2014-02-02 14:58:54,853 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - La Hyène - Python nothing new
2014-02-02 14:58:55,111 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HumanCoders Python nothing new
2014-02-02 14:58:55,222 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Frandroid nothing new
2014-02-02 14:58:55,380 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HauteFeuille Lab (python) nothing new
2014-02-02 14:58:55,696 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de PCInpact nothing new
2014-02-02 14:59:02,214 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Python Plone Planet nothing new
dimanche 2 février 2014, 14:59:02 (UTC+0100)

Comme on l’aura remarqué la différence entre les 2 versions est l’appel fait à a fonction go

avant :

def main():
    default_provider.load_services()
    # let's go
    go()

après :

def main():
    default_provider.load_services()
    # let's go
    trigger = TriggerService.objects.filter(status=True)
    if trigger:
        from multiprocessing import Pool
        pool = Pool(processes=5)
        result = pool.map(go, trigger)
    else:
        print "No trigger set by any user"

du coup l’appel de la fonction “go” implique de changer sa signature en lui filant comme argument “trigger” (le QuerySet de l’appli Django)

A présent donc pool.map fait l’itération des données trouvées dans le modele TriggerService et exécute tout le tintouin. *<:o)

ps : @Sam : chose promise chose dûe ;)

flattr this!

Un objet proxy : ce que c’est et à quoi ça sert

samedi 1 février 2014 à 18:18

Un objet proxy est un objet qui prend un autre objet en paramètre et le sauvegarde dans un de ses attributs. Quand on appelle les méthodes du proxy, le proxy appelle la même méthode de l’objet qu’il a en attribut, et retourne le résultat. Quand on set/get/delete un attribut du proxy, il fait la même chose sur l’autre objet.

Voilà une implémentation très basique d’un objet proxy en Python :

class Proxy(object):
 
    def __init__(self, obj):
        # L'objet passé en paramètre est
        # sauvegardé dans un attribut.
        # On le fait en utilisant 
        # object.__setattr__, qui est le
        # __setattr__ du parent, et non directement
        # en faisant self._obj = obj
        # afin d'éviter une boucle infinie car
        # nous écrasons __setattr__ plus bas.
        object.__setattr__(self, "_obj", obj)
 
    # On écrase les méthodes magiques __getattribute__
    # (qui est appelée quand on faire self.nom_attribut), 
    # __delattr__ (qui est appelée quand on fait 
    # del self.nom_attribut) et __setattr__ (qui est 
    # appelée quand on fait self.nom_attribut = truc)
    def __getattribute__(self, name):
        return getattr(object.__getattribute__(self, "_obj"), name)
    def __delattr__(self, name):
        delattr(object.__getattribute__(self, "_obj"), name)
    def __setattr__(self, name, value):
        setattr(object.__getattribute__(self, "_obj"), name, value)

Ca s’utilise comme ceci :

class UnObjetOrdinnaire(object):
 
    attribut = 'VALEUR !'
 
    def methode(self, param):
        return param * 2
 
 
objet_ordinnaire = UnObjetOrdinnaire()
# on passe l'objet ordinnaire au proxy
objet_proxy = Proxy(objet_ordinnaire)
 
# Accéder à des méthodes et attribut
# du proxy accède à ceux de l'objet
# derrière le proxy
print(objet_proxy.attribut)
## VALEUR !
 
print(objet_proxy.methode(3))
## 6
 
# Modifier l'objet proxy modifie
# l'objet derrière le proxy
objet_proxy.attribut = 'une autre valeur'
 
print(objet_ordinnaire.attribut)
## une autre valeur

Pour un exemple vraiment à l’épreuve des balles, il faut prendre en compte tout un tas de cas particuliers, ce qui fait qu’il est bien plus rentable d’utiliser une lib solide pour ça.

Attention, un objet proxy peut très bien avoir des méthodes qui n’appellent pas celles de l’objet derrière. On même avoir des méthodes qui appellent des méthodes qui n’ont pas le même nom, ou plusieurs méthodes… Ce que vous voyez en exemple est un proxy très basique.

Pourquoi voudrait-on obtenir ce résultat ?

Plusieurs design patterns font appel à des objets proxy. Par exemple, le design pattern “adapter” consiste à créer un objet proxy qui accepte plusieurs types d’objets en paramètres afin de présenter toujours la même interface.

Imaginez que vous ayez plusieurs objets qui servent à s’authentifier :

 
class Login(object):
 
    _is_logged = False 
 
    def is_logged(self):
        return self._is_logged
 
    def login(self, email, password):
        self._is_logged = True
 
 
class LoginWithUsername(object):
 
    _is_logged = False 
 
    def is_logged(self):
        return self._is_logged
 
    def login(self, username, password):
        self._is_logged = True
 
 
class SignIn(object):
 
    _is_logged = False 
 
    def is_logged(self):
        return self._is_logged
 
    def signin(self, email, password):
        self._is_logged = True
 
 
class LoginWithKey(object):
 
    _is_logged = False 
 
    key = "jfjkdlmqfjdmqsjdk"
 
    def is_logged(self):
        return self._is_logged
 
    def login(self, username):
        self._is_logged = True

Et vous avez un algo de parsing, qui attend un objet d’authentification. Si vous mettez le code qui permet de choisir la bonne API dans l’algo de parsing, vous liez l’algo (qui n’a rien à voir avec l’authentification) à toutes ces implémentations.

Une des manières de faire, est d’utiliser un adaptateur, dont voici une esquisse :

class AuthAdapter(object):
 
 
    def __init__(self, obj):
        object.__setattr__(self, "_obj", obj)
 
    def __getattribute__(self, name):
 
        try:
            # Si l'attribut existe sur le proxy, on l'utilise
            return object.__getattribute__(self, name)
        except AttributeError:
            # Sinon on tente le coup sur l'objet derrière le proxy
            return getattr(object.__getattribute__(self, "_obj"), name)
 
 
    def login(self, id_=None, secret=None):
        # Le login est différent pour chaque classe,
        # donc on s'arrange avec.
        try:
            self._obj.login(id_, secret)
        except AttributeError:
            self._obj.signin(id_, secret)
        except TypeError:
            self._obj.login(id_)

En gros, si on essaye d’appeler login(), il va lisser les contours et nous donner toujours la même interface, même si derrière l’objet peut marcher complètement différement. En revanche, si on appelle n’importe quel autre attribut ou méthode (par exemple is_logged, mais il pourrait y en avoir des dizaines d’autres dans la vrai vie vivante), ça tape directement dans l’objet derrière le proxy.

Donc si j’applique l’adaptateur systématiquement, quelle que soit la classe derrière, le comportement est toujours le même : j’appelle login(un id, un secret), et il se logge.

all_auth = (Login, LoginWithUsername, SignIn, LoginWithKey)
 
for auth in all_auth:
    # 'auth' est une des 4 classes de login. On l'instancie et
    # on met l'instance derrière le proxy
    auth = AuthAdapter(auth())
    print("Testing '%s'" % auth.__class__.__name__)
    print("Is logged : %s" % auth.is_logged())
    # Le login se passe toujours de la même manière, quelle que soit la classe
    auth.login('id', 'secret')
    print("Is logged after logging : %s" % auth.is_logged())

Comme d’habitude, ceci est un exemple naval. Il est bateau quoi. Mais cela vous démontre le principe.

Le design pattern façade ressemble à l’adapter, en fait c’est une spécialisation de l’adapter. Il s’agit juste d’exposer une interface plus simple, de l’objet derrière le proxy.

Un proxy peut aussi servir à hacker une lib. Par exemple, la lib attend un objet d’une ancienne version d’une autre lib dont l’auteur a déprécié un attribut. Avec un proxy, vous pouvez toujours faire semblant que l’attribut est toujours là : enrobez l’objet dans un proxy qui possède cet attribut, tout le reste de l’API sera la même.

Un proxy peut également être utile si vous voulez effectuer des actions quand l’objet est manipulé.

import logging
 
class ProxyLogger(object):
 
    def __init__(self, obj):
        object.__setattr__(self, "_obj", obj)
 
    def __getattribute__(self, name):
        obj = object.__getattribute__(self, "_obj")
        # On lance un warning à chaque accès à un attribut de l'objet
        # derrière le proxy
        logging.warning("%s.%s has been called" % (obj.__class__.__name__, name))
        return getattr(obj, name)

Maintenant, supposons que vous avez un objet d’une lib externe (que vous ne pouvez donc pas modifier) sur lequel vous avez besoin d’infos :

class ObjetExterne(object):
    def ahahah(self):
        pass
 
o = ProxyLogger(ObjetExterne())
o.ahahah()
## WARNING:root:ObjetExterne.ahahah has been called

Vous pouvez refiler le proxy à n’importe quel objet de sa lib d’origine, si le proxy est bien fait, elle ne vera pas la différence.

On finit sur une note culture, puisque le pattern decorator utilise souvent aussi un proxy. Cette fois d’une fonction sur un autre objet (en générale une autre fonction). Mais le principe est le même. Donc quand vous voyez @un_decorateur, il est peut être en train d’appliquer un proxy à la fonction.

D’ailleurs on dit souvent que le proxy décore l’objet qui est derrière.

flattr this!

Python, meilleur nul part, excellent partout

vendredi 31 janvier 2014 à 14:57

Je ne sais pas si vous l’aviez compris, mais j’aime bien Python. J’ai essayé de vous le cacher jusqu’ici, mais je sais que mon secret ne tiendra pas longtemps, alors j’avoues tout.

Je n’aime pas Python parce que c’est le meilleur langage pour X. Je ne pense pas que Python soit le meilleur langage pour quoi que ce soit. Regex ? Perl est meilleur ? IA ? Lisp est meilleur. Embarqué ? C est meilleur. Asynchrone ? Javascript est meilleur.

Non. J’aime Python parce qu’il est très bon à X. Presque tous les X. En fait, il existe très peu de X pour lequel Python n’est pas bon.

Python est excellent pour analyser du texte, pour faire un site Web (même asynchrone), de l’administration système, des interfaces graphiques, du calcul scientifique, de la 3D, des crawlers, etc. Pas le meilleur. Mais très bon.

Et ceci est fantastique. Cela veut dire que si je commence un projet, n’importe quel projet, je sais que je peux le commencer en Python. Je ne serai pas bloqué plus tard par tel ou tel besoin. Je sais que je pourrai m’en sortir agréablement avec Python.

Vous faites un site en JS ? Pour le sysadmin, il va falloir un autre langage.

Des scripts de maintenance en Perl ? J’espère que vous n’aurez pas besoin d’une UI.

Un crawler en PHP ? Pour l’analyse des données récupérer, vous aller vous amuser…

De l’extraction de data en Ruby ? Pourvu qu’il ne faille pas traiter plusieurs To.

Ce n’est pas que vous ne pourrez pas. Ils sont tous Turring complet après tout. Mais vous allez vraiment galérer.

Aucun langage n’est parfait, et Python n’est pas une exception.

Mais ce qui est génial, c’est que dans 90% des cas, vous pouvez avoir une solution propre et efficace en Python au problématique que vous ne savez même pas encore que vous aurez. Et le jour où vous atteindrez la limite, alors oui, vous pourrez utiliser une autre techno pour le faire. Mais juste pour cette partie. La plupart du temps ce sera une partie très réduite. En en plus, Python pourra probablement s’interfacer nativement avec le code de l’autre langage.

Il y a des tas de raisons de ne pas choisir Python bien entendu, mais probablement la meilleure raison de le choisir, c’est qu’il y a peu de chance de se planter.

Bon, évidement si vous voulez écrire un driver de carte graphique…

flattr this!

ERROR: invalid locale name avec PostGres

jeudi 30 janvier 2014 à 14:12

Il suffit de redémarrer PostGres.

Ca arrive quand on met à jour ses locales et que des services tournent. Ils ne reçoivent pas l’information, et utilisent l’ancienne locale.

Oui, j’avais besoin d’un poste bouche-trou.

flattr this!

Solution de l’exercice d’hier sur shadow

mercredi 29 janvier 2014 à 16:25

Ça va de soit, mais ça va mieux en le disant, ceci n’est pas la solution unique de l’exercice d’hier, mais une solution possible parmi d’autres.

On note l’usage de crypt, qui évite de se faire chier à trouver le bon algo de hashing et gère le salt automatiquement. spwd, c’est vraiment pour le grosses larves comme moi qui veulent même pas faire un split.

Et c’est du Python 3, yo dog !

import io
import os
import crypt
import spwd
 
from urllib.request import FancyURLopener
from zipfile import ZipFile
 
PASSWORDS_SOURCE = "http://xato.net/files/10k%20most%20common.zip"
PASSWORDS_LIST = '10k most common.txt'
 
# Le fichier ZIP est derrière cloudflare, qui vous ferme la porte au nez si
# vous n'avez pas de User-Agent. On va donc créer un UrlOpener, un objet qui
# ouvre des ressources en utilisant leurs URLs, qui a un User-Agent 'TA MERE'.
# CloudFlare ne check pas que le UA est valide.
class FFOpener(FancyURLopener):
   version = 'TA MERE'
 
# Si le dictionnaire de passwords n'est pas là, on le télécharge
# via FFOpener().open(PASSWORDS_SOURCE).read(). C'est verbeux, c'est urllib.
# Normalement je ferais ça avec requests. Ensuite on lui donne une interface
# file-like object avec io.BytesIO pour que ZipFile puisse le traiter en mémoire
# sans avoir à le sauvegarder dans un vrai fichier sur le disque, et on
# extrait le ZIP.
if not os.path.isfile(PASSWORDS_LIST):
    ZipFile(io.BytesIO(FFOpener().open(PASSWORDS_SOURCE).read())).extractall()
 
# On extrait les mots de passe de la liste sous forme de tuple car c'est rapide
# à lire. Un petit rstrip vire les sauts de ligne.
passwords = tuple(l.rstrip() for l in open(PASSWORDS_LIST))
 
# spwd.getspall() nous évite de parser le fichier shadow à la main.
for entry in spwd.getspall():
    print('Processing password for user "%s": ' % entry.sp_nam, end='')
 
    # Pas de hash ? On gagne du temps avec 'continue'
    if not '$' in entry.sp_pwd:
        print('no password hash to process.')
        continue
 
    # On teste chaque password avec la fonction crypt, qui accepte en deuxième
    # paramètre le hash du mot de passe complet. Pas besoin de se faire chier
    # à le spliter, il va analyser les '$' et se démerder avec ça. On a juste
    # à comparer le résultat avec le hash d'origine.
    for pwd in passwords:
        if crypt.crypt(pwd, entry.sp_pwd) == entry.sp_pwd:
            print('password is "%s".' % pwd)
            # On break pour gagner quelques tours de boucles, et pouvoir
            # utiliser la condition 'else'.
            break
    else:
        print('fail to break password.')

Télécharger le code.

flattr this!