You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-2.2-lib/pym/server/ldap.py

432 lines
17 KiB

#-*- 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 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