diff --git a/README b/README index 5b3f079..c1df372 100644 --- a/README +++ b/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 diff --git a/pym/cl_libserver.py b/pym/cl_libserver.py index fc99ebb..58eb4cf 100644 --- a/pym/cl_libserver.py +++ b/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) diff --git a/pym/cl_template.py b/pym/cl_template.py index e1d9ac6..83fc1af 100644 --- a/pym/cl_template.py +++ b/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) diff --git a/pym/cl_utils.py b/pym/cl_utils.py index 4e69b58..d2fd486 100644 --- a/pym/cl_utils.py +++ b/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) \ No newline at end of file diff --git a/pym/server/__init__.py b/pym/server/__init__.py new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/pym/server/__init__.py @@ -0,0 +1 @@ + diff --git a/pym/server/ldap.py b/pym/server/ldap.py new file mode 100644 index 0000000..89495e5 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/pym/server/services.py b/pym/server/services.py new file mode 100644 index 0000000..62bd022 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/pym/server/share.py b/pym/server/share.py new file mode 100644 index 0000000..b487c66 --- /dev/null +++ b/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) + \ No newline at end of file diff --git a/pym/server/users.py b/pym/server/users.py new file mode 100644 index 0000000..e815eb1 --- /dev/null +++ b/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, + destDir, message, unixObj) + # Если ошибка то выходим + if not resRestore: + flagError = True + # Флаг создания директории профиля пользователя + createDir = destDir + term = "" + if resRestore == True: + term = message + if not flagError and type(resRestore) == types.TupleType: + # Если cansel + if resRestore[0] == "Cancel": + # Удаляем пользователя + flagError = True + term = None + # Если No + elif resRestore[0] == "No": + if not removeDir(resRestore[1]): + flagError = True + if not flagError: + removedDir = resRestore[1] + term = False + elif resRestore[0] == "Yes": + createDir = False + removedDir = resRestore[1] + term = True + if flagError or term == "": + return False + else: + return (term, createDir, removedDir) + + + def backupDelUser(self, userName, service, srcDir, unixObj=False): + """Сохраняем данные удаляемого пользователя""" + # Ищем Unix пользователя + searchUnixUser = self.servicesAPIObj.searchUnixUser(userName, unixObj) + # id пользователя + strUid = "" + if searchUnixUser: + strUid = searchUnixUser[0][0][1]['uidNumber'][0] + if strUid: + delBackDir =\ + os.path.join(self.clVars.Get("sr_deleted_path"), + "%s-%s"%(userName,strUid), + service) + if os.path.exists(delBackDir) and os.listdir(delBackDir): + self.printERROR(_("Found deleted user data dir %s")\ + %delBackDir) + self.printERROR(_("Not created deleted user data dir %s")\ + %delBackDir) + return False + else: + delBackDir =\ + os.path.join(self.clVars.Get("sr_deleted_path"), + "%s"%(userName), + service) + i = 0 + while os.path.exists(delBackDir): + i += 1 + delBackDir =\ + os.path.join(self.clVars.Get("sr_deleted_path"), + "%s_%s"%(userName,i), + service) + # Cоздаем директорию хранения удаленных пользователей + if not os.path.exists(self.clVars.Get("sr_deleted_path")): + os.makedirs(self.clVars.Get("sr_deleted_path")) + #Делаем сохранение директории + try: + copyDir(delBackDir,srcDir) + except: + self.printERROR(_("Can not copy deleted user data in dir %s")\ + %delBackDir) + return False + self.printSUCCESS(_("Created deleted user data dir %s")\ + %delBackDir) + return True + + def removeEmptyDir(self, rmDir): + """Удаление пустых директорий""" + if not os.path.exists(rmDir): + self.printERROR(_("Not found remove dir %s") %rmDir) + return False + rDir = rmDir + while os.listdir(rDir) == []: + os.rmdir(rDir) + rDir = os.path.split(rDir)[0] + if rDir == "/": + break + return True + + def createUserDir(self, uid, gid, userDir, mode=0700): + """Создание пользовательской директории""" + if not os.path.exists(userDir): + os.makedirs(userDir) + if mode: + os.chmod(userDir,mode) + os.chown(userDir,uid,gid) + return True + else: + self.printERROR(_("Path %s exists") %userDir) + return False + + def createUserFile(self, fileName, fileTxt, uid, gid, mode=0644): + """Создает пользовательский файл с содержимым + + Если директория файла не существует то ошибка + """ + userDir = os.path.split(fileName)[0] + if not os.path.exists(userDir): + self.printERROR(_("Path %s not exists") %userDir) + return False + fd = os.open(fileName, os.O_CREAT) + os.close(fd) + os.chmod(fileName, mode) + os.chown(fileName,uid,gid) + if fileTxt: + FD = open(fileName, "r+") + FD.write(fileTxt) + FD.close() + return True + + def getUserAllowNetwork(self, strPrompt, strNetAllow): + """Получаем от пользователя доверительные сети + + вывод - список доверительных сетей + """ + def printW(): + print _("Incorrect string allow networks") + print _("Example - allow networks: 10.0.0.0/24 10.0.10.0/24") + print _("Try again\n") + strNet = rawInput(strPrompt, strNetAllow) + i = 0 + while i<3 and not isCorrectStringNet(strNet): + printW() + strNet = rawInput(strPrompt, strNet) + i +=1 + if i == 3 and not isCorrectStringNet(strNet): + printW() + self.printERROR(_("You used four attempts, \ +if you want to continue to run the program again")) + return False + return isCorrectStringNet(strNet) + + def getUserPassword(self, options, optDialog, optStdIn, pwDialog=False): + """Получить пароль у пользователя + + options - полученные опции командной строки + optDialog - опция командной строки для вывода диалога для получения + пароля + optStdIn - опция командной строки для получения пароля из + стандартного ввода (stdin) + pwDialog - структура для вывода приглашения в режиме диалога + """ + userPwd = "" + if optStdIn and options.has_key(optStdIn): + pwdA = sys.stdin.readline().rstrip() + pwdB = sys.stdin.readline().rstrip() + elif optDialog and options.has_key(optDialog): + if not pwDialog: + pwDialog = [_("New password"), + _("Retype new password")] + pwdA = getpass.getpass(pwDialog[0]+":") + pwdB = getpass.getpass(pwDialog[1]+":") + if (optStdIn and options.has_key(optStdIn)) or\ + (optDialog and options.has_key(optDialog)): + if not pwdA or not (pwdA == pwdB): + self.printERROR (_("ERROR") + ": " +\ + _("password incorrect")+ ": " + _("try again")) + return False + userPwd = pwdA + return userPwd \ No newline at end of file diff --git a/pym/server/utils.py b/pym/server/utils.py new file mode 100644 index 0000000..f7ef9b1 --- /dev/null +++ b/pym/server/utils.py @@ -0,0 +1,383 @@ +#-*- 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 re +import stat +# Вывод в строку ввода +import readline +# Файловый объект из строки +import cStringIO +import termios +# Для ввода символа +import tty +# Работа со временем +import time +from cl_utils import scan, getModeFile, getpathenv, runOsCommand + +"""общие функции серверных программ""" + +def unicList(listEl): + """Уникальный список с сохранением порядка""" + retList = [] + [not x in retList and retList.append(x) for x in listEl] + return retList + + +def dialogYn(message): + """Вывод сообщения, ожидание нажатия Y или n + + если Y - True если n - False""" + def getChar(): + fd = sys.stdin.fileno() + oldSet = termios.tcgetattr(fd) + tty.setraw(fd) + char = sys.stdin.read(1) + termios.tcsetattr(fd, termios.TCSADRAIN, oldSet) + return char + def term(char): + if ord(char) == 3: + return None + if char == "Y": + return True + elif char == "n": + return False + else: + char = getChar() + return term(char) + sys.stdout.write(message + ":") + char = getChar() + res = term(char) + sys.stdout.write("\n") + return res + + +def dialogYesNo(message): + """Вывод сообщения, ожидание набора Yes или No (в любом регистре) + + если Yes - True, если No - False""" + sys.stdout.write(message + ": ") + strIn=sys.stdin.readline().lower().strip() + sys.stdout.write("\n") + if "yes" == strIn: + return True + elif "no" == strIn: + return False + else: + return self.dialogYesNo(message) + + +def chownR(directory, uid, gid): + """изменяет владельца и группу + + для всех файлов и директорий внутри directory + """ + fileObj = scan() + scanObjs = fileObj.scanDirs([directory]) + # меняем владельца домашней директории + os.chown(directory, uid,gid) + # Меняем владельца директорий + for dirCh in scanObjs[0].dirs: + os.chown(dirCh, uid,gid) + # Меняем владельца файлов + for fileCh in scanObjs[0].files: + os.chown(fileCh, uid,gid) + # Меняем владельца ссылок + for linkCh in scanObjs[0].links: + os.lchown(linkCh[1], uid, gid) + return True + +def copyDir(destDir, srcDir): + """Копируем директорию srcDir в destDir + + При копировании сохраняются владелец, группа, права + """ + if os.path.exists(destDir) and not os.listdir(destDir): + os.rmdir(destDir) + if not os.path.exists(destDir): + # Создаем директорию + os.makedirs(destDir) + # Файловый объект + fileObj = scan() + # Сканируем директорию + scanObjs = fileObj.scanDirs([srcDir]) + if not scanObjs: + return True + for dirSrc in scanObjs[0].dirs: + #создаем в домашней директории директории из srcDir + dirName = destDir + dirSrc.split(srcDir)[1] + os.mkdir(dirName) + mode,uid,gid = getModeFile(dirSrc) + os.chown(dirName, uid,gid) + os.chmod(destDir, mode) + # размер файлового буфера в байтах (5Mb) + sizeBuffer = 5242880 + for fileCopy in scanObjs[0].files: + oldFile = destDir + fileCopy.split(srcDir)[1] + #копируем файлы + # Открываем файл - источник + fd = os.open(fileCopy, os.O_RDONLY) + fst = os.fstat(fd) + uid = fst.st_uid + gid = fst.st_gid + mode = stat.S_IMODE(fst.st_mode) + size = fst.st_size + # Открываем файл приемник + fdCr = os.open(oldFile, os.O_CREAT|os.O_WRONLY, mode) + if size: + # Количество циклов копирования + numbCycles = size/sizeBuffer + # Размер остатка файла после выполения циклов копирования + sizeTailFile = size - numbCycles * sizeBuffer + for i in xrange(numbCycles): + # Читаем порцию данных + buff = os.read(fd, sizeBuffer) + # Записываем порцию данных + os.write(fdCr, buff) + # Если есть остаток файла записываем его + if sizeTailFile: + # Читаем остаток файла + buff = os.read(fd, sizeTailFile) + # Записываем остаток файла + os.write(fdCr, buff) + # Закрываем файлы + os.close(fd) + os.close(fdCr) + # Меняем владельца + os.chown(oldFile, uid, gid) + #os.chmod(oldFile, mode) + for linkCreate in scanObjs[0].links: + #копируем ссылки + dst = destDir + linkCreate[1].split(srcDir)[1] + srcDestList = linkCreate[0].split(srcDir) + if len(srcDestList)>1: + src = destDir + srcDestList[1] + else: + src = linkCreate[0] + os.symlink(src,dst) + if os.path.exists(dst): + mode,uid,gid = getModeFile(dst) + #Изменение прав на ссылки + os.lchown(dst, uid, gid) + # Удаляем сокеты + for rmSocket in scanObjs[0].sockets: + os.remove(rmSocket) + mode,uid,gid = getModeFile(srcDir) + os.chmod(destDir, mode) + os.chown(destDir, uid,gid) + return True + +def rawInput(promptText="", inputText=""): + """Создает поле ввода + + promptText - текст перед полем ввода + inputText - текст в поле ввода + """ + if inputText: + # Записываем текст для последующего вывода в строке ввода + readline.set_pre_input_hook(lambda:\ + readline.insert_text(inputText) or\ + readline.redisplay()) + strInput = "" + if promptText: + # Получаем текст введенный пользователем + strInput = raw_input(promptText) + else: + strInput = raw_input() + if inputText: + # Сбрасываем строку ввода + readline.set_pre_input_hook(None) + return strInput + +def execProg(cmdStrProg, inStr=False, retFull=True, envProg={}): + """Выполняет внешнюю программу + + Параметры: + cmdStrProg внешняя программа + inStr данные передаваемые программе на страндартный вход. + Возвращаемые параметры: + строка которую выведет внешняя программа или False в случае ошибки + """ + env_path = {"PATH":getpathenv()} + env = {} + env.update(os.environ.items() + env_path.items() + envProg.items()) + retCode,programOut = runOsCommand(cmdStrProg,inStr,retFull,env) + if not retCode: + return programOut + return False + +def genSleep(timeSleep=(0.2, 0.4, 0.8)): + """Генератор задержек""" + for t in timeSleep: + time.sleep(t) + yield(t) + +def stringIsJpeg(string): + """Определяет является ли строка - jpeg изображением""" + if len(string)<8: + return False + FD = cStringIO.StringIO(string) + isJpeg = False + FD.seek(0, 0) + (firstByte, secondByte) = FD.read(2) + if (ord(firstByte) == 0xff and ord(secondByte) == 0xd8): + (firstByte, secondByte) = FD.read(2) + if (ord(firstByte) == 0xff and ord(secondByte) == 0xe0): + isJpeg = True + return isJpeg + +def isCorrectStringNet(strNetworks, checkNet=True): + """Проверяет на корректность строку доверительных сетей + + Выводит cписок сетей + """ + splNet = strNetworks.replace(","," ").split(" ") + if checkNet: + checkIP = False + res=re.compile("^\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?\/\d\d?$") + else: + checkIP = True + res=re.compile("^\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?$") + flagError = False + networks = [] + for i in splNet: + r = i.strip() + if not r: + continue + find =res.search(r) + if not find: + flagError = True + break + else: + splIP = map(lambda x: 255>=int(x.split("/")[0]) and\ + x.split("/")[0], find.group().split(".")) + if not splIP[0] or splIP[0] and int(splIP[0]) == 0: + flagError = True + break + if not splIP[3] or splIP[3] and int(splIP[3]) == 255: + flagError = True + break + if checkNet: + netList = r.split("/") + if len(netList)==2: + try: + netMaskInt = int(netList[1]) + except: + flagError = True + break + if netMaskInt>31 or netMaskInt<4: + flagError = True + break + else: + flagError = True + break + if checkIP and splIP[3] and int(splIP[3]) == 0: + flagError = True + break + for t in splIP: + if t == False: + flagError = True + break + if flagError: + break + networks.append(r) + if flagError: + return False + else: + return list(set(networks)) + +def searchLineInFile(name, fileName, numEl=0): + """Ищет строку в которой есть название разделенное ':' + + в файле похожем на /etc/passwd""" + if os.path.exists(fileName): + FD = open(fileName) + lines = FD.readlines() + FD.close() + lineFound = "" + for line in lines: + if name == line.split(":")[numEl]: + lineFound = line + break + if lineFound: + return lineFound + else: + return False + +def getMaxInFile(fileName, numMin, numMax, numEl=2): + """Получаем максимальный номер из файла похожего на /etc/group""" + lst = [] + lst.append(0) + if os.path.exists(fileName): + FD = open(fileName) + lines = FD.readlines() + FD.close() + for line in lines: + if not ':' in line: + continue + num = int(line.split(":")[numEl]) + if num<=numMax and num>=numMin: + lst.append(num) + return max(lst) + return False + +def chortToFullName(listNames, domain): + """Из списка коротких имен получаем cписок полных имен + + К коротким именам добавляем домен, длинные выдаем как есть + """ + listFillNames = [] + for name in listNames: + if "." in name: + listFillNames.append(name) + else: + listFillNames.append("%s.%s" %(name,domain)) + return listFillNames + +def addInfoUser(name, uid, gid, comment): + """Добавляем информацию о пользователе""" + class user(): + """Информация о пользователе""" + name = "" + uid = "" + gid = "" + comment = "" + us = user() + us.name = name + us.uid = uid + us.gid = gid + us.comment = comment + return us + +def addInfoGroup(name, gid, comment, rid="", type="", sid=""): + """Добавляем информацию о группе""" + class group(): + """Информация о группе""" + name = "" + gid = "" + comment = "" + rid = "" + type = "" + sid = "" + gr = group() + gr.name = name + gr.gid = gid + gr.comment = comment + gr.rid = rid + gr.type = type + gr.sid = sid + return gr \ No newline at end of file