#-*- coding: utf-8 -*-

# Copyright 2008-2010 Calculate Ltd. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os
from mod import mod_ldap
import sys
sys.modules['mod_ldap'] = mod_ldap

from mod_ldap import LDAPError, MOD_REPLACE, MOD_ADD, SCOPE_SUBTREE,\
                     SCOPE_ONELEVEL
from ldif import LDIFParser, LDIFWriter
import  cStringIO, StringIO
from cl_utils import _error
from cl_print import color_print
from cl_template import template, iniParser
from cl_ldap import ldapFun
from server.share import shareVars
from server.utils import genSleep, stringIsJpeg
# Перевод модуля
import cl_lang
tr = cl_lang.lang()
tr.setLocalDomain('cl_lib')
tr.setLanguage(sys.modules[__name__])

def adminConnectLdap(fun):
    """Cоединение с LDAP администратором сервиса (декоратор)

    соединение с LDAP и проверка установки необходимых переменных
    """
    def ret (self, *arg, **argv):
        flagError = False
        if not self.clVars:
            self.createClVars()
        if not self.ldapObj:
            if not self.getLdapObjInFile():
                flagError = True
        if not self.baseDN:
            if self.clVars.defined("ld_base_dn"):
                self.baseDN = self.clVars.Get("ld_base_dn")
            if not self.baseDN:
                self.printERROR (_('Not found LDAP base DN'))
        if flagError:
            return False
        else:
            return fun(self, *arg , **argv)
    return ret


class addLdif(LDIFParser):
    """Класс необходимый для добавления записей в LDAP"""
    def __init__(self, strInput,ldapCon):
        FD = cStringIO.StringIO(strInput)
        LDIFParser.__init__(self, FD)
        self.ldapCon = ldapCon

    def handle(self, dn, entry):
        self.ldapCon.add_s(dn, entry.items())


class ldapFunction(ldapFun):
    '''Объект для работы с LDAP сервером'''
    def __init__(self, dnUser, password):
        ldapFun.__init__(self, dnUser, password)

    def ldapAdd(self, strLdif):
        """Добавляем строку содержащую ldif в LDAP

        Если данные существуют - ошибка
        """
        if self.conLdap:
            try:
                # Записываем параметры из ldif файла в LDAP сервер
                parser = addLdif(strLdif,self.conLdap)
                parser.parse()
            except LDAPError, e:
                self.setError(e[0]['desc'])
                return False
            except:
                self.setError("Error in ldif file")
                return False
            return True
        else:
            self.setError(_("No connect to LDAP server"))
            return False


class iniLdapParser(iniParser):
    """Класс для работы c ini-файлом ldap"""
    def __init__(self):
        # название ini файла
        self.nameIniFile = "/etc/calculate/calculate.ldap"
        iniParser.__init__(self, self.nameIniFile)
        # права создаваемого ini-файла
        self.setMode(0600)
        pathIniFile = os.path.split(self.nameIniFile)[0]
        if not os.path.exists(pathIniFile):
            os.makedirs(pathIniFile)

