Plus utilisé...
darcs-hash:20051109075707-d1718-e897495d7de3b71faf67dea88a300a506b128637.gz
This commit is contained in:
parent
01f271d019
commit
1a28a11027
1 changed files with 0 additions and 311 deletions
|
@ -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))
|
Loading…
Add table
Add a link
Reference in a new issue