# -*- 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
#
# serveurs_rpc.py
#
# fonctions xmlrpc pour la gestion des serveurs dans Zephir
#
###########################################################################
"""module de gestion des serveurs
"""
from zephir.backend.db_utils import *
from zephir.backend import config
from zephir.backend.config import log
from zephir.backend.uucp_utils import UUCPError, uucp_pool
from zephir.backend.config import u, FILE_SECTION, RPM_SECTION
from zephir.backend.xmlrpceole import XMLRPCEole as XMLRPC
from zephir.backend.lib_backend import ResourceAuthError, istextfile
from zephir.utils.creolewrap import get_upgrade_infos
from zephir.utils.convert import to_bytes
from tiramisu.error import PropertiesOptionError as PropertiesOptionError2

import psycopg2 as PgSQL

import os,shutil,time,base64, traceback

class RPCServeurs(XMLRPC):
    """serveur XMLRPC zephir pour la gestion des 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

    def _got_serveur(self,serveurs,cred_user):
        """Formatage de la liste des serveurs
        """
        l=[]
        auth_error = False
        for serveur in serveurs:
            # on ne renvoie que les serveurs accessibles
            try:
                serv = self.parent.s_pool.get(cred_user,int(serveur[0]))
            except ResourceAuthError:
                auth_error = True
                continue
            l.append(self._format_serv(serveur, serv))
        if l == [] and auth_error == True:
            # aucun serveur n'est autorisé dans ce groupe
            return 0, u("Vous n'avez accès à aucun serveur dans ce groupe")
        return 1,u(l)

    def _format_serv(self, serveur, serv):
        if serveur[14] is None:
            timeout = 0
        else:
            timeout =int(serveur[14])
        if serveur[17] == None:
            params = serv.get_params()
        else:
            params = serveur[17]
        if serveur[18] == None:
            maj_serv = -1
        else:
            maj_serv = serveur[18]
        if serveur[19] == None:
            md5_serv = -1
        else:
            md5_serv = serveur[19]
        if serveur[20] == None:
            ip_publique = ""
        else:
            ip_publique = serveur[20]
        if serveur[21] in (None, 0):
            no_alert = 0
        else:
            no_alert = 1
        return {'id':serveur[0],
        'rne':serveur[1],
        'libelle':serveur[2],
        'materiel':serveur[3],
        'processeur':serveur[4],
        'disque_dur':serveur[5],
        'date_install':str(serveur[6]),
        'installateur':serveur[7],
        'tel':serveur[8],
        'remarques':serveur[9],
        'module_initial':serveur[10],
        'module_actuel':serveur[11],
        'variante':serveur[12],
        'timeout':timeout,
        'timestamp':serv.modified,
        'etat':serv.get_status(),
        'params':params,
        'maj':maj_serv,
        'md5s':md5_serv,
        'ip_publique':ip_publique,
        'no_alert':no_alert
        }

    def _got_log(self,lignes,cred_user):
        """Formattage de la liste des logs
        """
        # on vérifie quels serveurs sont accessibles
        servs = {}
        for ligne in lignes:
            id_serveur = ligne[1]
            try:
                if id_serveur not in list(servs.keys()):
                    self.parent.s_pool.get(cred_user,int(id_serveur))
            except:
                servs[id_serveur] = False
            else:
                servs[id_serveur] = True
        # création de la liste des logs à afficher
        l=[]
        for ligne in lignes:
            if servs[ligne[1]] == True:
                l.append(
                {'id':ligne[0]
                ,'id_serveur':ligne[1]
                ,'date':str(ligne[2])
                ,'action':ligne[3]
                ,'message':ligne[4]
                ,'etat':ligne[5]})
        return 1,u(l)


    #############################
    ## gestion des serveurs eole
    #############################

    def xmlrpc_regen_key(self,cred_user,serveurs, regen_certs=False, new_addr=None):
        """prépare une nouvelle clé ssh pour la communication client/zephir
        - génère la clé
        - prépare une action de mise en place de la clé sur le client
        """
        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))
            if not os.path.isfile(os.path.join(serv.get_confdir(), 'cle_publique')):
                erreurs.append("%s (%s): Le serveur n'est pas encore enregistré" % (serv.id_s, serv.rne))
            else:
                res = serv.regen_key(new_addr)
                if res != 0:
                    erreurs.append("%s (%s): Erreur lors de la génération de la clé" % (serv.id_s, serv.rne))
                else:
                    # envoi de la nouvelle clé publique au serveur par uucp
                    code, data =  self.parent.getSubHandler('uucp')._send_files(serv,'pubkey',['new_key.pub'],uucp=1)
                    if code == 0:
                        erreurs.append("%s (%s): Echec d'envoi de la clé publique (%s)" % (serv.id_s, serv.rne, data))
                    else:
                        id_uucp = str(serv.get_rne())+'-'+str(serv.id_s)
                        if new_addr is None:
                            # mise en file d'attente de la procédure de mise en place
                            # non utilisé dans le cas d'un changement d'adresse de zephir
                            # (le client installera la clé quand la nouvelle adresse répondra)
                            try:
                                if regen_certs:
                                    res = uucp_pool.add_cmd(id_uucp,"zephir_client update_key regen_certs")
                                else:
                                    res = uucp_pool.add_cmd(id_uucp,"zephir_client update_key")
                            except UUCPError as e:
                                erreurs.append("serveur %s (%s) : Erreur UUCP %s" % (serv.id_s, serv.rne, str(e)))
                        else:
                            try:
                                res = uucp_pool.add_cmd(id_uucp,"zephir_client change_ip")
                            except UUCPError as e:
                                erreurs.append("serveur %s (%s) : Erreur UUCP %s" % (serv.id_s, serv.rne, str(e)))
        return 1, u(erreurs)

    def xmlrpc_get_key(self,cred_user,id_serveur,old_key,new_key,confirm_ip=False):
        """retourne à un serveur une nouvelle clé ssh générée (appelé depuis le client)
        la clé est renvoyée seulement si les clés publiques correspondent
        confirm_ip : utilisé dans le cas d'un changement d'adresse de zephir pour confirmer
                    que l'adresse est bien prise en compte par le client
        """
        id_serveur = int(id_serveur)
        serv = self.parent.s_pool.get(cred_user, id_serveur)
        # récupération des données de la clé
        code, res = serv.get_key(old_key,new_key, confirm_ip)
        return code, u(res)

    def xmlrpc_update_key(self,cred_user,id_serveur):
        """valide la mise en place d'une nouvelle clé et invalide l'ancienne
        """
        id_serveur = int(id_serveur)
        serv = self.parent.s_pool.get(cred_user, id_serveur)
        """met en place la nouvelle clé d'enregistrement côté zephir
        Les clés publiques et privées doivent avoir été récupérées par le serveur"""
        actual_key_path = os.path.join(serv.get_confdir(), 'cle_publique')
        new_key_path = os.path.join(serv.get_confdir(), 'new_key.pub')
        new_key_priv_path = os.path.join(serv.get_confdir(), 'new_key')
        new_addr_path = os.path.join(serv.get_confdir(), 'new_addr_ok')
        if not os.path.isfile(new_key_path) or os.path.isfile(new_key_priv_path):
            return 0, """Nouvelles clés non récupérées par le serveur"""
        cle_rsa = open(new_key_path).read().strip()
        # on supprime la clé publique temporaire
        os.unlink(new_key_path)
        if os.path.isfile(new_addr_path):
            os.unlink(new_addr_path)
        return self._conf_ssh('',serv.id_s,cle_rsa)

    def xmlrpc_purge_key(self,cred_user,serveurs):
        """annule la prise en compte d'une nouvelle adresse ip pour une liste de serveurs
        """
        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:
                params = serv.get_params()
                if 'new_key' in params and params['new_key'][0] in (2,3):
                    id_uucp = str(serv.get_rne()) + '-' + str(serv.id_s)
                    uucp_pool.add_cmd(id_uucp,"zephir_client purge_ip")
            except UUCPError as e:
                erreurs.append("serveur %s (%s) : Erreur UUCP %s" % (serv.id_s, serv.rne, str(e)))
                continue
            for fic in ('new_key','new_key.pub','new_addr','new_addr_ok'):
                serv_fic = os.path.join(serv.get_confdir(), fic)
                if os.path.isfile(serv_fic):
                    os.unlink(serv_fic)
        return 1, erreurs

    def xmlrpc_check_serveurs(self,cred_user,serveurs,last_check=None):
        """permet de vérifier si des serveurs ont été modifiés
        """
        return 1, u(self.parent.s_pool.check_serveurs(cred_user, serveurs, last_check))

    def xmlrpc_check_groupes(self, cred_user, groupes, last_check=None):
        """permet de vérifier si des serveurs ont été modifiés
        """
        return 1, u(self.parent.s_pool.check_groupes(cred_user, groupes, last_check))

    def xmlrpc_variante_migration(self, cred_user, id_serveur):
        """renvoie la variante de migration choisie si disponible
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except KeyError:
            return 0, u("""serveur inconnu""")
        try:
            variante_dest = serv.variante_migration()
            assert variante_dest
            return 1, int(variante_dest)
        except:
            return 0, u('Variante de migration non définie')

    def xmlrpc_migrate_serveur(self, cred_user, id_serveur, hw_infos, module_dest, variante_dest="", upgrade_auto=False):
        """modifie un serveur Eole1 en serveur NG"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except KeyError:
            return 0, u("""serveur inconnu""")
        if not variante_dest:
            # on regarde si une variante de migration a été définie sur zephir
            try:
                variante_dest = serv.variante_migration()
                assert int(variante_dest) > 0
            except:
                variante_dest = None
        if variante_dest == None:
            query = "select modules.id, modules.libelle, modules.version, variantes.id from modules,variantes \
                    where modules.id=%s and modules.id=variantes.module and variantes.libelle='standard'"
            return self.dbpool.runQuery(query, (int(module_dest),)).addCallbacks(self._migrate_serveur2,db_client_failed,callbackArgs=[cred_user, serv, hw_infos, variante_dest, module_dest, upgrade_auto])
        else:
            query = "select modules.id, modules.libelle, modules.version, variantes.id from modules, variantes \
                    where modules.id=%s and modules.id=variantes.module and variantes.id=%s"
            return self.dbpool.runQuery(query, (int(module_dest), int(variante_dest))).addCallbacks(self._migrate_serveur2,db_client_failed,callbackArgs=[cred_user, serv, hw_infos, variante_dest, module_dest, upgrade_auto])

    def _migrate_serveur2(self, data, cred_user, serv, hw_infos, variante_dest, module_dest, upgrade_auto):
        # vérification de la validité des modules
        try:
            lib_new = data[0][1]
            id_new_mod = int(data[0][0])
            version_new = int(data[0][2])
            id_variante = int(data[0][3])
            lib_act = serv.module

            version_act = serv.module_version
            assert lib_new[:lib_new.rindex('-')] == lib_act[:lib_act.rindex('-')]
            assert lib_new != lib_act and version_new > 1
            if config.get_next_dist(version_act) not in ( -1, version_new ):
                # cas particulier, on regarde si le passage est autorisé entre ces 2 version pour ce module
                assert version_new in config.allowed_upgrades[lib_act[:lib_act.rindex('-')]][version_act]
        except:
            traceback.print_exc()
            return 0,u("""la migration vers le module demandé n'est pas gérée""")
        if not version_new in config.allowed_migrations.get(version_act, []):
            # migration transparente entre 2 versions d'eole NG
            migrate = False
            if variante_dest == None:
                # si pas de variante spécifiée, on regarde si on a une variante correspondant à la variante actuelle (table migration_variantes)
                query = "select id_dest, module from migration_variantes, variantes where id_source=%s and id_dest = id and module = %s"
                return self.dbpool.runQuery(query, (int(serv.id_var), int(module_dest))).addCallbacks(self._migrate_variante, db_client_failed, callbackArgs=[serv,id_new_mod,id_variante,hw_infos,cred_user,migrate, upgrade_auto])
        else:
            # migration avec préparation de configuration obligatoire
            # (aujourd'hui eole1 -> NG, eole 2.2 -> 2.3)
            migrate = True
        # si changement de version de Creole, on supprime la configuration actuelle du cache (#16135)
        if config.CREOLE_VERSIONS[version_new] != serv.version:
            serv.dico = None
        query = "select module_actuel, variante, materiel, processeur, disque_dur, date_install, installateur, tel, remarques, timeout from serveurs where id=%s" % (serv.id_s)
        return self.dbpool.runQuery(query, (int(serv.id_s),)).addCallbacks(self._backup_serveur_data, db_client_failed,callbackArgs=[serv,id_new_mod,id_variante, hw_infos, cred_user, migrate, upgrade_auto])

    def _migrate_variante(self, data, serv,id_new_mod,id_variante, hw_infos, cred_user, migrate, upgrade_auto):
        """récupère la variante à migrer automatiquement si disponible"""
        if len(data) > 0:
            id_var_mod = data[0][1]
            if id_var_mod == id_new_mod:
                # variante de migration trouvée dans le module de destination
                id_variante = data[0][0]
        query = "select module_actuel, variante, materiel, processeur, disque_dur, date_install, installateur, tel, remarques, timeout from serveurs where id=%s"
        return self.dbpool.runQuery(query, (int(serv.id_s),)).addCallbacks(self._backup_serveur_data, db_client_failed,callbackArgs=[serv,id_new_mod,id_variante, hw_infos, cred_user, migrate, upgrade_auto])

    def _backup_serveur_data(self, data, serv, id_new_mod, id_variante, hw_infos, cred_user, migrate, upgrade_auto):
        path_ori = serv.get_confdir()
        if migrate == True:
            path_bak = path_ori + "-backup"
        else:
            path_bak = path_ori + "-downgrade"
        # on fait un backup des anciennes données
        if os.path.exists(path_bak):
            try:
                prev_migration_var = int(open(os.path.join(path_bak, 'variante_migration')).read().strip())
            except:
                prev_migration_var = None
            if migrate and prev_migration_var == id_variante:
                # migration déjà effectuée: ne devrait pas se produire plusieurs fois
                return 0, u("""Migration vers cette variante déjà effectuée (backup présent: %s)""" % path_bak)
            else:
                # on supprime l'ancien backup pour permettre des upgrade/migrations successives
                shutil.rmtree(path_bak)
        # on sauvegarde dans un fichier les données sql d'origine
        sql_data = "::".join([str(val) for val in data[0]])
        f_var_ori = open(os.path.join(path_ori,"sql_data.ori"),'w')
        f_var_ori.write(sql_data)
        f_var_ori.close()
        # mise de coté des anciennes données
        res = os.system("/bin/mv %s %s" % (path_ori, path_bak))
        if res != 0:
            return 0, u("""erreur de création du répertoire de sauvegarde""")
        # mise en forme des infos materielles
        materiel = ""
        query_params = [int(id_new_mod), int(id_variante)]
        for row in ['materiel','processeur','disque_dur','installateur', 'date_install']:
            if row in hw_infos:
                if hw_infos[row] != '':
                    materiel += ", %s=%%s" % row
                    query_params.append(hw_infos[row])
        query_params.append(int(serv.id_s))
        # mise à jour de la base
        query = "update serveurs set module_actuel=%s, variante=%s, params=''" + materiel + " where id=%s"
        return self.dbpool.runOperation(query, query_params).addCallbacks(self._migrate_serveur_data, db_client_failed,callbackArgs=[serv,id_new_mod,id_variante,cred_user,migrate, upgrade_auto])

    def _migrate_serveur_data(self, res, serv, id_mod, id_var, cred_user, migrate, upgrade_auto):
        # stockage des anciens n° de module/variante pour mise à jour des statistiques
        old_mod = serv.id_mod
        old_var = serv.id_var
        # mise en place des répertoires
        serv.update_data()
        ret, msg = serv._cree_arbo_serveur()
        if ret != 1:
            self._revert_migration(serv, cred_user)
            return ret, u(msg)
        # mise à jour des statistiques de modules / variantes
        self.parent.s_pool.stats['serv_variantes'][str(old_var)] = self.parent.s_pool.stats['serv_variantes'][str(old_var)] - 1
        self.parent.s_pool.stats['serv_variantes'][str(id_var)] = self.parent.s_pool.stats['serv_variantes'].get(str(id_var), 0) + 1
        # mise à jour des statistiques des modules
        self.parent.s_pool.stats['serv_modules'][str(old_mod)] = self.parent.s_pool.stats['serv_modules'][str(old_mod)] - 1
        self.parent.s_pool.stats['serv_modules'][str(id_mod)] = self.parent.s_pool.stats['serv_modules'].get(str(id_mod), 0) + 1
        # copie des fichiers d'authentification
        path_ori = serv.get_confdir()
        if migrate:
            path_bak = path_ori + "-backup"
            alt_bak = path_ori + "-downgrade"
        else:
            path_bak = path_ori + "-downgrade"
            alt_bak = path_ori + "-backup"
        res = 0
        self.copy_serveur_data(path_ori, path_bak, migrate, upgrade_auto)
        if os.path.exists(alt_bak):
            # suppression des anciens backup d'upgrade si migration (et vice-versa)
            shutil.rmtree(alt_bak)
        # mise à jour du cache de configuration
        serv.dico = None
        serv.get_config()
        serv.maj_params({'migration_ok':1})

        if migrate == True and not upgrade_auto:
            # on invalide la cle ssh de l'ancien serveur (sur version antérieures à 2.4)
            try:
                fic_cle_publique = os.path.join(path_bak,'cle_publique')
                # si une clé existait déjà, on la supprime de authorized_keys avant de continuer
                if os.path.isfile(fic_cle_publique):
                    # on lit la sauvegarde de l'ancienne clé
                    backup_cle = open(fic_cle_publique,"r")
                    old_cle = backup_cle.read().strip().split('\n')[0]
                    backup_cle.close()
                    # on enlève les occurences de cette ancienne clé des clés autorisées
                    if old_cle != "":
                        self._remove_ssh_key(old_cle)
            except:
                traceback.print_exc()
            return self._update_conf_uucp(cred_user, serv.id_s, serv.rne, "")
        else:
            # vérifie la cohérence des dictionnaires et on reconstruit les liens
            self.parent.dictpool.check_variante_conflicts(serv.id_s)
            return 1, serv.id_s

    def copy_serveur_data(self, path_ori, path_bak, migrate, upgrade_auto):
        """copie des fichiers compatibles lors d'une migration/update
        """
        # fichier de configuration creole
        if os.path.isfile(os.path.join(path_bak,'zephir.eol')) and not migrate:
            res = os.system("/bin/cp -rf %s %s" % (os.path.join(path_bak,'zephir.eol'),os.path.join(path_ori,'zephir.eol')))
            if res != 0:
                return 0, u("""erreur de copie du fichier de configuration creole (zephir.eol)""")
        if os.path.isfile(os.path.join(path_bak,'migration.eol')) and migrate:
            res = os.system("/bin/cp -rf %s %s" % (os.path.join(path_bak,'migration.eol'),os.path.join(path_ori,'zephir.eol')))
            if res != 0:
                return 0, u("""erreur de copie du fichier de migration creole (migration.eol -> zephir.eol)""")
        # liste des fichiers à recopier
        data_files = ['auth_keys','vpn', 'replication']
        def append_files(data_dirs):
            for data_dir in data_dirs:
                for data_file in os.listdir(os.path.join(path_bak,data_dir)):
                    if not os.path.islink(os.path.join(path_bak,data_dir,data_file)):
                        data_files.append(os.path.join(data_dir, data_file))
            data_files.append('cle_publique')
        if migrate and upgrade_auto:
            # à partir de eole 2.4, on peut appeler upgrade_auto en mode 'migration'
            append_files(['uucp'])
        elif not migrate:
            # upgrade de distribution, tous les fichiers personnalisés sont repris tels quels
            append_files(['fichiers_zephir', 'fichiers_perso', 'patchs', 'uucp', 'dicos/local'])
        log.msg("\n !! copying data : %s !!\n" % (data_files))
        res = 0
        for fic in data_files:
            if os.path.isdir(os.path.join(path_ori,fic)):
                shutil.rmtree(os.path.join(path_ori,fic))
            if os.path.exists(os.path.join(path_bak,fic)):
                res = os.system("/bin/cp -rpf %s %s" % (os.path.join(path_bak,fic),os.path.join(path_ori,fic)))
                if res != 0:
                    break
        if res != 0:
            return 0, u("""erreur de copie des fichiers de configuration uucp""")

    def xmlrpc_migrate_data(self, cred_user, id_serveur, check=False):
        """fonction de récupération des données d'un serveur migré
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
            return serv.migrate_data(check)
        except KeyError:
            return 0, u("""serveur inconnu""")

    def xmlrpc_migrate_conf(self, cred_user, id_serveur, mode='migration', variante_dest=''):
        """fonction de création d'un fichier de configuration pour la migration (migration.eol)
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except KeyError:
            return 0, u("""serveur invalide""")
        if variante_dest == '':
            # on récupère la variante déjà enregistrée
            variante_dest = serv.variante_migration()
            if variante_dest == '':
                return 0, u("""variante de destination non définie""")

        query = 'select variantes.id, variantes.module, modules.version, modules.libelle from variantes,modules where variantes.id=%s and modules.id=variantes.module'
        return self.dbpool.runQuery(query, (int(variante_dest),)).addCallbacks(self._migrate_config, db_client_failed,callbackArgs=[cred_user, serv, mode])

    def _migrate_config(self, data, cred_user, serv, mode):
        # on vérifie le module de destination
        try:
            id_variante = int(data[0][0])
            id_new_mod = int(data[0][1])
            version_new = int(data[0][2])
            lib_new = str(data[0][3])
            lib_act = serv.get_module().split('-')[0]
            assert serv.module_version in config.allowed_migrations and version_new in config.allowed_migrations[serv.module_version]
            assert lib_new.split('-')[0] == lib_act.split('-')[0]
        except:
            return 0,u("""la migration vers le module demandé n'est pas gérée""")
        # récupération du dictionnaire correspondant à la variante avec les valeurs
        # importées de la configuration actuelle du serveur
        try:
            creole_version = config.CREOLE_VERSIONS[version_new]
            config_serv = serv.migrate_config(id_new_mod, id_variante, mode, version_new)
            data = config_serv.get_dict()
            # log des éventuelles erreurs à l'upgrade
            upgrade_infos = get_upgrade_infos(config_serv.dico)
            errors = upgrade_infos['load_errors']
            if len(errors) > 0:
                log.msg("Serveur {0}: problèmes rencontrés lors de la génération de la configuration de migration :".format(str(serv.id_s)))
                for load_error in list(errors.values()):
                    log.msg(" - {0}".format(load_error))
            # ajout des informations d'upgrade si besoin
            data.append(upgrade_infos)
        except:
            traceback.print_exc()
            return 0, u("""erreur lors de l'import des données de configuration""")
        return 1, u(data)

    def xmlrpc_revert_migration(self,cred_user,id_serveur):
        """retour en arrière sur la migration d'un serveur
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
            return self._revert_migration(serv,cred_user)
        except (KeyError, ValueError):
            return 0, u("""id de serveur non valide : %s""" % str(id_serveur))

    def _revert_migration(self, serv, cred_user):
        path_ori = serv.get_confdir()
        # récupération du répertoire de sauvegarde le plus récent
        backups = []
        for back_dir in ("-downgrade", "-backup"):
            if os.path.isdir(path_ori + back_dir):
                backups.append(path_ori + back_dir)
        if len(backups) == 0:
            return 0, u("""répertoire de backup non retrouvé""")
        else:
            # tri par ordre de création (plus récent en premier)
            backups.sort(key=os.path.getmtime, reverse=True)
        path_bak = backups[0]
        # on récupère les données sql d'origine
        f_var_ori = open(os.path.join(path_bak,"sql_data.ori"))
        data_ori = f_var_ori.read().strip().split('::')
        f_var_ori.close()
        try:
            assert len(data_ori) == 10
        except:
            return 0, u("""impossible de relire les données sql d'origine""")
        # réinsertion des valeur d'origine dans la base
        data_ori.append(serv.id_s)
        query = "update serveurs set \
                module_actuel=%s,\
                variante=%s,\
                materiel=E'%s',\
                processeur=E'%s',\
                disque_dur=E'%s',\
                date_install=E'%s',\
                installateur=E'%s',\
                tel=E'%s',\
                remarques=E'%s',\
                timeout=%s,\
                params=null, maj=null  where id=%s" % tuple(data_ori)
        return self.dbpool.runOperation(query).addCallbacks(self._revert_migration2,db_client_failed,callbackArgs=[serv, path_ori, path_bak,cred_user])

    def _revert_migration2(self, res, serv, path_ori, path_bak,cred_user):
        fic_cle_publique = os.path.join(path_ori,'cle_publique')
        try:
            # suppression de la cle eoleNG
            if os.path.isfile(fic_cle_publique):
                backup_cle = open(fic_cle_publique,"r")
                new_key = backup_cle.read().strip().split('\n')[0]
                backup_cle.close()
                if new_key != "":
                    # on enlève les occurences de cette ancienne clé des clés autorisées
                    self._remove_ssh_key(new_key)
        except:
            traceback.print_exc()
        # remise en place de l'ancien password uucp
        if os.path.isdir(path_ori):
            shutil.rmtree(path_ori)
        res = os.system("/bin/mv %s %s;/bin/sync" % (path_bak, path_ori))
        if res != 0:
            return 0, u("""erreur de mise en place des données sauvegardées""")
        os.unlink(os.path.join(path_ori,"sql_data.ori"))
        try:
            # réautorisation de la cle eole1
            if os.path.isfile(fic_cle_publique):
                # on lit la sauvegarde de l'ancienne clé
                backup_cle = open(fic_cle_publique,"r")
                old_key = backup_cle.read().strip().split('\n')[0]
                backup_cle.close()
                # on autorise l'utilisation de cette ancienne clé
                self._authorize_ssh_key(old_key)
        except:
            traceback.print_exc()
        # remise en place de l'ancien password uucp
        if os.path.isfile(os.path.join(path_ori,'uucp','sys')):
            data_uucp = open(os.path.join(path_ori,'uucp','sys')).read().split('\n')
            for line in data_uucp:
                if line.startswith('call-password') and line.count(serv.rne) > 0:
                    passwd_uucp = line.strip().split()[-1]
                    self._update_conf_uucp(cred_user, serv.id_s, serv.rne, "", passwd_uucp)
                    break
        serv.update_data()
        serv.get_params()
        # rechargement du cache de configuration
        serv.check_dict('modif_config')
        return 1, "OK"

    def xmlrpc_check_backup(self, cred_user, id_serveur):
        """regarde si un backup de migration est présent pour un serveur
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
            path_serv = serv.get_confdir()
        except KeyError:
            return 0, u("""serveur inconnu""")
        if os.path.isdir(path_serv + '-backup') or \
           os.path.isdir(path_serv + '-downgrade'):
            return 1, True
        else:
            return 1, False

    def xmlrpc_download_upgrade(self, cred_user, id_serveur, version, delay):
        """lancement telechargement pour migration sur un serveur"""
        return self._download_upgrade(cred_user, id_serveur, version, delay)

    def xmlrpc_download_upgrade_groupe(self, cred_user, liste, version, delay):
        """lancement telechargement pour migration sur un groupe de serveurs"""
        erreurs=[]
        for serveur in liste:
            retour = self._download_upgrade(cred_user, serveur['id'], version, delay)
            if retour[0] == 0:
                erreurs.append("serveur "+str(serveur['id'])+' : '+retour[1])
        if erreurs != []:
            return 1, u(erreurs)
        else:
            return 1, []

    def _download_upgrade(self, cred_user, id_serveur, version, delay):
        """lancement du téléchargement des isos de mise à jour pour upgrader
        vers une version supérieure du système

        Le téléchargement est disponible :
        * depuis une version 2.4.2 au minimum
        * vers l'ensemble des releases de la version suivante
        * on doit être sur la dernière release de la version actuelle
        * on ne peut pas upgrader avec plus d'une distribution majeure d'écart
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("""id de serveur non valide : %s""" % str(id_serveur))
        serveur_version = serv.module_version
        if serveur_version < 8:
            return 0, u("Téléchargement d'iso disponible à partir d'Eole 2.4.2 au minimum")
        if serveur_version > version:
            # déjà dans une version supérieure : pas de remontée d'erreur
            return 1, u("Serveur déjà dans une version supérieure")
        serveur_module = serv.module
        serveur_module = serveur_module[:serveur_module.rindex('-')]
        current_dist = config.DISTRIBS[serveur_version][0]
        dest_dist = config.DISTRIBS[version][0]
        if dest_dist == current_dist:
            return 0, u("Le serveur est déjà sur la distribution %s" % dest_dist)
        next_major_dist = ""
        for dist_version, infos in list(config.DISTRIBS.items()):
            if dist_version > serveur_version and infos[0] != current_dist:
                next_major_dist = infos[0]
                break
        if next_major_dist != dest_dist:
            return 0, u("Migration vers %s non gérée depuis eole %s" % (dest_dist, config.DISTRIBS[serveur_version][1]))
        # recherche des informations sur la version suivante
        next_dist = config.get_next_dist(serveur_version)
        if config.DISTRIBS[serveur_version][0] == config.DISTRIBS[next_dist][0]:
            return 0, u("Pas sur la dernière release disponible de la distribution %s" % config.DISTRIBS[serveur_version][0])
        if version != next_dist:
            # saut > 1 : permis seulement dans certains cas
            try:
                assert version in config.allowed_upgrades[serveur_module][serveur_version]
            except:
                return 0, u("Migration de Eole %s vers Eole %s non supportée" % \
                        (config.DISTRIBS[serveur_version][1], config.DISTRIBS[version][1]))
        id_uucp = str(serv.get_rne()) + '-' + str(serv.id_s)
        upgrade_version = config.DISTRIBS[version][1]
        try:
            # construction de la commande uucp
            uucp_pool.add_cmd(id_uucp,"zephir_client download_upgrade %s %s" % (upgrade_version, str(delay)))
        except UUCPError as e:
            return 0, u("Erreur UUCP (%s)" % str(e))
        return 1, u("ok")

    def xmlrpc_add_serveur(self, cred_user, rne, libelle, materiel, processeur, disque_dur,
                           date_install, installateur, tel, remarques,
                           module_initial, module_actuel, timeout, variante=None, cle_rsa1="", id_groupe=-1
                           ):
        """ajout d'un serveur dans la base zephir"""
        # on vérifie qu'on a bien récupéré un serveur
        if rne:
            # on recherche la variante par défaut du module
            liste_donnees = [cred_user, rne, libelle, materiel, processeur, disque_dur, date_install, installateur, tel, remarques, module_initial, module_actuel, variante, timeout, cle_rsa1,id_groupe]
            query = """select id from variantes where module = %s and libelle = 'standard'"""
            return self.dbpool.runQuery(query, (int(module_actuel),)).addCallbacks(self._add_serveur1, db_client_failed,callbackArgs=liste_donnees)
        else:
            # des attributs manquent
            return 0,u("""arguments à fournir:
                     rne,libelle,materiel,processeur,disque_dur,date_install,
                     installateur,tel,remarques,module_initial,module_actuel,variante""")


    def _add_serveur1(self, search_result, cred_user, rne, libelle, materiel, processeur, disque_dur,
                      date_install, installateur, tel, remarques,
                      module_initial, module_actuel, variante, timeout, cle_rsa1="", id_groupe=-1
                      ):
        """insertion du serveur dans la base de données"""
        # dans le cas ou la variante n'est pas donnée, on prend la variante par défaut
        if variante is None or variante == "":
            variante = search_result[0][0]
        timestamp_serveur = str(time.time())
        try:
            timeout = int(timeout)
        except:
            timeout = 0
        # ajout du serveur dans la base
        try:
            serv = self.parent.s_pool.add_serveur(cred_user, rne,libelle,materiel,processeur,disque_dur,date_install,installateur,tel,remarques,module_initial,module_actuel,variante,timestamp_serveur,timeout)
            id_serveur = serv.id_s
            # si on a demandé l'ajout à un groupe, on étend ce groupe
            if int(id_groupe) >= 0:
                self.parent.s_pool.extend_groupe(cred_user,int(id_groupe),[id_serveur])
        except Exception as e:
            return 0, u(str(e))
        # mise en place de l'arborescence pour le serveur et préparation des actions uucp
        result,errmsg = serv._cree_arbo_serveur()
        if result == 0:
            # 'rollback'
            self.xmlrpc_del_serveur(cred_user, id_serveur)
            return 0, u(errmsg)
        else:
            return self._update_conf_uucp(cred_user, serv.id_s, rne, cle_rsa1)

    def _update_conf_uucp(self,cred_user,id_serveur,rne, cle_rsa1, passwd_uucp=""):
        # création de la configuration du système distant
        id_uucp = str(rne)+'-'+str(id_serveur)
        if passwd_uucp == "":
            passwd_uucp = str(rne)+'-'+str(id_serveur)+'-'+str(time.time())
        id_uucp = str(rne)+'-'+str(id_serveur)
        # path_rcv = os.path.abspath(config.PATH_ZEPHIR)+os.sep+'conf'+os.sep+str(rne)+os.sep+str(id_serveur)
        chaine_conf = config.CONFIG_UUCP % (id_serveur,id_uucp,id_uucp,passwd_uucp)
        try:
            if not os.path.isdir("/etc/uucp/serveurs/"):
                os.makedirs("/etc/uucp/serveurs/")
            fic_conf = open("/etc/uucp/serveurs/"+id_uucp+".sys","w")
            fic_conf.write(chaine_conf)
            fic_conf.close()
        except:
            # 'rollback'
            self.xmlrpc_del_serveur(cred_user, id_serveur)
            return 0,u("""erreur de création de la configuration uucp""")
        else:
            # on met en place la configuration uucp du module sur zephir
            test=os.system('grep "sysfile /etc/uucp/serveurs/'+id_uucp+'.sys" /etc/uucp/config_zephir')
            result = 0
            if test != 0:
                # si le serveur n'est pas encore listé, on l'ajoute
                result = os.system('echo "sysfile /etc/uucp/serveurs/'+id_uucp+'.sys" >>/etc/uucp/config_zephir')
            if result == 0:
                # on indique un login et un mot de passe pour ce serveur
                try:
                    fic_pass = open('/etc/uucp/passwd_zephir')
                    lines = fic_pass.read().strip().split('\n')
                    fic_pass.close()
                    # on parcourt la liste des serveurs et on remplace si il est déjà présent
                    content = []
                    for line in lines:
                        # on supprime l'ancien mot de passe du serveur
                        if not line.startswith(id_uucp+' '):
                            content.append(line)
                    # on ajoute le nouveau mot de passe
                    content.append("%s %s" % (id_uucp,passwd_uucp))
                    # réécriture du fichier
                    fic_pass = open('/etc/uucp/passwd_zephir','w')
                    lines = fic_pass.write("\n".join(content))
                    fic_pass.close()
                except:
                    result = 1
                if result == 0:
                    # Ok, configuration terminée
                    return self._conf_uucp(id_serveur, rne, passwd_uucp, cle_rsa1)
            if result != 0:
                self.xmlrpc_del_serveur(cred_user, id_serveur)
                return 0,u("""erreur de mise à jour de la configuration uucp""")

    def _conf_uucp(self,id_serveur,rne,passwd_uucp,cle_rsa1=""):
        """création des fichiers de configuration uucp du serveur"""
        path_dest=os.path.abspath(config.PATH_ZEPHIR)+'/conf/'+rne+os.sep+str(id_serveur)+os.sep+'uucp'
        # fichier call,dial,dialcode et port
        # --> copie sans modification
        cmd = """cp %s/call %s/dial %s/dialcode %s/passwd %s""" % (config.TEMPLATE_DIR,config.TEMPLATE_DIR,config.TEMPLATE_DIR,config.TEMPLATE_DIR,path_dest)
        os.system(cmd)
        # fichier config, passwd, sys
        for filepath in ["config","sys","port"]:
            # on lit le fichier par défaut
            try:
                fic_ori=open(config.TEMPLATE_DIR+os.sep+filepath,'r')
                lines = fic_ori.readlines()
                fic_ori.close()
            except:
                return 0,u('erreur de lecture du fichier %s ' % filepath)
            conf=[]
            for line in lines:
                # l'adresse est maintenant remplie par le client lors de l'enregistrement
                # (pour etre sur d'avoir une adresse accessible de l'exérieur)
                line = line.replace('%%serveur%%',str(rne)+'-'+str(id_serveur))
                line = line.replace('%%password%%',passwd_uucp)
                # line = line.replace('%%zephir%%',str(config.ADRESSE_ZEPHIR))
                conf.append(line)
            # on écrit le fichier de destination
            try:
                fic_dest=open(path_dest+os.sep+filepath,'w')
                fic_dest.writelines(conf)
                fic_dest.close()
            except:
                return 0,u('erreur de création du fichier %s ' % path_dest+os.sep+filepath)
        if cle_rsa1 != "":
            # si une cle est donnée, on effectue son ajout maintenant
            return self._conf_ssh('',id_serveur,base64.decodebytes(to_bytes(cle_rsa1)))
        else:
            return 1, id_serveur

    def xmlrpc_get_conf_uucp(self,cred_user,id_serveur,cle_rsa1,hw_infos={}):
        """permet de récupérer la configuration uucp d'un serveur via xmlrpc
        Les informations matérielles du serveur sont mises à jour si transmises (hw_infos)
        """
        if cle_rsa1 == "":
            return 0, u("""clé rsa invalide""")
        if hw_infos:
            # mise à jour des infos sur le matériel
            hw_data = {}
            # on vérifie qu'on ne récupère que les informations voulues
            for datafield in ('materiel', 'processeur', 'disque_dur', 'installateur', 'date_install'):
                if hw_infos.get(datafield, ""):
                    hw_data[datafield] = hw_infos[datafield]
            if hw_data:
                serv = self.parent.s_pool.get(cred_user, id_serveur)
                code_res, data = self.parent.s_pool.edit_serveur(serv.id_s, hw_data)
        if id_serveur in self.parent.s_pool:
            # on logue l'accès du serveur (pour avoir un premier contact lors de l'enregistrement)
            self.parent.s_pool.update_contact(id_serveur)
            return self._conf_ssh('',id_serveur,base64.decodebytes(to_bytes(cle_rsa1)))
        else:
            return 0, u("""erreur, serveur non retrouvé""")

    def _conf_ssh(self,cred_user,id_serveur,cle_rsa1):
        """mise en place de la clé pour l'authentification d'uucp sur ssh"""
        if isinstance(cle_rsa1, bytes):
            cle_rsa1 = cle_rsa1.decode()
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except KeyError:
            return 0, u("""serveur inconnu""")
        path_dest=serv.get_confdir()
        # construction de l'autorisation pour la connexion d'uucp via ssh
        ligne_rsa = 'command="sudo /usr/sbin/uucico2 -D -l" '+cle_rsa1
        # on stocke la clé publique dans le répertoire du serveur
        try:
            fic_cle_publique = os.path.join(path_dest,'cle_publique')
            # si une clé existait déjà, on la supprime de authorized_keys avant de continuer
            if os.path.isfile(fic_cle_publique):
                # on lit la sauvegarde de l'ancienne clé
                backup_cle = open(fic_cle_publique,"r")
                old_cle = backup_cle.read().strip().split('\n')[0]
                backup_cle.close()
                if old_cle != "":
                    # on enlève les occurences de cette ancienne clé des clés autorisées
                    self._remove_ssh_key(old_cle)
            # on sauvegarde la nouvelle clé sur zephir
            backup_cle = open(fic_cle_publique,"w")
            backup_cle.write(ligne_rsa)
            backup_cle.close()
            serv.maj_params({'cle_ok':1})
            # et on ajoute la nouvelle clé à la liste des clés ssh autorisées pour uucp
            self._authorize_ssh_key(ligne_rsa)
        except:
            traceback.print_exc()
            # erreur d'écriture dans un des fichiers
            self.xmlrpc_del_serveur(cred_user,id_serveur)
            return 0,u("Erreur d'ajout de la cle rsa sur le serveur")
        else:
            # création des chaines permettant de transférer la conf uucp minimale
            files = []
            for path_conf in ['config','sys','port']:
                # on lit le fichier de conf
                file_conf=open(path_dest+os.sep+'uucp'+os.sep+path_conf,"r")
                lines_conf=file_conf.read()
                file_conf.close()
                # cas particulier : config
                # correction au cas où le client ne comporte pas la correction du lockdir
                if path_conf == 'config':
                    if lines_conf.count('lockdir') == 0:
                        lines_conf = lines_conf + "\nlockdir /tmp"
                # on encode le contenu en base 64 et on l'ajoute à la liste des fichiers de conf
                files.append(base64.encodebytes(lines_conf.encode()).decode())
            # envoi du fichier config.eol si il est déjà présent
            if os.path.exists(path_dest+os.sep+'zephir.eol'):
                # lecture du contenu
                file_conf=open(path_dest+os.sep+'zephir.eol',"rb")
                lines_conf = file_conf.read()
                file_conf.close()
                files.append(base64.encodebytes(lines_conf).decode())
            else:
                # sinon on envoie un chaine vide
                files.append("")
            return 1,id_serveur,files[0],files[1],files[2],files[3]

    def _authorize_ssh_key(self, cle):
        """ajoute une cle ssh dans authorized_keys
        """
        if os.path.isfile("/var/spool/uucp/.ssh/authorized_keys"):
            auth_keys = open("/var/spool/uucp/.ssh/authorized_keys","r")
            lines=auth_keys.read().strip().split('\n')
            auth_keys.close()
        else:
            lines = []
        # ajout de la nouvelle clé
        lines.append(cle)
        # on enregistre le fichier modifié
        auth_keys = open("/var/spool/uucp/.ssh/authorized_keys","w")
        auth_keys.write("\n".join(lines))
        auth_keys.close()

    def _remove_ssh_key(self, cle):
        """supprime une cle ssh de authorized_keys
        """
        auth_keys = open("/var/spool/uucp/.ssh/authorized_keys","r")
        lines=auth_keys.read().strip().split('\n')
        auth_keys.close()
        new_lines = []
        for line in lines:
            if line.startswith(cle.strip()):
                # si c'est la clé recherchée, on ne l'écrit pas
                pass
            else:
                # sinon on reprend la ligne telle quelle
                new_lines.append(line)
        # on enregistre le fichier modifié
        auth_keys = open("/var/spool/uucp/.ssh/authorized_keys","w")
        auth_keys.write("\n".join(new_lines))
        auth_keys.close()

    def xmlrpc_del_serveur(self,cred_user,id_serveur):
        """supression d'un serveur de la base zephir"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (ValueError, KeyError):
            # id_serveur non valide
            return 0, u("""donnez un identifiant de serveur valide""")

        # on supprime l'objet de la base de données (en principe un seul objet, l'id est unique)
        # avant de supprimer le serveur et la config uucp :
        # effectuer une procédure sur le serveur pour qu'il ne se connecte plus
        # avec son id uucp actuel ?
        id_uucp = serv.rne+'-'+str(id_serveur)
        # on supprime le serveur des groupes où il est référencé
        query = """select id,libelle,serveurs from groupes_serveurs"""
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        cursor=cx.cursor()
        cursor.execute(query)
        groupes=cursor.fetchall()
        # recherche des groupes concernés
        for groupe in groupes:
            serv_gr = eval(groupe[2])
            if id_serveur in serv_gr:
                # modification de la liste des serveurs
                serv_gr.remove(id_serveur)
                # sauvegarde du groupe
                if serv_gr == []:
                    # suppression si vide
                    self.xmlrpc_del_group(cred_user,int(groupe[0]))
                else:
                    self.xmlrpc_edit_group(cred_user,int(groupe[0]),groupe[1],serv_gr)
        # fernmeture de la connexion sql
        # XXX: FIXME - suppression des restrictions sur ce serveur
        cursor.close()
        cx.close()

        serveur_dir = serv.get_confdir()
        self.parent.s_pool.del_serveur(cred_user, id_serveur)
        # supression de ce répertoire ?
        # on récupère la clé publique si elle existe
        cle_pub=""
        if os.path.isfile(serveur_dir+os.sep+"cle_publique"):
            fic_rsa=open(serveur_dir+os.sep+"cle_publique","r")
            cle_pub = fic_rsa.read().strip().split('\n')[0]
            fic_rsa.close()
        try:
            shutil.rmtree(serveur_dir)
        except:
            return 1, u("""erreur de supression du repertoire du serveur""")
        # supression du site de surveillance si présent
        if os.path.exists(os.path.abspath(config.PATH_ZEPHIR)+os.sep+"data"+os.sep+str(id_serveur)):
            stat_dir = os.path.abspath(config.PATH_ZEPHIR)+os.sep+"data"+os.sep+str(id_serveur)
        else:
            stat_dir = os.path.abspath(config.PATH_ZEPHIR)+os.sep+"sites"+os.sep+str(id_serveur)
        try:
            shutil.rmtree(stat_dir)
        except:
            # pas encore de stats
            pass
        # suppression des fichiers de md5 et des listes de paquets
        for fic_data in ['config%s.md5' % str(id_serveur),'packages%s.list' % str(id_serveur)]:
            if os.path.isfile(os.path.join(os.path.abspath(config.PATH_ZEPHIR),"data",fic_data)):
                try:
                    os.unlink(os.path.join(os.path.abspath(config.PATH_ZEPHIR),"data",fic_data))
                except:
                    pass
        if os.path.exists("/var/spool/uucppublic/site%s.tar" % id_serveur):
            try:
                os.unlink("/var/spool/uucppublic/site%s.tar" % id_serveur)
                os.unlink("/var/spool/uucppublic/site%s.md5" % id_serveur)
            except:
                pass

        # spool uucp du serveur
        if os.path.exists("/var/spool/uucp/"+id_uucp):
            shutil.rmtree("/var/spool/uucp/"+id_uucp)
        # supression de la configuration uucp
        try:
            os.unlink("/etc/uucp/serveurs/"+id_uucp+".sys")
        except:
            pass
        fic_config=open("/etc/uucp/config_zephir","r")
        config_uucp=fic_config.readlines()
        fic_config.close()
        try:
            config_uucp.remove('sysfile /etc/uucp/serveurs/'+id_uucp+'.sys\n')
        except ValueError:
            return 1, u("""configuration uucp non retouvée""")
        else:
            try:
                fic_config=open("/etc/uucp/config_zephir","w")
                fic_config.writelines(config_uucp)
                fic_config.close()
            except:
                return 1, u("""erreur d'écriture dans /etc/uucp/config_zephir""")
            else:
                # suppression du login / password du serveur
                fic_config=open("/etc/uucp/passwd_zephir","r")
                config_uucp=fic_config.readlines()
                fic_config.close()
                for line in config_uucp:
                    if line.startswith(id_uucp+' '):
                        config_uucp.remove(line)
                try:
                    fic_config=open("/etc/uucp/passwd_zephir","w")
                    fic_config.writelines(config_uucp)
                    fic_config.close()
                except:
                    return 1, u("""erreur d'écriture dans /etc/uucp/passwd_zephir""")
                # supression de la cle publique dans 'authorized_keys' si on la connait
                if cle_pub != "":
                    try:
                        self._remove_ssh_key(cle_pub)
                    except:
                        # erreur de suppression de la clé
                        return 1, u("""clé rsa du serveur non retrouvée""")
        return 1, 'ok'

    def xmlrpc_serveurs_etab(self,cred_user,rne=None):
        """Liste des serveurs d'un etablissement
        """
        if rne :
            query="""select * from serveurs where rne ilike %s"""
            return self.dbpool.runQuery(query, (rne,)).addCallbacks(self._got_serveur,db_client_failed,callbackArgs=[cred_user])
        else :
            query="""select * from serveurs"""
            return self.dbpool.runQuery(query).addCallbacks(self._got_serveur,db_client_failed,callbackArgs=[cred_user])


    def xmlrpc_get_serveur(self,cred_user,id_serveur=None):
        """récupération d'un serveur particulier (ou tous)
        """
        if id_serveur:
            query="""select * from serveurs where id = %s"""
            return self.dbpool.runQuery(query, (int(id_serveur),)).addCallbacks(self._got_serveur,db_client_failed,callbackArgs=[cred_user])
        else:
            query="""select * from serveurs"""
            return self.dbpool.runQuery(query).addCallbacks(self._got_serveur,db_client_failed,callbackArgs=[cred_user])

    def xmlrpc_creole_version(self,cred_user,id_serveur):
        """renvoie la version de creole d'un serveur particulier
        """
        try:
            serv = self.parent.s_pool.get(cred_user,int(id_serveur))
            return 1, u(serv.version)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))

    def xmlrpc_module_version(self,cred_user,id_serveur):
        """renvoie la version de distribution d'un serveur particulier
        """
        try:
            serv = self.parent.s_pool.get(cred_user,int(id_serveur))
            return 1, u(serv.module_version)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))

    def xmlrpc_groupe_serveur(self,cred_user,criteres={},variables={},strict=True):
        """récupération d'un groupe de serveurs à partir de critères
        """
        # construction de la requête SQL de recherche
        if ('type_etab_lib' in criteres and criteres['type_etab_lib'] != '') or \
           ('type_etab' in criteres and criteres['type_etab'] != ''):
            query = ["select serveurs.* from serveurs, etablissements, types_etab "]
        else:
            query = ["select * from serveurs "]
        params = {}
        # recherches sur des informations du champ 'params',
        # géré par le cache interne (serveur_pool),
        # la recherche se fait sur l'information de statut
        if 'params' in criteres:
            params.update(criteres['params'])
            del(criteres['params'])
        # recherches sur les autres champs de la table serveur
        query_params = []
        if criteres != {}:
            query.append("where ")
            for nom_champ in list(criteres.keys()):
                if criteres[nom_champ] != "":
                    if nom_champ == 'type_etab_lib':
                        query.append("(serveurs.rne = etablissements.rne) and \
                        (etablissements.type = types_etab.id) and \
                        (types_etab.libelle ilike %s) and ")
                        query_params.append(str(criteres[nom_champ]))
                    elif nom_champ == 'type_etab':
                        query.append("(serveurs.rne = etablissements.rne) and \
                        (etablissements.type = types_etab.id) and \
                        (etablissements.type = %s) and ")
                        query_params.append(int(criteres[nom_champ]))
                    else:
                        query.append("(serveurs."+str(nom_champ))
                        if (nom_champ == 'last_contact') or (nom_champ == 'etat' and criteres[nom_champ] == "null"):
                            query.append(" is null) and ")
                        elif nom_champ == 'etat' and criteres[nom_champ] != '':
                            if criteres[nom_champ] == "alertes":
                                query.append(" <> 1) and (etat is not null) and ")
                            else:
                                query.append(" = %s) and ")
                                query_params.append(criteres[nom_champ])
                        elif (nom_champ == 'md5s' and criteres[nom_champ] == "null"):
                            query.append(" is null) and ")
                        elif nom_champ in ['module_actuel','module_initial','variante','timeout','md5s', 'id','no_alert'] and criteres[nom_champ] != '':
                            query.append(" = %s) and ")
                            query_params.append(int(criteres[nom_champ]))
                        elif nom_champ == 'date_install':
                            # cas particulier : l'operateur est inclus dans les données
                            query.append(" %s) and " % str(criteres[nom_champ]))
                        elif nom_champ == 'maj':
                            if str(criteres[nom_champ]) == 'outdated':
                                query.append(" > 0) and ")
                            elif str(criteres[nom_champ]) == 'uptodate':
                                query.append(" = 0) and ")
                            else:
                                query.append(" < 0 or %s is null) and " % str(nom_champ))
                        else:
                            query.append(" ilike %s) and ")
                            query_params.append(str(criteres[nom_champ]))
            query[-1] = query[-1][:query[-1].rindex('and')]

        query.append("order by RNE,module_actuel")
        query = "".join(query)
        if variables != {} or params != {}:
            return self.dbpool.runQuery(query, query_params).addCallbacks(self._groupe_serveur_vars, db_client_failed, callbackArgs=[variables, strict, cred_user, params])
        else:
            return self.dbpool.runQuery(query, query_params).addCallbacks(self._got_serveur, db_client_failed,callbackArgs=[cred_user])

    def _groupe_serveur_vars(self, serveurs, variables, strict, cred_user, params):
        """Recherche des serveurs en fonction d'un variable de configuration
        """
        l=[]
        auth_error = False
        for serveur in serveurs:
            # on ne renvoie que les serveurs accessibles
            try:
                serv = self.parent.s_pool.get(cred_user,int(serveur[0]))
            except ResourceAuthError:
                auth_error = True
                continue
            if variables:
                # lecture de la configuration du serveur
                try:
                    d = serv.parsedico(encode=True)
                except:
                    # pas de config définie ?
                    continue
                val_found = False
                for var, criteres in list(variables.items()):
                    name, val, negate = criteres
                    val = val.split('| ')
                    val = [value.strip() for value in val]
                    try:
                        if (d[name].split('| ') == val and not negate) or (d[name].split('| ') != val and negate):
                            val_found = True
                            if not strict:
                                # mode non strict : un seul critère suffit
                                break
                        elif strict:
                            # mode strict : tous les critères doivent être remplis
                            val_found = False
                            break
                    except:
                        # variable inconnue ?
                        pass
            if params:
                try:
                    serv_params = serv.get_params()
                    params_ok = False
                    for nom_champ, val in list(params.items()):
                        data = serv_params.get(nom_champ)
                        # pour les champs de type liste, on ne gère que le premier élément (statut)
                        if type(data) == list: data = data[0]
                        if data == val:
                            params_ok = True
                            if not strict:
                                break
                        elif strict:
                            params_ok = False
                            break
                except:
                    traceback.print_exc()
                    # ne devrait pas arriver, on valide params pour permettre
                    # la validation si les variables sont ok
                    params_ok = True
            # vérification de la validation des variables et champs params si nécessaire
            # (même en mode non strict, les 2 doivent être respectés)
            if not variables or val_found:
                if not params or params_ok:
                    l.append(self._format_serv(serveur,serv))

        if l == [] and auth_error == True:
            # aucun serveur n'est autorisé dans ce groupe
            raise ResourceAuthError("""Vous n'avez accès à aucun serveur dans ce groupe""")
        return 1,u(l)

    def xmlrpc_groupe_reload(self,cred_user,liste_serveurs):
        """renvoie un groupe à partir d'une liste d'id de serveurs
        """
        if liste_serveurs != []:
            params = []
            # construction de la requête SQL de recherche
            query=["select * from serveurs where id=%s"]
            for serveur in liste_serveurs:
                params.append(int(serveur))
                query.append(" or id=%s")
            query = "".join(query)
            query = query[:query.rindex('or')]
            query += "order by rne,module_actuel"
            return self.dbpool.runQuery(query, params).addCallbacks(self._got_serveur, db_client_failed,callbackArgs=[cred_user])
        else:
            return 1, []

    #FIXME : trou de sécu si on peut étendre les groupes
    # faire un nouveau groupe de droits ?
    # (ex: cas de l'envoi de clés ssh de connexion)
    def xmlrpc_groupe_extend(self,cred_user,id_groupe,liste_serveurs):
        """ajoute une liste de serveurs à un groupe enregistré
        """
        if liste_serveurs != [] and type(liste_serveurs) == list:
            try:
                self.parent.s_pool.extend_groupe(cred_user,int(id_groupe),liste_serveurs)
            except (KeyError, ValueError):
                return 0, u("""groupe non trouvé dans la base""")
            else:
                return 1, "ok"
        else:
            return 1, u("liste de serveurs à insérer vide")


    def xmlrpc_groupe_params(self,cred_user,liste_serveurs,dico_modifs):
        """modification d'un serveur
        cette fonction prend en compte un dictionnaire qui indique les
        champs à modifier et leur nouvelle valeur. l'application cliente
        doit s'assurer que ces champs existent dans la base"""
        # on  autorise seulement le timeout et la variante
        modifs = {}
        if 'timeout' in dico_modifs:
            modifs['timeout'] = int(dico_modifs['timeout'])
        if 'variante' in dico_modifs:
            modifs['variante'] = int(dico_modifs['variante'])
        if 'no_alert' in dico_modifs:
            modifs['no_alert'] = int(dico_modifs['no_alert'])

        # on vérifie l'existence des serveurs
        liste_serv = []
        for id_serveur in liste_serveurs:
            try:
                id_serveur = int(id_serveur)
                serv = self.parent.s_pool.get(cred_user, id_serveur)
                liste_serv.append(serv)
            except (KeyError, ValueError):
                return 0, u("""id de serveur non valide : %s""" % str(id_serveur))

        if modifs != {}:
            # on parcourt les serveurs
            erreurs = []
            for serveur in liste_serv:
                code = 1
                if 'variante' in list(modifs.keys()):
                    # on vérifie que la variante appartient à ce module
                    cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
                    cursor = cx.cursor()
                    cursor.execute("""select module from variantes where id=%s""", (int(modifs['variante']),))
                    var_mod = cursor.fetchone()
                    cursor.close()
                    cx.close()
                    libelle = serveur.get_libelle()
                    if serveur.id_mod in var_mod:
                        code,raison = self.parent.s_pool.edit_serveur(serveur.id_s, dico_modifs)
                        if 'variante' in modifs:
                            # si édition de variante et utilisation de dictpool,
                            # on vérifie la cohérence des dictionnaires
                            self.parent.dictpool.check_variante_conflicts(serv.id_s)
                        raison = "%s (%s) : erreur d'application de la variante" % (libelle,serveur.id_s)
                    else:
                        code = 0
                        raison = "%s (%s) : le module ne correspond pas à la variante" % (libelle,serveur.id_s)
                if code == 0:
                    erreurs.append(raison)
                else:
                    # ok , on modifie les infos dans la base postgresql
                    params = []
                    query=["update serveurs set "]
                    for cle in list(modifs.keys()):
                        query.append(str(cle))
                        query.append("=%s, ")
                        params.append(modifs[cle])
                    query="".join(query)[:-2]
                    query += """ where id=%s"""
                    params.append(int(serveur.id_s))
                    # exécution de la requête
                    try:
                        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
                        cursor=cx.cursor()
                        cursor.execute(query, params)
                        cursor.close()
                        cx.commit()
                        cx.close()
                    except:
                        erreurs.append("%s (%s) : erreur de modification de la base" % (serveur.get_libelle(),serveur.id_s))
                    # mise à jour du pool de serveurs
                    serveur.update_data()

            if erreurs != []:
                return 1, u(erreurs)
            else:
                return 1, u("OK")
        else:
            return 1,u("Pas de modifications à effectuer")


    def xmlrpc_edit_serveur(self,cred_user,id_serveur,dico_modifs):
        """modification d'un serveur
        cette fonction prend en compte un dictionnaire qui indique les
        champs à modifier et leur nouvelle valeur. l'application cliente
        doit s'assurer que ces champs existent dans la base"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
            code_res, data = self.parent.s_pool.edit_serveur(serv.id_s, dico_modifs)
            # si édition de variante et utilisation de dictpool,
            # on vérifie la cohérence des dictionnaires
            if 'variante' in dico_modifs or 'module_actuel' in dico_modifs:
                self.parent.dictpool.check_variante_conflicts(serv.id_s)
            return code_res, data
        except (KeyError, ValueError):
            return 0, u("""id de serveur non valide : %s""" % str(id_serveur))

    def xmlrpc_get_status(self,cred_user,id_serveur):
        """fonction qui renvoie différentes informations sur les(s) serveur(s) :
           - présence de dico.eol
           - présence de config.eol
           - présence de la clé rsa (uucp)
           - état des différentes actions
        """
        if type(id_serveur) == list:
            results = []
            for id_serv in id_serveur:
                code, res = self._get_status(cred_user,id_serv)
                if code == 1:
                    results.append(u(res))
            return 1, results
        else:
            return self._get_status(cred_user,id_serveur)

    def _get_status(self,cred_user,id_serveur):
        # récupération des infos sur le serveur
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
            res = serv.get_params()
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        else:
            return 1, u(res)

    def xmlrpc_get_maj_infos(self, cred_user, id_serveur, show_installed=False, debnames=[]):
        """retourne la liste des paquets non à jour"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
            if self.parent.maj_checker:
                res = serv.check_maj_status(self.parent.maj_checker, show_installed, debnames)
            else:
                # service de vérification des mises à jour non disponible (instance)
                res = []
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        else:
            return 1, u(res)

    def xmlrpc_get_alertes(self, cred_user):
        """renvoie la liste des serveurs en alerte"""
        serveurs = self.parent.s_pool.get_alertes(cred_user)
        # mise en forme des informations renvoyées
        res = []
        for serv in serveurs:
            if not serv.no_alert:
                res.append([serv.get_rne(), serv.get_etab(), serv.get_libelle(), serv.get_module(), serv.id_s, serv.get_status()])

        return 1, u(res)

    def xmlrpc_get_migration_status(self, cred_user):
        """renvoie la liste des serveurs en alerte"""
        serveurs_migration = self.parent.s_pool.get_migration_status(cred_user)
        # mise en forme des informations renvoyées
        res = {}
        for mod_vers, serveurs in list(serveurs_migration.items()):
            res_mod = res.get(mod_vers, [[],[]])
            for type_serv in [0,1]:
                for serv in serveurs[type_serv]:
                    res_mod[type_serv].append([serv.get_rne(), serv.get_etab(), serv.get_libelle(), serv.get_module(), serv.id_s, type_serv])
            res[mod_vers] = res_mod
        return 1, u(res)

    def xmlrpc_add_files(self,cred_user,id_serveur,dico_files,encode=False):
        """ajoute des fichiers, patchs, dictionnaires à un serveur
        """
        # récupération des infos sur le 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 : %s""" % str(id_serveur))

        # on vérifie si des paquets non autorisés sont listés
        if 'rpms' in dico_files:
            use_pool = len(''.join(dico_files['rpms'])) > 0
            if use_pool:
                # liste des paquets gérés au niveau variante/module
                denied = self.parent.dictpool.list_module(serv.id_mod, False)
                denied.extend(self.parent.dictpool.list_variante(serv.id_var, False))
                denied = set([paq_name for paq_name in denied if not paq_name.endswith('.xml')])
                # si un paquet demandé est dans la liste, on l'interdit
                bad_paqs = denied.intersection(set(dico_files['rpms']))
                if bad_paqs:
                    return 0, u("""paquet déjà référencé au niveau du module ou de la variante : %s""" % ', '.join(bad_paqs))
        # on met en place les différents types de fichiers
        dest_dir = serv.get_confdir()
        # on récupère la liste des fichiers à supprimer existante
        liste_remove = []
        if os.path.isfile(dest_dir+os.sep+'fichiers_zephir/removed'):
            f_rem = open(dest_dir+os.sep+'fichiers_zephir/removed')
            for fic in f_rem.read().strip().split('\n'):
                liste_remove.append(fic)
            f_rem.close()
        def check_removed(nom_dest):
            # on regarde si le fichier était dans la liste des fichiers obsolètes
            if nom_dest in liste_remove:
                liste_remove.remove(nom_dest)
                f_rem = open(dest_dir+os.sep+'fichiers_zephir/removed', 'w')
                f_rem.write('\n'.join(liste_remove))
                f_rem.close()
        # dictionnaires locaux
        if 'dicos' in dico_files:
            for dico in dico_files['dicos']:
                try:
                    if dico[0].strip() != "":
                        f=open(dest_dir+os.sep+'dicos/local/'+os.sep+os.path.basename(dico[0].strip().replace("\\","/")),'wb')
                        f.write(base64.decodebytes(to_bytes(dico[1])))
                        f.close()
                except:
                    traceback.print_exc()
                    return 0,u("erreur de sauvegarde de %s" % dico)

        if 'persos' in dico_files:
            for template in dico_files['persos']:
                try:
                    if template[0].strip() != "":
                        f=open(dest_dir+os.sep+'fichiers_perso'+os.sep+os.path.basename(template[0].strip().replace("\\","/")),'wb')
                        f.write(base64.decodebytes(to_bytes(template[1])))
                        f.close()
                        check_removed(os.path.join('fichiers_perso', template[0].strip().replace("\\","/")))
                except:
                    traceback.print_exc()
                    return 0,u("erreur de sauvegarde de %s" % template)

        if 'patchs' in dico_files:
            for patch in dico_files['patchs']:
                try:
                    if patch[0] != "":
                        f=open(dest_dir+os.sep+'patchs'+os.sep+os.path.basename(patch[0].strip().replace("\\","/")),'wb')
                        f.write(base64.decodebytes(to_bytes(patch[1])))
                        f.close()
                except:
                    traceback.print_exc()
                    return 0,u("erreur de sauvegarde de %s" % patch)

        # on reprend la liste des fichiers existants
        try:
            f=open(dest_dir+os.sep+'fichiers_zephir/fichiers_zephir')
            old_content=f.read()
            f.close()
            fichiers=old_content.split('%%\n')[0]
            rpms=old_content.split('%%\n')[1]
        except:
            fichiers=FILE_SECTION
            rpms=RPM_SECTION

        if 'fichiers_zeph' in dico_files:
            # récupération de la liste des fichiers obsolètes
            for fichier in dico_files['fichiers_zeph']:
                localpath = b""
                if len(fichier) == 3:
                    localpath = to_bytes(fichier[2])
                # on enlève le nom de conteneur si présent
                conteneur = ""
                if '::' in fichier[0]:
                    fic_path, conteneur = fichier[0].strip().split('::')
                else:
                    fic_path = fichier[0].strip()
                # nettoyage du nom de fichier
                nom_fic = fic_path.replace("\\","/")
                # on supprime les séparateurs en fin de ligne
                if fic_path.endswith('/'):
                    nom_fic = fic_path[:-1]
                if fic_path.endswith("\\"):
                    nom_fic = fic_path[:-2]
                if conteneur:
                    nom_final = "%s::%s" % (nom_fic, conteneur)
                else:
                    nom_final = nom_fic
                check_removed(nom_final)
                # on ajoute le fichier à la liste si il n'est pas déjà présent et si il n'est pas dans un sous répertoire
                if nom_final not in fichiers.split('\n') and localpath == b"":
                    fichiers = fichiers.strip() + '\n' + nom_final +'\n'
                # on écrit le contenu du fichier
                try:
                    if nom_fic != "":
                        if localpath == b"":
                            f=open(os.path.join(dest_dir,'fichiers_zephir',os.path.basename(nom_final)),'wb')
                        else:
                            f=open(os.path.join(dest_dir,localpath.decode(),os.path.basename(nom_final)),'wb')
                        f.write(base64.decodebytes(to_bytes(fichier[1])))
                        f.close()
                except:
                    traceback.print_exc()
                    return 0,u("erreur de sauvegarde de %s" % fichier)

        # rpms
        if 'rpms' in dico_files:
            for rpm in dico_files['rpms']:
                # on ajoute le rpm si il n'est pas présent
                if rpm not in rpms.split('\n'):
                    rpms = rpms.strip() + '\n' + rpm +'\n'

            f=open(dest_dir+os.sep+'fichiers_zephir/fichiers_zephir','w')
            f.write(fichiers+"%%\n" + rpms)
            f.close()

            if use_pool:
                # synchronisation des fichiers de dictionnaires
                # si gérés pour ce module
                self.parent.dictpool.sync_serveur_packages(serv.id_s)
        try:
            # recalcul de l'état de la configuration
            serv.check_md5conf()
        except:
            traceback.print_exc()
            log.msg("Serveur %s : erreur de mise à jour des données md5 (ajout d'un fichier)" % str(serv.id_s))
        return 1,u("ok")

    def xmlrpc_del_files(self,cred_user,id_serveur,dico_files, remove=False):
        """suppression de fichiers, patchs, dictionnaires d'un serveur
        @remove : si True, les fichiers (divers) retirés seront également
                 supprimés sur le serveur cible
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        # répertoire de destination du serveur
        dest_dir = serv.get_confdir()
        if remove:
            liste_remove = []
            # on récupère la liste des fichiers à supprimer existante
            if os.path.isfile(dest_dir+os.sep+'fichiers_zephir/removed'):
                f_rem = open(dest_dir+os.sep+'fichiers_zephir/removed')
                for fic in f_rem.read().strip().split('\n'):
                    liste_remove.append(fic)
                f_rem.close()
        # dictionnaires locaux
        if 'dicos' in dico_files:
            for dico in dico_files['dicos']:
                try:
                    if dico != "":
                        os.unlink(dest_dir+os.sep+'dicos/local'+os.sep+dico)
                except:
                    return 0,u("erreur de suppression de %s" % dico)

        # fichiers templates creole
        if 'persos' in dico_files:
            for template in dico_files['persos']:
                try:
                    if template != "":
                        os.unlink(dest_dir+os.sep+'fichiers_perso'+os.sep+template)
                except:
                    return 0,u("erreur de supression de %s" % template)
                # on supprime les droits associés si nécessaire
                self.parent.s_pool.del_file_perms(dest_dir,'fichiers_perso'+os.sep+template)
                # si demandé, on ajoute le template à la liste des fichiers à supprimer sur le serveur
                if remove:
                    fic_sup = os.path.join('fichiers_perso', template)
                    if fic_sup not in liste_remove:
                        liste_remove.append(fic_sup)


        # patchs
        if 'patchs' in dico_files:
            for patch in dico_files['patchs']:
                try:
                    if patch != "":
                        os.unlink(dest_dir+os.sep+'patchs'+os.sep+patch)
                except:
                    return 0,u("erreur de suppression de %s" % patch)

        if 'fichiers_zeph' in dico_files or 'rpms' in dico_files:
            # on reprend la liste des fichiers existants
            try:
                f=open(dest_dir+os.sep+'fichiers_zephir/fichiers_zephir')
                old_content=f.read()
                f.close()
                fichiers=old_content.split('%%\n')[0]
                rpms=old_content.split('%%\n')[1]
            except:
                fichiers="""# section 1
    # liste des fichiers à sauvegarder pour la variante
    # (ne pas modifier sauf pour créer ou mettre à jour la variante)"""
                rpms="""# section 2
    # inscrire les noms des paquetages qui seront installés à la mise à jour du serveur
    # (ils doivent être présents sur le serveur de mise à jour)"""

            # fichiers divers
            if 'fichiers_zeph' in dico_files:
                liste=fichiers.split('\n')
                for fichier in dico_files['fichiers_zeph']:
                    localpath = "fichiers_zephir"
                    if type(fichier) in (list, tuple) and len(fichier) == 2:
                        localpath = fichier[1]
                        fichier = fichier[0]
                    fichier = fichier.replace("\\","/")
                    # on supprime le fichier de la liste
                    if fichier in liste:
                        liste.remove(fichier)
                        fichiers = "\n".join(liste)
                    fic_path = os.path.join(dest_dir,localpath,os.path.basename(fichier))
                    # on efface le fichier
                    try:
                        if fichier != "":
                            if os.path.isdir(fic_path):
                                shutil.rmtree(fic_path)
                            elif os.path.isfile(fic_path):
                                os.unlink(fic_path)
                    except:
                        f=open(dest_dir+os.sep+'fichiers_zephir/fichiers_zephir','w')
                        f.write(fichiers+"%%\n"+rpms)
                        f.close()
                        return 0,u("erreur de suppression de %s" % fichier)
                    # on supprime les droits associés si nécessaire
                    fic_sup = os.path.join(localpath,os.path.basename(fichier))
                    self.parent.s_pool.del_file_perms(dest_dir,fic_sup,True)
                    # si demandé, on ajoute le fichier à la liste des fichiers à supprimer
                    # sur le serveur cible
                    if remove:
                        if fichier not in liste_remove:
                            liste_remove.append(fichier)

            # rpms
            if 'rpms' in dico_files:
                for rpm in dico_files['rpms']:
                    # on supprime le rpm si il existe
                    liste=rpms.split('\n')
                    if rpm in liste:
                        liste.remove(rpm)
                    else:
                        f=open(dest_dir+os.sep+'fichiers_zephir/fichiers_zephir','w')
                        f.write(fichiers+"%%\n"+rpms)
                        f.close()
                        return 0,u("rpm non trouvé dans la liste : %s" % rpm)

                    rpms = "\n".join(liste)

            f=open(dest_dir+os.sep+'fichiers_zephir/fichiers_zephir','w')
            f.write(fichiers+"%%\n"+rpms)
            f.close()
        if remove and liste_remove:
            # liste des fichiers à supprimer
            f=open(dest_dir+os.sep+'fichiers_zephir/removed', 'w')
            f.write("\n".join(liste_remove))
            f.close()
        try:
            # recalcul de l'état de la configuration
            serv.check_md5conf()
        except:
            traceback.print_exc()
            log.msg("Serveur %s : erreur de mise à jour des données md5 (suppression d'un fichier)" % str(serv.id_s))

        return 1,u("ok")

    def xmlrpc_get_file_content(self,cred_user,id_serveur,path,show_details=False):
        """renvoie le contenu d'un fichier de serveur
        si le fichier est un fichier binaire, renvoie la chaine BINARY"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        # définition du répertoire du serveur
        try:
            dest_dir=serv.get_confdir()
        except:
            return 0,u("""lecture du fichier: paramètres non valides""")
        # on lit le fichier
        try:
            dest_path = os.path.join(dest_dir, path)
            # cas d'un répertoire
            if os.path.isdir(dest_path):
                dirfiles = os.listdir(dest_path)
                content = []
                if show_details:
                    for f in dirfiles:
                        f_local = os.path.join(dest_dir,path,f)
                        f_info = config.get_file_info(f_local)
                        content.append((f,f_info))
                else:
                    content = dirfiles
                return 1, u(content)
            elif os.path.isfile(dest_path):
                # on regarde si c'est un fichier texte ou binaire
                if istextfile(dest_path):
                    with open(dest_path, 'rb') as f:
                        content=f.read()
                    content = base64.encodebytes(content).decode()
                else:
                    content = "BINARY"
                return 1, content
            else:
                raise FileNotFoundError(f"{dest_path} Not Found.")
        except:
            #traceback.print_exc()
            return 0,u("""erreur de lecture du fichier""")

    def xmlrpc_get_groupe_vars(self,cred_user,serveurs,encode=False):
        """récupère la liste des variables communes à un groupe de serveur"""
        # vérification de l'existence des serveurs
        liste_serv = []
        for id_serveur in serveurs:
            try:
                serv = self.parent.s_pool.get(cred_user, id_serveur)
                liste_serv.append(serv)
            except (KeyError, ValueError):
                return 0, u("""serveur inconnu dans la base zephir : %s""" % str(id_serveur))
        liste_vars = {}
        liste_modules = []
        erreurs = []
        group_vars = []
        first_iter=1
        for serveur in liste_serv:
            # chemin vers le dictionnaire et les dicos persos
            try:
                serveur_dir = serveur.get_confdir()
                d = serveur.get_config('modif_config',encode=encode)
                # instance du dictionnaire
                if d == None:
                    # on ne prend pas en compte les serveurs non configurés
                    erreurs.append("""le serveur %s (%s) n'a pas de fichier zephir.eol""" % (serveur.id_s,serveur.get_libelle()))
                else:
                    # liste des variables groupées
                    info_groups = {}
                    info_groups = d.groups
                    for master, slaves in list(info_groups.items()):
                        if master not in group_vars:
                            group_vars.append(master)
                        for slave in slaves:
                            if slave not in group_vars:
                                group_vars.append(slave)
                    # on stocke la liste des variables
                    if first_iter == 1:
                        for var in list(d.liste_vars.keys()):
                            liste_vars[var]=[" | ".join(d.get_value(var))]
                        first_iter = 0
                    else:
                        for var in list(liste_vars.keys()):
                            if var not in list(d.liste_vars.keys()):
                                # on supprime les variables qui ne sont pas dans ce dictionnaire
                                del(liste_vars[var])
                            else:
                                val=d.get_value(var)
                                val = " | ".join(val)
                                if val not in liste_vars[var]:
                                    liste_vars[var].append(val)
                    # liste des différents modules
                    if serveur.id_mod not in liste_modules:
                        liste_modules.append(serveur.id_mod)
            except Exception as err:
                traceback.print_exc()
                erreurs.append("""erreur sur le serveur %s (%s), voir les logs pour plus d'informations""" % (serveur.id_s, serveur.get_libelle()))

        # on retourne la liste des variables et des modules
        return 1, u([liste_vars,liste_modules,erreurs,group_vars])

    def xmlrpc_set_groupe_var(self,cred_user,serveurs,var,val,encode=False,slaves={}):
        """modifie une variable commune à un groupe de serveur

        @param serveurs: liste d'identifiants des serveurs concernés
        @param var: nom de la variable à modifier
        @param val: nouvelle valeur de la variable
        @param encode: (obsolète) True pour convertir les valeurs en ISO-8859-1
        @param slaves: dictionnaire de variables/valeurs supplémentaires à modifier (#15224)

        slaves permet permet modifier un groupe de variables (maître/esclaves) sur les serveurs
        dont la version est > 2.3. Nécessaire en cas de modification du nombre de valeurs.
        Dans ce cas, la valeur maître doit être renseignée avec var et val pour être modifiée en premier
        Attention, toutes les valeurs des esclaves sont supprimés au début du processus dans ce cas
        """
        def check_slaves(serv, var, slaves):
            d = serv.get_config('modif_config',encode)
            option = d.dico.unwrap_from_path(d.var_paths[var][0])
            if serv.version == "creole3":
                assert option.impl_is_multi() and option.impl_get_multitype() == multitypes1.master, """{} n'est pas une variable maitre""".format(var)
            else:
                assert option.impl_is_master_slaves('master'), """{} n'est pas une variable maitre""".format(var)
            for slave in slaves.keys():
                opt = d.dico.unwrap_from_path(d.var_paths[slave][0])
                if serv.version == "creole3":
                    assert opt in option.impl_get_master_slaves(), """{} n'est pas une variable esclave de la variable maitre {}""".format(slave, var)
                else:
                    assert opt in option.impl_get_master_slaves().getslaves(option), """{} n'est pas une variable esclave de la variable maitre {}""".format(slave, var)


        # calcul de l'ensemble des variables à appliquer
        var_list = [var]
        gr_vars = {var:val}
        # var reprise dans slaves ? (ne devrait pas arriver)
        if var in slaves:
           del(slaves[var])
        var_list.extend(list(slaves.keys()))
        gr_vars.update(slaves)
        # vérification de l'existence des serveurs
        liste_serv = []
        slaves_ok = slaves == {}
        for id_serveur in serveurs:
            try:
                serv = self.parent.s_pool.get(cred_user, id_serveur)
                liste_serv.append(serv)
                # si passage de slaves, on vérifie la cohérence sur le premier serveur 'creole3'
                if not slaves_ok:
                    try:
                        check_slaves(serv, var, slaves)
                        slaves_ok = True
                    except AssertionError as err:
                        return 0, u(str(err))
                    except PropertiesOptionError2:
                        # variable désactivée ou non accessible, on passe ce serveur
                        continue
            except (KeyError, ValueError):
                return 0, u("""serveur inconnu dans la base zephir : %s""" % str(id_serveur))
        erreurs = []
        for serveur in liste_serv:
            # chemin vers le dictionnaire et les dicos persos
            serveur_dir = serveur.get_confdir()
            # instance du dictionnaire
            try:
                d = serveur.get_config('modif_config',encode)
                assert d is not None
            except Exception as e:
                erreurs.append('%s-%s (%s)' % (str(serveur.id_s),serveur.get_libelle(),str(e)))
            else:
                # mise en place des valeurs (variable 'principale', puis 'slaves')
                vars_ok = True
                if slaves != {}:
                    d.get_var(var)
                    try:
                        option = d.dico.unwrap_from_path(d.var_paths[var][0])
                        # Si édition avec 'slaves', on réinitialise à 0 les valeurs de tout le groupe
                        # (permet de modifier le nombre de valeurs)
                        multi = getattr(d.dico, d.var_paths[var][0])
                        for i in range(len(multi)):
                            multi.pop(0)
                    except PropertiesOptionError2:
                        # variable désactivée ou non accessible, on passe ce serveur
                        continue
                for cur_var in var_list:
                    cur_val = gr_vars[cur_var]
                    try:
                        final_val = cur_val
                        var_info = d.get_var(cur_var)
                        try:
                            option = d.dico.unwrap_from_path(d.var_paths[cur_var][0])
                        except PropertiesOptionError2:
                            # variable désactivée ou non accessible, on passe ce serveur
                            continue
                        multi = option.impl_is_multi()
                        if multi:
                            final_val = [value.strip() for value in cur_val.split("|")]
                        log.msg('%s - nouvelle valeur de %s : %s' % (str(serveur.id_s), cur_var, str(final_val)))
                        d.set_value(final_val)
                    except KeyError as e:
                        vars_ok = False
                        erreurs.append('%s-%s (%s)' % (str(serveur.id_s),serveur.get_libelle(),"variable inexistante : {0}".format(str(e))))
                    except Exception as e:
                        traceback.print_exc()
                        vars_ok = False
                        erreurs.append('%s-%s (%s)' % (str(serveur.id_s),serveur.get_libelle(),str(e)))
                if vars_ok:
                    # on sauvegarde zephir.eol
                    try:
                        res = serveur.save_config(d,'modif_config',encode,force=True)
                        if res != "":
                            erreurs.append(res)
                    except Exception as e:
                        traceback.print_exc()
                        erreurs.append('%s-%s (Sauvegarde de configuration: %s)' % (str(serveur.id_s),serveur.get_libelle(),str(e)))
        return 1, u(erreurs)

    def xmlrpc_get_dico(self,cred_user,id_serveur,mode='config',encode=False,errors=False):
        """récupération du dictionnaire de configuration selon le mode demandé
        (gen_dico, gen_config, modification du fichier déjà rempli"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("""Serveur inconnu : %s""" % str(id_serveur))
        # récupération de la configuration:
        try:
            config_serv = serv.get_config(mode,encode)
            # on envoie le dictionnaire en base64
            data = config_serv.get_dict()
            if errors:
                upgrade_infos = get_upgrade_infos(config_serv.dico)
                if upgrade_infos['load_errors']:
                    data.append(upgrade_infos)
        except Exception as e:
            traceback.print_exc()
            return 0, u("""Erreur de lecture de la configuration : %s""" % str(e))
        return 1, u(data)

    def xmlrpc_get_config(self,cred_user,id_serveur,encode=False):
        """renvoie un dictionnaire contenant la configuration du serveur demandé
        format {variable:valeur}"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        # récupération de la configuration:
        try:
            data = serv.parsedico(encode=encode)
        except Exception as e:
            traceback.print_exc()
            return 0, u("""Erreur de lecture de la configuration : %s""" % str(e))
        # on envoie le dictionnaire en base64
        return 1, u(data)

    def xmlrpc_save_conf(self,cred_user,id_serveur,dico_zeph,mode='config',encode=False):
        """sauvegarde d'un dictionnaire de configuration sur zephir
        (soit sur zephir.eol, soit sur dico.eol)"""
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        try:
            # décodage du dictionnaire
            if mode.endswith('migration'):
                # migration : validation de la variante choisie
                confdir = serv.get_confdir()
                if os.path.exists(os.path.join(confdir,'tmp_variante_migration')):
                    if os.path.exists(os.path.join(confdir,'variante_migration')):
                        os.unlink(os.path.join(confdir,'variante_migration'))
                    os.rename(os.path.join(confdir,'tmp_variante_migration'), os.path.join(confdir,'variante_migration'))
                # initialisation du dictionnaire creole2
                variante = serv.variante_migration()
                assert int(variante) > 0
                cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
                cursor = cx.cursor()
                query = "select modules.id, modules.version from modules, variantes where modules.id=variantes.module and variantes.id=%s"
                cursor.execute(query, (int(variante),))
                var_mod, mod_version = cursor.fetchone()
                cursor.close()
                cx.close()
                d = serv.migrate_config(var_mod, variante, 'modif_migration', mod_version)
            else:
                d = serv.get_config('modif_config', encode)
            # mise en place des valeurs
            d.init_from_zephir(dico_zeph)
            serv.save_config(d,mode,encode)
        except Exception as err:
            traceback.print_exc()
            return 0,u(str(err))
        else:
            return 1,u('ok')

    def xmlrpc_save_bastion(self,cred_user,id_serveur,bastion_base64,modele,encode=False):
        """sauvegarde d'un modèle de firewall
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user, id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        return self._save_bastion([serv],bastion_base64,'fichiers_zephir/modeles/%s.xml' % modele, encode)

    def xmlrpc_save_bastion_groupe(self,cred_user,groupe,bastion_base64,modele,encode=False):
        """ récupère les informations sur les serveurs du groupe
        """
        query = """select serveurs from groupes_serveurs where id = %s"""
        return self.dbpool.runQuery(query, (int(groupe),)).addCallbacks(self._save_bastion_groupe,db_client_failed,callbackArgs=[cred_user,bastion_base64,modele,encode])

    def _save_bastion_groupe(self,data,cred_user,bastion_base64,modele,encode):
        """sauvegarde d'un modèle de firewall sur un groupe de serveurs
        """
        try:
            serveurs=eval(data[0][0])
        except:
            return 0, u("impossible de retrouver les serveurs du groupe")
        # vérification de la validité des serveurs
        liste_serv = []
        for id_serveur in serveurs:
            try:
                serv = self.parent.s_pool.get(cred_user, int(id_serveur))
            except (KeyError, ValueError):
                return 0, u("""serveur inconnu : %s""" % str(id_serveur))
            if serv.get_module().startswith('amon'):
                liste_serv.append(serv)
        return self._save_bastion(liste_serv, bastion_base64, 'fichiers_zephir/modeles/%s.xml' % modele, encode)

    def _save_bastion(self,serveurs,fichier_base64,filename,encode):
        """sauvegarde d'un fichier dans l'arborescence d'un serveur"""
        # décodage du fichier
        contenu = base64.decodebytes(to_bytes(fichier_base64)).decode()
        # sauvegarde
        erreurs = []
        for serveur in serveurs:
            # on ajoute les fichiers du modèle
            serveur_dir = serveur.get_confdir()
            # on cherche le nom du modèle
            try:
                d = serveur.get_config('modif_config',encode)
                try:
                    if not os.path.isdir(serveur_dir+'/fichiers_zephir/modeles'):
                        os.mkdir(serveur_dir+'/fichiers_zephir/modeles')
                        # on ajoute le répertoire des modèles à la liste des fichiers à sauvegarder
                        # (il devrait déjà y être si une sauvegarde a eu lieu)
                    modele=open(serveur_dir+'/fichiers_zephir/modeles'+os.sep+os.path.basename(filename),'w')
                    modele.write(contenu)
                    modele.close()
                except:
                    # le répertoire des modèles n'est peut-être pas encore remonté sur zephir
                    pass
                else:
                    # on demande à amon d'utiliser modele.xml
                    d.get_var('type_amon')
                    d.set_value(os.path.splitext(os.path.basename(filename))[0])
                    # sauvegarde du dictionnaire
                    serveur.save_config(d,'modif_config',encode)
            except:
                # problème avec le fichier zephir.eol
                erreurs.append(str(serveur.id_s))
            # on ajoute le répertoire des modèles à la liste des fichiers zephir si il n'y est pas déjà
            # on reprend la liste des fichiers existants
            try:
                f=open(serveur_dir+os.sep+'fichiers_zephir/fichiers_zephir')
                old_content=f.read()
                f.close()
                fichiers=old_content.split('%%\n')[0]
                rpms=old_content.split('%%\n')[1]
            except:
                fichiers=FILE_SECTION
                rpms=RPM_SECTION

            # on ajoute le fichier à la liste si il n'est pas déjà présent
            ligne = "/usr/share/eole/bastion/modeles"
            if ligne not in fichiers.split('\n'):
                fichiers = fichiers.strip() + '\n' + ligne +'\n'
            try:
                f=open(serveur_dir+os.sep+'fichiers_zephir/fichiers_zephir','w')
                f.write(fichiers+"%%\n"+rpms)
                f.close()
            except:
                erreurs.append("ajout à la liste des fichiers zephir")
        if erreurs != []:
            return 0,u("""erreur de sauvegarde du fichier %s sur le(s) serveur(s) %s""" % (filename,",".join(erreurs)))

        return 1,u('ok')

    def xmlrpc_get_bastion(self,cred_user,id_serveur,encode=False):
        """récupération d'un modèle de firewall
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        # lecture
        try:
            d = serv.parsedico(encode=encode)
            modele = d['type_amon'].split("| ")[0]
            data_dir = os.path.join(serv.get_confdir(),'fichiers_zephir')
            model_files = [ os.path.join(data_dir, 'modeles/%s.xml' % modele),
                            os.path.join(data_dir, '%s.xml' % modele),
                            os.path.join(data_dir, 'variante/%s.xml' % modele)
                          ]
            contenu = None
            for model_file in model_files:
                if os.path.isfile(model_file):
                    fic_zephir = open(model_file)
                    contenu = fic_zephir.read()
                    fic_zephir.close()
            assert contenu is not None
        except:
            return 0,u("""fichier de modele non trouvé pour le serveur %s""" % serv.id_s)
        else:
            if isinstance(contenu, str):
                contenu = contenu.encode()
            return 1,base64.encodebytes(contenu).decode(), modele

    def xmlrpc_get_log(self,cred_user,id_serveur=None,mode='zlog',liste_types=[]):
        """rècupère les logs d'un serveur particulier
        mode : - zlog : tous les logs remontés par le serveur (ou spécifiés)
               - autre : récupère la liste des actions zephir effectuées sur ce serveur
        """
        params = []
        query = """select id,id_serveur,date,type,message,etat from log_serveur where """
        if id_serveur:
            query += """id_serveur=%s and"""
            params.append(int(id_serveur))
        if mode == 'zlog':
            # défaut : tout sauf les commandes
            comp = ["<> 'COMMAND'"]
            # si filtre spécifié, on l'applique
            if liste_types != []:
                comp=[" in ('"]
                for typelog in liste_types:
                    comp.append(typelog)
                    comp.append("','")
                comp = comp[:-1]
                comp.append("')")
        else:
            comp = ["= 'COMMAND'"]
        query += """ type%s order by date desc, id desc""" % "".join(comp)
        return self.dbpool.runQuery(query, params).addCallbacks(self._got_log,db_client_failed,callbackArgs=[cred_user])

    def xmlrpc_del_log(self,cred_user,liste_serveurs,liste_types,date):
        """supression des logs d'un certain type antérieurs à une certaine date
        Attention, cette purge est effectuée sur l'ensemble des serveurs du zephir !
        liste_serveurs : liste des serveurs dont on veut purger les logs
        liste_types : spécifie les types d'action à purger (ex : ['COMMAND','SURVEILLANCE','MAJ']
        """
        # création de la condition 'type de logs' de la query
        cond_params = []
        if liste_types != []:
            # supression de tous les listes de log
            if 'TOUT' in liste_types:
                cond_types = ""
            else:
                #spécification des types à supprimer
                cond_liste = []
                for type_log in liste_types:
                    cond_liste.append("type=%s")
                    cond_params.append(type_log)
                cond_types = " and (" + " or ".join(cond_liste) + ")"
        else:
            return 0,u("paramètres invalides")

        date = str(self.dbpool.dbapi.Timestamp(int(date[2]),int(date[1]),int(date[0]),0,0,0).adapted)
        for serveur in liste_serveurs:
            # pour chaque serveur de la liste
            # on vérifie les droits d'accès au serveur
            try:
                self.parent.s_pool.get(cred_user, int(serveur))
            except (KeyError, ValueError):
                return 0, u("""serveur inconnu : %s""" % str(serveur))
            if int(serveur) > 0 and type(date) == str and type(liste_types) == list:
                query =  """delete from log_serveur where id_serveur=%s""" + cond_types + " and date <= %s"
                params = [int(serveur)]
                params.extend(cond_params)
                params.append(date)
                try:
                    # on supprime les champs
                    self.dbpool.runOperation(query, params)
                except:
                    pass
            else:
                return 0,u("paramètres invalides")

        return 1,u("ok")

    def xmlrpc_fichiers_zephir(self,cred_user,id_serveur,show_details=False):
        """retourne la liste des fichiers personnalisés pour ce serveur
        @param show_detail : ajoute une information pour chaque fichier de type fichiers divers (serveur et variante)
                             types de fichiers dispos : missing, dir ou file
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        else:
            # répertoire de stockage du serveur sur zephir
            serveur_dir = serv.get_confdir()
            # création du dictionnaire de listes des différents fichiers
            dico_res={}
            try:
                # dictionnaires additionnels
                liste_dicos = []
                liste_dicos_var = []
                dico_dir = serveur_dir+os.sep+'dicos/local'
                for fic in os.listdir(dico_dir):
                    if fic == 'variante':
                        pass
                    elif fic.endswith('.eol') or fic.endswith('.xml'):
                        liste_dicos.append(fic)
                dico_res['dicos'] = liste_dicos
                try:
                    # dictionnaires de la variante
                    for fic in os.listdir(serveur_dir+os.sep+'dicos/variante'):
                        if fic.endswith('.eol') or fic.endswith('.xml'):
                            liste_dicos_var.append(fic)
                    dico_res['dicos_var'] = liste_dicos_var
                except OSError:
                    dico_res['dicos_var'] = ['répertoire non trouvé !']
            except OSError:
                dico_res['dicos'] = ['répertoire non trouvé !']
            try:
                # fichiers personnels
                dico_res['persos'] = os.listdir(serveur_dir+os.sep+'fichiers_perso')
                try:
                    dico_res['persos'].remove('variante')
                except:
                    pass
            except OSError:
                dico_res['persos'] = ['répertoire non trouvé !']
            try:
                # fichiers personnels variante
                dico_res['persos_var'] = (os.listdir(serveur_dir+os.sep+'fichiers_perso/variante'))
            except OSError:
                dico_res['persos_var'] = ['répertoire non trouvé !']
            try:
                # patchs
                dico_res['patchs'] = os.listdir(serveur_dir+os.sep+'patchs')
                try:
                    dico_res['patchs'].remove('variante')
                except:
                    pass
            except OSError:
                dico_res['patchs'] = ['répertoire non trouvé !']
            try:
                # patchs variante
                dico_res['patchs_var'] = os.listdir(serveur_dir+os.sep+'patchs/variante')
            except OSError:
                dico_res['patchs_var'] = ['répertoire non trouvé !']
            try:
                # RPMS
                # lecture de la liste des rpms supplémentaires
                fic = open(serveur_dir+'/fichiers_zephir/fichiers_zephir')
                data = fic.read().strip().split("\n")
                fic.close()
                # recherche de la section des RPMS
                liste_pkg = []
                section_rpm = 0
                for ligne in data:
                    ligne = ligne.strip()
                    if section_rpm == 1:
                        # on regarde si on a bien affaire à un paquetage
                        if not ligne.startswith('#') and ligne != '':
                            # on affiche le nom du paquetage
                            liste_pkg.append(ligne)
                    if ligne == '%%':
                        section_rpm = 1
                dico_res['rpms'] = liste_pkg
            except IOError:
                dico_res['rpms'] = []
            try:
                # RPMS variante
                # lecture de la liste des rpms supplémentaires
                fic = open(serveur_dir+'/fichiers_zephir/variante/fichiers_variante')
                data = fic.read().strip().split("\n")
                fic.close()
                # recherche de la section des RPMS
                liste_pkg_var = []
                section_rpm = 0
                for ligne in data:
                    ligne = ligne.strip()
                    if section_rpm == 1:
                        # on regarde si on a bien affaire à un paquetage
                        if not ligne.startswith('#') and ligne != '':
                            # on affiche le nom du paquetage
                            liste_pkg_var.append(ligne)
                    if ligne == '%%':
                        section_rpm = 1
                dico_res['rpms_var'] = liste_pkg_var
            except IOError:
                dico_res['rpms_var'] = []
            # fichiers du module
            liste_fic=[]
            # templates du serveur
            try:
                f=open(serveur_dir+os.sep+'fichiers_zephir/fichiers_zephir')
                old_content=f.read()
                f.close()
                fichiers=old_content.split('%%\n')[0]
            except:
                fichiers=""
            for f_zeph in fichiers.split('\n'):
                # si conteneur présent, on garde seulement le chemin de fichier
                if f_zeph.strip().startswith("""/"""):
                    f_local = os.path.join(serveur_dir,'fichiers_zephir',os.path.basename(f_zeph.strip()))
                    if show_details:
                        f_info = config.get_file_info(f_local)
                        liste_fic.append((f_zeph,f_info))
                    elif os.path.exists(f_local):
                        liste_fic.append(f_zeph)
            dico_res['fichiers_zeph'] = liste_fic
            liste_fic=[]
            try:
                f=open(serveur_dir+os.sep+'fichiers_zephir/variante/fichiers_variante')
                old_content=f.read()
                f.close()
                fichiers=old_content.split('%%\n')[0]
            except:
                fichiers=""
            for f_zeph in fichiers.split('\n'):
                if f_zeph.strip().startswith("/"):
                    f_local = os.path.join(serveur_dir,'fichiers_zephir','variante',os.path.basename(f_zeph.strip()))
                    if show_details:
                        f_info = config.get_file_info(f_local)
                        liste_fic.append((f_zeph,f_info))
                    elif os.path.exists(f_local):
                        liste_fic.append(f_zeph)
            dico_res['fichiers_var'] = liste_fic

            return 1,u(dico_res)

    def xmlrpc_get_serveur_perms(self, cred_user, id_serveur, filepath=""):
        """renvoie les informations de permissions associées à un fichier
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        else:
            result = self.parent.s_pool.get_file_perms(serv.get_confdir(), filepath)
            return 1, u(result)

    def xmlrpc_del_serveur_perms(self, cred_user, id_serveur, filepath=""):
        """renvoie les informations de permissions associées à un fichier
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        else:
            result = self.parent.s_pool.del_file_perms(serv.get_confdir(), filepath)
            return 1, u(result)

    def xmlrpc_set_serveur_perms(self, cred_user, id_serveur, rights):
        """enregistre les informations de permissions associées à un(des) fichier(s)
        @param rights: dictionnaire au format suviant : {'filepath':[mode,ownership]}
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        else:
            data_dir = serv.get_confdir()
            res = self.parent.s_pool.set_file_perms(rights, data_dir)
            if res:
                return 1,"OK"
            else:
                return 0, u("""erreur d'enregistrement des permissions""")

    def xmlrpc_copy_perms(self, cred_user, id_src, serveurs, keep=True):
        """copie les permissions définies sur le serveur id_src
        sur le groupe de serveurs serveurs
        @param keep: si True, on n'écrase pas les permissions restantes pour un fichier
        """
        try:
            id_src = int(id_src)
            src = self.parent.s_pool.get(cred_user,id_src)
            # on récupère les permissions à insérer
            perms = self.parent.s_pool.get_file_perms(src.get_confdir())
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_src))
        else:
            forbidden = []
            # on recherche les destinations accessibles
            for id_dst in serveurs:
                try:
                    id_dst = int(id_dst)
                    dst = self.parent.s_pool.get(cred_user, id_dst)
                except:
                    forbidden.append(id_dst)
                else:
                    if id_dst != id_src:
                        # si keep est vrai, on récupère les permissions existantes
                        existing = []
                        if keep == True:
                            existing = list(self.parent.s_pool.get_file_perms(dst.get_confdir()).keys())
                        # calcul des permissions a modifier
                        updates = {}
                        for ficperm, data in list(perms.items()):
                            if ficperm not in existing:
                                updates[ficperm] = perms[ficperm]
                        # application
                        self.parent.s_pool.set_file_perms(updates, dst.get_confdir())
        # on renvoie la liste des serveurs non accessibles
        return 1, u(forbidden)

    def xmlrpc_global_status(self,cred_user,id_serveur):
        """récupère l'etat général d'un ou plusieurs serveur(s)
        """
        if type(id_serveur) == list:
            results = []
            for id_serv in id_serveur:
                code, res = self._global_status(cred_user,id_serv)
                if code == 0:
                    return code, res
                else:
                    results.append(res)
            return 1, u(results)
        else:
            return self._global_status(cred_user,id_serveur)

    def _global_status(self, cred_user, id_serveur):
        """lit l'état d'un 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 : %s""" % str(id_serveur))
        etat = serv.get_status()
        if etat == None:
            return 1, -1
        if etat in [0,2]:
            return 1, 0
        else:
            return 1, etat
        return 0, u("""erreur de récupération des données""")

    def xmlrpc_get_measure(self, cred_user, serveurs=None):
        """renvoie les données de la dernière mesure des agents
        """
        if serveurs == None:
            serveurs = list(self.agent_manager.keys())
        elif type(serveurs) != list:
            serveurs = [serveurs]
        measures = []
        for id_serveur in serveurs:
            try:
                serv = self.parent.s_pool.get(cred_user,int(id_serveur))
            except (KeyError, ValueError):
                # serveur non accessible
                pass
            if self.agent_manager[str(id_serveur)] != None:
                measures.append("%s:%s" % (str(id_serveur), str(self.agent_manager[str(id_serveur)].get_measure())))
                #measures[str(id_serveur)] = self.agent_manager[str(id_serveur)].get_measure()
        # on remplace la dernière virgule par la fermeture du dictionnaire
        measures = "{" + ",".join(measures) + "}"
        #measures = base64.encodebytes(measures)
        return 1, u(measures)

    def xmlrpc_agents_status(self,cred_user,id_serveur):
        """récupère l'etat des agents d'un serveur
        """
        if type(id_serveur) == list:
            results = []
            for id_serv in id_serveur:
                try:
                    code, res = self._agents_status(cred_user,id_serv)
                    assert code == 1
                    results.append(u(res))
                except:
                    results.append({})
            return 1, results
        else:
            return self._agents_status(cred_user,id_serveur)

    def _agents_status(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 : %s""" % str(id_serveur))
        try:
            if self.agent_manager.has_key(str(id_serveur)):
                result=self.agent_manager[str(id_serveur)].agents_status()
            else:
                result = ''
        except Exception as e:
            return 0, u("""erreur de récupération des données""")
        else:
            return 1, u(result)

    ###################################
    ## Gestion des groupes de serveurs
    ###################################

    def xmlrpc_save_group(self,cred_user,libelle,serveurs):
        """enregistre un groupe de serveurs dans la base
        """
        if self.parent.s_pool.add_groupe(cred_user, libelle, serveurs):
            return 1, "ok"
        else:
            return 0, u("erreur lors de l'insertion du groupe")

    def xmlrpc_del_group(self,cred_user,id_groupe):
        """supprime un groupe de serveurs de la base
        """
        try:
            id_groupe = int(id_groupe)
            self.parent.s_pool.del_groupe(cred_user, id_groupe)
        except (KeyError, ValueError):
            return 0, u("""id de groupe invalide : %s""" % str(id_groupe))
        return 1, "ok"

    def xmlrpc_edit_group(self,cred_user,id_groupe,libelle,serveurs):
        """modifie un groupe existant
        """
        try:
            id_groupe = int(id_groupe)
            self.parent.s_pool.edit_groupe(cred_user, id_groupe, libelle, serveurs)
        except (KeyError, ValueError):
            return 0, u("""id de groupe invalide : %s""" % str(id_groupe))
        return 1, "ok"

    def xmlrpc_get_groups(self,cred_user,id_groupe=None):
        """récupère un ou plusieur groupe de serveurs dans la base
        """
        try:
            liste_gr = self.parent.s_pool.get_groupes(cred_user, id_groupe)
        except (KeyError, ValueError):
            return 0, u("""id de groupe invalide : %s""" % str(id_groupe))
        return 1, u(liste_gr)

    def xmlrpc_authorize_user(self,cred_user,username,serveurs):
        """autorise la connexion ssh par clé pour un utilisateur
        """
        query="""select login from users where login = %s"""
        return self.dbpool.runQuery(query, (username,)).addCallbacks(self._authorize_user, db_client_failed, callbackArgs=[username, serveurs])

    def _authorize_user(self,data,username,serveurs):
        try:
            user = data[0][0]
        except:
            return 0, u("L'utilisateur %s est inconnu" % username)
        for serveur in serveurs:
            query="""insert into serveur_auth values (%s,%s)"""
            self.dbpool.runOperation(query, (int(serveur), user)).addErrback(db_client_failed)
        return 1, u('ok')

    def xmlrpc_deny_user(self,cred_user,username,serveurs):
        """enlève la connexion ssh par clé pour un utilisateur
        """
        liste=[int(i) for i in serveurs]
        query="""delete from serveur_auth where login=%s and (id_serveur=""" + " or id_serveur=".join(["%s" for serv in liste]) + ")"
        params = [username]
        params.extend(liste)
        return self.dbpool.runOperation(query, params).addCallbacks(lambda x : [1,'ok'], db_client_failed)

    def xmlrpc_get_locks(self,cred_user,serveur=None):
        """liste des tags de procédures interdites pour un serveur
        """
        if serveur is None:
            query = """select tag,libelle from procedures"""
            return self.dbpool.runQuery(query).addCallbacks(self._get_locks, db_client_failed)
        else:
            query = """select lock_serveur.tag,libelle from lock_serveur,procedures where lock_serveur.tag=procedures.tag and id_serveur=%s"""
            return self.dbpool.runQuery(query, (int(serveur),)).addCallbacks(self._get_locks, db_client_failed)

    def _get_locks(self,data):
        """formatte la sortie pour la recherche des fonctions lockées"""
        locks=[]
        for tag in data:
            locks.append([tag[0],tag[1]])
        return 1, u(locks)

    def xmlrpc_maj_locks(self,cred_user,serveurs,tags,notags=[]):
        """interdit un type de procédure sur un ensemble de serveurs
        tags : liste des tags à interdire
        """
        if type(serveurs) != list:
            # si on passe un seul serveur, on le met dans une liste
            serveurs=[serveurs]
        cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD)
        cursor=cx.cursor()
        erreur= ""
        for serveur in serveurs:
            try:
                id_serveur = int(serveur)
                serv = self.parent.s_pool.get(cred_user,id_serveur)
            except (KeyError, ValueError):
                erreur = """serveur %s non retrouvé""" % str(serveur)
            try:
                # on supprime les anciennes interdictions annulées
                if len(notags) > 0:
                    cursor.executemany("delete from lock_serveur where id_serveur=%s and tag=%s", [(serveur, tag) for tag in notags])
                # on met à jour les interdictions
                query = "insert into lock_serveur (id_serveur,tag) values (%s,%s)"
                cursor.executemany(query, [(serveur, tag) for tag in tags])
            except Exception as e:
                erreur = """erreur de mise à jour des locks : serveur %s""" % (str(serveur))
            # si il y a une erreur, on annule tout
            if erreur != "":
                cx.rollback()
                cx.close()
                return 0, u(erreur)
        # mise à jour ok
        cursor.close()
        cx.commit()
        cx.close()
        return 1, 'OK'

    def xmlrpc_get_timeout(self,cred_user,id_serveur):
        """récupère le délai de connexion des serveurs (en secondes)
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        try:
            timeout = serv.get_timeout()
        except:
            return 0, "erreur de récupération du timeout"
        return 1, timeout

    def xmlrpc_check_min_version(self, cred_user, id_serveur, nom_paq, version, num_distrib=False, default=False):
        """vérifie quel a version du paquet <nom_paq> installée sur le serveur est > <version>

        si <num_distrib> est donné:
          - on ne vérifie que pour cette version de la distribution
          - si la version de distrib est inférieure -> False, si supérieure -> True

        default: résultat à renvoyer si la version du paquet installé n'est pas connue
        """
        try:
            id_serveur = int(id_serveur)
            serv = self.parent.s_pool.get(cred_user,id_serveur)
        except (KeyError, ValueError):
            return 0, u("""serveur inconnu : %s""" % str(id_serveur))
        try:
            res = serv.check_min_version(self.parent.maj_checker, nom_paq, version, num_distrib or None, default)
        except:
            # pas d'info sur la version, on considère que le paquet n'est pas ok ?
            return 1, False
        return 1, res