class shareldap(_error, color_print, shareVars):
    """Общие методы для работы с LDAP для серверных программ"""

    # DN сервисов относительно базового
    ServicesDN = "ou=Services"
    # Переменная объект Vars
    clVars = False
    # Переменная объект ldapFunction
    ldapObj = False
    # Переменная соединение с LDAP сервером
    conLdap = False
    # Базовый DN LDAP сервера
    baseDN = False

    def addDN(self, *arg):
        """Складывает текстовые элементы DN"""
        DNs = []
        for dn in arg:
            if dn:
                DNs.append(dn)
        return ','.join(DNs)

    @adminConnectLdap
    def searchLdapDN(self, name, relDN, attr, retAttr=None):
        """Находит DN в LDAP"""
        DN = self.addDN(relDN,self.baseDN)
        #searchScope = SCOPE_SUBTREE
        searchScope = SCOPE_ONELEVEL
        searchFilter = "%s=%s" %(attr,name)
        retrieveAttributes = retAttr
        resSearch = self.ldapObj.ldapSearch(DN, searchScope,
                           searchFilter, retrieveAttributes)
        return resSearch

    @adminConnectLdap
    def addEntry(self, DN, entry, errorMessage):
        """Добавление узла в LDAP"""
        try:
            self.conLdap.add_s(DN, entry)
        except LDAPError, e:
            self.printERROR(_("LDAP Error") + ": " + e[0]['desc'].strip())
            self.printERROR(errorMessage)
            return False
        return True

    def getLdapObjInFile(self, part="admin"):
        """Получаем объект ldapFunction из ini файла

        В выходном объекте есть соединение с LDAP сервером: self.conLdap
        """
        # Если  раннее была ошибка то выходим
        if self.getError():
            self.printERROR (_("ERROR") + ": " +\
                self.getError().strip())
            return False
        ldapParser = iniLdapParser()
        adminDn = ldapParser.getVar(part,"DN")
        adminPw = ldapParser.getVar(part,"PASS")
        if not (adminDn or adminPw):
            if part == "admin":
                service = "LDAP"
            else:
                service = part
            self.printERROR(\
            _("Admin password for the service %s could not be found")%service)
            return False
        ldapObj = ldapFunction(adminDn, adminPw)
        # Генератор задержек
        wait = genSleep()
        while ldapObj.getError():
            try:
                # Задержка
                wait.next()
            except StopIteration:
                break
            # Очистка ошибки
            _error.error = []
            ldapObj = ldapFunction(adminDn, adminPw)
        if ldapObj.getError():
            # Удаляем одинаковые ошибки
            listError = []
            for e in ldapObj.error:
                if not e in listError:
                    listError.append(e)
            _error.error = listError
            self.printERROR (_("LDAP connect error") + ": " +\
                ldapObj.getError().strip())
            return False
        # Устанавливаем у объекта соединение и объект LDAP функций
        self.ldapObj = ldapObj
        self.conLdap = ldapObj.conLdap
        return True

    def connectToLDAP(self, adminDn, adminPw):
        """Подключаемся к LDAP - для внешних программ запускающихся не от root
        """
        ldapObj = ldapFunction(adminDn, adminPw)
        # Генератор задержек
        wait = genSleep()
        while ldapObj.getError():
            try:
                # Задержка
                wait.next()
            except StopIteration:
                break
            # Очистка ошибки
            _error.error = []
            ldapObj = ldapFunction(adminDn, adminPw)
        if ldapObj.getError():
            # Удаляем одинаковые ошибки
            listError = []
            for e in ldapObj.error:
                if not e in listError:
                    listError.append(e)
            _error.error = listError
            self.printERROR (_("LDAP connect error") + ": " +\
                ldapObj.getError().strip())
            return False
        # Устанавливаем у объекта соединение и объект LDAP функций
        self.ldapObj = ldapObj
        self.conLdap = ldapObj.conLdap
        return True

    @adminConnectLdap
    def modAttrsDN(self, relDN, modAttrs):
        """Модифицирует аттрибуты DN"""
        DN = self.addDN(relDN,self.baseDN)
        if modAttrs:
            try:
                self.conLdap.modify_s(DN, modAttrs)
            except LDAPError, e:
                self.printERROR(e[0]['desc'])
                return False
        return True

    @adminConnectLdap
    def modifyElemDN(self, relDN, newFirstDn):
        """Изменяет основной элемент DN (uid, cn и др.)"""
        DN = self.addDN(relDN,self.baseDN)
        try:
            self.conLdap.modrdn_s(DN, newFirstDn)
        except LDAPError, e:
            self.printERROR(e[0]['desc'])
            return False
        return True

    def getMaxAttrDN(self, relDN, name, attr, numMin, numMax, attrSearch):
        """Находит максимальный добавленный аттрибут в LDAP DN"""
        resSearch = self.searchLdapDN(name, relDN, attr, [attrSearch])
        lst = []
        lst.append(0)
        if resSearch:
            for scope in resSearch:
                if scope[0][1].has_key(attrSearch):
                    uid = int(scope[0][1][attrSearch][0])
                    if uid<=numMax and uid>=numMin:
                        lst.append(uid)
            return max(lst)
        return False

    def createLdif(self, ldifFile):
        """Cоздает ldif из ldif - шаблона"""
        if not os.access(ldifFile, os.F_OK):
            self.setError(_("File not found") + ":\n  " + ldifFile)
            return False
        FD = open (ldifFile)
        ldifTemplate = FD.read()
        FD.close()
        clTempl = template(self.clVars)
        # Применяем условия к шаблону
        ldifTemplate = clTempl.applyTermsTemplate(ldifTemplate, ldifFile)
        # Заменяем переменные в шаблоне
        ldifTemplate = clTempl.applyVarsTemplate(ldifTemplate, ldifFile)
        return ldifTemplate

    @adminConnectLdap
    def delDN(self, relDN):
        """Удаляет одиночный DN"""
        DN = self.addDN(relDN,self.baseDN)
        try:
            self.conLdap.delete_s(DN)
        except LDAPError, e:
            self.printERROR(e[0]['desc'])
            return False
        return True

    @adminConnectLdap
    def deleteDN(self, relDelDN):
        """Удаляет DN и все внутренние элементы"""
        delDN = self.addDN(relDelDN, self.baseDN)
        delListDN=[]
        try:
            dnList = self.conLdap.search_s(delDN,
                                            SCOPE_SUBTREE,
                                            '(objectclass=*)',
                                            [''])
        except LDAPError, e:
            self.printERROR("deleteDN: "+e[0]['desc'])
            return False
        for dn, f in dnList:
            delListDN.append(dn)
        delListDN.sort(lambda x, y: cmp(len(y), len(x)))
        for dn in delListDN:
            try:
                self.conLdap.delete_s(dn)
            except LDAPError, e:
                self.printERROR("deleteDN: "+e[0]['desc'])
                return False
        return True

    @adminConnectLdap
    def fullElementDNtoText(self, relDN="", ldapFilter='(objectclass=*)'):
        """Выводит все внутренние элементы DN виде текста"""
        DN = self.addDN(relDN, self.baseDN)
        listDN=[]
        try:
            dnList = self.conLdap.search_s(DN,
                                            SCOPE_SUBTREE,
                                            ldapFilter,None)
        except LDAPError, e:
            self.printERROR("fullElementDN: "+e[0]['desc'])
            return False
        FDOUT = StringIO.StringIO("")
        writer = LDIFWriter(FDOUT)
        for dn, f in dnList:
            writer.unparse(dn, f)
        FDOUT.seek(0)
        return FDOUT.read()

    @adminConnectLdap
    def fullElementSambaDNtoText(self, relDN=""):
        """Выводит все внутренние элементы ветки Samba в виде текста"""
        return self.fullElementDNtoText(relDN,'(|(|(|(|(ou:dn:=Samba)\
(ou:dn:=Unix))(ou:dn:=LDAP))(!(ou:dn:=Services)))(ou=Services))')

    @adminConnectLdap
    def fullElementUnixDNtoText(self, relDN=""):
        """Выводит все внутренние элементы ветки Unix в виде текста"""
        return self.fullElementDNtoText(relDN,'(|(|(|(ou:dn:=Unix)\
(ou:dn:=LDAP))(!(ou:dn:=Services)))(ou=Services))')

    @adminConnectLdap
    def fullElementMailDNtoText(self, relDN=""):
        """Выводит все внутренние элементы ветки Mail в виде текста"""
        baseDN = self.clVars.Get("ld_base_dn")
        baseDNName, baseLogin = baseDN.split(",")[0].split("=")
        proxyDN = self.clVars.Get("ld_bind_dn")
        proxyDNName, proxyLogin = proxyDN.split(",")[0].split("=")
        #return self.fullElementDNtoText(relDN,'(&(|(|(&(ou:dn:=Replication)\
