#-*- 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 (_("Base DN not found for LDAP")) 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(_("Unable to connect to the 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 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 connection 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 connection 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(_("Cannot convert file '%s' in jpeg format")\ %photoPath) flagError = True fErr.close() if not flagError: photoData = fOut.read() if not stringIsJpeg(photoData): self.printERROR(\ _("Cannot 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