Browse Source

reorganization of the library

develop
Самоукин Алексей 13 years ago
parent
commit
6eaa98e058
  1. 2
      README
  2. 5
      pym/cl_libserver.py
  3. 26
      pym/cl_template.py
  4. 17
      pym/cl_utils.py
  5. 1
      pym/server/__init__.py
  6. 432
      pym/server/ldap.py
  7. 564
      pym/server/services.py
  8. 138
      pym/server/share.py
  9. 354
      pym/server/users.py
  10. 383
      pym/server/utils.py

2
README

@ -4,7 +4,7 @@ INSTALL
-------
calculate-lib needs the following library version installed, in order to run:
Python >= 2.5
Python >= 2.6
python-ldap >= 2.0.0
pyxml >= 0.8

5
pym/cl_libserver.py

@ -25,16 +25,19 @@ from base64 import urlsafe_b64encode as b64encode
from cl_print import color_print
import cl_lang
from server.utils import copyDir
tr = cl_lang.lang()
tr.setLocalDomain('cl_lib')
tr.setLanguage(sys.modules[__name__])
class shareServer(color_print):
"""Класс хранения общих методов используемых для настройки сервисов"""
def __GenCryptSalt__(self):
"""Генерация соли для хеширования пароля (CRYPT)"""
chars = string.letters + string.digits+ "./"
chars = string.letters + string.digits + "./"
salt = ""
for i in range(2):
salt = salt + choice(chars)

26
pym/cl_template.py

