# -*- coding: UTF-8 -*-
###########################################################################
# Eole NG - 2007
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  cf /root/LicenceEole.txt
# eole@ac-dijon.fr
#
# uucp_rpc.py
#
# fonctions xmlrpc pour la gestion des actions sur les serveurs dans Zephir
#
###########################################################################
"""Module gérant les actions sur les serveurs (via uucp)
"""
from zephir.backend.db_utils import *
from zephir.backend.uucp_utils import uucp_pool, UUCPError, COMMANDS
from zephir.backend import config
from zephir.backend.config import u, log
from zephir.backend.xmlrpceole import XMLRPCEole as XMLRPC
from zephir.backend.lib_backend import AptChecker, ServiceError
from zephir.lib_zephir import save_modes
from zephir.utils.convert import to_bytes
from twisted.internet import defer, reactor, threads
from xmlrpc.client import Binary
from pyeole.sendmail import send_mail

from tiramisu.config import Config as Config2

import sys,os,shutil,time,smtplib,re,time,base64,glob,email
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formatdate
from datetime import datetime
import traceback
import psycopg2 as PgSQL
from io import BytesIO

class RPCUucp(XMLRPC):
    """serveur XMLRPC zephir pour la gestion des actions sur les serveurs
    """

    def __init__(self,parent,agent_manager):
        self.dbpool = db_connect()
        self.dbpool.noisy = 0
        XMLRPC.__init__(self)
        self.agent_manager = agent_manager
        self.parent=parent
        # booléen pour empêcher le lancement de plusieurs boucles
        # de vérification des timeouts
        self.scan_delay = config.SCAN_DELAY
        # au lancement, on modifie la date de dernier contact
        # des serveurs pour éviter des alertes injustifiées
        self.start_time = str(time.time())

    def _send_files(self,serv,archive,files,uucp=0):
        """met en queue des fichiers ou répertoires pour un serveur distant
        et stocke la somme md5 des fichiers envoyés dans le fichier cheksum.txt du
        systeme en question (dans zephir/conf/rne/serveur/)"""
        # définition du nodename uucp du système
        id_uucp = str(serv.get_rne())+'-'+str(serv.id_s)
        # chemin vers les fichiers du serveur sur zephir
        serveur_dir = serv.get_confdir()
        cmd_tar = ['cd ',serveur_dir,';','/bin/tar','--same-owner','-chpf',archive+'.tar']
        # création du fichier tar à envoyer
        for fic in files:
            # on vérifie l'existence du fichier
            if os.path.exists(os.path.join(serveur_dir,fic)):
                cmd_tar.append(fic)
            else:
                # cas spécial : lien sur dico.eol, zephir.eol, droits_zephir et droits_variante (si conf pas encore saisie)
                if fic not in ['dico.eol','zephir.eol', 'droits_variante', 'droits_zephir']:
                    # un fichier n'est pas trouvé, on annule
                    return 0, u("""fichier %s introuvable""" % os.path.join(serveur_dir,fic) )
        cmd_tar.append('>/dev/null 2>&1')
        res=os.system(" ".join(cmd_tar))
        if res != 0:
            return 0, u("""erreur de creation de l'archive %s.tar dans %s""" % (archive,serveur_dir))
        # calcul et stockage d'un checksum md5 de l'archive
        cmd_checksum = """cd %s ;md5sum -b %s.tar > %s.md5""" % (serveur_dir,archive,archive)
        os.system(cmd_checksum)
        if uucp:
            # envoi de l'archive par uucp si demandé
            try:
                res = uucp_pool.add_file(id_uucp,os.path.join(serveur_dir,archive+".tar"))
            except UUCPError as e:
                return 0, u("Erreur UUCP %s" % str(e))
        return 1,u(archive+'.tar')

    def _check_inactive_packages(self, serv, init=False):
        """vérification des paquets de dictionnaires non activés (eole 2.4 et >)
        """
        if not init:
            # récupération des paquets détectés précédemment
            liste = serv.dict_packages
            # mise à jour de la liste des paquets de dictionnaires installés
            self.parent.dictpool.update_serveur_packages(serv)
            # si de nouveaux paquets sont identifiés, on les remonte pour alerte
            packages = self.parent.dictpool.check_inactive_packages(serv)
            return [paq for paq in packages if paq not in liste]
        elif serv.installed_packages == None:
            # si init et liste des paquets installés non initialisés,
            # on fait une première passe avant mise à jour de cette liste
            # (peut être nécessaire après relance du service zephir)
            self.parent.dictpool.check_inactive_packages(serv)
        return []

    def xmlrpc_reconfigure_groupe(self,cred_user,liste, delay=0):
        """prépare la reconfiguration d'un groupe de serveurs"""
        erreurs=[]
        for serveur in liste:
            retour = self.xmlrpc_reconfigure(cred_user, serveur['id'], delay)
            if retour[0] == 0:
                erreurs.append(str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u([])

    def xmlrpc_reconfigure(self,cred_user,id_serveur,delay=0):
        """prépare la reconfiguration d'un serveur"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            if delay != 0:
                delay_opt = " %s" % str(delay)
            else:
                delay_opt = ""
            id_uucp = str(serv.get_rne())+'-'+str(id_serveur)
            try:
                res = uucp_pool.add_cmd(id_uucp,"zephir_client reconfigure%s" % delay_opt)
            except UUCPError as e:
                return 0, u("Erreur UUCP %s" % str(e))
        return 1, u('ok')

    def xmlrpc_service_restart_groupe(self,cred_user,liste,service, delay=0):
        """prépare le redémarrage d'un service sur un groupe"""
        erreurs=[]
        for serveur in liste:
            retour = self.xmlrpc_service_restart(cred_user, serveur['id'], service, delay)
            if retour[0] == 0:
                erreurs.append(str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u([])

    def xmlrpc_service_restart(self,cred_user,id_serveur,service, delay=0):
        """exécution de la commande uucp pour redémarrer un service"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            if delay != 0:
                delay_opt = " %s" % str(delay)
            else:
                delay_opt = ""
            id_uucp = str(serv.get_rne()) + '-' + str(id_serveur)
            # appel uucp
            try:
                uucp_pool.add_cmd(id_uucp,"zephir_client service_restart %s%s" % (service, delay_opt))
            except UUCPError as e:
                return 0, u("erreur uucp (%s)" % str(e))
            return 1,u("ok")

    def xmlrpc_reboot_groupe(self,cred_user,liste, delay=0):
        """prépare le redémarrage d'un groupe de serveurs"""
        erreurs=[]
        for serveur in liste:
            retour = self.xmlrpc_reboot(cred_user, serveur['id'], delay)
            if retour[0] == 0:
                erreurs.append(str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u([])

    def xmlrpc_reboot(self,cred_user,id_serveur, delay=0):
        """exécution de la commande uucp pour redémarrer un serveur"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            if delay != 0:
                delay_opt = " %s" % str(delay)
            else:
                delay_opt = ""
            id_uucp = str(serv.get_rne()) + '-' + str(id_serveur)
            # appel uucp
            try:
                uucp_pool.add_cmd(id_uucp,"zephir_client reboot%s" % delay_opt)
            except UUCPError as e:
                return 0, u("serveur %s : erreur uucp (%s)" % (str(id_serveur), str(e)))
            return 1,u("ok")


    def xmlrpc_configure(self,cred_user,id_serveur,restart=0,content=0):
        """prépare la configuration automatique d'un serveur
        (envoi des fichiers de configuration)"""

        return self._configure(id_serveur, restart, cred_user, content)

    def xmlrpc_configure_groupe(self,cred_user,liste,restart=0,content=0):
        """prépare la configuration automatique d'un groupe de serveurs
        (envoi des fichiers de configuration)"""
        erreurs = []
        for serveur in liste:
            retour = self._configure(serveur['id'], restart, cred_user, content)
            if retour[0] == 0:
                erreurs.append(str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u(erreurs)

    def _configure(self, id_serveur,restart,cred_user,content):
        """envoie les fichiers de configuration et demande un configure-zephir"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        query = """select users.cle from users,serveur_auth where users.login=serveur_auth.login and serveur_auth.id_serveur=%s"""
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        cursor=cx.cursor()
        cursor.execute(query, (int(id_serveur),))
        data=cursor.fetchall()
        cursor.close()
        cx.close()
        id_uucp = str(serv.get_rne())+'-'+str(id_serveur)
        # définition du répertoire du serveur
        serveur_dir = serv.get_confdir()
        # définition des fichiers à transférer
        try:
            cles = b""
            # on prépare le fichier des clefs ssh à transférer
            for cle in data:
                if cle[0]:
                    cles += base64.decodebytes(to_bytes(cle[0])) + b"\n"
            cles.strip()
            with open(os.path.join(serveur_dir, 'auth_keys'), 'wb') as fic_cle:
                fic_cle.write(base64.encodebytes(cles))
        except:
            traceback.print_exc()
            return 0, u("erreur de création du fichier des cles de connexion ssh")
        # récupération de la liste des fichiers à envoyer
        if content not in config.data_files:
            return 0, u("le type de données à envoyer est invalide")
        files = config.data_files[content][1]
        if 'fichiers_zephir' in files:
            # synchronisation des paquets de dictionnaires avant envoi
            self.parent.dictpool.sync_serveur_packages(serv.id_s)
        # appel de la fonction d'envoi et de calcul des md5
        code, message = self._send_files(serv,'config-zephir',files,uucp=1)
        try:
            os.unlink('auth_keys')
        except:
            pass
        if code == 0:
            return code, u(message)
        else:
            # les fichiers sont prêts à l'envoi, on demande un
            # configure sur le serveur distant
            try:
                uucp_pool.add_cmd(id_uucp,"zephir_client configure")
                if content in [0, 3]:
                    uucp_pool.add_cmd(id_uucp,"zephir_client install_paqs")
            except UUCPError as e:
                return 0, u("Erreur uucp : %s" % str(e))
            if restart == 1:
                # on demande un reconfigure
                code, data = self.xmlrpc_reconfigure(cred_user, id_serveur)
                if code == 1:
                    return 1, u([])
                else:
                    return code, data
            else:
                return 1, u([])

    def xmlrpc_save_conf(self,cred_user,id_serveur,mode=0):
        """prépare la sauvegarde de configuration d'un serveur"""
        # on vérifie l'existence du serveur dans la base
        return self._save_conf(cred_user, id_serveur, mode)

    def xmlrpc_save_conf_groupe(self,cred_user,liste,mode=0):
        """prépare la sauvegarde de configuration d'un serveur"""
        erreurs=[]
        old_clients = []
        for serveur in liste:
            retour = self._save_conf(cred_user, serveur['id'], mode)
            if retour[0] == 0:
                erreurs.append(str(serveur['id'])+' : '+retour[1])
            else:
                old_clients.extend(retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u(old_clients)

    def _save_conf(self,cred_user,id_serveur,mode):
        """exécution de la commande uucp pour la mise à jour"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            old_clients = []
            id_uucp = str(serv.get_rne()) + '-' + str(id_serveur)
            # appel uucp
            # vérification du mode demandé
            cmd_mode = ""
            if mode in save_modes:
                cmd_mode = " %s" % str(mode)
            try:
                uucp_pool.add_cmd(id_uucp,"zephir_client save_files%s" % cmd_mode)
            except UUCPError as e:
                return 0, u("erreur uucp (%s)" % str(e))
            return 1, (old_clients)

    def xmlrpc_maj(self,cred_user,id_serveur,reconf = 0, delay = "", options = ""):
        """prépare la mise à jour d'un serveur Eole par l'intermédiare d'uucp"""
        # on vérifie l'existence du serveur dans la base
        if delay > 0:
            # mise à jour différée : reconfigure est géré par la mise à jour ead
            reconf = 0
        return self._maj(cred_user, id_serveur, reconf, delay, options)

    def xmlrpc_maj_groupe(self,cred_user,liste,reconf = 0, delay = "", options = ""):
        """prépare la mise à jour d'un groupe de serveurs Eole par l'intermédiare d'uucp"""
        erreurs=[]
        for serveur in liste:
            retour = self._maj(cred_user, serveur['id'],reconf,delay,options)
            if retour[0] == 0:
                erreurs.append("serveur "+str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u('ok')

    def _maj(self, cred_user, id_serveur, reconf, delay,options):
        """exécution de la commande uucp pour la mise à jour"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            id_uucp = str(serv.get_rne()) + '-' + str(id_serveur)
            # construction de la commande
            try:
                assert int(delay) > 0
                # si delai, on ne lance pas reconfigure par une action (lancé par la maj différée)
                reconf = 0
            except:
                delay = ""
            try:
                uucp_pool.add_cmd(id_uucp,"zephir_client maj_auto %s %s" % (str(delay), options))
            except UUCPError as e:
                return 0, u("Erreur UUCP (%s)" % str(e))
            # si cela est demandé, on reconfigure le serveur
            # query = """insert into log_serveur (id_serveur,date,type,message,etat) values (%s,'%s','%s','Mise a jour',0)""" % (int(id_serveur),str(time.ctime()),'COMMAND')
            # self.dbpool.runOperation(query)
            if reconf == 1:
                return self.xmlrpc_reconfigure(cred_user, id_serveur)
            else:
                return 1, u("ok")

    def xmlrpc_maj_client(self,cred_user,id_serveur):
        """prépare la mise à jour de zephir-client sur un serveur"""
        # on vérifie l'existence du serveur dans la base
        return self._maj_client(cred_user, id_serveur)

    def xmlrpc_maj_client_groupe(self,cred_user,liste):
        """prépare la mise à jour de zephir-client sur un groupe de serveurs"""
        erreurs=[]
        for serveur in liste:
            retour = self._maj_client(cred_user, serveur['id'])
            if retour[0] == 0:
                erreurs.append("serveur "+str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u('ok')

    def _maj_client(self,cred_user,id_serveur):
        """exécution de la commande uucp pour la mise à jour de zephir-client"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            id_uucp = str(serv.get_rne()) + '-' + str(id_serveur)
            # construction de la commande
            try:
                uucp_pool.add_cmd(id_uucp,"zephir_client maj_client")
            except UUCPError as e:
                return 0, u("Erreur UUCP (%s)" % str(e))
            else:
                return 1, u("ok")

    def xmlrpc_sphynx_add(self,cred_user,id_sphynx,id_amon,content):
        """stocke la configuration RVP d'un amon vers ce sphynx"""
        try:
            id_sphynx = int(id_sphynx)
            id_amon = int(id_amon)
            sphynx = self.parent.s_pool.get(cred_user,id_sphynx)
            amon = self.parent.s_pool.get(cred_user,id_amon)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        # on regarde si ce tunnel existe déjà
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        cursor=cx.cursor()
        query = """select id_amon from conf_vpn where id_sphynx=%s"""
        cursor.execute(query, (int(id_sphynx),))
        data=cursor.fetchall()
        cursor.close()
        cx.close()
        # chemin de sauvegarde de l'archive contenant la conf RVP
        sphynx_dir = sphynx.get_confdir()
        if not os.path.exists(sphynx_dir + os.sep + 'vpn'):
            os.makedirs(sphynx_dir + os.sep + 'vpn')
        archive = sphynx_dir + os.sep + 'vpn' + os.sep + str(id_amon) + '.tar.gz'
        # mise à jour de la table des tunnels configurés
        confs = [ligne[0] for ligne in data]
        if int(id_amon) in confs:
            # on a déjà une conf vpn pour cet amon, mise à jour
            query = """update conf_vpn set etat=%s where id_sphynx=%s and id_amon=%s"""
            params = (0, int(id_sphynx), int(id_amon))
        else:
            # sinon, insertion
            query = """insert into conf_vpn (id_sphynx,id_amon,etat) values (%s,%s,%s)"""
            params = (int(id_sphynx), int(id_amon), 0)
        return self.dbpool.runOperation(query, params).addCallbacks(self._sphynx_add,db_client_failed,callbackArgs=[archive,content])

    def _sphynx_add(self,result,archive,content):
        """écriture de la conf RVP"""
        try:
            file = BytesIO()
            data = base64.decodebytes(to_bytes(content))
            fd = open(archive,'wb')
            # sauvegarde du fichier
            file.write(data)
            file.seek(0)
            fd.write(file.read())
            fd.close()
        except:
            return 0, u("erreur de sauvegarde de l'archive")
        return 1, "OK"

    def xmlrpc_sphynx_del(self,cred_user,id_sphynx,id_amon,del_row=0):
        """supprime la configuration RVP d'un amon vers ce sphynx"""
        try:
            id_sphynx = int(id_sphynx)
            id_amon = int(id_amon)
            sphynx = self.parent.s_pool.get(cred_user,id_sphynx)
            amon = self.parent.s_pool.get(cred_user,id_amon)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        cursor=cx.cursor()
        query = """select etat from conf_vpn where id_sphynx=%s and id_amon=%s"""
        params = (int(id_sphynx), int(id_amon))
        cursor.execute(query, params)
        data=cursor.fetchone()
        etat = data[0]
        cursor.close()
        cx.close()
        try:
            # chemin de l'archive contenant la conf RVP
            sphynx_dir = sphynx.get_confdir()
            archive = sphynx_dir + os.sep + 'vpn' + os.sep + str(id_amon) + '.tar.gz'
            os.unlink(archive)
        except:
            if del_row == 0:
                return 0, u("fichier de configuration RVP non supprimé (ou inexistant)")
        if del_row == 1:
            query = """delete from conf_vpn where id_sphynx=%s and id_amon=%s"""
            params = (int(id_sphynx), int(id_amon))
        else:
            nouv_etat=2
            # si l'archive n'a pas été récupérée : anomalie
            if int(etat) == 0:
                nouv_etat=3
            query = """update conf_vpn set etat=%s where id_sphynx=%s and id_amon=%s"""
            params = (int(nouv_etat), int(id_sphynx), int(id_amon))
        return self.dbpool.runOperation(query, params).addCallbacks(lambda x : [1,u("OK")],db_client_failed)

    def xmlrpc_sphynx_get(self,cred_user,id_sphynx,id_amon):
        """envoie la configuration RVP d'un amon vers ce sphynx"""
        try:
            id_sphynx = int(id_sphynx)
            id_amon = int(id_amon)
            sphynx = self.parent.s_pool.get(cred_user,id_sphynx)
            amon = self.parent.s_pool.get(cred_user,id_amon)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        try:
            # chemin de l'archive contenant la conf RVP
            sphynx_dir = sphynx.get_confdir()
            archive = sphynx_dir + os.sep + 'vpn' + os.sep + str(id_amon) + '.tar.gz'
            file_conf=open(archive, 'rb')
            content=base64.encodebytes(file_conf.read())
            file_conf.close()
        except:
            return 0, u("fichier de configuration RVP non trouvé")
        query = """update conf_vpn set etat=1 where id_sphynx=%s and id_amon=%s"""
        params = (int(id_sphynx), int(id_amon))
        return self.dbpool.runOperation(query, params).addCallbacks(lambda x : [1,content],db_client_failed)

    def xmlrpc_sphynx_list(self,cred_user,id_sphynx):
        """liste les configs RVP amon présentes pour un sphynx"""
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        cursor=cx.cursor()
        query = """select id_amon,etat from conf_vpn where id_sphynx=%s order by etat,id_amon desc""" % id_sphynx
        cursor.execute(query, (int(id_sphynx),))
        data=cursor.fetchall()
        cursor.close()
        cx.close()
        # on retourne l'id amon et son état
        liste_amons=[]
        for ligne in data:
            liste_amons.append([int(ligne[0]),int(ligne[1])])
        return 1,liste_amons

    def xmlrpc_add_replication(self, cred_user, id_serv, id_client, content):
        """ajoute un fichier de configuration pour réplication d'un annuaire sur seshat (ou autre)"""
        try:
            id_serv = int(id_serv)
            id_client = int(id_client)
            serv = self.parent.s_pool.get(cred_user,id_serv)
            client = self.parent.s_pool.get(cred_user,id_client)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        try:
            rne = client.parsedico()['numero_etab']
            assert rne != ""
        except:
            # si pas défini dans la configuration, on prend le rne
            # de l'établissement du serveur dans la base zephir
            rne = client.rne
        code, message = serv.add_replication(rne, content, id_client)
        if code == 0:
            return code, message
        else:
            # configuration ajoutée, on demande automatiquement
            # une prise en compte sur le serveur de réplication
            return self._update_replication(serv)

    def xmlrpc_del_replication(self, cred_user, id_serv, conf_file):
        """supprime un fichier de configuration de réplication"""
        try:
            id_serv = int(id_serv)
            serv = self.parent.s_pool.get(cred_user,id_serv)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        return serv.del_replication(conf_file)

    def xmlrpc_update_replication(self, cred_user, id_serveur):
        """prépare l'envoi des configurations de réplication à un serveur central,
        et demande une regénération de la configuration
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        # on effectue une mise à jour des informations sur les établissements répliqués
        # (infos utilisées par eole-sso et par le service dispatcher)
        try:
            self.parent.s_pool.check_replication_infos(id_serveur)
        except:
            traceback.print_exc()
            log.msg('Erreur de mise à jour du fichier etabs.ini')
        return self._update_replication(serv)

    def _update_replication(self, serv):
        # définition du répertoire du serveur
        id_uucp = '%s-%s' % (str(serv.get_rne()), str(serv.id_s))
        code, message = self._send_files(serv, 'replication', ['replication'], uucp=1)
        if code == 0:
            return code, u(message)
        else:
            # les fichiers sont prêts à l'envoi, on demande la prise en compte sur le serveur seshat
            try:
                res = uucp_pool.add_cmd(id_uucp, "zephir_client update_replication")
            except UUCPError as e:
                return 0, u("Erreur uucp : %s" % str(e))
        if os.path.isfile(os.path.join(serv.get_confdir(), 'replication', '.modified')):
            os.unlink(os.path.join(serv.get_confdir(), 'replication', '.modified'))
        return 1, "OK"

    def xmlrpc_get_replication_info(self, cred_user, id_serveur):
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        # on force une vérification du fichier etabs.ini avant l'envoi
        try:
            self.parent.s_pool.check_replication_infos(id_serveur)
        except:
            traceback.print_exc()
            return 0, u('Erreur de mise à jour du fichier etabs.ini')
        # lecture du fichier et envoi des informations
        try:
            data_etab = serv.get_replication_infos()
        except:
            return 0, u('Erreur de lecture du fichier etabs.ini')
        if isinstance(data_etab, str):
            data_etab = data_etab.encode()
        return  1, base64.encodebytes(data_etab)


    def xmlrpc_check_replication(self, cred_user, id_serveur):
        """renvoie l'état de la configuration de réplication LDAP
        0 : pas de réplication gérée sur ce serveur
        1 : configurations de réplication en place
        2 : la configuration doit être renvoyée au serveur (si suppression manuelle de fichiers)
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        return serv.check_replication()

    def xmlrpc_get_replication(self, cred_user, id_serveur):
        """renvoie la liste des configurations de réplication présentes sur un serveur
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        return serv.get_replication()

    def xmlrpc_confirm_transfer(self,cred_user,id_serveur,archive):
        """confirme la réception d'une archive par un serveur"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        # définition du répertoire du serveur
        serveur_dir = serv.get_confdir()
        try:
            # suppression de l'archive
            os.unlink(serveur_dir+os.sep+archive+'.tar')
            # supression du fichier de checksum
            os.unlink(serveur_dir+os.sep+archive+'.md5')
        except:
            return 0, u("""erreur de suppression de l'archive""")
        else:
            return 1, u('ok')

    def xmlrpc_get_checksum(self,cred_user,id_serveur,archive):
        """confirme la réception d'une archive par un serveur"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        # définition du répertoire du serveur
        serveur_dir = serv.get_confdir()
        # lecture du fichier md5
        try:
            fic_md5=open(serveur_dir+os.sep+archive+'.md5')
            md5sum = fic_md5.readlines()
            fic_md5.close()
        except:
            return 0, u("""fichier %s.md5 non trouve""" % (archive))
        else:
            # ok, on renvoie la chaine de contrôle
            return 1, base64.encodebytes(md5sum[0].encode()).decode()

    def xmlrpc_install_module(self,cred_user,id_module,dico_b64):
        """installation d'un module (récupération d'un dictionnaire)"""
        # pour l'instant, cette procédure sert à créer ou
        # mettre à jour le dictionnaire principal du module
        # on récupère le libellé du module dans la base
        query = """select id, libelle, from modules where id=%s""" % id_module
        return self.dbpool.runQuery(query, (int(id_module),)).addCallbacks(self._install_module,db_client_failed,callbackArgs=[dico_b64])

    def _install_module(self,data,dico_b64):
        if data == []:
            return 0, u("""erreur, module non trouvé""")
        else:
            id_module = data[0][0]
            libelle = data[0][1]
            dico_path = os.path.abspath(config.ROOT_DIR)+os.sep+'dictionnaires'+os.sep+str(libelle)
            if not os.path.isdir(dico_path):
                # module supplémentaire non géré par eole
                dico_path = os.path.join(os.path.abspath(config.PATH_MODULES),str(id_module),"dicos")
            # traitement des dictionnaires définis
            for dic_name, data in list(dico_b64.items()):
                dico = os.path.join(dico_path, dic_name)
                try:
                    if os.path.isfile(dico):
                        # le dictionnaire existe déjà
                        os.unlink(dico)
                    # recréation du fichier avec les données transmises
                    fic_dico = open(dico,'wb')
                    fic_dico.write(base64.decodebytes(to_bytes(data)))
                    fic_dico.close()
                except:
                    traceback.print_exc()
                    return 0, u("erreur de mise a jour du dictionnaire du module")

        return 1,u("ok")

    def xmlrpc_exec_script(self, cred_user, serveurs, script_name, params):
        """exécution d'un script client sur un serveur/groupe
        serveurs: id du serveur ou liste d'id
        script_name: nom du script à exécuter
        params:paramètres supplémentaires à donner au script
        """
        if type(serveurs) != list:
            serveurs = [serveurs]
        erreurs = []
        for id_serveur in serveurs:
            try:
                id_serveur = int(id_serveur)
                serv = self.parent.s_pool.get(cred_user, id_serveur)
            except:
                erreurs.append("Serveur %s : inexistant ou accès refusé" % str(id_serveur))
                continue
            try:
                id_uucp = str(serv.get_rne()) + '-' + str(serv.id_s)
                cmd = "zephir_client %s %s" % (script_name, params)
                uucp_pool.add_cmd(id_uucp,cmd.strip())
            except UUCPError as e:
                erreurs.append("serveur %s (%s) : Erreur UUCP %s" % (serv.id_s, serv.rne, str(e)))
                continue
        if len(erreurs) == len(serveurs):
            return 0, "Echec de l'exécution ou action interdite sur tous les serveurs"
        return 1, erreurs

    def xmlrpc_save_files(self,cred_user,id_serveur,checksum):
        """sauvegarde des fichiers de configuration d'un serveur"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        module = serv.id_mod
        variante = serv.id_var
        archive='fichiers_zephir'+str(id_serveur)
        public_dir = '/var/spool/uucppublic'
        temp_dir = public_dir+os.sep+str(id_serveur)
        # création d'un répertoire temporaire pour éviter que tout le monde
        # écrive dans le même répertoire
        if os.path.isdir(temp_dir):
            shutil.rmtree(temp_dir)
        try:
            os.mkdir(temp_dir)
        except:
            return 0, u("""erreur de creation du repertoire temporaire""")
        serveur_dir = serv.get_confdir()
        # on vérifie la validité de l'archive
        try:
            fic_md5 = open(public_dir+os.sep+archive+'.md5','w')
            fic_md5.write(checksum)
            fic_md5.close()
        except:
            return 0, u("""erreur d'écriture du fichier de checksum""")
        cmd_md5 = """cd %s ; md5sum -c %s.md5 2>&1 > /dev/null""" % (public_dir,archive)
        res=os.system(cmd_md5)
        if res != 0:
            return 0, u("""archive corrompue""")
        else:
            # l'archive est valide, on la décompresse
            cmd_tar = """cd %s ; tar -C %s --same-owner -xhpf %s.tar >/dev/null 2>&1""" % (public_dir,str(id_serveur),archive)
            os.system(cmd_tar)
            # on met ensuite les fichiers en place
            # supression des anciens fichiers ?
            directories = ['fichiers_perso','patchs','fichiers_zephir']
            for rep in directories:
                if os.path.exists(os.path.join(temp_dir, 'temp_zephir', rep)):
                    try:
                        if rep == 'dicos' and os.path.isdir(os.path.join(os.path.abspath(config.PATH_MODULES),str(module),'dicos')):
                            # creole2 : on conserve module et variante
                            shutil.rmtree(serveur_dir+os.sep+rep+'/local')
                        else:
                            shutil.rmtree(serveur_dir+os.sep+rep)
                    except:
                        # le repertoire (ou fichier) n'existe pas encore
                        pass
            # on déplace les fichiers de l'archive
            res = 0
            for rep in directories:
                if res == 0:
                    if os.path.exists(os.path.join(temp_dir, 'temp_zephir', rep)):
                        if os.path.isdir(os.path.join(os.path.abspath(config.PATH_MODULES),str(module),'dicos')) and rep == 'dicos':
                            # cas d'eole2 : répertoire de dictionnaires locaux
                            res = os.system("mv -f %s %s" % (temp_dir+'/temp_zephir/'+rep+'/local', os.path.join(serveur_dir,rep)))
                        else:
                            res = os.system("mv -f %s %s" % (temp_dir+'/temp_zephir/'+rep,serveur_dir))
                            if res == 0:
                                res = os.system('ln -s '+os.path.abspath(config.PATH_MODULES)+os.sep+str(module)+'/variantes/'+str(variante)+os.sep+rep+' '+serveur_dir+os.sep+rep+os.sep+'variante')

            if res != 0:
                return 0, u("""erreur de mise en place des fichiers""")
            err_msg = ""
            if os.path.exists(os.path.join(temp_dir, 'temp_zephir', 'zephir.eol')):
                # mise en place de zephir.eol
                res = os.system("mv -f %s %s" % (temp_dir+'/temp_zephir/zephir.eol',serveur_dir))
                if res != 0:
                    return 0, u("""erreur de mise en place de zephir.eol""")
                else:
                    # mise à jour de l'état du serveur
                    serv.maj_params({'config_ok':1})
                    # prise en compte de la configuration dans le cache mémoire
                    try:
                        if serv.dico is not None:
                            if config.CREOLE_CACHE:
                                serv.dico.mode = None
                                serv.load_conf('modif_config')
                            else:
                                serv.last_mode = None
                                serv.get_config('modif_config')
                    except Exception as e:
                        log.err("""Erreur Lors du rechargement de la configuration remontée par le serveur %s""" % str(id_serveur))
                        traceback.print_exc()
                        err_msg = """Erreur Lors du rechargement de la configuration remontée sur Zéphir"""
            if not err_msg:
                try:
                    # vérification des md5 de la configuration
                    serv.check_md5conf()
                except:
                    traceback.print_exc()
                    log.err("""serveur %s : erreur de mise à jour des données md5 (sauvegarde des fichiers)""" % str(id_serveur))
                    err_msg = """Erreur de vérification des fichiers reçus (fichier de configuration invalide ?) """
            # vidage du répertoire public
            try:
                # on supprime les fichiers temporaires
                os.unlink(public_dir+os.sep+archive+'.tar')
                os.unlink(public_dir+os.sep+archive+'.md5')
                shutil.rmtree(temp_dir)
            except:
                traceback.print_exc()
                log.msg("""erreur de supression des fichiers temporaires""")
            if err_msg:
                return 0, u(err_msg)
            else:
                return 1, u('ok')


    def xmlrpc_install_variante(self,cred_user,id_serveur,checksum,login,passwd_md5):
        """installation d'une variante pour un module"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        module = serv.id_mod
        variante = serv.id_var
        query = """select id,module,owner,passmd5 from variantes where id = %s and module = %s"""
        params = (variante, module)
        return self.dbpool.runQuery(query, params).addCallbacks(self._install_variante2,db_client_failed,
               callbackArgs=[cred_user,checksum,login,passwd_md5,serv.get_rne(),serv.module_version,id_serveur])


    def _install_variante2(self,data,cred_user,checksum,login,passwd_md5,rne,mod_version,id_serveur):
        """vérification de l'archive et stockage des fichiers"""
        if data == []:
            return 0, u("""variante non retrouvée dans la base""")
        variante = data[0][0]
        module = data[0][1]
        owner_var = data[0][2]
        passwd_var= data[0][3]
        archive='variante'+str(id_serveur)
        public_dir = '/var/spool/uucppublic'
        temp_dir = public_dir+os.sep+str(id_serveur)
        variante_dir = os.path.abspath(config.PATH_ZEPHIR)+os.sep+'modules'+os.sep+str(module)+os.sep+'variantes'+os.sep+str(variante)
        # si le mot de passe de la variante n'existe pas (première installation), on le stocke dans la base
        # si on est propriétaire de la variante : ok
        if cred_user != owner_var:
            # si pas de mot de passe : ok
            if passwd_var not in [None,'']:
                # sinon on vérifie le mot de passe
                if passwd_md5 != passwd_var:
                    return 0, u("""erreur, le mot de passe est invalide""")

        # création d'un répertoire temporaire pour éviter que tout le monde
        # écrive dans le même répertoire
        if os.path.isdir(temp_dir):
            shutil.rmtree(temp_dir)
        try:
            os.mkdir(temp_dir)
        except:
            return 0, u("""erreur de création du repertoire temporaire""")
        # on vérifie la validité de l'archive
        try:
            fic_md5 = open(public_dir+os.sep+archive+'.md5','w')
            fic_md5.write(checksum)
            fic_md5.close()
        except:
            return 0, u("""erreur d'écriture du fichier de checksum""")
        cmd_md5 = """cd %s ; md5sum -c %s.md5 2>&1 > /dev/null""" % (public_dir,archive)
        res=os.system(cmd_md5)
        if res != 0:
            return 0, u("""archive corrompue""")
        else:
            # l'archive est valide, on la décompresse
            cmd_tar = """cd %s ; tar -C %s --same-owner -xhpf %s.tar > /dev/null""" % (public_dir,str(id_serveur),archive)
            os.system(cmd_tar)
            # on met ensuite les fichiers en place
            # supression des anciens patchs et dictionnaires locaux
            try:
                shutil.rmtree(variante_dir+os.sep+'patchs')
                shutil.rmtree(variante_dir+os.sep+'fichiers_perso')
                shutil.rmtree(variante_dir+os.sep+'fichiers_zephir')
            except:
                return 0, u("""erreur de supression de l'ancienne variante""")
            # on déplace les fichiers de l'archive
            res = os.system("mv %s %s" % (temp_dir+os.sep+'patch/variante',variante_dir+os.sep+'patchs'))
            if res == 0:
                res = os.system("mv %s %s" % (temp_dir+os.sep+'fichiers_perso',variante_dir+os.sep+'fichiers_perso'))
            if res == 0:
                res = os.system("mv %s %s" % (temp_dir+os.sep+'fichiers_zephir',variante_dir+os.sep+'fichiers_zephir'))
            if res != 0:
                return 0, u("""erreur de mise en place de la variante""")
            # vidage du répertoire public
            try:
                # on supprime les fichiers temporaires
                os.unlink(public_dir+os.sep+archive+'.tar')
                os.unlink(public_dir+os.sep+archive+'.md5')
                shutil.rmtree(temp_dir)
            except:
                return 0, u("""erreur de supression des fichiers temporaires""")
        # on vérifie si des paquets contenant des dictionnaires ont été ajoutés/retirés
        self.parent.dictpool.sync_variante_paqs(variante)
        if passwd_var in [None,'']:
            # stockage des informations d'authentification
            query = """update variantes set owner=%s, passmd5=%s where id = %s and module = %s"""
            params = (login, passwd_md5, variante, module)
            return self.dbpool.runOperation(query, params).addCallbacks(lambda x : [1,'ok'],lambda x : [0,'erreur de stockage du mot de passe'])
        else:
            return 1,u('ok')

    def xmlrpc_log_serveur(self,cred_user,id_serveur,date,type_action,etat,msg):
        """ met à jour la table d'état du serveur pour une action précise
        (ex: MAJ ou CONFIG) afin de refléter l'état de cohérence actuelle du serveur
        """
        params = {'last_log':str(date)}
        if type_action in ['MAJ','CONFIGURE','RECONFIGURE','SAUVEGARDE','REBOOT','SERVICE_RESTART','UPGRADE','PERSO']:
            # on met à jour le champs params du serveur pour refléter un éventuel changement d'état
            if int(etat) == -1:
                params['%s_ok' % type_action.lower()] = [2, str(date), msg]
            elif int(etat) > 0:
                params['%s_ok' % type_action.lower()] = [0, str(date), msg]
            elif int(etat) == 0:
                if type_action == "MAJ":
                    # cas spécial, maj lancée : on force query_maj à 0 (plus de paquets non à jour)
                    # params['query_maj'] = [0, str(date)]
                    pass
                elif type_action == "CONFIGURE":
                    # cas de la fin d'envoi de configuration
                    # on estime que les données de configuration sont synchronisées (md5)
                    # si ce n'est pas le cas, la non concordance sera détectée au prochain envoi de stats
                    md5file = os.path.join(os.path.abspath(config.PATH_ZEPHIR),'data','config%s.md5' % id_serveur)
                    if os.path.isfile(md5file):
                        self.parent.s_pool.edit_serveur(id_serveur,{'md5s':1})
                        params['md5s'] = [1,""]
                params['%s_ok' % type_action.lower()] = [1, str(date), msg]
        elif type_action == 'LOCK':
            if int(etat) == 1:
                params['lock_ok'] = [2, str(date), msg]
            else:
                params['lock_ok'] = [1,'']
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool[id_serveur]
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            # on met à jour la date de dernier contact dans la base
            self.parent.s_pool.update_contact(id_serveur)

        # on regarde si l'action en question a déjà un état
        query = """select id,etat,date,type from last_log_serveur where id_serveur=%s and type=%s"""
        sql_params = (int(id_serveur), type_action)
        return self.dbpool.runQuery(query, sql_params).addCallbacks(self._log_serveur,db_client_failed,callbackArgs=[id_serveur,type_action,date,int(etat),msg, params, serv])

    def _log_serveur(self,data,id_serveur,type_action,date,etat,msg,params,serv):
        # si l'action existe déjà on la met à jour, sinon on l'insère
        if data != []:
            # on insère que si la date du nouveau log est > à l'ancien (cas d'anciens logs non remontés)
            last_date = data[0][2]
            try:
                new_date = datetime.strptime(str(date), "%c")
            except:
                try:
                    # problème de conversion de date (pb de locale ?)
                    # on convertit le jour/mois en français
                    new_date = str(date).split()
                    new_date[0] = config.days[new_date[0]]
                    new_date[1] = config.months[new_date[1]]
                    new_date = " ".join(new_date)
                    new_date = datetime.strptime(str(new_date),"%a %b %d %H:%M:%S %Y")
                except:
                    log.msg("Serveur %s - erreur de lecture de la date pour le log suivant : %s, %s, %s (%s)" % \
                            (str(id_serveur), type_action, str(etat), msg, date))
                    log.msg("utilisation de la date courante pour ce log")
                    new_date = datetime.utcnow()
            if new_date >= last_date:
                query = """update last_log_serveur set id_serveur=%s,date=%s,type=%s,message=%s,etat=%s where id=%s"""
                sql_params = (int(id_serveur), date, type_action, msg, int(etat), int(data[0][0]))
                # on met à jour le champs params et le cache mémoire
                #if params.has_key('query_maj'):
                #    self.parent.s_pool.edit_serveur(id_serveur,{'maj':params['query_maj'][0]})
                serv.maj_params(params)
            else:
                # on a reçu un ancien log (reprise d'anciens logs ayant échoué) -> pas de maj de last_log
                return self._log_serveur2(None,id_serveur,type_action,date,etat,msg)
        else:
            query = """insert into last_log_serveur (id_serveur,date,type,message,etat) values (%s,%s,%s,%s,%s)"""
            sql_params = (int(id_serveur), date, type_action, msg, int(etat))
            # on met à jour le champs params et le cache mémoire
            #if params.has_key('query_maj'):
            #    self.parent.s_pool.edit_serveur(id_serveur,{'maj':params['query_maj'][0]})
            serv.maj_params(params)
        return self.dbpool.runOperation(query, sql_params).addCallbacks(self._log_serveur2,db_client_failed,callbackArgs=[id_serveur,type_action,date,etat,msg])

    def _log_serveur2(self,data,id_serveur,type_action,date,etat,msg):
        query = """insert into log_serveur (id_serveur,date,type,message,etat) values (%s,%s,%s,%s,%s)"""
        sql_params = (int(id_serveur), date, type_action, msg, etat)
        # on effectue la mise à jour de la base
        return self.dbpool.runOperation(query, sql_params).addCallbacks(lambda x : [1,'ok'],db_client_failed)

    def xmlrpc_release_lock_groupe(self,cred_user,liste):
        """prépare la suppression des verrous sur un groupe"""
        erreurs=[]
        for serveur in liste:
            retour = self.xmlrpc_release_lock(cred_user, serveur['id'])
            if retour[0] == 0:
                erreurs.append(str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 0, u(erreurs)
        else:
            return 1, u('ok')

    def xmlrpc_release_lock(self,cred_user,id_serveur):
        """demande la libération des verrous sur un serveur"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            serv.maj_params({'del_locks':True})
        return 1, 'ok'

    def xmlrpc_unlock(self,cred_user,id_serveur,unlocked=False):
        """indique si les locks doivent être ignorés
        @params unlocked: si True, on enlève l'attibut del_lock de params"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        params = serv.get_params()
        if 'del_locks' in params:
            if params['del_locks'] == True:
                if unlocked:
                    # les verrous ont été supprimés, on revient en état normal
                    serv.maj_params({'del_locks':False})
                # les verrous doivent être bypassés
                return 1, True
        return 1, False

    def _sendmail(self,adresses,subject,msg):
        """envoi d'un message d'alerte à une liste d'adresses mail
        """
        bad_addr={}
        mail = MIMEText(msg)
        mail['Subject'] = Header("[Zephir] %s" % subject,"utf-8")
        mail['From'] = config.MAIL_ACCOUNT
        mail['To'] = ', '.join(adresses)
        mail.set_charset('UTF-8')
        mail.add_header('Date', formatdate())
        # si le serveur de mail est local, utiliser la commande sendmail plutôt que le protocole SMTP
        if config.MAIL_ADRESSE == "localhost":
            return_code = send_mail(mail["From"],
                                    mail["To"],
                                    mail["Subject"].encode("utf-8"),
                                    msg)
            if return_code[0] != 0:
                return 0, u("erreur d'envoi du mail d'alerte")
        else:
            mail_client=smtplib.SMTP(config.MAIL_ADRESSE)
            orig_timeout = smtplib.socket.getdefaulttimeout()
            try:
                smtplib.socket.setdefaulttimeout(3)
                mail_client.connect(config.MAIL_ADRESSE, config.MAIL_PORT)
                smtplib.socket.setdefaulttimeout(orig_timeout)
            except:
                smtplib.socket.setdefaulttimeout(orig_timeout)
                mail_client.quit()
                return 0, u('erreur de connexion au serveur smtp')
            else:
                try:
                    mail_client.ehlo_or_helo_if_needed()
                    try:
                        mail_client.starttls()
                    except smtplib.SMTPException as e:
                        mail_client.quit()
                        return 0, u("Erreur d'envoi du mail d'alerte : le serveur %s ne gère pas les connexions cryptées (TLS)" % config.MAIL_ADRESSE)
                    bad_addr = mail_client.sendmail(config.MAIL_ACCOUNT,adresses, mail.as_string())
                    mail_client.quit()
                except:
                    mail_client.quit()
                    return 0, u("erreur d'envoi du mail d'alerte")
        return 1, bad_addr

    def xmlrpc_maj_site(self,cred_user,ip_publique,id_serveur,checksum,new_agents=0):
        if config.USE_THREADS:
            return threads.deferToThread(self._maj_site, cred_user,ip_publique, \
                                         id_serveur,checksum)
        else:
            return self._maj_site(cred_user, ip_publique, id_serveur, checksum)

    def _maj_site(self,cred_user,ip_publique,id_serveur,checksum):
        """vérifie l'archive envoyée par le serveur et met le site et les données xml en place
        """
        # si réacteur en cours d'arrêt, on renvoie une erreur
        if not reactor.running:
            return 0, u("Service Zéphir en cours d'arrêt")
        log.msg ("connexion du serveur %s" % str(id_serveur))
        # vérification du md5 de l'archive
        public_dir = '/var/spool/uucppublic'
        archive = 'site%s' % id_serveur
        try:
            fic_md5 = open(public_dir+os.sep+archive+'.md5','w')
            fic_md5.writelines(checksum)
            fic_md5.close()
        except:
            return 0, u("""erreur d'écriture du fichier de checksum""")
        cmd_md5 = """cd %s ; md5sum -c %s.md5 2>&1 > /dev/null""" % (public_dir, archive)
        res=os.system(cmd_md5)

        if config.USE_THREADS:
            reactor.callFromThread(reactor.callLater, 0, self._maj_site2, cred_user, ip_publique, id_serveur, checksum, public_dir, archive, res)
        else:
            reactor.callLater(0, self._maj_site2, cred_user, ip_publique, id_serveur, checksum, public_dir, archive, res)
        if res != 0:
            return 0, u("""archive corrompue""")
        else:
            # archive valide, on indique au client que les données sont reçues
            return 1,u("ok")

    def _maj_site2(self, cred_user, ip_publique, id_serveur, checksum, public_dir, archive, res_md5):
        # on regarde dans les logs si un lock est indiqué
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        query="""select type,etat from last_log_serveur where id_serveur=%s and type='LOCK' order by date desc, id desc"""
        cursor=cx.cursor()
        cursor.execute(query, (int(id_serveur),))
        data=cursor.fetchall()
        cursor.close()
        cx.close()
        etat=0
        if data != []:
            etat=int(data[0][1])
        if etat == 1:
            # si il y avait un lock sur uucp, on l'annule
            self.xmlrpc_log_serveur(cred_user,id_serveur,str(time.ctime()),'LOCK','0',"""Reprise de l'activité uucp""")
        # on met à jour la date de dernier contact dans la base
        self.parent.s_pool.update_contact(id_serveur)
        if res_md5 == 0:
            # l'archive est valide, on la décompresse
            rep_dest = os.path.abspath(config.PATH_ZEPHIR)
            # on supprime l'ancien répertoire
            try:
                shutil.rmtree(rep_dest+os.sep+"data"+os.sep+str(id_serveur))
            except:
                pass

            serv = self.parent.s_pool[int(id_serveur)]
            self._check_inactive_packages(serv, init=True)
            cmd_tar = """cd %s ; /bin/tar -C %s -xzf %s.tar 2> /dev/null""" % (public_dir, rep_dest, archive)
            res=os.system(cmd_tar)
            if res != 0:
                return 0, u("erreur de mise en place du site sur zephir")
            if self.parent.maj_checker:
                serv.check_maj_status(self.parent.maj_checker)
            try:
                serv.check_md5conf()
            except:
                traceback.print_exc()
                log.msg("Serveur %s : erreur de mise à jour des données md5 (synchronisation du client)" % str(serv.id_s))

            serv.update_ip_pub(ip_publique)
            # on met à jour la liste d'agents (en cas de nouvel agent)
            # et on regarde si un des agents a remonté une erreur
            list_errors = []
            try:
                # on force un update du cache pour ce serveur (prise en compte de nouveaux agents sans redémarrer l'application)
                self.agent_manager[str(id_serveur)].update_structure()
                stats = self.agent_manager[str(id_serveur)].get_measure()
                result_ag = self.agent_manager[str(id_serveur)].global_status()
                if result_ag == 0:
                    # détail des agents en erreur
                    list_errors = []
                    for ag_name, detail_ag in list(self.agent_manager[str(id_serveur)].agents_status().items()):
                        if detail_ag[1] == 0 and ag_name != 'tcpservices':
                            list_errors.append(detail_ag[0])
            except:
                traceback.print_exc()
                result_ag = 1

            # vérfication d'éventuels nouveaux paquets avec dictionnaires installés
            new_dictpaqs = self._check_inactive_packages(serv)
            # on vérifie la cohérence du serveur au niveau des logs zephir
            try:
                etat_zeph = serv.get_params()
            except:
                log.msg("erreur de récupération de l'état zephir du serveur %s" % id_serveur)
            d = defer.Deferred()
            d.addCallback(self._alerte, etat_zeph, result_ag, list_errors, new_dictpaqs)
            d.callback(id_serveur)


    def _alerte(self, id_serveur, etat_zeph, etat_ag, list_errors, new_dictpaqs):
        """vérification de l'état du serveur et envoi mail si nécessaire
        """
        # on récupére la liste des utilisateurs à contacter
        query="select serveurs.id,serveurs.rne,installateur,serveurs.libelle, \
        modules.libelle,etablissements.libelle,serveurs.etat \
        from serveurs,etablissements,modules \
        where serveurs.id=%s and etablissements.rne=serveurs.rne and modules.id=module_actuel"
        self.dbpool.runQuery(query, (int(id_serveur),)).addCallbacks(self._alerte2,db_client_failed,
                                                                     callbackArgs=[etat_zeph, etat_ag, list_errors, new_dictpaqs])

    def _alerte2(self,data,etat_zeph,etat_ag,list_errors, new_dictpaqs):
        # si erreur zephir ou agent --> mail
        if data == []:
            # pas de données récupérées (serveur inexistant ?)
            pass
        else:
            id_serveur = data[0][0]
            rne = data[0][1]
            libelle = data[0][3]
            module = data[0][4]
            libel_etab = data[0][5]
            etat_precedent = data[0][6]
            if etat_precedent != None:
                try:
                    etat_precedent = int(etat_precedent)
                except Exception as e:
                    etat_precedent = 1
            erreur = 0
            msg = """le serveur %s (%s - %s)\n établissement : %s (%s)""" % (libelle, id_serveur, module, rne, libel_etab)
            # vérification de l'état du serveur
            for cle in list(etat_zeph.keys()):
                etat = etat_zeph[cle]
                if type(etat) == list:
                    if len(etat) == 3:
                        if etat[0] == 0:
                            if erreur == 0:
                                msg += """\n(se reporter à la page d'état du serveur dans l'application web)"""
                                erreur = 1
                            msg +="""\n\nlog du %s : %s""" % (etat[1], etat[2])
                if (cle == 'lock_ok') and (etat[0] != 1):
                    msg += """\n\n Fonction Zephir verrouillées, connectez vous sur le serveur pour vérifier son état
                    (commande '/usr/share/zephir/scripts/zephir_client del_lock' pour déverrouiller)"""
            # on stocke l'etat global des agents dans le champs params du serveur
            serv = self.parent.s_pool[id_serveur]
            serv.maj_params({'agents':etat_ag})
            if etat_ag == 0:
                erreur = 2
                # erreur remontée dans le site de surveillance
                msg += """\n\nerreur remontée par le serveur (cf. https://%s:%s/agents/%s)""" % (config.ADRESSE_ZEPHIR,config.PORT_HTTP,id_serveur)
                if list_errors != []:
                    msg += """\n\n* %s""" % "\n* ".join(list_errors)
            if len(new_dictpaqs) > 0:
                # nouveaux paquets avec dictionnaires installés, on remonte la liste
                # de tout ceux qui ne sont pas encore activés (message séparé)
                liste_paqs = etat_zeph['dictpaqs_ok'][1]
                msg_paq = """Serveur %s (%s - %s) de l'établissement : %s (%s)""" % (libelle, id_serveur, module, rne, libel_etab)
                msg_paq += """\n\nDes paquets contenant des dictionnaires de configuration ont été installés sur ce serveur."""
                msg_paq += """\n\nLes paquets suivants ne sont pas activés dans l'application Zéphir :\n - %s""" % "\n - ".join(liste_paqs)
                msg_paq += """\n\nIl est recommandé de les activer au niveau du serveur (fichiers personnalisés) ou de sa variante."""
                self._send_alerte("Nouveaux dictionnaires de configuration détectés : serveur ", {serv.id_s : msg_paq})
            # envoi de message si erreur non envoyée précédemment
            # on regarde le dernier état enregistré pour ce serveur
            msg_sent = False
            if erreur != 0:
                if etat_precedent not in [0,4]:
                    if etat_precedent == 3:
                        serv.set_status(4)
                    else:
                        serv.set_status(0)
                    if serv.no_alert == False:
                        # début d'alerte
                        if etat_precedent == 2:
                            # on vient de reprendre contact et il y a un problème
                            subject = "problème détecté à la reprise de contact: serveur %s (%s)" % (libelle,rne)
                        else:
                            subject = "problème détecté : serveur %s (%s)" % (libelle,rne)
                        msg = "\nproblème détecté sur " + msg
                        self._send_alerte("problème détecté : serveur ",{int(id_serveur):msg})
            else:
                # pas d'erreur détectée, si l'état était à 2, on ne le change pas
                # (l'état sera remis à 1 par la fonction de vérification du timeout)
                if etat_precedent in [0,4]:
                    if etat_precedent == 4:
                        serv.set_status(3)
                    else:
                        serv.set_status(1)
                    if serv.no_alert == False:
                        # fin d'alerte
                        subject = "fin d'alerte : serveur %s (%s)" % (libelle,rne)
                        msg = "\n fin d'alerte pour " + msg
                        self._send_alerte("fin d'alerte : ",{int(id_serveur):msg})
                # si on n'avait pas d'info : etat ok
                if etat_precedent == None:
                    serv.set_status(1)

    def _send_alerte(self,subject,msgs):
        """recherche les mails et sms des personnes surveillant un serveur particulier
        """
        if msgs != {}:
            query = """select id,serveurs from groupes_serveurs"""
            self.dbpool.runQuery(query).addCallbacks(self._send_alerte2,db_client_failed,callbackArgs=[subject,msgs])

    def _send_alerte2(self,data,subject,msg):
        # on récupère les groupes
        # et on regarde quels groupes contiennent ce serveur
        groupes=[]
        for id_serveur in list(msg.keys()):
            for groupe in data:
                if id_serveur in eval(groupe[1]):
                    if groupe[0] not in groupes:
                        groupes.append(groupe[0])
        query = """select groupes,mail,sms,mail_actif,sms_actif from users \
                where groupes != '' and (mail_actif=1 or sms_actif=1)"""
        self.dbpool.runQuery(query).addCallbacks(self._send_alerte3,db_client_failed,callbackArgs=[groupes,subject,msg])

    def _send_alerte3(self,data,groupes,subject,msg):
        """regarde quels utilisateurs surveillent les groupes
        en question et envoie un mail ou sms si besoin
        """
        try:
            # utilisateurs avec les bons groupes
            destinataires = []
            for user in data:
                try:
                    groupes_user=eval(user[0])
                except:
                    groupes_user=[]
                for groupe in groupes_user:
                    # un des groupes contient ce serveur
                    if groupe in groupes:
                        # on regarde si il faut envoyer des mails ou sms
                        if user[3]==1:
                            # on vérifie la syntaxe de l'adresse mail
                            for adresse in user[1].split(','):
                                r=re.match(r"""^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9]+)*$""",adresse)
                                if r is not None:
                                    # adresse valide : on envoie le mail
                                    if adresse not in destinataires:
                                        destinataires.append(adresse)
        except:
            log.msg("Erreur lors de la recherche des destinataires d'alertes (mail)")
            traceback.print_exc()
        else:
            try:
                if destinataires != []:
                    # envoi effectif des messages à tous les utilisateurs concernés
                    envoi_ok, detail = self._sendmail(destinataires,subject + str(list(msg.keys())),"\n".join(list(msg.values())))
                    assert envoi_ok == 1, str(detail)
                    # appel smtp réussi pour au mois un des destinataires
                    if detail:
                        log.msg("Envoi d'alerte : des erreurs ont été remontées pour les destinataires suivants :")
                        for dest, msg in list(detail.items()):
                            log.msg("  %s : %s" % (str(dest), str(msg)))
            except:
                log.msg("Erreur interne rencontrée lors de l'envoi d'alertes (mail)")
                traceback.print_exc()

    def xmlrpc_scan_timeouts(self,*args):
        """vérifie toutes les x minutes si des serveurs ont dépassé leur timeout.
        vérification toutes les 5 miuntes par défaut"""
        # requete de récupération des données
        try:
            # premier démarrage de la boucle de vérification des contacts
            if self.start_time is not None:
                # self.parent.scheduler.start()
                # on démarre également la vérification des paquets disponibles
                self.parent.maj_checker = AptChecker()
                query = "update serveurs set last_contact=%s where last_contact is not null and etat <> 2"
                self.dbpool.runOperation(query, (self.start_time,)).addCallbacks(lambda x : [1, "OK"],db_client_failed)
                reactor.callLater(5, self._scan_timeouts)
        except:
            traceback.print_exc()
            return 0, "erreur de démarrage de la boucle de surveillance"
        return 1,""

    def _scan_timeouts(self):
        reactor.callLater(self.scan_delay, self._scan_timeouts)
        if config.LOG_ACTIONS:
            log.msg('Recherche des pertes de contact')
        query = """select serveurs.id,timeout,last_contact,serveurs.libelle, \
        serveurs.rne,etablissements.libelle,modules.libelle,serveurs.etat \
        from serveurs,etablissements,modules where timeout > 0 and last_contact is not null and \
        serveurs.rne=etablissements.rne and module_actuel=modules.id"""
        self.dbpool.runQuery(query).addCallbacks(self._scan_timeouts2,db_client_failed)

    def _scan_timeouts2(self, data):
        """vérifie si le dernier contact est moins ancien que le timeout du serveur.
        """
        # lancement du préchargement des configurations en tâche de fond au premier lancement
        if self.start_time is not None and config.CREOLE_CACHE:
            self.start_time = None
            # recherche des serveurs dont la configuration est présente
            serveurs = []
            for serv in list(self.parent.s_pool.values()):
                if os.path.exists(os.path.join(serv.get_confdir(), 'zephir.eol')):
                    serveurs.append(serv.id_s)
            if config.USE_THREADS:
                reactor.callInThread(self.update_creole, serveurs)
            else:
                self.update_creole(serveurs)
        # vérification des serveurs en timeout
        cmds=uucp_pool._scan_pool()
        if data:
            # vérification des serveurs en timeout
            d_list = []
            for serveur in data:
                # on lance la boucle de vérification sans attendre le résultat
                if config.USE_THREADS:
                    def_check = threads.deferToThread(self.check_timeout, serveur)
                else:
                    def_check = defer.maybeDeferred(self.check_timeout, serveur)
                d_list.append(def_check)
            d = defer.DeferredList(d_list, consumeErrors=True)
            d.addCallback(self._send_alertes)

    def _send_alertes(self, results):
        erreurs = {}
        reprises = {}
        bloquages = {}
        debloquages = {}
        # collecte des résultats pour chaque serveur
        for success, data in results:
            if success:
                erreurs.update(data[0])
                reprises.update(data[1])
                bloquages.update(data[2])
                debloquages.update(data[3])
            else:
                log.msg("! Erreur lors de la vérification du timeout d'un serveur : %s" \
                        % str(data.getErrorMessage()))
        if config.LOG_ACTIONS:
            log.msg('Vérification des pertes de contact terminée, envoi des alertes mail')
        self._send_alerte("commandes non lancées : ", bloquages)
        self._send_alerte("commandes débloquées : ", debloquages)
        self._send_alerte("perte de contact : ", erreurs)
        self._send_alerte("reprise du contact : ", reprises)

    def check_timeout(self, serveur):
        """vérifie si un serveur a dépassé le délai de connexion autorisé
        """
        if not reactor.running:
            # arrêt du service en cours, on arrête le traitement (utile si lancé dans threads)
            return defer.fail(ServiceError("Service Zéphir en cours d'arrêt"))
        erreurs = {}
        reprises = {}
        bloquages = {}
        debloquages = {}
        # calcul du temps écoulé depuis le dernier contact (en secondes)
        serv = self.parent.s_pool[int(serveur[0])]
        params = {'timeout':[-2, ""]}
        last = float(serveur[2])
        delta = float(time.time()) - last
        try:
            etat_actuel = int(serveur[7])
        except:
            etat_actuel = 1
        # on regarde si on a dépassé le timeout
        try:
            timeout = int(serveur[1])
        except:
            pass
        else:
            params['timeout'] = [1, time.ctime(last)]
            # on laisse un délai de 2x le délai de connexion + 4 minutes avant de lever une alerte
            # (on autorise de rater une connexion)
            max_delay = int(timeout) * 2 + 240
            if ( delta > max_delay ) and timeout != 0:
                params['timeout'][0]=0
                if etat_actuel != 2:
                    # on prévient les utilisateurs concernés si ce n'est pas déjà fait
                    if config.USE_THREADS:
                        reactor.callFromThread(log.msg, "timeout du serveur %s" % serveur[0])
                    else:
                        log.msg("timeout du serveur %s" % serveur[0])
                    serv.set_status(2)
                    # construction du message d'erreur
                    if serv.no_alert == False:
                        subject = """perte du contact : serveur(s) %s (%s)""" % (serveur[3],serveur[4])
                        msg="""Dernier contact avec le serveur n°%s - %s (%s) de l'établissement %s (%s) : %s""" % (serveur[0],serveur[3],serveur[6],serveur[4],serveur[5],time.ctime(last))
                        # ajout de l'alerte
                        erreurs[serveur[0]]=msg
            else:
                if etat_actuel == 2:
                    # on était en timeout auparavant
                    serv.set_status(1)
                    if serv.no_alert == False and timeout != 0:
                        subject = """reprise du contact : serveur(s) %s (%s)""" % (serveur[3],serveur[4])
                        msg = """reprise du contact avec le serveur n°%s - %s (%s) de l'établissement %s (%s) : %s""" % \
                        (serveur[0],serveur[3],serveur[6],serveur[4],serveur[5],time.ctime(last))
                        reprises[serveur[0]]=msg

                # vérification de la bonne exécution des commandes
                id_uucp = serveur[4]+"-"+str(serveur[0])
                old_cmds = uucp_pool.check_timeout(max_delay, id_uucp)
                if old_cmds != {} and timeout != 0:
                    if etat_actuel not in [3,4]:
                        # mail pour le bloquage des commandes en attente
                        if serv.no_alert == False:
                            msg = """commandes bloquées en attente pour le serveur n°%s - %s (%s) de l'établissement %s (%s)
Vous pouvez afficher les logs de transfert UUCP sur ce serveur à l'aide de la commande uulog"""
                            bloquages[serveur[0]] = msg % (serveur[0],serveur[3],serveur[6],serveur[4],serveur[5])
                        if etat_actuel == 0:
                            # bloquage + erreur agents
                            serv.set_status(4)
                        else:
                            # bloquage
                            serv.set_status(3)
                elif etat_actuel in [3,4]:
                    # les commandes sont débloquées
                    if serv.no_alert == False and timeout != 0:
                        msg = """débloquage des commandes (reprise d'activité uucp) pour le serveur n°%s - %s (%s) de l'établissement %s (%s)"""
                        debloquages[serveur[0]] = msg % (serveur[0],serveur[3],serveur[6],serveur[4],serveur[5])
                    if etat_actuel == 4:
                        # erreur agents
                        serv.set_status(0)
                    else:
                        # aucune erreur
                        serv.set_status(1)
        serv.maj_params(params)
        return erreurs, reprises, bloquages, debloquages

    def update_creole(self, serveurs):
        # initialisation du premier serveur dans la liste
        log.msg("Début de mise en cache des configurations des serveurs")
        def_update = defer.Deferred()
        def_update.addCallback(self._update_creole, serveurs)
        # lancement du chargement dans un Deffered (on n'attend pas le résultat)
        def_update.callback(0)

    def _update_creole(self, current, serveurs):
        variantes = {}
        start = time.time()
        if config.USE_THREADS:
            for current, serveur in enumerate(serveurs):
                if not reactor.running:
                    log.msg('Fin de la mise en cache (Arrêt du service en cours)')
                    break
                else:
                    self._load_creole(current, serveurs, variantes, start)
            self.parent.s_pool.stats['load_percent'] = ""
            log.msg("Fin de mise en cache des configurations (temps {0} secondes)".format(int(time.time() - start)))
        else:
            if not reactor.running:
                log.msg('Fin de la mise en cache (Arrêt du service en cours)')
            else:
                self._load_creole(current, serveurs, variantes, start)
                # mode non threadé, on permet une temporisation par le réacteur
                current += 1
                if current == len(serveurs):
                    self.parent.s_pool.stats['load_percent'] = ""
                    log.msg("Fin de mise en cache des configurations (temps {0} secondes)".format(int(time.time() - start)))
                else:
                    # chargement de la configuration suivante
                    reactor.callLater(0, self._update_creole, current, serveurs)

    def _load_creole(self, current, serveurs, variantes, start):
        try:
            id_serv = int(serveurs[current])
            serv = self.parent.s_pool[id_serv]
            # on vérifie que tous les liens sont bien créés
            self.parent.dictpool.check_dirs(serv)
            if serv.dico is None:
                store = False
                groups = None
                separators = None
                cfg = None
                if config.LOAD_BY_VARIANTE:
                    local_dict = glob.glob(os.path.join(serv.get_confdir(), 'dicos', 'package', '*'))
                    if not local_dict:
                        if serv.id_var in variantes:
                            groups, separators, cfg_descr, permissive = variantes[serv.id_var]
                            cfg = Config2(cfg_descr)
                            cfg.read_write()
                            settings = cfg.cfgimpl_get_settings()
                            settings.remove('hidden')
                            settings.setpermissive(tuple(permissive))
                        else:
                            store = True
                start2 = time.time()
                serv.get_config('modif_config', groups=groups, separators=separators, config=cfg)
                if store:
                    variantes[serv.id_var] = (serv.dico.groups, serv.dico.separators, serv.dico.dico.cfgimpl_get_description(), serv.dico.dico.cfgimpl_get_settings()._p_.getpermissive(None))
            nb_serv = len(serveurs)
            if divmod(current, 50)[1] == 0 and current != 0:
                # affichage d'un message tous les 50 serveur et en fin de chargement
                log.msg("Mise en cache des configurations : %d/%d effectué en %d secondes" % (current, nb_serv, int(time.time() - start)))
            # calcul du % de chargement pour affichage dans l'application
            if current < nb_serv and nb_serv:
                perc_load = int(current * 100 / nb_serv)
                self.parent.s_pool.stats['load_percent'] = str(perc_load)
        except Exception as e:
            log.msg("Erreur de lecture de la configuration du serveur %s (%s)" % (str(id_serv), str(e)))


    def xmlrpc_get_actions(self,cred_user,id_serveur):
        """retourne la liste des actions uucp en attente"""
        # on vérifie l'existence du serveur dans la base
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            id_uucp = str(serv.get_rne()) + '-' + str(id_serveur)
            # construction de la commande
            try:
                cmds=uucp_pool.list_cmd(id_uucp)[id_uucp]
            except:
                cmds={}
            try:
                files=uucp_pool.list_files(id_uucp)[id_uucp]
            except:
                files={}
            return 1,u([cmds,files])

    def xmlrpc_check_queue(self,cred_user,id_serveur):
        """indique à un serveur si il doit ou non effectuer des actions
        """
        # on vérifie l'existence du serveur dans la base
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            id_uucp = str(serv.get_rne()) + '-' + str(id_serveur)
            # on commence par mettre à jour le pool uucp
            cmds=uucp_pool._scan_pool()
            if len(uucp_pool.pool[id_uucp]) > 0:
                return 1, True
            else:
                return 1, False

    def xmlrpc_purge_actions(self,cred_user,serveurs,id_tache=None):
        """annule toutes les actions en attente sur un/plusieurs serveur(s)"""
        # on vérifie l'existence du serveur dans la base
        try:
            for id_serveur in serveurs:
                id_serveur = int(id_serveur)
                assert id_serveur in self.parent.s_pool
        except (KeyError, ValueError):
            return 0, u("serveur inconnu dans la base zephir")
        else:
            for id_serveur in serveurs:
                rne = self.parent.s_pool.get(cred_user,int(id_serveur)).get_rne()
                id_uucp = str(rne) + '-' + str(id_serveur)
                # on regarde si un transfert est lié à cette action (configure)
                if id_tache is None:
                    try:
                        uucp_pool.flush([id_uucp])
                    except UUCPError as e:
                        return 0, u("erreur de purge des commandes : %s" % str(e))
                else:
                    try:
                        uucp_pool.remove_cmd(id_uucp,int(id_tache))
                    except UUCPError as e:
                        return 0, u("Erreur de supression de la commande uucp : %s" % str(e))
                    except KeyError:
                        pass

        return 1,"OK"

