#-*- 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 re import sys import cl_base import cl_profile import cl_utils2 import cl_utils import ldap import types import getpass import _cl_keys import time import stat import subprocess import time from encrypt import encrypt Version = "calculate-client 2.1.10" tr = cl_base.lang() tr.setLanguage(sys.modules[__name__]) pcs = cl_utils.prettyColumnStr class printNoColor: def colorPrint(self,attr,fg,bg,string): sys.stdout.write(string) class ProgressBar: suffixSet = 'org.freedesktop.DBus.Properties.Set \ org.kde.kdialog.ProgressDialog' execenv = {"HOME":"/root"} max = 100 kdialog = None label = None def __init__(self,title,kdialog=None): self.title = title if kdialog == None: self.openDialog(self.title) def openDialog(self,title,max=None): if max != None: self.max = max self.title = title if os.system('which kdialog >/dev/null') == 0: self.label ="LOGINKDIALOG=%d" % os.getpid() env = {} env.update(os.environ.items() + self.execenv.items() +\ [tuple(self.label.split("="))]) pipe = subprocess.Popen('/usr/bin/kdialog --progressbar "%s" %d'\ %(" "*(len(title)+20), self.max), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, env=env, shell=True) pipe.stdin.close() if pipe.poll() == None: # ожидание в 5 сек for t in range(500): time.sleep(0.01) if pipe.poll() != None: break if pipe.poll() == 0: self.kdialog = pipe.stdout.readline().strip() while not "org.kde.kdialog" in self.kdialog: s = pipe.stdout.readline() if s == "": pipe.stdout.close() pipe.stderr.close() self.shutdownDialog() break self.kdialog = s.strip() self.setTitle(self.title) pipe.stdout.close() pipe.stderr.close() else: pipe.stdout.close() pipe.stderr.close() self.shutdownDialog() def shutdownDialog(self): '''Принудительно уничтожить процесс kdialog''' self.kdialog = None pipe = subprocess.Popen("/bin/ps axeo pid,cmd", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True) if self.label != None: for s in pipe.stdout.readlines(): if self.label in s: try: os.kill( int(s.split()[0]), 9 ) except (OSError,ValueError): pass self.label = None def setValue(self,value): '''Установить текущее значения для прогресса''' if self.kdialog and value <= self.max: env = "" if self.execenv: env = " ".join(map(lambda x: '%s="%s"'%(x[0],x[1]),\ self.execenv)) + " " os.system(env + '/usr/bin/qdbus %s %s value %d >/dev/null'\ %(self.kdialog,self.suffixSet, value)) def setMaximum(self,max): '''Установить максимальное значения для прогресса''' self.max = max if self.kdialog: env = "" if self.execenv: env = " ".join(map(lambda x: '%s="%s"'%(x[0],x[1]),\ self.execenv)) + " " os.system(env + '/usr/bin/qdbus %s %s maximum %d >/dev/null'\ %(self.kdialog,self.suffixSet, self.max)) def setTitle(self,title): '''Установить описания прогресса''' self.title = title if self.kdialog: env = "" if self.execenv: env = " ".join(map(lambda x: '%s="%s"'%(x[0],x[1]),\ self.execenv)) + " " os.system(env + '/usr/bin/qdbus %s setLabelText "%s" >/dev/null'\ %(self.kdialog,self.title)) def close(self): '''Закрыть прогресс''' self.shutdownDialog() class ProgressProfile(cl_profile.profile): def __init__(self, vars): cl_profile.profile.__init__(self,vars) self.progress = ProgressBar(_("Setting up user profile") + " ...") def numberAllProfiles(self, number): self.progress.setMaximum(number) return True def numberProcessProfiles(self,number): self.progress.setValue(number) return True class RsyncProgressBar(ProgressBar): '''Объект запуска rsync для получения количества созданных файлов и при необходимости вывода progressbar ''' # получение номера передаваемого файла из инф потока rsync senderre = re.compile("\[sender\] i=(\d+) ", re.S) # получение номера получаемого файла из потока rsync receiverre = re.compile("recv_generator\(.+,([0-9]+)\)", re.S) pipe = None maximum = 1 copyStarting = False def __init__(self, title, secondtitle, rsyncstr, maximum=1): self.title = title self.secondtitle = secondtitle self.maximum = maximum self.rsyncstr = rsyncstr def getFilesNum(self): '''Получить количество файлов созданных генератором''' if self.pipe: return self.value def getExitCode(self): '''Получить код выхода rsync''' if self.pipe: return os.WEXITSTATUS(self.pipe.wait()) def runsilent(self): '''Запустить rsync без progressbar''' self.pipe = subprocess.Popen(self.rsyncstr, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True) while True: s = self.pipe.stdout.readline() if len(s) == 0: break q = self.receiverre.search(s) if q: self.value = int(q.groups()[0]) def run(self): '''Запустить rsync с progressbar''' self.openDialog(self.title,0) self.pipe = subprocess.Popen(self.rsyncstr, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True) oldpercent = 0 while True: s = self.pipe.stdout.readline() if len(s) == 0: break q = self.senderre.search(s) if q: maximum = int(q.groups()[0]) if self.maximum < maximum: self.maximum = maximum continue q = self.receiverre.search(s) if q: if not self.copyStarting: self.shutdownDialog() self.max = 100 self.openDialog(self.secondtitle) self.copyStarting = True self.value = int(q.groups()[0]) newpercent = self.value * 100 / self.maximum if oldpercent < newpercent: oldpercent = newpercent self.setValue(oldpercent) # Импортированные классы в cl_ldap # Запись ошибок imp_cl_err = cl_profile._error # Работа с XML imp_cl_xml = cl_profile.xmlShare # Обработка параметров командной строки imp_cl_help = cl_utils2.cl_help # Форматированный вывод imp_cl_smcon = cl_utils2.cl_smartcon class cl_client(imp_cl_err, imp_cl_xml, imp_cl_help, imp_cl_smcon, encrypt): """Основной класс для работы клиентских приложений""" # Пути к профилям объединяемых с системными # относительный путь при объединении '/' rootProfilePaths=['/usr/lib/calculate/calculate-client/profiles', '/var/calculate/remote/profiles', '/var/calculate/profiles'] def __init__(self, cmdName): # объект для форматированного вывода imp_cl_help.__init__(self, cmdName) servName = "" if "user" in cmdName: servName = _("user") self.chapter = [\ # расположение разделов на странице # имя раздела, видимый или невидимый, кол. "\n" после # названия раздела, кол. "\n" после раздела # тип раздела ("Copyright",False,0,2,""), (_("Usage"),True,0,1,""), ("Function",False,0,2,""), (_("Examples"),True,1,1,""), (_("Common options"),True,1,0,"options"), ] # имена используемых программ и их номера для доступа к переменным # self.data self.progName = { 'cl-client':0, 'cl-createhome':1, 'cl-sync':2, 'cl-passwd':3 } # Cвязь длинных опций помощи и выводимых разделов помощи с опциями self.relOptions = {"h":[_("Common options")],} # список разделов, которые на наличие в ней информации # используется для автоматического отображения/скрытия # опций help-имя # Пример: self.relOption = # { "help-all":[_("Common options"], _("Unix service options"), # _("Samba service options"), _("LDAP service options")}] # self.relChapterPass = (_("Common options"),) # это означается что опция будет активна, если только в разделах # кроме Common options есть хоть одна доступная опция. self.relChapterPass = (_("Common options"),) self.data = [\ { #Copyright #'progAccess':(3,), 'helpChapter':"Copyright", 'help':Version }, #Usage { 'progAccess':(0,), 'helpChapter':_("Usage"), 'help': cmdName + " [" + _("options") + "] " + _("domain") }, { 'progAccess':(1,), 'helpChapter':_("Usage"), 'help': cmdName + " " + _("user") }, { 'progAccess':(2,), 'helpChapter':_("Usage"), 'help': cmdName + " [" + _("options") + "] " + _("user") }, { 'progAccess':(3,), 'helpChapter':_("Usage"), 'help': cmdName }, # Function { 'progAccess':(0,), 'helpChapter':"Function", 'help':_("Changes settings for connecting to domain \ (calculate-server)") }, { 'progAccess':(1,), 'helpChapter':"Function", 'help':_("Create home directory for the new user account") }, { 'progAccess':(2,), 'helpChapter':"Function", 'help':_("Mounting resources and synchronize the user preferences") }, { 'progAccess':(3,), 'helpChapter':"Function", 'help':_("Change user password") }, # Examples { 'progAccess':(0,), 'helpChapter':_("Examples"), 'help':pcs( " cl-client 192.168.0.1", self.column_width, "# " + _("Adds settings for connecting to domain") + \ " (ip 192.168.0.1)", self.consolewidth-self.column_width ) }, { 'progAccess':(3,), 'helpChapter':_("Examples"), 'help':pcs(" cl-passwd", self.column_width, "# "+_("change password of user for Samba and Unix services")+".", self.consolewidth-self.column_width) }, # Options {'shortOption':"h", 'longOption':"help", 'helpChapter':_("Common options"), 'help':_("display this help and exit") }, {'progAccess':(0,), 'shortOption':"r", 'helpChapter':_("Common options"), 'help':_("remove the settings for connecting to a domain") }, {'progAccess':(2,), 'longOption':'progress', 'helpChapter':_("Common options"), 'help':_("show progress bar for kde startup (works only with options \ --login)") }, {'progAccess':(1,), 'longOption':'progress', 'helpChapter':_("Common options"), 'help':_("show progress bar for kde startup") }, {'progAccess':(0,1,2,3), 'longOption':"vars", 'optVal':_("TYPE_VAR"), 'helpChapter':_("Common options"), 'help':_("print variables (TYPE_VAR - all:full var)") }, {'progAccess':(0,1,2,3), 'longOption':"color", 'optVal':_("WHEN"), 'helpChapter':_("Common options"), 'help':_("control whether color is used. \ WHEN may be 'never', 'always', or 'auto'") }, {'progAccess':(1,), 'shortOption':"f", 'longOption':"force", 'helpChapter':_("Common options"), 'help':_("always join the user profiles and preferences") }, {'progAccess':(0,), 'longOption':"install", 'helpChapter':_("Common options"), 'help':_("add use of scripts this package for window manager") }, {'progAccess':(0,), 'longOption':"uninstall", 'helpChapter':_("Common options"), 'help':_("remove use of scripts this package for window manager and, \ if necessary, removes from domain") }, {'progAccess':(0,), 'longOption':"mount", 'helpChapter':_("Common options"), 'help':_("mount [remote] resource for Samba (calculate-server)") }, {'progAccess':(2,), 'longOption':"login", 'helpChapter':_("Common options"), 'help':_("mount user resource")+ " " +\ _("and synchronize the user preferences") }, {'progAccess':(2,), 'longOption':"logout", 'helpChapter':_("Common options"), 'help':_("synchronize the user preferences") + " " +\ _("and umount user resource") }, {'progAccess':(2,), 'longOption':"nosync", 'helpChapter':_("Common options"), 'help':_("not synchronize the user preferences, is used in \ conjunction with the 'login' or 'logout'") } ] self._cl_help__setParamHelp() # Удаляем ненужный аттрибут класса cl_profile.xmlShare self._createElement = False delattr(self, "_createElement") # Переменная объект Vars self.clVars = False # Переменная объект ldapFunction self.ldapObj = False # Переменная соединение с LDAP сервером self.conLdap = False # Базовый DN LDAP сервера self.baseDN = False # DN сервисов относительно базового ServicesDN = "ou=Services" relGrDN = 'ou=Groups' relUsDN = 'ou=Users' relServDN = 'ou=Unix' relDN = self.addDN(relServDN,ServicesDN) # DN пользователей, относительно базового DN self.relUsersDN = self.addDN(relUsDN, relDN) # DN групп, относительно базового DN self.relGroupsDN = self.addDN(relGrDN, relDN) # DN Samba групп self.relSambaGroupsDN = self.addDN(relGrDN,"ou=Samba", ServicesDN) # Объект хранения переменных self.clVars = False # файл с дополнительной информацией о профиле # на данный момент только количество файлов для rsync # используемое для прогрессбара self.configFile = ".calculate.ini" # При включениии репликации # Временные задержки для монтирования в секундах self.sleeps = [0.5, 2, 5] # Название ветки хранения последнего посещенного пользователя self.replBranchName = "Worked" # Аттрибут ветки хранения последнего посещенного пользователя self.replAttr = "ou" # DN системной ветки self.sysDN = self.addDN("ou=Replication","ou=LDAP", ServicesDN) replBranch = self.replAttr + "=" + self.replBranchName # DN хранения последнего посещенного пользователя self.replHostsDN = self.addDN(replBranch,self.sysDN) # Файл репликации self.replLogoutFile = ".logout" self.skipHomeFile = ["Home","Disks","FTP", self.replLogoutFile] # Если атрибут установлен то значит (ошибка и отмонтируются # пользовательские ресурсы) self.errorAndUnmountUserRes = False # Имя пользователя self.userName = "" def getUserPwd(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 def exit(self, exitCode): """Метод выхода при ошибке""" self.errorExit() sys.exit(exitCode) def errorExit(self): """Отмонтирование пользовательских ресурсов при ошибке""" # TODO: добавить удаление приватных файлов if self.errorAndUnmountUserRes and self.userName: self.umountUserResNoSync(self.userName, False, False, False, True) def __del__(self): """Выполняется при удалении объекта""" self.errorExit() def isRoot(self, printError=True): """Определяет является ли пользователь root""" if os.getuid() == 0 and os.getgid() == 0: return True else: if printError: self.printERROR(_("The user is not root")) return False def createClVars(self, clVars=False): """Создает объект Vars""" if not clVars: clVars = cl_base.DataVars() clVars.flClient() clVars.flIniFile() # Устанавливаем у объекта объект Vars self.clVars = clVars return True def addDN(self, *arg): """Складывает текстовые элементы DN""" DNs = [] for dn in arg: if dn: DNs.append(dn) return ','.join(DNs) def searchLdapDN(self, name, relDN, attr, retAttr=None): """Находит DN в LDAP""" baseDN = self.clVars.Get("ld_base_dn") DN = self.addDN(relDN,baseDN) #searchScope = ldap.SCOPE_SUBTREE searchScope = ldap.SCOPE_ONELEVEL searchFilter = "%s=%s" %(attr,name) retrieveAttributes = retAttr resSearch = self.ldapObj.ldapSearch(DN, searchScope, searchFilter, retrieveAttributes) return resSearch def searchPrevHost(self, userName): """Находит сервер к которому был подключен пользователь""" # Короткое имя системы osLinuxShort = self.clVars.Get("os_linux_shortname") # Имя для поиска в служебной ветке репликации userLogin = "%s@%s"%(userName,osLinuxShort) resSearch = self.searchLdapDN(userLogin, self.replHostsDN, "uid") return resSearch def searchReplBranch(self): """Находит ветку создаваемую при включении репликации В ветке находятся ((имя пользователя, имя системы, хост) ....) """ resSearch = self.searchLdapDN(self.replBranchName, self.sysDN, self.replAttr) return resSearch def searchUnixUser(self, userName): """Находит пользователя сервиса Unix""" resSearch = self.searchLdapDN(userName, self.relUsersDN, "uid") return resSearch def searchUnixGid(self, groupId): """Находит группу сервиса Unix по ёе id в ветке Unix""" resSearch = self.searchLdapDN(str(groupId), self.relGroupsDN, "gidNumber") return resSearch def searchSambaGid(self, groupId): """Находит группу сервиса Unix по ёе id в ветке Samba""" resSearch = self.searchLdapDN(str(groupId), self.relSambaGroupsDN, "gidNumber") return resSearch def getLdapObjBind(self, host, printError=True): """Получаем объект ldapFunction Соединяемся пользователем bind В выходном объекте есть соединение с LDAP сервером: self.conLdap """ self.createClVars(self.clVars) bindDn = self.clVars.Get("ld_bind_dn") bindPw = self.clVars.Get("ld_bind_pw") if not (bindDn or bindPw): if printError: self.printERROR(_("Not found LDAP bind DN or password") +\ " ...") return False ldapObj = cl_utils2.ldapFun(bindDn, bindPw, host) if ldapObj.getError(): if printError: self.printERROR (_("LDAP connect error") + ": " +\ ldapObj.getError().strip()) return False # Устанавливаем у объекта соединение и объект LDAP функций self.ldapObj = ldapObj self.conLdap = ldapObj.conLdap return True def searchLineInFile(self, 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 searchPasswdUser(self, userName): """Ищет пользователей в /etc/passwd""" filePasswd = "/etc/passwd" return self.searchLineInFile(userName, filePasswd) def searchGroupGid(self, groupId): """Ищет gid в /etc/group""" gid = str(groupId) fileGroup = "/etc/group" return self.searchLineInFile(gid, fileGroup, 2) def getUserLdapInfo(self, userName, printError=True): """Выдаем uid и gid пользователя""" searchUser = self.searchUnixUser(userName) if not searchUser: if printError: self.printERROR(_("User %s not found in Unix service")\ %str(userName)) return False uid = False gid = False fullName = "" mail = "" jid = "" group = "" if searchUser[0][0][1].has_key('uidNumber'): uid = searchUser[0][0][1]['uidNumber'][0] if searchUser[0][0][1].has_key('gidNumber'): gid = searchUser[0][0][1]['gidNumber'][0] searchGroup = self.searchUnixGid(gid) if searchGroup and searchGroup[0][0][1].has_key('cn'): group = searchGroup[0][0][1]['cn'][0] else: searchGroup = self.searchSambaGid(gid) if searchGroup and searchGroup[0][0][1].has_key('cn'): group = searchGroup[0][0][1]['cn'][0] if searchUser[0][0][1].has_key('cn'): fullName = searchUser[0][0][1]['cn'][0] if searchUser[0][0][1].has_key('mail'): mail = searchUser[0][0][1]['mail'][0] if searchUser[0][0][1].has_key('registeredAddress'): jid = searchUser[0][0][1]['registeredAddress'][0] if searchUser[0][0][1].has_key('homeDirectory'): home = searchUser[0][0][1]['homeDirectory'][0] if uid and gid: return (uid, gid, fullName, mail, jid ,home, group) else: return () 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 applyProfilesFromUser(self,progress=False): """Применяем профили для пользователя""" # Cоздаем объект профиль if progress: clProf = ProgressProfile(self.clVars) else: clProf = cl_profile.profile(self.clVars) # Объединяем профили dirsFiles = clProf.applyProfiles() if progress: clProf.progress.close() if clProf.getError(): self.printERROR(clProf.getError()) return False else: return dirsFiles def applyProfilesFromSystem(self): """Применяем профили для cистемы""" # Cоздаем объект профиль clProf = cl_profile.profile(self.clVars) # Объединяем профили dirsFiles = clProf.applyProfiles() if clProf.getError(): self.printERROR(clProf.getError()) return False else: return dirsFiles def checkUserPwdLDAP(self, server, userDN, password): """Проверка пароля Unix пользователя на сервере""" ldapInit = ldap.initialize("ldap://%s"%server) errMessage = "" try: ldapInit.bind_s(userDN, password) except ldap.INVALID_CREDENTIALS: errMessage = _("Your username or password is incorrect.") return False, errMessage except ldap.LDAPError, e: if type(e.message) == dict and e.message.has_key('desc'): errMessage = e.message['desc'] else: errMessage = e return False, errMessage return True, errMessage def getServerDataUser(self): """Получение имени LDAP сервера и DN пользователей под пользователем""" fileName = "/etc/ldap.conf" serverName = "" usersDN = "" strServer = ("host","HOST") lenStrServer = len(strServer[0]) strDN = ("nss_base_passwd","NSS_BASE_PASSWD") lenStrDN = len(strDN[0]) splList = (" ", "\t") try: for i in open(fileName): if not serverName and\ filter(lambda x: i.startswith(x),strServer) and\ len(i)>lenStrServer: spl = i[lenStrServer] if spl in splList: serverName = i.rpartition(spl)[2].strip() if not usersDN and filter(lambda x: i.startswith(x), strDN) and\ len(i)>lenStrDN: spl = i[lenStrDN] if spl in splList: usersDN = i.rpartition(spl)[2].partition('?')[0].strip() if serverName and usersDN: break except: self.printERROR(_("Can not open %s")%fileName) return False return (serverName, usersDN) def setUserPasswordToServer(self, options): """Установка пароля пользователя на сервере""" # Проверяем на root if self.isRoot(False): self.printERROR(_("The user is root")) self.printWARNING(\ _("The program can be executed from a non-root user")) return False # DNS имя хоста data = self.getServerDataUser() if not data: self.printERROR(_("The computer is not in domain")) self.printWARNING(_("Use passwd")) return False server, usersDN = data # Получаем старый пароль пользователя curPassword = self.getUserPassword(_("Enter current password")) if not curPassword: self.printERROR(_("Current password is empty")) return False userDN = self.addDN("uid=%s"%os.environ["USER"], usersDN) # Проверяем в LDAP сервере текущий пароль пользователя ret, err = self.checkUserPwdLDAP(server, userDN, curPassword) if not ret: self.printERROR(err) return False optPasswd = options if not options: optPasswd = {"p":""} password = self.getUserPwd(optPasswd, "p", False) if password == False: return False # ~/.calculate pathConfig = os.path.join(os.environ["HOME"],".calculate") # Создаем директорию if not os.path.exists(pathConfig): os.makedirs(pathConfig) # Переменные для записи в env файл varsConfig = {"unix_hash":self.getHashPasswd(password,"ssha"), "samba_lm_hash":self.getHashPasswd(password,"lm"), "samba_nt_hash":self.getHashPasswd(password,"nt")} if filter(lambda x: not x, varsConfig.values()): return False # ~/.calculate/server.env fileConfig = os.path.join(pathConfig,"server.env") txtConfig = cl_base.iniParser(fileConfig) if not txtConfig.setVar(["passwd","samba"], varsConfig): self.printERROR(_("Can not write password variable in file %s")\ %fileConfig) return False self.printOK(_("Changed password of user %s")%os.environ["USER"] + \ " ...") self.printWARNING(_("Password will be changed when you logout from the \ X session")) return True def getUserPassword(self, pwDialog=False): """Получить пароль у пользователя pwDialog - приглашение ввода пароля """ if not pwDialog: pwDialog = _("Password") userPwd = getpass.getpass(pwDialog+":") return userPwd def chownR(self, directory, uid, gid, dirsAndFiles=False): """изменяет владельца и группу для всех файлов и директорий внутри directory """ if dirsAndFiles: dirs, files = dirsAndFiles # меняем владельца домашней директории os.chown(directory, uid,gid) # Меняем владельца директорий for dirCh in dirs: if os.path.exists(dirCh): os.chown(dirCh, uid,gid) # Меняем владельца файлов for fileCh in files: if os.path.exists(fileCh): if os.path.islink(fileCh): os.lchown(fileCh, uid, gid) else: os.chown(fileCh, uid,gid) return True else: fileObj = cl_profile._file() 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 execProg(self, cmdStrProg, inStr=False, retFull=True, envProg={}): """Выполняет внешнюю программу Параметры: cmdStrProg внешняя программа inStr данные передаваемые программе на страндартный вход. Возвращаемые параметры: строка которую выведет внешняя программа или False в случае ошибки """ env_path = {"PATH":cl_utils.getpathenv()} env = {} env.update(os.environ.items() + env_path.items() + envProg.items()) retCode,programOut = cl_utils.runOsCommand(cmdStrProg,inStr,retFull,env) if not retCode: return programOut return False def getUserPasswdInfo(self, userName): """получаем uid и gid пользователя из /etc/passwd""" resPasswd = self.searchPasswdUser(userName) if resPasswd: uid = resPasswd.split(":")[2] gid = resPasswd.split(":")[3] fullName = resPasswd.split(":")[4] mail = "" group = "" jid = "" home = os.path.join("/home",userName) resGroup = self.searchGroupGid(gid) if resGroup: group = resGroup.split(":")[0] return (uid, gid, fullName, mail, jid, home, group) else: return False def setDaemonAutostart(self, daemon): """Прописывает демона в автозагрузку""" execStr = "rc-update add %s default" %daemon textLine = self.execProg(execStr) if textLine: return True else: self.printERROR(_("ERROR") + ": " + execStr) self.printERROR(_("Can not add at default runlevel")) return False def delDaemonAutostart(self, daemon): """Удаляет демона из автозагрузки""" execStr = "rc-update del %s default" %daemon textLine = self.execProg(execStr) if textLine: return True else: self.printERROR(_("ERROR") + ": " + execStr) self.printERROR(_("Can not delete from default runlevel")) return False def createHome(self, userName, applyAlways=False, progress=False): """Создание пользовательской директории с настройками для kde4""" # Имя пользователя self.userName = userName # Проверяем на root if not self.isRoot(): return False # Создаем объект переменных self.createClVars() uidGid = False # Подсоединяемся к серверу domain = self.clVars.Get("cl_remote_host") connectLdap = False if domain: if not self.getLdapObjBind(domain): return False connectLdap = True if connectLdap: # uid и gid и mail из Ldap uidGid = self.getUserLdapInfo(userName,False) if not domain: # uid и gid и mail из passwd uidGid = self.getUserPasswdInfo(userName) if not uidGid: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True self.printERROR(_("Not found user uid and gid")) return False uid = int(uidGid[0]) gid = int(uidGid[1]) fullName = uidGid[2] mail = uidGid[3] jid = uidGid[4] homeDir = uidGid[5] group = uidGid[6] if not group: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True self.printERROR(_("Not found user primary group (gid=%s)"%str(gid))) return False # Создаем пользовательскую директорию self.clVars.Set('cl_root_path',homeDir,True) homeExists = os.path.exists(homeDir) # Первый проход self.clVars.Set('cl_pass_step','first',True) if homeExists: self.printWARNING(_("Home dir %s exists")%homeDir) if set(os.listdir(homeDir))-set(self.skipHomeFile): if not applyAlways: # Второй и последующие проходы self.clVars.Set('cl_pass_step','next',True) self.printSUCCESS(_("Apply always profiles") + " ...") # Создаем домашнюю директорию if not os.path.exists(homeDir): self.createUserDir(uid, gid, homeDir) # Записываем переменные self.clVars.Set('ur_login', userName) self.clVars.Set('ur_fullname', fullName) self.clVars.Set('ur_mail', mail) self.clVars.Set('ur_jid', jid) self.clVars.Set('ur_group', group) # Применяем профили для пользователя dirsAndFiles = self.applyProfilesFromUser(progress) if not dirsAndFiles: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True self.printERROR(_("Can not apply user profile")) return False self.chownR(homeDir, uid, gid, dirsAndFiles) if not homeExists: self.printSUCCESS(_("Created home dir %s")%homeDir + " ...") self.printSUCCESS(_("User account is configured") + " ...") return True def isDomain(self): """Находится ли компьютер в домене""" self.createClVars(self.clVars) foundMountRemote =self.isMount("/var/calculate/remote" ,"cifs") foundMountHome =self.isMount("/var/calculate/client-home","none",False) if not (self.clVars.Get("cl_remote_host") and foundMountRemote and\ foundMountHome): self.printERROR(_("The computer is not in domain")) return False return (foundMountRemote,foundMountHome) def killRsync(self): """Убивает все процессы rsync и cl-sync --login""" listProcess = self.execProg("ps ax",False,False) if not listProcess: return False killPid = [] flagError = False for process in listProcess: if "rsync" in process: killPid.append(process.split(" ")[0]) if "--login" in process and "cl-sync" in process: killPid.append(process.split(" ")[0]) strPid = str(os.getpid()) # удалим свой pid из списка завершаемых процессов if strPid in killPid: killPid.remove(strPid) if killPid and " ".join(killPid).strip(): textLine = self.execProg("kill -9 %s" %" ".join(killPid)) if not (textLine == None): self.printERROR(_("Can not 'kill %s'")\ %" ".join(killPid)) flagError = True if flagError: return False else: return True def isMount(self, pathMount ,typeMount, secondPath=True): """Примонтирована ли директория""" path = os.path.realpath(pathMount) foundMount = False if secondPath: reFoundMount = re.compile("on\s+%s\s+type\s+%s"%(path,typeMount)) else: reFoundMount = re.compile("%s\s+.+type\s+%s"%(path,typeMount)) resMount = self.execProg("mount",False,False) if resMount and type(resMount) == types.ListType: for string in resMount: if reFoundMount.search(string): foundMount = string break return foundMount def relevanceProfiles(self, hostAuth): """Определяем актуальность наложенных профилей в зависимости от версии программы Перед запуском обязательно должен быть определен объект переменных self.clVars Если актуальны - True, если нет False """ # 'local' или имя хоста if hostAuth != self.clVars.Get("os_remote_auth"): return False clProf = cl_profile.profile(self.clVars) # Текущая версия программы currentVersion = self.clVars.Get("cl_ver") # Версия программы которая ранее работала с профилями previousVersion = self.clVars.Get("os_remote_client") cVersion, pVersion = clProf._convertVers(currentVersion,previousVersion) # Если версии программ не равны то профили не актуальные if cVersion != pVersion: return False return True def getPathProfiles(self, listPath): """Получаем список директорий хранения профилей""" """список накладываемых профилей при установке, наложении профилей""" profpath = [] for profPath in listPath: if os.path.isdir(profPath): paths = os.listdir(profPath) for path in paths: ph = os.path.join(profPath,path) filesAndDirs = os.listdir(ph) if os.path.isdir(ph) and filesAndDirs: profpath.append(ph) return profpath def applyRelevanceProfiles(self, hostAuth): """Накладывает релевантные профили Перед запуском обязательно должен быть определен объект переменных self.clVars """ if not self.relevanceProfiles(hostAuth): # Обнуляем переменную удаленный хост if hostAuth == "local": self.clVars.Set("cl_remote_host","",True) # Изменяем базовую директорию наложения профилей self.clVars.Set("cl_root_path","/",True) # Устанавливаем действие profiles_domain self.clVars.Set("cl_pass_type","domain",True) # Наложим профили profiles/domain # Новые пути к профилям profPaths = self.getPathProfiles(self.rootProfilePaths) if not profPaths: self.printERROR(_("Empty profile paths %s")\ %", "(self.rootProfilePaths)) return False # Изменяем переменную хранения профилей self.clVars.Set("cl_profile_path",profPaths,True) # Наложим профили dirsAndFiles = self.applyProfilesFromSystem() if not dirsAndFiles: self.printERROR(_("Can not apply 'profiles/domain' profiles")) return False if hostAuth == "local": self.printOK(_("Set profiles of local mode")) else: self.printOK(_("Set profiles of network mode")) currentVersion = self.clVars.Get("cl_ver") self.clVars.Write("os_remote_client", currentVersion) self.clVars.Write("os_remote_auth", hostAuth) return True def mountRemote(self): """Монтирование remote и домашней директории если компьютер в домене а так-же ввод в домен если найдено имя хоста и пароль для подключения """ # Проверяем на root if not self.isRoot(): return False self.createClVars(self.clVars) domain = self.clVars.Get("cl_remote_host") if domain: foundMountRemote = self.isMount("/var/calculate/remote" ,"cifs") foundMountHome = self.isMount("/var/calculate/client-home","none", False) else: self.printWARNING(_("This computer is not in domain")) # Если профили не актуальны накладываем новую версию профилей if not self.applyRelevanceProfiles("local"): return False return True pathHome = "/var/calculate/client-home" if foundMountRemote: self.printWARNING(_("Samba resource [%s] is mount")%"remote" + \ " ...") if foundMountHome: self.printWARNING(str(pathHome) + " " +_("is mount")+ " ...") if foundMountHome and foundMountRemote: # Накладываем сетевые профили if domain: self.clVars.flIniFile() if not self.applyRelevanceProfiles(domain): return False return True flagLocalProfile = False if not foundMountRemote: pathRemote = "/var/calculate/remote" pwdRemote = self.clVars.Get("cl_remote_pw") if not (domain and pwdRemote): self.printERROR(_("Not found variable")+\ ": cl_remote_pw ...") return False if not os.path.exists(pathRemote): os.makedirs(pathRemote) mountStr = "mount -t cifs -o user=client //%s/remote %s"\ %(domain,pathRemote) textLine = self.execProg(mountStr, None, True, {"PASSWD":pwdRemote}) if not (textLine == None): self.printWARNING(_("Can not mount Samba resource [%s]")%\ "remote" + " ...") flagLocalProfile = True # Если профили не актуальны накладываем новую версию профилей if not self.applyRelevanceProfiles("local"): return False if foundMountHome: umountStr = "umount /home" textLine = self.execProg(umountStr) if not (textLine == None): self.printERROR(_("Can not unmount") + " /home") return False return True self.printSUCCESS(_("Mount Samba resource [%s]") % "remote" +\ " ...") if (not foundMountHome) and (not flagLocalProfile): if not os.path.exists(pathHome): os.makedirs(pathHome) mountStr = "mount -o bind %s /home" %pathHome textLine = self.execProg(mountStr) if not (textLine == None): self.printERROR(_("Can not mount") + " " + str(pathHome) +\ " ...") return False self.printSUCCESS(_("Mount") + " " + str(pathHome) + " " +\ " ...") # Накладываем сетевые профили if domain: self.clVars.flIniFile() if not self.applyRelevanceProfiles(domain): return False self.restartDBus() return True def delDomain(self): """выводим из домена""" # Проверяем на root if not self.isRoot(): return False self.createClVars() pathRemote = "/var/calculate/remote" pathHome = "/var/calculate/client-home" foundMountRemote = self.isMount(pathRemote ,"cifs") foundMountHome = self.isMount(pathHome ,"none",False) domain = self.clVars.Get("cl_remote_host") if not domain: self.printWARNING(_("The computer is not in domain")) return True if foundMountRemote: textLineUmount = self.umountSleepPath(pathRemote) if not textLineUmount: return False if foundMountHome: textLineUmount = self.umountSleepPath(pathHome ,"none", False) if not textLineUmount: return False self.clVars.Delete("cl_remote_host","local") self.clVars.Delete("cl_remote_pw","local") self.clVars.Set("cl_remote_host","",True) self.clVars.Set("cl_remote_pw","",True) # Изменяем базовую директорию наложения профилей self.clVars.Set("cl_root_path","/",True) # Наложим профили profiles/domain # Новые пути к профилям # Устанавливаем действие profiles_domain self.clVars.Set("cl_pass_type","domain",True) # Новые пути к профилям profPaths = self.getPathProfiles(self.rootProfilePaths) if not profPaths: self.printERROR(_("Empty profile paths %s")\ %", "(self.rootProfilePaths)) return False # Изменяем переменную хранения профилей self.clVars.Set("cl_profile_path",profPaths,True) # Наложим профили dirsAndFiles = self.applyProfilesFromSystem() if not dirsAndFiles: self.printERROR(_("Can not apply 'profiles/domain' profiles")) return False if not self.delDaemonAutostart("client"): return False self.printOK(_("Computer removed from domain %s")%domain + " ...") return True def uninstallClient(self): """Наложение профиля клиента по умолчанию и при необходимости отключение от домена""" if not self.isRoot(): return False self.createClVars() clVars = self.clVars messageOk = _("Removed use of scripts this package for window \ manager") + " ..." # Устанавливаем переменную профиля clVars.Set("cl_pass_run","off",True) # Изменяем базовую директорию наложения профилей clVars.Set("cl_root_path","/",True) # Устанавливаем действие profiles_client clVars.Set("cl_pass_type","install",True) # Новые пути к профилям profPaths = self.getPathProfiles(self.rootProfilePaths) if not profPaths: self.printERROR(_("Empty profile paths %s")\ %", "(self.rootProfilePaths)) return False # Изменяем переменную хранения профилей clVars.Set("cl_profile_path",profPaths,True) # Наложим профили dirsAndFiles = self.applyProfilesFromSystem() if not dirsAndFiles: self.printERROR(_("Can not apply 'profiles/client' profiles")) return False remoteHost = clVars.Get("cl_remote_host") if remoteHost: if not self.delDomain(): return False self.printOK(messageOk) return True def installClient(self, clVars=False, printSuccess=True): """Наложение профиля клиента и при необходимости подключение к домену""" if not self.isRoot(): return False if not clVars: #Создаем объект переменных self.createClVars() clVars = self.clVars messageOk = _("Added use of scripts this package for window \ manager") + " ..." # Устанавливаем переменную профиля clVars.Set("cl_pass_run","on",True) # Изменяем базовую директорию наложения профилей clVars.Set("cl_root_path","/",True) # Устанавливаем действие profiles_client clVars.Set("cl_pass_type","install",True) # Новые пути к профилям profPaths = self.getPathProfiles(self.rootProfilePaths) if not profPaths: self.printERROR(_("Empty profile paths %s")\ %", "(self.rootProfilePaths)) return False # Изменяем переменную хранения профилей clVars.Set("cl_profile_path",profPaths,True) # Наложим профили dirsAndFiles = self.applyProfilesFromSystem() if not dirsAndFiles: self.printERROR(_("Can not apply 'profiles/client' profiles")) return False if printSuccess: self.printOK(messageOk) return True def addDomain(self, domainName): """Вводим в домен""" # Проверяем на root if not self.isRoot(): return False # Создаем объект переменных self.createClVars() netDomain = self.clVars.Get("os_net_domain") # Получам имя сервера (домена) if "." in domainName: domain = domainName else: domain = "%s.%s" %(domainName,netDomain) execStr = "ping -c 2 -i 0.3 %s" %domain resPing = self.execProg(execStr, False, False) if not resPing: self.printERROR(_('Can not execute "%s"')%execStr) return False foudHost = False foudHostSamba = False foundMountRemote = False reFoundHost = re.compile("(\d+)\% packet loss") if resPing and type(resPing) == types.ListType and len(resPing)>=2: pingStr = resPing[-2].strip() reSearch = reFoundHost.search(pingStr) if reSearch and reSearch.group(1) == "0": foudHost = True if not foudHost: self.printERROR(_("Not found domain %s")%domain) return False reFoundHostSamba = re.compile("Server=\[Samba.+\]") resSmbClient = self.execProg("smbclient -N -L %s" %domain, False,False) if resSmbClient and type(resSmbClient) == types.ListType: for string in resSmbClient: if reFoundHostSamba.search(string): foudHostSamba = True break if not foudHostSamba: self.printERROR(_("Not found Samba server in %s")%domain) return False pwd = False if self.clVars.Get("cl_remote_host") and \ self.clVars.Get("cl_remote_host") != domain: if not self.delDomain(): return False elif self.clVars.Get("cl_remote_host") and \ self.clVars.Get("cl_remote_host") == domain: pwd = self.clVars.Get("cl_remote_pw") foundMountRemote =self.isMount("/var/calculate/remote" ,"cifs") foundMountHome =self.isMount("/var/calculate/client-home","none",False) if foundMountRemote: self.printWARNING(_("Samba resource [%s] is mount")%\ "remote" + " ...") else: if pwd: userPwd = pwd else: userPwd=self.getUserPassword(\ _("Domain password for the desktop")) pathRemote = "/var/calculate/remote" pwdRemote = userPwd if not os.path.exists(pathRemote): os.makedirs(pathRemote) mountStr = "mount -t cifs -o user=client //%s/remote %s"\ %(domain,pathRemote) textLine = self.execProg(mountStr, None, True, {"PASSWD":pwdRemote}) if not (textLine == None): self.printERROR(_("Can not mount Samba resource [%s]")%\ "remote" + " ...") return False else: self.printSUCCESS(_("Mount Samba resource [%s]")%"remote" + \ " ...") self.clVars.Write("cl_remote_host", domain, False, "local") self.clVars.Write("cl_remote_pw", userPwd, False, "local") pathHome = "/var/calculate/client-home" if foundMountHome: self.printWARNING(str(pathHome)+ " " +_("is mount")+ " ...") else: if not os.path.exists(pathHome): os.makedirs(pathHome) mountStr = "mount -o bind %s /home" %pathHome textLine = self.execProg(mountStr) if not (textLine == None): self.printERROR(_("Can not mount") + " " + str(pathHome) +\ " ...") return False self.printSUCCESS(_("Mount") + " " + str(pathHome) + " " +\ " ...") self.clVars.flIniFile() #считаем переменные для клиента servDn = self.clVars.Get("ld_services_dn") unixDN = self.clVars.Get("ld_unix_dn") bindDn = self.clVars.Get("ld_bind_dn") bindPw = self.clVars.Get("ld_bind_pw") # запишем их if not (servDn and unixDN and bindDn and bindPw): self.printERROR(_("Not found variables") + ":") self.printERROR("ld_services_dn or ld_unix_dn \ or ld_bind_dn or ld_bind_pw") return False # Наложим профили profiles/client if not self.installClient(self.clVars, False): return False # Наложим профили profiles/domain # Устанавливаем действие profiles_client self.clVars.Set("cl_pass_type","domain",True) # Новые пути к профилям profPaths = self.getPathProfiles(self.rootProfilePaths) if not profPaths: self.printERROR(_("Empty profile paths %s")\ %", "(self.rootProfilePaths)) return False # Изменяем переменную хранения профилей self.clVars.Set("cl_profile_path",profPaths,True) # Наложим профили dirsAndFiles = self.applyProfilesFromSystem() if not dirsAndFiles: self.printERROR(_("Can not apply 'profiles/domain' profiles")) return False # Рестартуем dbus self.restartDBus() if not self.setDaemonAutostart("client"): return False self.printOK(_("Computer added to domain %s")%domain + " ...") return True def restartDBus(self): """Перезапускаем службу D-Bus""" dbusDaemon = '/etc/init.d/dbus' if os.path.exists(dbusDaemon): if os.system(dbusDaemon + ' restart &>/dev/null') != 0: self.printWARNING(_("Error restarting")+" "+dbusDaemon+" ...") return False return True def removePrivateFiles(self, userHome): """Удаление приватных файлов""" privateFiles = ['.kde4/share/apps/kwallet/kdewallet.kwl', '.calculate/server.env'] # файлы в .ssh sshHome = ".ssh" sshPath = os.path.join(userHome,sshHome) if os.path.isdir(sshPath): # .ssh файлы относительно домашней директории пользователя privateFiles += map(lambda x:os.path.join(sshHome,x),\ filter(lambda x:\ os.path.isfile(os.path.join(sshPath,x)),\ os.listdir(sshPath))) for prFile in privateFiles: rmFile = os.path.join(userHome, prFile) if os.path.exists(rmFile): os.remove(rmFile) return True def umountSleepPath(self, path, typeMount='cifs', secondPath=True): """Отмонтирует путь при неудаче задержка потом повтор""" #Проверяем на монтирование директории if self.isMount(path, typeMount, secondPath): textLine = self.execProg("umount %s"%path) if textLine != None: i = 0 flagError = False while (i1: foundTwoSession = True self.printERROR(\ _("Second X session for user %s can not be opened.")\ %userName) break return foundTwoSession def mountSambaRes(self,userName,userPwd,uid,gid,res,path, mountUidList=['ftp','home','share']): """Монтирует Samba ресурсы""" if res in mountUidList: # Монтируем директории c uid mountStr="mount -t cifs -o user=%s,uid=%s,gid=%s,noperm"\ %(userName,uid,gid) +\ " //%s/%s %s" %(self.clVars.Get("cl_remote_host"), res, path) else: # Монтируем директории mountStr="mount -t cifs -o user=%s"%(userName)+\ " //%s/%s %s" %(self.clVars.Get("cl_remote_host"), res, path) textLine = self.execProg(mountStr, None, True, {"PASSWD":userPwd}) return textLine def removeDir(self, rmDirOrScanObjs): """Рекурсивное удаление директории входной параметр директория или результат сканирования файлов (объект) """ rmDir = False if type(rmDirOrScanObjs) == types.StringType: rmDir = rmDirOrScanObjs if not os.path.exists(rmDir): self.printERROR(_("Not found remove dir %s") %rmDir) return False fileObj = cl_profile._file() # Сканируем директорию scanObjs = fileObj.scanDirs([rmDir]) else: scanObjs = rmDirOrScanObjs for socketRm in scanObjs[0].sockets: # Удаляем сокеты if os.path.exists(socketRm): os.remove(socketRm) for linkRm in scanObjs[0].links: # Удаляем ссылки os.unlink(linkRm[1]) for fileRm in scanObjs[0].files: # Удаляем файлы os.remove(fileRm) scanObjs[0].dirs.sort(lambda x, y: cmp(len(y), len(x))) for dirRm in scanObjs[0].dirs: # Удаляем директории os.rmdir(dirRm) if rmDir: os.rmdir(rmDir) return True def isCorrectProfileOnLocalServer(self,userName): """Узнать находится ли актуальный профиль пользователя на локальном сервере """ searchPrevHost = self.searchPrevHost(userName) if searchPrevHost and searchPrevHost[0][0][1].has_key('host'): prevHost = searchPrevHost[0][0][1]['host'][0] else: prevHost = None # если местоположение актуального профиля найти не удалось # или его местоположение не на локальном сервере if not prevHost or prevHost == self.clVars.Get('cl_remote_host'): return True else: return False def mountUserRes(self, userName, sync=True, progress=False): """Монтирование пользовательских ресурсов и синхронизация настроек""" # Имя пользователя self.userName = userName # Проверяем на root if not self.isRoot(): return False # Проверка на повторный вход пользователя if self.isTwoSessionsUser(userName): return False self.createClVars() # В случае компьютера вне домена if not self.clVars.Get("cl_remote_host"): self.printSUCCESS(_("To be used local profile.")) return True # Проверим что компьютер в домене и смонтирован [remote] connectDomain = self.isDomain() if not connectDomain: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False elif not connectDomain[0]: self.printERROR(_("Can not mount Samba resource [%s]")%"remote"+ \ " ...") # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False # Подсоединяемся к серверу domain = self.clVars.Get("cl_remote_host") if not self.getLdapObjBind(domain): # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False # homeDir из LDAP resLdap = self.getUserLdapInfo(userName) if not resLdap: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False uid = int(resLdap[0]) gid = int(resLdap[1]) homeDir = resLdap[5] # При отсуствии создаем домашнюю директорию if not os.path.exists(homeDir): os.makedirs(homeDir) os.chown(homeDir,uid,gid) os.chmod(homeDir,0700) # записываем в .logout файл статус "в процессе" logOutFile = os.path.join(homeDir,self.replLogoutFile) self.createUserFile(logOutFile,"PROCESS", uid, gid) # Получаем пароль пользователя из ключей ядра userPwd = _cl_keys.getKey(userName) if not userPwd: self.printERROR(_("Not found user password")) # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False home = os.path.split(homeDir)[0] pathRemote = [] # Удаленный ресурс share pathRemote.append((os.path.join(homeDir,"Disks"), "share")) # Удаленный ресурс профилей pathRemote.append((os.path.join(home,"." + userName), "unix")) # Удаленный ресурс home pathRemote.append((os.path.join(homeDir,"Home"), "homes")) if self.clVars.Get("cl_remote_ftp"): # Удаленный ресурс ftp pathRemote.append((os.path.join(homeDir,"FTP"), "ftp")) flagError = False # В случае включения репликации на сервере True replOn = self.searchReplBranch() # Путь к профилю пользователя по умолчанию defaultPath = "" # Хост пользователя по умолчанию defaultHost = self.clVars.Get("cl_remote_host") for path, res in pathRemote: # Создаем директории для монтирования if not os.path.exists(path): try: os.mkdir(path) os.chown(path, uid, gid) os.chmod(path,0700) except OSError: self.printERROR(_("Error creating directory")) self.printERROR(_("Permission denied: '%s'")%path) flagError = True break # Проверяем на монтирование директории if self.isMount(path, 'cifs'): continue # В случае репликации не монтируем профиль пользователя if replOn and res=="unix": defaultPath = path continue # Монтируем Samba ресурс textLine = self.mountSambaRes(userName,userPwd,uid,gid,res,path) if not (textLine == None): self.printERROR(_("Can not mount Samba resource [%s]")\ %res + " ...") flagError = True break if flagError: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False flagFirstSync = False if replOn or sync: # Ошибка при монтировании unix ресурса удаленного сервера при # включенной репликации replErrorMount = False # Ошибка при cинхронизации unix ресурса удаленного сервера при # включенной репликации replErrorSync = False # Синхронизируем настройки if sync: # Короткое имя системы osLinuxShort = self.clVars.Get("os_linux_shortname") # В случае репликации prevHost = "" # Монтирование по умолчанию (default - cвой сервер, remote - чужой) mountServer = "" if replOn: searchPrevHost = self.searchPrevHost(userName) if searchPrevHost and searchPrevHost[0][0][1].has_key('host'): prevHost = searchPrevHost[0][0][1]['host'][0] # Монтируем ресурс unix текущего сервера mountServer = "default" textLine = self.mountSleepRes(userName,userPwd,uid,gid, "unix",defaultPath) if not (textLine == None): self.printERROR(_("Can not mount Samba resource [%s]")\ %"unix" + " ...") # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False # Переносим настройки пользователя в новую директорию # .CLD self.upgradeProfileClient(userName, homeDir) # Если на текущем сервере в ресурсе unix есть файлы # то синхронируем настройки # Получаем директорию хранения профиля на сервере homeProfile = os.path.join(home, "." + userName, osLinuxShort) if not os.path.exists(homeProfile): homeProfile = os.path.join(home, "." + userName, "." + osLinuxShort) if os.listdir(homeProfile): if not self.syncUser(userName, homeDir, "login", uid, gid,\ progress=progress, host=self.clVars.Get('cl_remote_host')): # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False else: #Удаляем ненужные файлы rmFiles = list(set(os.listdir(homeDir))-\ set(self.skipHomeFile)) for rmFile in rmFiles: delFile = os.path.join(homeDir,rmFile) if os.path.islink(delFile): os.unlink(delFile) elif os.path.isfile(delFile): os.remove(delFile) elif os.path.isdir(delFile): if not self.removeDir(delFile): # Отмонтируем пользовательские ресурсы # в случае ошибки self.errorAndUnmountUserRes = True return False elif stat.S_ISSOCK(os.stat(delFile)[stat.ST_MODE]): os.remove(delFile) # Отмонтируем ресурс textLine = self.umountSleepPath(defaultPath) if not textLine: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False if prevHost and prevHost != self.clVars.Get("cl_remote_host"): # Монтируем настройки пользователя удаленного сервера # если сервер найден self.clVars.Set("cl_remote_host", prevHost, True) mountServer = "remote" textLine = self.mountSleepRes(userName,userPwd,uid,gid, "unix",defaultPath) self.clVars.Set("cl_remote_host", defaultHost, True) if not (textLine == None): if self.isMount(defaultPath, 'cifs'): textLine = self.umountSleepPath(defaultPath) if not textLine: # Отмонтируем пользовательские ресурсы # в случае ошибки self.errorAndUnmountUserRes = True return False # Монтируем текущий сервер если ошибка подключения к # к найденному серверу mountServer = "default" textLine = self.mountSleepRes(userName,userPwd,uid,gid, "unix",defaultPath) if not (textLine == None): self.printERROR(_("Can not mount Samba resource \ [%s]")%"unix" + " ...") # Отмонтируем пользовательские ресурсы # в случае ошибки self.errorAndUnmountUserRes = True return False replErrorMount = True else: mountServer = "default" # Монтируем текущий сервер если сервер не найден в LDAP textLine = self.mountSleepRes(userName,userPwd,uid,gid, "unix",defaultPath) if not (textLine == None): self.printERROR(_("Can not mount Samba resource \ [%s]")%"unix" + " ...") # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False # если репликации нет, то prevHost и remote_host одинаковые else: prevHost=self.clVars.Get('cl_remote_host') # Синхронизируем настройки пользователя if not (replOn and mountServer == "default"): # Переносим настройки пользователя в новую директорию # .CLD self.upgradeProfileClient(userName, homeDir) # Получаем директорию хранения профиля на сервере homeProfile = os.path.join(home, "." + userName, osLinuxShort) if not os.path.exists(homeProfile): homeProfile = os.path.join(home, "." + userName, "." + osLinuxShort) if os.listdir(homeProfile): if not self.syncUser(userName, homeDir, "login", uid, gid,\ progress=progress,host=prevHost): if replOn and mountServer == "remote": replErrorSync = True else: # Отмонтируем пользовательские ресурсы # в случае ошибки self.errorAndUnmountUserRes = True return False else: #Удаляем ненужные файлы rmFiles = list(set(os.listdir(homeDir))-\ set(self.skipHomeFile)) for rmFile in rmFiles: delFile = os.path.join(homeDir,rmFile) if os.path.islink(delFile): os.unlink(delFile) elif os.path.isfile(delFile): os.remove(delFile) elif os.path.isdir(delFile): if not self.removeDir(delFile): # Отмонтируем пользовательские ресурсы # в случае ошибки self.errorAndUnmountUserRes = True return False elif stat.S_ISSOCK(os.stat(delFile)[stat.ST_MODE]): os.remove(delFile) if replOn and mountServer == "remote": # В случае репликации перемонтируем ресурс профилей # на текущий сервер (в случае необходимости) textLine = self.umountSleepPath(defaultPath) if not textLine: # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False textLine = self.mountSambaRes(userName,userPwd,uid,gid, "unix",defaultPath) if not (textLine == None): self.printERROR(_("Can not mount Samba resource [%s]")\ %"unix" + " ...") # Отмонтируем пользовательские ресурсы в случае ошибки self.errorAndUnmountUserRes = True return False if replOn: if replErrorMount or replErrorSync: self.createUserFile(logOutFile,"ERROR", uid, gid) else: self.createUserFile(logOutFile,"SUCCESS", uid, gid) else: self.createUserFile(logOutFile,"SUCCESS", uid, gid) self.printSUCCESS(_("Mounted user resource of the domain")) self.printOK(_("Get a user profile from the domain") + " ...") return True def mountSleepRes(self,userName,userPwd,uid,gid,res,path): """Монтирует ресурс при неудаче задержка потом повторное монитрование""" textLine = self.mountSambaRes(userName,userPwd,uid,gid,res,path) if not (textLine == None): # Проверяем на монтирование директории if self.isMount(path, 'cifs'): textLineUmount = self.umountSleepPath(path) if not textLineUmount: return False i = 0 while (i/dev/null"%(srcFile,destFile)) != 0: self.printERROR(_("Can not move %s")%srcFile + " " +\ _("to %s")%destFile) flagError=True break if flagError: return False return True def upgradeProfileClient(self, userName, userHome): """Переносит данные клиента в директорию .CLD Перед вызовом этого метода обязательно должен быть определен атрибут объекта self.clVars - объект переменных """ # Директория хранения старых профилей home = os.path.split(userHome)[0] pathOldProfile = os.path.join(home, "." + userName) if os.path.exists(pathOldProfile): osLinuxShort = self.clVars.Get("os_linux_shortname") # В случае пустой директории профиля if not os.listdir(pathOldProfile): pathNewProfile = os.path.join(pathOldProfile, "." + osLinuxShort) # Создаем директорию для хранения профиля os.mkdir(pathNewProfile) os.chmod(pathNewProfile, 0700) return True skipDirs = [".CLD", ".CLDX", "." + osLinuxShort] # Если есть скрытые файлы кроме skipDir # а так-же нет файлов skipDir - делаем апгрейд if filter(lambda x: x[0]==".", list(set(os.listdir(pathOldProfile))-set(skipDirs))) and\ len(filter(lambda x: not os.path.exists(os.path.join(pathOldProfile,x)), skipDirs))==len(skipDirs): pathNewProfile = os.path.join(pathOldProfile,".CLD") # Копируем профиль в новое место try: self.copyProfileDir(pathNewProfile, pathOldProfile) except: self.printERROR(_("Error updating user profile")) self.printERROR(_("path: %s")%pathNewProfile) return False pathNewProfile = os.path.join(pathOldProfile, "." + osLinuxShort) if not os.path.exists(pathNewProfile): # Создаем директорию для хранения профиля os.mkdir(pathNewProfile) os.chmod(pathNewProfile, 0700) return True def syncUser(self, userName, userHome, sync, uid, gid, progress=False,\ host="default"): """Синхронизация пользовательских настроек Перед вызовом этого метода обязательно должен быть определен атрибут объекта self.clVars - объект переменных """ home = os.path.split(userHome)[0] osLinuxShort = self.clVars.Get("os_linux_shortname") homeProfile = os.path.join(home, "." + userName, osLinuxShort) if not os.path.exists(homeProfile): homeProfile = os.path.join(home, "." + userName, "." + osLinuxShort) flagError = False execStr = "" if sync == "login": # исключаемые пути при синхронизации # /.local/share/akonadi/db_data # хранит базу acanadi # /.mozilla/firefox/calculate.default/urlclassifier3.sqlite # хранит БД для firefox # /.local/share/mime/mime.cache # отключение ошибочного кэширования изображений # /.kde4/share/apps/nepomuk/repository/main/data # база nepomuk # /.VirtualBox # содержит данные о виртуальных машинах if os.path.exists(userHome) and\ os.path.exists(homeProfile): execStr = '/usr/bin/rsync --delete-excluded --delete \ --exclude="/.googleearth" --exclude="/.kde4/share/config/phonondevicesrc" \ --exclude="*~" --exclude="/Home" --exclude="/Disks" --exclude="/FTP" \ --exclude="/.local/share/akonadi/db_data" --exclude="/.VirtualBox" \ --exclude="/.mozilla/firefox/calculate.default/urlclassifier3.sqlite" \ --exclude="/.local/share/mime/mime.cache" \ --exclude="/.kde4/share/apps/nepomuk/repository/main/data" \ --exclude="/.logout" \ --exclude="/.Xauthority" \ --filter="P /.googleearth" --filter="P /Home" --filter="P /Disks" \ --filter="P /.local/share/akonadi/db_data" --filter="P /.VirtualBox" \ --filter="P /.mozilla/firefox/calculate.default/urlclassifier3.sqlite" \ --filter="P /.local/share/mime/mime.cache" \ --filter="P /.kde4/share/apps/nepomuk/repository/main/data" \ --filter="P /.logout" \ --filter="P /.Xauthority" \ --filter="P /FTP" -a -x -v -v -v -v %s/ %s/' %(homeProfile,userHome) elif sync == "logout": if os.path.exists(userHome) and os.listdir(userHome) and\ os.path.exists(homeProfile): execStr = '/usr/bin/rsync --delete-excluded --delete \ --exclude="/.googleearth" --exclude="/Home" --exclude="/Disks" --exclude="/FTP"\ --exclude="*~" --exclude="/.kde4/cache-*" --exclude="/.kde4/tmp-*" \ --exclude="/.local/share/akonadi/db_data" --exclude="/.VirtualBox" \ --exclude="/.mozilla/firefox/calculate.default/urlclassifier3.sqlite" \ --exclude="/.local/share/mime/mime.cache" \ --exclude="/.Xauthority" \ --exclude="/.kde4/share/apps/nepomuk/repository/main/data" \ --exclude="/.kde4/socket-*" --exclude="/.kde4/share/config/phonondevicesrc"\ -a -x -v -v -v -v %s/ %s/'%(userHome,homeProfile) else: self.printERROR(_("Method syncUser: option sync=%s incorrect")\ %str(sync)) return False if execStr: host = "" + host +"" rsync = RsyncProgressBar(\ _("Receiving file list from %s") % host + " ...", _("Downloading the user profile from %s") % host \ + " ...", execStr) configFileName = os.path.join(homeProfile,self.configFile) if sync == "login": # получить переменную files из секции Rsync файла # .calculate.ini try: numfiles = cl_base.iniParser( \ configFileName).getVar('rsync','files') if numfiles == False: if os.path.exists(configFileName): os.remove(configFileName) numfiles = 1 else: numfiles = int(numfiles) except: numfiles = 1 rsync.maximum = numfiles if progress: rsync.run() else: rsync.runsilent() if sync == "logout": rsync.runsilent() try: if cl_base.iniParser(configFileName).setVar('rsync', {'files':rsync.getFilesNum()}): os.chmod(configFileName, 0600) os.chown(configFileName,uid,gid) except: pass rsync.close() if rsync.getExitCode() != 0: try: if cl_base.iniParser(configFileName).setVar(\ 'rsync',{'exitcode':rsync.getExitCode()}): os.chmod(configFileName, 0600) os.chown(configFileName,uid,gid) except: pass self.printERROR(_("Can not execute rsync") + " " + str(sync) +\ " ...") flagError = True else: if sync == "login": if not (os.path.exists(userHome)): self.printERROR(_("Directory %s not exists")%userHome) else: self.printERROR(_("Directory %s not exists")%homeProfile) elif sync == "logout": if not (os.path.exists(userHome)): self.printERROR(_("Directory %s is empty or not exists")\ %userHome) else: self.printERROR(_("Directory %s not exists")%homeProfile) flagError = True if flagError: return False else: # Изменим если нужно права на директории fileObj = cl_profile._file() # Домашняя директория и директория хранения профиля changeDirs = [userHome, homeProfile] for changeDir in changeDirs: # Получаем права на директорию mode,uid,gid = fileObj.getModeFile(changeDir) # Если права не равны 0700 меняем их if mode != 0700: os.chmod(changeDir,0700) return True class tsOpt(cl_base.opt): """Класс для обработки параметров и вывода help Параметры: helpObj объект-справка содержащий необходимые опции notOptError выдавать ошибку при отсутствии опций командной строки optUser проверять хвост командной строки на наличие пользователя """ def __init__(self, helpObj, notOptError=False, optUser=True): # от cl_help получаем короткие и длинные опции shortOpt,longOpt = helpObj.getAllOpt('all', helpObj.relOptions['h']) # вызвать конструктор объекта, распознающего опции cl_base.opt.__init__(self,shortOpt,longOpt) self.nameParams = ['user'] self.sysArgv = sys.argv[1:] self.helpObj = helpObj self.__iter = 0 self.opt = {} self.params = {} self.getopt() # Обработка help self.flagHelp = False # определяем есть ли среди опций опции, которые влияют на показ # опциональных разделов (метод пересечения множеств) helpopt = \ tuple(set(self.opt.keys()).intersection(helpObj.relOptions.keys())) #Если есть опции help if len(helpopt) > 0: print helpObj.getHelp(helpObj.relOptions[helpopt[0]]) self.flagHelp = True else: if optUser and self.params.has_key('user'): if len(self.nameParams) != self.__iter: self.handlerErrOpt() # В случае остсутствия опций командной строки и имени пользователя if notOptError: if not self.opt: self.printErrorNotOpt() self.flagHelp = True if optUser and not self.params.has_key('user'): print helpObj.getHelp(helpObj.relOptions['h']) self.flagHelp = True elif optUser and not self.opt and not self.params.has_key('user'): print helpObj.getHelp(helpObj.relOptions['h']) self.flagHelp = True if not optUser and self.params.has_key('user'): print helpObj.getHelp(helpObj.relOptions['h']) self.flagHelp = True def printErrorNotOpt(self): """Сообщение в случае отсутствия опций""" print _("Options are absent.") def handlerOpt(self,option,value): # Обработчик (опция значение) #print option, value shortOpt = self.helpObj.getShortOpt(option) if not shortOpt: shortOpt = option if not shortOpt in self.opt: self.opt[shortOpt] = value def handlerErrOpt(self): # Обработчик ошибок argv = " ".join(sys.argv[1:]) print _("Unrecognized option") + ' "' + argv + '"\n' + \ _("Try") + ' "' + sys.argv[0].split("/")[-1] + ' --help" ' +\ _("for more information.") def handlerParam(self,param): # Обработчик хвостов (значение) self.__iter += 1 if self.__iter<=len(self.nameParams): self.params[self.nameParams[self.__iter-1]] = param