From 1a28a1102759d0f3e6c5de7a1f80e4434e79db3e Mon Sep 17 00:00:00 2001 From: bernat Date: Wed, 9 Nov 2005 08:57:07 +0100 Subject: [PATCH] =?UTF-8?q?Plus=20utilis=C3=A9...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit darcs-hash:20051109075707-d1718-e897495d7de3b71faf67dea88a300a506b128637.gz --- gestion/wifi-update.py | 311 ----------------------------------------- 1 file changed, 311 deletions(-) delete mode 100755 gestion/wifi-update.py diff --git a/gestion/wifi-update.py b/gestion/wifi-update.py deleted file mode 100755 index 634822ee..00000000 --- a/gestion/wifi-update.py +++ /dev/null @@ -1,311 +0,0 @@ -#! /usr/bin/env python -# -*- coding: iso-8859-15 -*- - -# Ce script est un serveur SSL permettant aux bornes d'exécuter -# certains scripts. - -# Les scripts sont découpés en deux catégories : -# - ceux exécutés au démarrage de la borne -# - ceux à exécuter au plus tôt (pour une mise à jour) - -# Ils se trouvent dans /etc/wifi/wifi-update (ou dans le contenu de la -# variable ROOT). Ce répertoire contient plusieurs sous-répertoires. Chaque -# sous-répertoire correspond et est nommé selon le nom de la borne -# (ex : valhalla.wifi.crans.org). - -# Dans chacun de ces répertoires, il doit y avoir des scripts qui commencent -# par 0 et des scripts qui commencent par un autre chiffre. Les scripts -# qui commencent par 0 sont ceux exécutés au démarrage de la borne et les autres -# sont ceux devant être mis à jour. - -# Comme un certain nombre de bornes partagent les même scripts, ceux-ci pourront -# être des liens symboliques. Enfin, si une borne n'est pas présente dans -# l'arborescence, on utilise le répertoire "unknown". - -# Le serveur accepte trois commandes : -# BOOT qui permet d'obtenir le script de boot -# UPDATE qui permet d'avoir le script de mise à jour -# RESET qui permet de dire que l'on a bien reçu le script de mise à jour -# et qu'il peut donc être effacé sur le serveur - -# La transmission d'un script se termine par un "." isolé sur une ligne. - -# La création des scripts et leur gestion est laissée à un programme externe. - -# Lancement : twistd -n -y wifi-update.py --pidfile=/var/run/wifi-update.pid -# Pas de -n pour qu'il passe en fond - -# TODO: meilleure gestion des erreurs - -import time, os -import commands,re - -# On utilise twisted -# http://twistedmatrix.com/documents/current/howto/tutorial/intro -from twisted.internet import protocol, reactor, defer, utils -from twisted.internet.ssl import ContextFactory -from twisted.protocols import basic -from twisted.application import internet, service -from twisted.python import log - -import sys, resource -sys.path.append('/usr/scripts/gestion') - -# LDAP -from ldap_crans import crans_ldap - -# Divers -from iptools import AddrInNet -from OpenSSL import SSL -import re, errno, tempfile -from stat import * - -class ServerContextFactory(ContextFactory): - - def getContext(self): - """Création d'un contexte SSL côté serveur.""" - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_certificate_file('/etc/ssl/certs/nectaris.pem') - ctx.use_privatekey_file('/etc/ssl/private/nectaris.pem') - return ctx - -class UpdateProtocol(basic.LineReceiver): - """Protocole de communication pour mettre à jour une borne wifi. - - Trois commandes sont possibles : - BOOT qui permet d'obtenir le script de boot - UPDATE qui permet d'obtenir le script de mise à jour - RESET qui permet de confirmer la bonne réception de ce dernier - """ - def lineReceived(self, ligne): - ligne = ligne.strip() - if ligne == 'BOOT': - self.do_BOOT() - elif ligne == 'UPDATE': - self.do_UPDATE() - elif ligne == 'RESET' or ligne == 'ACK': - self.do_RESET() - else: - self.sendError("`%s' is an unknown command for this server." % ligne) - - def sendError(self, error): - """Renvoie une erreur sous forme de script.""" - if error.__class__.__name__ != 'str': - error = error.getTraceback() - for ligne in error.split("\n"): - self.sendLine("# %s" % ligne) - self.sendLine(".") - # Le plus simple est de couper la connexion - self.transport.loseConnection() - # On loggue aussi niveau serveur - print error - - def do_BOOT(self): - """Répond à une commande de type BOOT""" - d = self.factory.getBootScripts(self.transport.getPeer().host) - d.addCallback(self.sendScript) - d.addErrback(self.sendError) - - def do_UPDATE(self): - """Répond à une commande de type UPDATE""" - d = self.factory.getUpdateScripts(self.transport.getPeer().host) - d.addCallback(self.sendScript) - d.addErrback(self.sendError) - - def do_RESET(self): - """Répond à une commande de type RESET""" - d = self.factory.reset(self.transport.getPeer().host) - d.addCallback(self.sendScript) - d.addErrback(self.sendError) - - def sendScript(self, script): - """Renvoie un script arbitraire.""" - if len(script) > 0: - for ligne in script.split("\n"): - if ligne != ".": - self.sendLine(ligne) - else: - self.sendLine("..") - else: - self.sendLine("") - self.sendLine(".") - -class UpdateFactory(protocol.ServerFactory): - """Backend du serveur. Fournit les scripts.""" - protocol = UpdateProtocol - - BASE = "/etc/wifi/wifi-update" - - def reset(self, host): - """Efface le contenu éventuel du répertoire retry. - - Il n'y a pas de lock mis en place pour cette opération : - aucun processus externe n'est censé écrire là-dedans.""" - def reset_host(self, host): - # ETAPE 1 - # On commence par résoudre "host". - return defer.succeed(reset_del(self, RE.get_reverse(host))) - - def reset_del(self, host): - # ETAPE 2 - # On efface - try: - os.chdir('%s/%s' % (self.BASE, host)) - except OSError, e: - if e.errno != errno.ENOENT or host == "unknown": raise - os.chdir('%s/%s' % (self.BASE, "unknown")) - # On regarde si le répertoire retry existe - if not os.path.isdir('retry'): - os.mkdir('retry') - for f in os.listdir('retry'): - try: - os.remove('retry/%s' % f) - except OSError: - pass - return "" # Aucun script à retourner - - return reset_host(self,host) - - def getBootScripts(self, host): - """Retourne les scripts de boot. - - Les scripts commençant par 0 sont sélectionnés. Lorsque l'on - accuse réception de la commande, ce seront les scripts de mise - à jour présents lors de la commande qui seront effacés car - ils sont considérés comme inclus dans les scripts de démarrage. - """ - return self.getScriptsAndDelete("^0.*[^~]$", "^[1-9].*[^~]$", host) - - def getUpdateScripts(self, host): - """Retourne les scripts de mise à jour.""" - return self.getScriptsAndDelete("^[1-9].*[^~]$", "^[1-9].*[^~]$", host) - - def getScriptsAndDelete(self, getre, delre, host): - """Retourne un ensemble de scripts et enregistre à la suppression un autre ensemble. - - Les scripts correspondant à l'IP `host' et à l'expression - régulière `getre' sont concatanés dans l'ordre et renvoyé - et ceux correspondant à l'expression régulière `delre' sont - marqués à la suppression. - - En pratique, c'est un peu plus compliqué. Les scripts communs - à delre et getre sont placés dans un sous-répertoire retry, - ceux qui sont uniquement dans delre sont effacés. - - Si le répertoire retry n'est pas vide, son contenu est envoyé - et rien d'autre n'est fait. - """ - def getSAD_host(self, getre, delre, host): - # ETAPE 1 - # On commence par résoudre "host". - return getSAD_lock(self, getre, delre, RE.get_reverse(host)) - - def getSAD_lock(self, getre, delre, host): - # ETAPE 2 - # Plus de lock pour le moment... - d = defer.succeed(None) - d.addCallback(lambda _: getSAD_script(self, getre, delre, host)) - return d - - def getSAD_script(self, getre, delre, host): - # ETAPE 3 - # On fait le reste du boulot et on retourne le script à exécuter - try: - os.chdir('%s/%s' % (self.BASE, host)) - except OSError, e: - if e.errno != errno.ENOENT or host == "unknown": raise - os.chdir('%s/%s' % (self.BASE, "unknown")) - # On regarde si le répertoire retry existe - if not os.path.isdir('retry'): - os.mkdir('retry') - if os.listdir('retry'): - # Le répertoire retry n'est pas vide - result = "" - # On va concaténer ces fichiers et en renvoyer le contenu - # Il ne devrait n'y en avoir qu'un, mais... - for f in os.listdir('retry'): - result = result + file('retry/%s' % f).read() - # remove_lock('gen_confs.wifi') - return result - else: - # Il n'y a rien dans le répertoire retry - result = "" - getre = filter(lambda f: re.match(getre, f), os.listdir(".")) - delre = filter(lambda f: re.match(delre, f), os.listdir(".")) - # On s'occupe de mettre ce qu'il faut dans retry - both = filter(lambda f: f in delre, getre) - both.sort() - for f in both: - result = result + file(f).read() - # On écrit result dans un fichier temporaire dans retry - retry = os.fdopen(tempfile.mkstemp('','','retry')[0],'w') - retry.write(result) - del result - # On s'occupe de construire le résultat - result = "" - getre.sort() - for f in getre: - result = result + file(f).read() - # On efface ce qui correspond à delre - for f in delre: - os.remove(f) - - # remove_lock('gen_confs.wifi') - if len(result) > 5: - print "We send the following script to %s :" % host - print result - return result - - - return getSAD_host(self, getre, delre, host) - - - -# Corps du programme - -class ReverseEngine: - - def __init__(self): - self.iphost = None - self.lastupdate = 0 - - def get_reverse(self,ip): - - def build_reverse(server="138.231.144.3"): - """Construit une correspondance IP -> nom""" - pattern = re.compile("^(.*).in-addr.arpa domain name pointer (.*)\.") - result = {} - for line in commands.getoutput("host -l 148.231.138.in-addr.arpa %s" % server).split("\n"): - mo = pattern.match(line.strip()) - if mo: - ip = ".".join(mo.group(1).split(".")[::-1]) - result[ip] = mo.group(2) - return result - - current = time.time() - if not self.iphost or current - self.lastupdate > 60: - self.iphost = build_reverse() - self.lastupdate = current - - if ip not in self.iphost: - return "unknown" - else: - return self.iphost[ip] - -RE = ReverseEngine() - - -# On augmente la limite soft (qui semble arreter python). -# Sous Open, on peut utiliser fstat pour voir les fichiers ouverts. -# On a besoin de 3 * nombre de bornes pour le nombre de descripteurs -# de fichiers : 1 descripteur pour le stream TCP et deux descripteurs -# pour la crypto. -limite = resource.getrlimit(resource.RLIMIT_NOFILE) -resource.setrlimit(resource.RLIMIT_NOFILE, (300, limite[1])) - -# On écoute sur le port 9999 -application = service.Application('wifi-update') -factory = UpdateFactory() -server = internet.SSLServer(9999, factory, - ServerContextFactory()) -server.setServiceParent(service.IServiceCollection(application))