@ -24,7 +24,7 @@ import subprocess
import types
import random
import string
from cl_utils import _error, scan, _toUNICODE, removeDir
from cl_utils import _error, scan, _toUNICODE, removeDir, getModeFile
from cl_overriding import exit
import cl_lang
@ -1859,18 +1859,6 @@ class _file(_error, scan):
return False
return True
def getModeFile(self, nameFile):
"""Выдает информацию о файле
права файла, владелец, группа файла (777,test, group)
"""
fd = os.open(nameFile, os.O_RDONLY)
fst = os.fstat(fd)
uid = fst.st_uid
gid = fst.st_gid
mode = stat.S_IMODE(fst.st_mode)
os.close(fd)
return (mode,uid,gid)
def openNewFile(self, nameFileNew):
"""Открыть файл шаблона"""
@ -2558,7 +2546,7 @@ class template(_file, _terms, xmlShare, _shareTemplate):
# Создаем файл если его нет
if not os.path.exists(pathTemplate):
try:
dMode, uid, gid = self.getModeFile(path)
dMode, uid, gid = getModeFile(path)
#mode = dMode & ~0111
# Создаем файл
open(pathTemplate,"w").close()
@ -2581,7 +2569,7 @@ class template(_file, _terms, xmlShare, _shareTemplate):
createDirs.append(prevDir)
prevDir = os.path.split(prevDir)[0]
try:
tmpMode,dUid,dGid = self.getModeFile(prevDir)
tmpMode,dUid,dGid = getModeFile(prevDir)
except OSError:
self.setError (_("Not access dir: " ) + prevDir)
return False
@ -2762,7 +2750,7 @@ class template(_file, _terms, xmlShare, _shareTemplate):
if dirObj.flagError:
return dirObj
absPath = os.path.join(dirName,fileOrDir)
statInfo = os.stat(absPath)[stat.ST_MODE]
statInfo = os.lstat(absPath)[stat.ST_MODE]
if stat.S_ISREG(statInfo):
# Свойства файла
propF, applyFile=self.__isApplyHeadTemplate(dirObj.fHeadObj,
@ -2789,7 +2777,7 @@ class template(_file, _terms, xmlShare, _shareTemplate):
dirInfoFile = os.path.join(absPath,
self.templDirNameFile)
if os.path.exists(dirInfoFile) and\
stat.S_ISREG(os.stat(dirInfoFile)[stat.ST_MODE]):
stat.S_ISREG(os.lstat(dirInfoFile)[stat.ST_MODE]):
# Настройки директории и применение
pDir, applyDir = self.__isApplyHeadDir(dirObj.dHeadObj,
dirInfoFile)
@ -2813,13 +2801,13 @@ class template(_file, _terms, xmlShare, _shareTemplate):
return dirObj
# Сканирование для получения настроек директории (первое)
else:
if templatesDir and stat.S_ISDIR(os.stat(templatesDir)[stat.ST_MODE]):
if templatesDir and stat.S_ISDIR(os.lstat(templatesDir)[stat.ST_MODE]):
# настройки директории
pDir = {}
# Файл информации о директории
dirInfoFile = os.path.join(templatesDir, self.templDirNameFile)
if os.path.exists(dirInfoFile) and\
stat.S_ISREG(os.stat(dirInfoFile)[stat.ST_MODE]):
stat.S_ISREG(os.lstat(dirInfoFile)[stat.ST_MODE]):
# Настройки директории и применение
pDir,applyDir = self.__isApplyHeadDir(dirObj.dHeadObj,
dirInfoFile)

17
pym/cl_utils.py

@ -179,10 +179,10 @@ class scan:
def __scanDir(self, scanDir, dirObj, flagDir=False):
"""Сканирование одной директории"""
if flagDir or stat.S_ISDIR(os.stat(scanDir)[stat.ST_MODE]):
if flagDir or stat.S_ISDIR(os.lstat(scanDir)[stat.ST_MODE]):
for fileOrDir in os.listdir(scanDir):
absPath = os.path.join(scanDir,fileOrDir)
statInfo = os.stat(absPath)[stat.ST_MODE]
statInfo = os.lstat(absPath)[stat.ST_MODE]
if stat.S_ISDIR(statInfo):
dirObj.dirs.append(absPath)
self.__scanDir(absPath, dirObj, True)
@ -242,3 +242,16 @@ def removeDir(rmDir):
if os.path.exists(rmDir):
os.rmdir(rmDir)
return True
def getModeFile(nameFile):
"""Выдает информацию о файле
права файла, владелец, группа файла (777,test, group)
"""
fd = os.open(nameFile, os.O_RDONLY)
fst = os.fstat(fd)
uid = fst.st_uid
gid = fst.st_gid
mode = stat.S_IMODE(fst.st_mode)
os.close(fd)
return (mode,uid,gid)

1
pym/server/__init__.py

@ -0,0 +1 @@

432
pym/server/ldap.py

@ -0,0 +1,432 @@
#-*- coding: utf-8 -*-
# Copyright 2008-2010 Mir 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 ldap(_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

564
pym/server/services.py

@ -0,0 +1,564 @@
#-*- coding: utf-8 -*-
# Copyright 2008-2010 Mir 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
import sys
import types
import time
from cl_print import color_print
from cl_template import template
from server.utils import execProg
from server.users import users
# Перевод модуля
import cl_lang
tr = cl_lang.lang()
tr.setLocalDomain('cl_lib')
tr.setLanguage(sys.modules[__name__])
class services(color_print):
"""Общие методы для серверных программ,
Методы для работы с сервисами"""
# Переменная объект Vars
clVars = False
# Сервисы и демоны
servicesDaemons = {"ldap":["slapd"],
"unix":["slapd"],
"samba":["samba"],
"mail":["postfix","dovecot"],
"mail_relay":["postfix"],
"jabber":["ejabberd"],
"ftp":["proftpd"],
"proxy":["squid"],
"dns":["named"],
"dhcp":["dhcpd"]}
def getServiceSetup(self):
"""находит установленные сервисы
Выдаем список [установленные сервисы]
"""
# инсталированнные сервисы
servInstalled = []
# доступные сервисы
services = ('ldap', 'unix', 'samba', 'mail',
'jabber', 'ftp', 'proxy', 'dns', 'dhcp')
for serv in services:
if self.clVars.Get("sr_%s_set"%serv) == "on":
servInstalled.append(serv)
return servInstalled
def _getDefaultRunlevelDaemons(self):
"""Получаем всех демонов в default уровне"""
execStr = "rc-update show"
textLine = execProg(execStr, None, False)
if textLine == False:
self.printERROR(_("ERROR") + ": " + execStr)
return False
else:
splLines = filter(lambda x: len(x)==2 and "default" in x[1],\
map(lambda x: x.split("|"),textLine))
splLines = map(lambda x: x[0].strip(), splLines)
return splLines
def setDaemonAutostart(self, daemon):
"""Прописывает демона в автозагрузку"""
if daemon in self._getDefaultRunlevelDaemons():
return True
execStr = "rc-update add %s default" %daemon
textLine = execProg(execStr)
if textLine == False:
self.printERROR(_("ERROR") + ": " + execStr)
self.printERROR(_("Can not add '%s' at default runlevel")%daemon)
return False
else:
return True
def delDaemonAutostart(self, daemon):
"""Удаляет демона из автозагрузки"""
if not (daemon in self._getDefaultRunlevelDaemons()):
return True
self._getDefaultRunlevelDaemons()
execStr = "rc-update del %s default" %daemon
textLine = execProg(execStr)
if textLine == False:
self.printERROR(_("ERROR") + ": " + execStr)
self.printERROR(_("Can not deleted '%s' from default runlevel")\
%daemon)
return False
else:
return True
def runLdapServer(self):
"""Запускает LDAP сервер"""
textLines = execProg("/etc/init.d/slapd start")
if textLines == False:
self.printNotOK(_("Starting LDAP")+ " ...")
return False
else:
return True
def restartLdapServer(self):
"""Запускает LDAP сервер"""
textLines = execProg("/etc/init.d/slapd restart")
if textLines == False:
self.printNotOK(_("Restarting LDAP")+ " ...")
return False
else:
return True
def stopLdapServer(self):
"""Останавливает LDAP сервер"""
textLines = execProg("/etc/init.d/slapd stop")
if textLines == False:
self.printNotOK(_("Stopping LDAP")+ " ...")
return False
else:
return True
def getALLServices(self):
"""Получаем все сервисы которые описаны в шаблонах"""
# путь к директории шаблонов
templatePath = self.clVars.Get("cl_template_path")[0]
data = os.listdir(templatePath)
service = []
for fileData in data:
if os.path.isdir(os.path.join(templatePath, fileData)):
service.append(fileData)
if service:
# После добавления сервисов в класс необходимо удалить
# apache и backup
if 'backup' in service:
service.remove('backup')
if 'apache' in service:
service.remove('apache')
return service
def getTemplatePath(self, nameService):
"""список накладываемых профилей в зависимости от сервиса"""
profpath = []
profPaths = ['/usr/lib/calculate/calculate-server/profile/%s'\
%nameService,
'/var/calculate/remote/server-profile/%s'%nameService,
'/var/calculate/server-profile/%s'%nameService]
for profPath in profPaths:
if os.path.exists(profPath):
profpath.append(profPath)
return profpath
def applyTemplatesFromService(self, service, verbose=False):
"""Применяем шаблоны для данного сервиса"""
# Пути к шаблонам для сервиса
profPaths = self.getTemplatePath(service)
# Устанавливаем пути к шаблонам для сервиса
self.clVars.Set("cl_template_path", profPaths, True)
clTempl = template(self.clVars)
# Объединяем шаблоны
data = clTempl.applyTemplates()
if clTempl.getError():
self.printERROR(clTempl.getError())
return False
else:
if verbose and type(data) == types.TupleType:
dirs, files = data
return files
return True
def delServicesAutostart(self, servInstalled):
"""Удаляет из автозагрузки сервисы
Входные данные - список названий сервисов
"""
flagError = False
delDaemons = []
for service in servInstalled:
if not service in self.servicesDaemons.keys():
self.printERROR(_("Not supported service '%s'")%service)
self.printERROR(\
_("Can not deleted service from default runlevel"))
flagError = True
break
for daemon in self.servicesDaemons[service]:
if not daemon in delDaemons:
delDaemons.append(daemon)
if not self.delDaemonAutostart(daemon):
flagError = True
break
if flagError:
break
if flagError:
return False
else:
return True
def startDaemons(self, service, daemons, printSuccess=True):
"""Стартует демонов"""
flagError = False
for daemon in daemons:
if not self.getRunDaemons([daemon]):
textLines = execProg("/etc/init.d/%s start" %(daemon))
if textLines == False:
self.printERROR( _("Daemon %s was not started") %daemon)
flagError = True
break
prnService = self.printNameService(service)
if flagError:
self.printNotOK(_("Starting") + " " + prnService + " " +\
_("service") + " ...")
return False
else:
if printSuccess:
self.printOK(_("Starting") + " " + prnService + " "+\
_("service") + " ...")
return True
def startServices(self, servInstalled, printSuccess=True):
"""Запускает все сервисы поданные на вход этому методу
Также прописывает в автозагрузку
Входные даннные - список названий сервисов
"""
addDaemons = []
if 'ldap' in servInstalled or 'unix' in servInstalled:
if not self.startDaemons('ldap',['slapd'], printSuccess):
return False
# Устанавливаем автозапуск демона
if not self.setDaemonAutostart("slapd"):
return False
addDaemons.append("slapd")
flagError = False
for service in servInstalled:
if not service in self.servicesDaemons.keys():
self.printERROR(_("Can not supported service '%s'")%service)
self.printERROR(_("Can not add service at default runlevel"))
flagError = True
break
for daemon in self.servicesDaemons[service]:
if not daemon in addDaemons:
addDaemons.append(daemon)
if not self.startDaemons(service, [daemon], printSuccess):
flagError = True
break
if not self.setDaemonAutostart(daemon):
flagError = True
break
if flagError:
break
if flagError:
return False
else:
return True
def stopServices(self, servInstalled):
"""Останавливает все сервисы поданные на вход этому методу
Входные даннные - список названий сервисов
"""
addDaemons = []
if 'ldap' in servInstalled or 'unix' in servInstalled:
addDaemons.append("slapd")
flagError = False
for service in servInstalled:
if not service in self.servicesDaemons.keys():
self.printERROR(_("Can not supported service '%s'")%service)
self.printERROR(_("Can not stop service"))
flagError = True
break
# Название сервиса для вывода на экран
servicePrn = self.printNameService(service)
for daemon in self.servicesDaemons[service]:
if not daemon in addDaemons:
addDaemons.append(daemon)
# Если демон запущен и не squid то останавливаем его
if not daemon in ["squid"] and self.getRunDaemons([daemon]):
ret = execProg("/etc/init.d/%s stop"%daemon)
if ret == False:
self.printERROR(servicePrn + " " +\
_("service is not stopped"))
flagError = True
break
# Удаляем процессы ejabberd
if daemon == "ejabberd":
strCmd = "ps ax"
listProcess = execProg(strCmd,False,False)
if listProcess == False:
self.printERROR(_('Can not execute "%s"')%strCmd)
return False
killPid = []
for process in listProcess:
if "erlang" in process:
killPid.append(process.split(" ")[0])
if killPid and " ".join(killPid).strip():
textLine=execProg("kill %s" %" ".join(killPid))
if textLine == False:
self.printERROR(_("Can not 'kill %s'")\
%" ".join(killPid))
flagError = True
break
elif daemon == "squid" and self.getRunDaemons(["squid"]):
errStopProxy = False
# Создаем 2 процесса
pid = os.fork()
tic = 0
if pid:
message = _("Waiting for squid to shutdown") + " "
self.printSUCCESS(message, 0, False)
perm = [0]
offset = 0
while not perm[0]:
perm = os.waitpid(pid, os.WNOHANG)
if perm[1]:
errStopProxy = True
break
time.sleep(0.1)
tic += 1
if tic>=15:
sys.stdout.flush()
sys.stdout.write(".")
offset += 1
tic = 0
if errStopProxy:
self.printOnlyNotOK(" ",\
self.lenString(message)+\
offset+3)
else:
self.printOnlyOK(" ",self.lenString(message)+\
offset+3)
else:
sys.stdout.flush()
if os.system("/etc/init.d/squid stop &>/dev/null"):
sys.exit(1)
else:
sys.exit(0)
# Если ошибка при остановке proxy сервера
if errStopProxy:
self.printERROR(servicePrn + " " +\
_("service is not stopped"))
flagError = True
break
if flagError:
break
if not flagError and "slapd" in addDaemons:
if self.getRunService('ldap'):
textLines = execProg("/etc/init.d/slapd stop")
if textLines == False:
self.printERROR("LDAP" + " " +\
_("service is not stopped"))
flagError = True
if flagError:
return False
else:
return True
def getRunDaemons(self, daemons, printError=False):
"""Проверка, запущены ли демоны"""
baseDir = "/var/run"
runDaemons = {}
flagBaselayoutDir = False
for daemon in daemons:
# Проверка на запуск демона postfix
if daemon == 'postfix':
flagRun = False
strCmd = "ps ax"
listProcess = execProg(strCmd, False, False)
if not listProcess:
self.printERROR(_('Can not execute "%s"')%strCmd)
return False
for process in listProcess:
if "postfix/master" in process:
flagRun = True
break
if flagRun:
runDaemons['postfix'] = True
else:
runDaemons['postfix'] = False
continue
addDirDict = {"slapd":("openldap","slapd.pid"),
"dovecot":("dovecot","master.pid"),
"proftpd":("","proftpd.pid"),
"squid":("","squid.pid")}
baselayoutDir = "/var/lib/init.d/daemons"
if not flagBaselayoutDir:
if os.path.exists(baselayoutDir):
flagBaselayoutDir = True
if flagBaselayoutDir:
addDirDict["ejabberd"] = (baselayoutDir,"ejabberd")
addDirDict["samba"] = (baselayoutDir,"samba")
addDirDict["named"] = (baselayoutDir,"named")
addDirDict["dhcpd"] = (baselayoutDir,"dhcpd")
elif daemon in ["ejabberd", "samba", "named", "dhcpd"]:
if not os.system("/lib/rc/bin/service_started %s" %daemon):
runDaemons[daemon] = True
else:
runDaemons[daemon] = False
continue
if addDirDict[daemon][0][:1] == "/":
pidDir = addDirDict[daemon][0]
else:
pidDir = os.path.join(baseDir,addDirDict[daemon][0])
if os.access(pidDir, os.F_OK) and os.listdir(pidDir) and\
os.path.exists(os.path.join(pidDir,addDirDict[daemon][1])):
runDaemons[daemon] = True
else:
runDaemons[daemon] = False
if printError:
for daemon in daemons:
if not runDaemons[daemon]:
self.printERROR(_("Daemon %s is not started") %daemon)
if False in runDaemons.values():
return False
else:
return True
def getRunService(self, nameService, printError=False):
"""Проверка, запущен ли сервис с данным именем"""
flagError = False
if not nameService in self.servicesDaemons.keys():
self.printERROR(_("Can not supported service '%s'")%nameService)
self.printERROR(_("Can not check run service"))
return False
# Названия демонов для сервиса
daemons = self.servicesDaemons[nameService]
if not self.getRunDaemons(daemons, printError):
flagError = True
if flagError:
if printError:
self.printERROR(self.printNameService(nameService) + " " +\
_("service is not started"))
return False
return True
def isServiceSetup(self, service, printError=True):
"""Проверяет установлен ли сервис"""
if not self.clVars:
# Cоздаем объект переменные
self.createClVars()
if self.clVars.Get("sr_%s_set"%service) == "on":
return True
if printError:
self.printERROR(_("Service %s is not installed")%service)
return False
def initialChecks(self, service, printError=True):
"""Начальная проверка перед запуском методов сервиса"""
# Создаем объект переменных
self.createClVars()
if self.clVars.Get("sr_mail_relay_set") == "on":
if printError:
self.printERROR(_("This server is a mail relay. \
This command is not allowed."))
return False
if not self.isServiceSetup(service, printError):
return False
return True
def initialChecksSetup(self):
# Создаем объект переменных
self.createClVars()
"""Начальная проверка перед запуском метода setup"""
if self.clVars.Get("sr_mail_relay_set") == "on":
self.printERROR(_("This server is a mail relay. \
This command is not allowed."))
return False
return True
def createCertificate(self, sslCountry="US",
sslState="California",
sslLocality="Santa Barbara",
sslOrganization="SSL Server",
sslUnit="For Testing Purposes Only",
sslCommonName="localhost",
sslEmail="root@localhost",
nsCertType="server",
sslDays=730,
sslBits=1024,
userName="root",groupName="root",
certFile="/tmp/server.pem",
certFileMode=0400,
keyFile="/tmp/server.key",
keyFileMode=0400):
"""Создает сертификат"""
certAndKeyFiles = [certFile, keyFile]
foundCertFiles = filter(lambda x: os.path.exists(x), certAndKeyFiles)
if len(foundCertFiles)==2:
return True
# Удаляем файл сертификата
map(lambda x: os.remove(x), foundCertFiles)
# Объект для работы с пользователями
usersObj = users()
# получаем id и gid пользователя
uidAndGid = usersObj.getUserUidAndGid(userName, groupName)
if not uidAndGid:
return False
uid, gid = uidAndGid
textCnf="""[ req ]
prompt = no
default_bits = %s
distinguished_name = req_dn
[ req_dn ]
C = %s
ST = %s
L = %s
O = %s
OU = %s
CN = %s
emailAddress = %s
[ cert_type ]
nsCertType = %s
"""%(sslBits, sslCountry, sslState, sslLocality, sslOrganization, sslUnit,
sslCommonName, sslEmail, nsCertType)
# генерируем название файла конфигурации
strData = time.strftime("%Y%m%d%H%M%S",time.localtime(time.time()))
cnfFile = "/tmp/%s.cnf" %strData
sslFile = "/usr/bin/openssl"
if not os.path.exists(sslFile):
self.printERROR(_("Can not found %s")%sslFile)
return False
# Cоздание директорий
for fileName in certAndKeyFiles:
dirName = os.path.split(fileName)[0]
if not os.path.exists(dirName):
self.createUserDir(0, 0, dirName, 0755)
# Создание конфигурационного файла
self.createUserFile(cnfFile, textCnf, 0, 0, 0600)
# Создание сертификата
textLine = execProg(\
"%s req -new -x509 -nodes -config %s -days %s -out %s -keyout %s"\
%(sslFile, cnfFile, sslDays, certFile, keyFile))
# Удаление конфигурационного файла
if os.path.exists(cnfFile):
os.remove(cnfFile)
# Меняем права
if os.path.exists(certFile):
os.chown(certFile, uid,gid)
os.chmod(certFile, certFileMode)
if os.path.exists(keyFile):
os.chown(keyFile, uid,gid)
os.chmod(keyFile, keyFileMode)
if textLine == False:
self.printERROR(_("Can not create certificate %s")%certFile)
return False
# Проверка сертификата
textLine = execProg("%s x509 -subject -fingerprint -noout -in %s"\
%(sslFile, certFile))
if textLine == False:
self.printERROR(_("Can not create certificate %s")%certFile)
return False
return True

138
pym/server/share.py

@ -0,0 +1,138 @@
#-*- coding: utf-8 -*-
# Copyright 2008-2010 Mir 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.
from cl_data import DataVars
from cl_print import color_print
# Перевод модуля
import cl_lang
tr = cl_lang.lang()
tr.setLocalDomain('cl_lib')
tr.setLanguage(sys.modules[__name__])
class shareVars:
"""Общие методы для классов модулей серверных программ"""
# Переменная объект Vars
clVars = False
def createClVars(self, clVars=False, returnImportVar=False):
"""Создает объект Vars"""
# Словарь импортируемых переменных из ini Файлов
dictImportVars = {}
if not clVars:
clVars = DataVars()
clVars.flServer()
dictImportVars = clVars.flIniFile()
# Устанавливаем у объекта объект Vars
self.clVars = clVars
if returnImportVar:
return dictImportVars
return True
def deleteServiceVarsInFile(self, service):
"""Удаляет переменные сервиса из ini файлов
После запуска этого метода объект self.clVars должен быть пересоздан
"""
importVarsDict = self.createClVars(False,True)
serviceNameVars = "_%s_" %service
for location in importVarsDict.keys():
for nameVar in importVarsDict[location]:
# Если имя сервиса присутствует в переменной -
# Удаляем переменную
if serviceNameVars in nameVar:
if not self.clVars.Delete(nameVar, location, "server"):
return False
self.clVars = False
return True
def saveVarsClient(self, listVarName):
"""Записывает переменные для клиента calcualte-client"""
#считаем переменные для клиента
dictVar = {}
flagError = False
for varName in listVarName:
value = self.clVars.Get(varName)
if not value and value != "":
self.printERROR(_("Variables %s is empty")%varName)
flagError = True
break
dictVar[varName] = value
if flagError:
return False
#Запишем переменные в клиентскую секцию
for name,value in dictVar.items():
value = str(value)
if not value.strip():
self.clVars.Delete(name)
if not self.clVars.Write(name,value,True,"remote","client"):
self.printERROR(_("Error writing variable %s")%name)
flagError = True
break
if flagError:
return False
return True
def reloadDefaultVar(self, nameVar):
"""При получениии значения переменной снова
вызывается метод заполнения переменной"""
self.clVars.Set(nameVar,"",True)
self.clVars.__getattribute__(nameVar).countFill = 0
self.clVars.__getattribute__(nameVar).fillStart = True
return True
class servicesAPI(color_print):
"""Методы сервисов используемые другими сервисами"""
# Путь к модулю сервера
__pathServer__ = "/usr/lib/calculate/calculate-server/pym"
# Названия импортированных классов
__imports_names__ = []
# Импортированные классы
__imports_classes__ = {}
def __getServiceObj__(self, serviceName):
"""Получаем объект сервиса"""
if not serviceName in __imports_names__:
try:
classImport = getattr(__import__("cl_ldap", globals(),\
locals(),[]), serviceName)
except (ImportError, AttributeError):
self.printERROR(_("Can not found service class '%s'")\
%serviceName)
return False
__imports_classes__[serviceName] = classImport
__imports_names__.append(serviceName)
retObj = classImport()
else:
retObj = __imports_classes__[serviceName]()
return retObj
def searchUnixUser(self, userName, servUnixObj=None):
"""Поиск пользователя в LDAP ветке Unix сервиса"""
if not servUnixObj:
servUnixObj = self.__getServiceObj__("unix")
if not servUnixObj:
exit(1)
return servUnixObj.searchUnixUser(userName)
def searchPasswdUser(self, userName, servUnixObj=None):
"""Поиск пользователя в /etc/passwd"""
if not servUnixObj:
servUnixObj = self.__getServiceObj__("unix")
if not servUnixObj:
exit(1)
return servUnixObj.searchPasswdUser(userName)

354
pym/server/users.py

@ -0,0 +1,354 @@
#-*- coding: utf-8 -*-
# Copyright 2008-2010 Mir 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
import sys
# Ввод pwd
import getpass
import types
from cl_utils import removeDir
from cl_print import color_print
from server.share import servicesAPI
from server.utils import rawInput, isCorrectStringNet, addInfoUser, addInfoGroup
# Перевод модуля
import cl_lang
tr = cl_lang.lang()
tr.setLocalDomain('cl_lib')
tr.setLanguage(sys.modules[__name__])
class users(color_print):
"""Общие методы для серверных программ,
(работа с пользователями и группами)"""
# Объект с методами доступа к другим сервисам
servicesAPIObj = servicesAPI()
# Статические группы
staticGroups = {\
'client':addInfoGroup('client',
'900',
'Client group',
'2801',
'2'),
'Domain Admins':addInfoGroup('Domain Admins',
'512',
'Domain Administrators',
'512',
'2'),
'Domain Users':addInfoGroup('Domain Users',
'513',
'Domain Users',
'513',
'2'),
'Domain Guests':addInfoGroup('Domain Guests',
'514',
'Domain Guests Users',
'514',
'2'),
'Domain Computers':addInfoGroup('Domain Computers',
'515',
'Domain Computers accounts',
'515',
'2'),
'Administrators':addInfoGroup('Administrators',
'544',
'Domain Members can fully \
administer the computer/sambaDomainName',
'544',
'5',
"S-1-5-32-544"),
'Account Operators':addInfoGroup('Account Operators',
'548',
'Domain Users to manipulate \
users accounts',
'548',
'5',
"S-1-5-32-548"),
'System Operators':addInfoGroup('System Operators',
'549',
'Domain System Operators',
'549',
'5',
"S-1-5-32-549"),
'Print Operators':addInfoGroup('Print Operators',
'550',
'Domain Print Operators',
'550',
'5',
"S-1-5-32-550"),
'Backup Operators':addInfoGroup('Backup Operators',
'551',
'Domain Members can bypass \
file security to back up files',
'551',
'5',
"S-1-5-32-551"),
'Replicators':addInfoGroup('Replicators',
'552',
'Domain Supports file replication \
in a sambaDomainName',
'552',
'5',
"S-1-5-32-552"),
}
# Статические пользователи
staticUsers = {\
'client':addInfoUser('client',
'900',
'900',
'Client samba user'),
'admin':addInfoUser('admin',
'901',
'544',
'Admin samba user')}
def getUserUidAndGid(self, userName, groupName=""):
"""Находит в системе uid и gid пользователя
userName - имя пользователя и имя группы пользователя
"""
if not groupName:
groupName = userName
import pwd
try:
uid = pwd.getpwnam(userName)[2]
except:
self.printERROR(_("Can not found user %s in this system")%userName)
return ()
try:
import grp
gid = grp.getgrnam(groupName)[2]
except:
self.printERROR(_("Can not found user %s in this system")%groupName)
return ()
return (uid, gid)
def __restoreDelUser(self,userName,service,srcDir,message,unixObj=False):
"""Возвращаем данные удаленного пользователя"""
# Ищем Unix пользователя
searchUnixUser = self.servicesAPIObj.searchUnixUser(userName, unixObj)
# id пользователя
strUid = ""
if searchUnixUser:
strUid = searchUnixUser[0][0][1]['uidNumber'][0]
else:
resPasswd = self.servicesAPIObj.searchPasswdUser(userName, unixObj)
if resPasswd:
strUid = resPasswd.split(":")[2]
if strUid:
delBackDir =\
os.path.join(self.clVars.Get("sr_deleted_path"),
"%s-%s"%(userName,strUid),
service)
if strUid and os.path.exists(delBackDir) and os.listdir(delBackDir):
if message == None or type(message) == types.BooleanType:
dialogRes = message
else:
dialogRes = dialogYesNo(message)
if dialogRes and dialogRes == True:
try:
copyDir(srcDir, delBackDir)
except:
self.printERROR(_("Not restore user data in dir %s")\
%srcDir)
return False
self.printSUCCESS(_("Restore user data in dir %s")\
%srcDir)
return "Yes", delBackDir
elif dialogRes == False:
return "No", delBackDir
elif dialogRes == None:
return "Cancel", delBackDir
return True
def restorePathDelUser(self,userName,destDir,relDir,message,unixObj=False):
"""Восстанавливает директорию удаленного пользователя"""
removedDir = False
flagError = False
resRestore = self.__restoreDelUser(userName, relDir,