#(ou:dn:=Mail))(!(ou:dn:=Services)))(ou=Services))(!(&(%s:dn:=%s)\
#(%s:dn:=%s))))'%(proxyDNName, proxyLogin, baseDNName, baseLogin))
        return self.fullElementDNtoText(relDN,'(&(&(|(|(|(ou:dn:=LDAP)\
(ou=Mail))(!(ou:dn:=Services)))(ou=Services))(!(&(%s:dn:=%s)(%s:dn:=%s))))\
(!(ou:dn:=Worked)))'%(proxyDNName, proxyLogin, baseDNName, baseLogin))

    @adminConnectLdap
    def fullElementMailSambaDNtoText(self, relDN=""):
        """Выводит все внутренние элементы ветки Samba и Mail в виде текста"""
        return self.fullElementDNtoText(relDN,'(&(|(|(|(|(|(ou:dn:=Samba)\
(ou:dn:=Unix))(ou:dn:=LDAP))(ou:dn:=Mail))(!(ou:dn:=Services)))(ou=Services))\
(!(|(&(&(ou:dn:=Users)(ou:dn:=Mail))(uid=*))(&(&(ou:dn:=Groups)(ou:dn:=Mail))\
(cn=*)))))')

    @adminConnectLdap
    def fullElementMailUnixDNtoText(self, relDN=""):
        """Выводит все внутренние элементы ветки Unix и Mail в виде текста"""
        return self.fullElementDNtoText(relDN,'(&(|(|(|(|(ou:dn:=Unix)\
(ou:dn:=LDAP))(ou:dn:=Mail))(!(ou:dn:=Services)))(ou=Services))\
(!(|(&(&(ou:dn:=Users)(ou:dn:=Mail))(uid=*))(&(&(ou:dn:=Groups)(ou:dn:=Mail))\
(cn=*)))))')

    def setJpegPhotoUser(self, userName, photoPath, attr="uid"):
        """Добавляем jpeg фотографию пользователя в LDAP"""
        import subprocess
        try:
            FD = open(photoPath)
            photoData = FD.read()
            FD.close()
        except:
            self.printERROR(_("Not open file") + ": " + str(photoPath))
            return False
        searchUser = self.searchLdapDN(userName, self.relUsersDN, attr)
        if not searchUser:
            self.printERROR(_("User") + " " + str(userName) + " "+\
            _("not found"))
            return False
        modAttrs = []
        if not stringIsJpeg(photoData):
            flagError = False
            pipe = subprocess.Popen("convert '%s' jpg:-" %photoPath,
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE, 
                            stderr=subprocess.PIPE, close_fds=True,
                            shell=True)
            fOut, fIn, fErr = (pipe.stdout, pipe.stdin, pipe.stderr)
            fIn.close()
            # Код возврата
            retcode = pipe.wait()
            if retcode != 0:
                self.printERROR(_("Can not convert file '%s' in jpeg format")\
                                %photoPath)
                flagError = True
            fErr.close()
            if not flagError:
                photoData = fOut.read()
                if not stringIsJpeg(photoData):
                    self.printERROR(\
                    _("Can not convert file '%s' in jpeg format") %photoPath)
                    flagError = True
            fOut.close()
            if flagError:
                return False
        if searchUser[0][0][1].has_key('jpegPhoto'):
            modAttrs.append((MOD_REPLACE, 'jpegPhoto', photoData))
        else:
            modAttrs.append((MOD_ADD, 'jpegPhoto', photoData))
        userDN = self.addDN("%s=%s"%(attr,userName),self.relUsersDN)
        if not self.modAttrsDN(userDN, modAttrs):
            return False
        return True

    def searchService(self):
        """Поиск DN сервиса"""
        name, value = self.relServDN.split('=')
        resSearch = self.searchLdapDN(value, self.ServicesDN, name)
        return resSearch