#-*- coding: utf-8 -*- # Copyright 2010 Calculate Ltd. http://www.calculate-linux.org # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. __version__ = "2.2.30" __app__ = "calculate-client" import os import stat import re import sys import pwd import subprocess import ldap import time import types import getpass from cl_lang import lang from cl_template import template, iniParser from cl_datavars import DataVars from cl_print import color_print from cl_ldap import ldapUser from client.progressbar import ProgressBar from cl_utils import runOsCommand, getpathenv, getModeFile, removeDir, isMount,\ appendProgramToEnvFile, removeProgramToEnvFile, pathJoin, \ tarLinks from _cl_keys import getKey, clearKey from convertenv import convertEnv from encrypt import encrypt from cl_client_cache import userCache from shutil import copy2 from socket import gethostbyname import tarfile lang().setLanguage(sys.modules[__name__]) class DataVarsClient(DataVars): """Хранение переменных""" def importClient(self, **args): '''Импорт переменных для клиента''' # Имя секции в calculate2.env envSection = "client" # импорт переменных self.importData(envSection, ('cl_vars_client','cl_fill_client')) class share(color_print): """Общие методы""" # Объект хранения переменных clVars = False 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 = DataVarsClient() # Импортируем переменные clVars.importClient() # Заменяем значения переменных переменными из env файлов clVars.flIniFile() # Устанавливаем у объекта атрибут объект переменных self.clVars = clVars return True def applyTemplatesFromSystem(self): """Применяем шаблоны для cистемы""" # Cоздаем объект обработки шаблонов clTempl = template(self.clVars) # Объединяем шаблоны dirsFiles = clTempl.applyTemplates() if clTempl.getError(): self.printERROR(clTempl.getError().strip()) return False else: return dirsFiles def printVars(self, *arg, **argv): """Печать существующих переменных""" self.clVars.printVars(*arg, **argv) class RsyncProgressBar: '''Объект запуска 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 self.progress = ProgressBar(title,1) self.progress.setMaximum(self.maximum) def getFilesNum(self): '''Получить количество файлов созданных генератором''' if self.pipe: return self.value def getExitCode(self): '''Получить код выхода rsync''' if self.pipe: return self.pipe.wait() return 255 def getErrMessage(self): '''Сообщение ошибки rsync при синхронизации''' if self.pipe: return self.pipe.stderr.read() return _("RsyncProgressBar: Wrong pipe") 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.progress.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.receiverre.search(s) if q: if not self.copyStarting: self.progress.shutdownDialog() self.progress.openDialog(self.secondtitle) self.progress.setMaximum(self.maximum) self.copyStarting = True value = int(q.groups()[0]) self.progress.setValue(value) def close(self): self.progress.shutdownDialog() class ldapData(ldapUser): """Методы для LDAP""" def addDN(self, *arg): """Складывает текстовые элементы DN""" DNs = [] for dn in arg: if dn: DNs.append(dn) return ','.join(DNs) def getReplDN(self): """Получаем DN ветки хранения последнего посещенного сервера В ветке находятся ((имя пользователя, имя системы, хост) ....) """ usersDN = self.getUsersDN() if usersDN: partDN = "ou=Worked,ou=Replication,ou=LDAP" servicesDN = "ou=Services" baseDN = usersDN.rpartition(servicesDN+",")[2] replDN = self.addDN(partDN, servicesDN, baseDN) return replDN return False def searchPrevHost(self, userName, osLinuxShort): """Находит сервер к которому был подключен пользователь""" connectData = self.getBindConnectData() if connectData: bindDn, bindPw, host = connectData replDN = self.getReplDN() # cтрока для поиска в служебной ветке репликации userAndOsName = "%s@%s"%(userName,osLinuxShort) findAttr = "uid=%s"%userAndOsName # Соединяемся с LDAP if not self.ldapConnect(bindDn, bindPw, host): return False resSearch = self.ldapObj.ldapSearch(replDN, ldap.SCOPE_ONELEVEL, findAttr, ["host"]) return resSearch return False def _gethostbyname(self,hostname): try: return gethostbyname(hostname) except: pass return None def getNameRemoteServer(self,userName, osLinuxShort, curHost): """Если профиль на удаленном сервере, то выдать DNS имя этого сервера """ searchPrevHost = self.searchPrevHost(userName, osLinuxShort) if searchPrevHost and searchPrevHost[0][0][1].has_key('host'): prevHost = searchPrevHost[0][0][1]['host'][0] else: prevHost = None # get ip address of previous server and current server prevIp = self._gethostbyname(prevHost) curIp = self._gethostbyname(curHost) # если местоположение актуального профиля найти не удалось # или его местоположение не на локальном сервере if not prevHost or curIp and prevIp == curIp: return False else: return prevHost def isRepl(self): """Включена ли репликация на сервере""" connectData = self.getBindConnectData() if connectData: bindDn, bindPw, host = connectData usersDN = self.getUsersDN() partDN = "ou=Replication,ou=LDAP" servicesDN = "ou=Services" baseDN = usersDN.rpartition(servicesDN+",")[2] replDN = self.addDN(partDN, servicesDN, baseDN) findAttr = "ou=Worked" # Соединяемся с LDAP if not self.ldapConnect(bindDn, bindPw, host): return False resSearch = self.ldapObj.ldapSearch(replDN, ldap.SCOPE_ONELEVEL, findAttr, [findAttr.partition("=")[0]]) if resSearch: return True return False class commandServer(color_print): """Отправка команд на сервер (изменение пароля пользователя на сервере)""" def setServerCommand(self, command, varsCommand, fileConfig, uid=None, gid=None): """Установить команду для сервера""" pathConfig = os.path.split(fileConfig)[0] # Создаем директорию если ее нет if not os.path.exists(pathConfig): os.makedirs(pathConfig) if not uid is None and not gid is None: os.chown(pathConfig, uid, gid) objConfig = iniParser(fileConfig) varsRun = {"run":"on"} varsRun.update(varsCommand) if not objConfig.setVar(["command"]+command, varsRun): self.printERROR(_("Failed to write variables in file %s")\ %fileConfig) return False if not uid is None and not gid is None: os.chown(fileConfig, uid, gid) return True 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 = _("Wrong password") return False, errMessage except ldap.LDAPError, e: errMessage = e[0]['desc'] return False, errMessage return True, errMessage 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 the 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") + ": " +\ _("passwords do not match")) return False userPwd = pwdA return userPwd class client(share, commandServer, encrypt): """Методы работы для подключения пользователя к серверу и синхронизации""" # Объект для поиска пользовательских данных в LDAP ldapDataObj = ldapData() # Путь относительно домашней директории в котором находятся # конфигурационные файлы pathConfig = ".calculate" # Конфигурационный файл для клиента configFileDesktop = os.path.join(pathConfig, "desktop.env") # Конфигурационный файл для сервера configFileServer = os.path.join(pathConfig, "server.env") # Файл - список файлов профиля пользователя listTemplFile = os.path.join(pathConfig, "files.txt") logOutFile = ".logout" # Файлы котороые не удаляются при очистке домашней директории skipHomeFile = ["Home","Disks","Share","FTP",logOutFile,configFileDesktop] # словарь опций сервисов из /var/calculate/remote/server.env optionsInfo = {} # объект конвертирования из старого remote env файла convObj = None # Приватный файлы пользователя privateFiles = [configFileServer] # Приватные директории пользователя privateDirs = [] def removeVars(self): """Удаление переменных шаблонов при удалении пакета и выходе из домена""" self.clVars.Delete("cl_remote_host", "local") self.clVars.Delete("cl_remote_pw", "local") self.clVars.Delete("os_remote_auth") self.clVars.Delete("os_remote_client") self.clVars.Set("cl_remote_host", "", True) self.clVars.Set("cl_remote_pw", "", True) self.clVars.Set("os_remote_auth", "", True) self.clVars.Set("os_remote_client", "", True) def isTwoSessionsUser(self, userName): """Проверка на повторный вход пользователя""" xSession = 0 foundTwoSession = False reFoundUser = re.compile("%s\s+:\d+\s+"%(userName)) resWho = self.execProg("who") if resWho: for string in resWho: if reFoundUser.search(string): xSession +=1 if xSession>1: foundTwoSession = True self.printERROR(\ _("Second X session for user %s cannot be opened.")\ %userName) break return foundTwoSession def isDomain(self): """Находится ли компьютер в домене""" foundMountRemote = isMount("/var/calculate/remote") remoteHost = self.clVars.Get("cl_remote_host") if remoteHost and foundMountRemote: return True self.printERROR(_("The computer is not in the domain")) return False def getUserMountResources(self, userName, homeDir, flagRemoteServer): """Получение монтируемых ресурсов для пользователя""" home = os.path.split(homeDir)[0] dictResources = {} names = [] # Pесурс share name = "share" dictResources[name] = {"resource":"share", "path":os.path.join(homeDir,"Share")} names.append(name) # Ресурс шаблонов name = "profile" dictResources[name] = {"resource":"unix", "path":os.path.join(home,"."+userName)} names.append(name) # Ресурс - домашняя директория (файлы пользователя на сервере) name = "home" dictResources[name] = {"resource":"homes", "path":os.path.join(homeDir,"Home")} names.append(name) if self.getInfoService("ftp", "host"): # Ресурс ftp name = "ftp" dictResources[name] = {"resource":"ftp", "path":os.path.join(homeDir,"FTP")} names.append(name) if flagRemoteServer: # Ресурс шаблонов на удаленном сервере name = "remote_profile" dictResources[name] = {"resource":"unix", "path":os.path.join(home,"."+userName+"."+"remote")} names.append(name) return names, dictResources def mountSambaRes(self,host,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" %(host, res, path) else: # Монтируем директории mountStr = "mount -t cifs -o user=%s"%(userName)+\ " //%s/%s %s" %(host, res, path) textLine = self.execProg(mountStr, envProg={"PASSWD":userPwd}) return textLine def mountSleepRes(self,host,userName,userPwd,uid,gid,res,path): """Монтирует ресурс при неудаче задержка потом повторное монитрование""" textLine = self.mountSambaRes(host,userName,userPwd,uid,gid,res,path) if textLine is False: # Проверяем на монтирование директории if isMount(path): textLineUmount = self.umountSleepPath(path) if not textLineUmount: return False i = 0 sleeps = [0.5, 2, 5] while (i/dev/null"%(srcDir, destDir)) != 0: self.printERROR(_("Failed to move %s")%srcDir + " " +\ _("to %s")%destDir) return False return True def upgradeTemplateClient(self, userName, userHome, pathTemplates): """Переносит данные клиента в директорию без точки например: было /home/.user/.CLD стало /home/.user/CLD Перед вызовом этого метода обязательно должен быть определен атрибут объекта self.clVars - объект переменных """ # Директория хранения старых шаблонов home = os.path.split(userHome)[0] if os.path.exists(pathTemplates): if self.clVars.Get("cl_profile_all_set") == "on": osLinuxShort = "all" else: osLinuxShort = self.clVars.Get("os_linux_shortname") pathNewTemplate = os.path.join(pathTemplates, osLinuxShort) pathOldTemplate = os.path.join(pathTemplates, "."+osLinuxShort) if not os.path.exists(pathNewTemplate) and\ os.path.exists(pathOldTemplate) and\ os.listdir(pathOldTemplate): # Переносим профиль if not self.copyTemplateDir(pathOldTemplate, pathNewTemplate): return False if not os.path.exists(pathNewTemplate): # Создаем директорию для хранения профиля os.mkdir(pathNewTemplate) os.chmod(pathNewTemplate, 0700) if os.path.exists(pathOldTemplate) and\ not os.listdir(pathOldTemplate): os.rmdir(pathOldTemplate) 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 syncUser(self, userName, userHome, sync, uid, gid, homeTemplate, \ progress=False, host="default"): """Синхронизация пользовательских настроек Перед вызовом этого метода обязательно должен быть определен атрибут объекта self.clVars - объект переменных """ flagError = False execStr = "" skipPaths = self.clVars.Get("cl_sync_skip_path") if not skipPaths: self.printERROR( _("The variable 'cl_sync_skip_path' is empty")%userHome) return False deletePaths = self.clVars.Get("cl_sync_del_path") if not deletePaths: deletePaths = [] excludePaths = " ".join(map(lambda x: '--exclude="/%s"'\ %x.replace('"',"").replace("'",""), skipPaths + deletePaths)) if sync == "login": if os.path.exists(userHome) and\ os.path.exists(homeTemplate): filterPath = " ".join(map(lambda x: '--filter="P /%s"'\ %x.replace('"',"").replace("'",""), skipPaths)) execStr = '/usr/bin/rsync --delete-excluded --delete %s %s '\ '-rlptgo -x -v -v -v %s/ %s/' \ %(excludePaths, filterPath, homeTemplate, userHome) elif sync == "logout": if os.path.exists(userHome) and os.listdir(userHome) and\ os.path.exists(homeTemplate): execStr = '/usr/bin/rsync --delete-excluded --delete %s \ -rlptgo -x -v -v -v %s/ %s/'%(excludePaths, userHome, homeTemplate) else: self.printERROR(_("Method syncUser: option sync=%s incorrect")\ %str(sync)) return False if execStr: host = "" + host +"" rsync = RsyncProgressBar(\ _("Receiving the file list from %s") % host + " ...", _("Downloading the user profile from %s") % host +\ " ...", execStr) pathConfig = os.path.join(homeTemplate, self.pathConfig) # Удаляем предыдущий ini файл prevIniFile = os.path.join(homeTemplate,".calculate.ini") if os.path.exists(prevIniFile): os.remove(prevIniFile) # Создаем директорию для конфигурационных файлов if not os.path.exists(pathConfig): self.createUserDir(uid, gid, pathConfig, mode=False) configFileName = os.path.join(homeTemplate, self.configFileDesktop) if sync == "login": # получить переменную files из секции Rsync файла # .calculate.ini try: numfiles = iniParser(\ configFileName).getVar('rsync','files') if numfiles is False: if os.path.exists(configFileName): os.remove(configFileName) numfiles = 0 else: numfiles = int(numfiles) except: numfiles = 0 rsync.maximum = numfiles if progress: rsync.run() else: rsync.runsilent() if sync == "logout": rsync.runsilent() try: if 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 iniParser(configFileName).setVar(\ 'rsync',{'exitcode':rsync.getExitCode()}): os.chmod(configFileName, 0600) os.chown(configFileName,uid,gid) except: pass self.printERROR(rsync.getErrMessage()) self.printERROR(_("Failed to execute rsync") + " " + \ str(sync) + " ...") flagError = True else: if sync == "login": if not (os.path.exists(userHome)): self.printERROR(_("Directory %s does not exist")%userHome) else: self.printERROR( _("Directory %s does not exist")%homeTemplate) elif sync == "logout": if not (os.path.exists(userHome)): self.printERROR( _("Directory %s is empty or does not exist")\ %userHome) else: self.printERROR( _("Directory %s does not exist")%homeTemplate) flagError = True if flagError: return False else: # Домашняя директория и директория хранения профиля changeDirs = [userHome, homeTemplate] for changeDir in changeDirs: # Получаем права на директорию mode = getModeFile(changeDir, mode="mode") # Если права не равны 0700 меняем их if mode != 0700: os.chmod(changeDir,0700) return True def clearHomeDir(self, homeDir): """Очистка домашней директории от файлов""" 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 removeDir(delFile): return False elif stat.S_ISSOCK(os.stat(delFile)[stat.ST_MODE]): os.remove(delFile) return True def syncLoginTemplate(self, host, userName, homeDir, homeTemplate, uid, gid, progress, flagClearHomeDir=True): """Синхронизация профиля пользователя с сервера в переменной 'cl_remote_host' хост для прогрессбара """ home = os.path.split(homeDir)[0] # Если на текущем сервере в ресурсе unix есть файлы # то синхронируем настройки if filter(lambda x: not x in ('.calculate',), os.listdir(homeTemplate)): if not self.syncUser(userName, homeDir, "login", uid, gid,\ homeTemplate, progress=progress, host=host): return False else: if flagClearHomeDir: # Удаляем ненужные файлы (очищаем домашнюю директорию) if not self.clearHomeDir(homeDir): return False return True def getDataInConfig(self, section, listVars, objConfig): """Читаем переменные listVars из секции section конф. файла""" varsConfig = {} for varName in listVars: varsConfig[varName] = objConfig.getVar(section,varName) if objConfig.getError(): return False return varsConfig def foundArchFile(self,strCurrentTime,archPathProcess, archPathSuccess, progress=False, remoteServer=False): """Поиск архива профиля пользователя""" # Ищем архив профиля # Задержки при поиске файла архива .process if progress: title = _("Packing the archive on the server") if remoteServer: title += " " + remoteServer title += " ..." progressObj = ProgressBar(title, 0) progressObj.openDialog(title, 0) sleepsFindProcess = [0.1, 0.2, 0.5] # Задержки при проверке размера архива sleepsArch = [0.5, 1, 2] lenFP = len(sleepsFindProcess) lenA = len(sleepsArch) i = 0 # Ждем появления файла архива while(not (os.path.exists(archPathProcess) or\ os.path.exists(archPathSuccess)) and i=lenA: # rsync схема синхронизации if progress: progressObj.shutdownDialog() return False startSize = os.stat(archPathProcess).st_size time.sleep(sleepsArch[i]) if os.path.exists(archPathSuccess): break endSize = os.stat(archPathProcess).st_size if startSize == endSize: i += 1 if progress: progressObj.shutdownDialog() return True def fileReader(self, fileName, stdin, progressObj=False): """Читает файл блоками в поток""" fd = os.open(fileName, os.O_RDONLY) if progressObj: currSize = 0 # Размер файлового буфера в байтах buffSize=131072 dataBlock = os.read(fd, buffSize) while dataBlock: if progressObj: currSize += len(dataBlock) progressObj.setValue(currSize) stdin.write(dataBlock) dataBlock = os.read(fd, buffSize) stdin.close() os.close(fd) def moveArch(self, srcArchFile, dstArchFile, progress=False,\ remoteServer=False, mode=0600): """Перенос архива из одной директории в другую""" class copyTo(color_print): def __init__(self, destFile): self.FD = open(destFile, "w") os.chmod(destFile, mode) def write(self, data): self.FD.write(data) def close(self): self.FD.close() # Создаем прогрессбар if progress: title = _("Copying archive from the server") if remoteServer: title += " " + remoteServer title += " ..." progressObj = ProgressBar(title) archiveSize = os.stat(srcArchFile).st_size progressObj.setMaximum(archiveSize) try: copyToObj = copyTo(dstArchFile) except: self.printERROR(_("Failed to create file '%s'")%dstArchFile) return False # Копирование файла try: if progress: self.fileReader(srcArchFile, copyToObj, progressObj) else: self.fileReader(srcArchFile, copyToObj) except: self.printERROR(_("Failed to copy '%(from)s' -> '%(to)s'")\ %{'from':srcArchFile, 'to':dstArchFile}) return False if progress: progressObj.shutdownDialog() return True def unpackArch(self, homeDir, archFile, progress=False,\ remoteServer=False): """Распаковка архива в домашнюю директорию пользователя""" # Создаем прогрессбар if progress: title = _("Unpacking the archive from the server") if remoteServer: title += " " + remoteServer title += " ..." progressObj = ProgressBar(title) archiveSize = os.stat(archFile).st_size progressObj.setMaximum(archiveSize) execStr = "tar -C '%s' -xzf -" %homeDir # Выполняем разархивацию pipe = subprocess.Popen(execStr, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, env=os.environ, shell=True) # Чтение файла в поток if progress: self.fileReader(archFile, pipe.stdin, progressObj) else: self.fileReader(archFile, pipe.stdin) ret = pipe.wait() pipe.stdout.close() pipe.stderr.close() if progress: progressObj.shutdownDialog() if ret: self.printERROR(_("Failed to execute %s") %execStr) self.printERROR(_("Failed to unpack %s") %archFile) return False return True def setVarToConfig(self, nameSection, varsDict, configFileName, uid, gid): """Записывает переменную в файл конфигурации""" # Создаем директорию для конфигурационных файлов pathConfig = os.path.split(configFileName)[0] if not os.path.exists(pathConfig): self.createUserDir(uid, gid, pathConfig, mode=False) try: if iniParser(configFileName).setVar(nameSection, varsDict): os.chmod(configFileName, 0600) os.chown(configFileName,uid,gid) except: return False return True 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 does not exist") %userDir) return False FD = open(fileName, "w") modeFile, uidFile, gidFile = getModeFile(fileName) if mode != modeFile: os.chmod(fileName, mode) if (uidFile, gidFile) != (uid, gid): os.chown(fileName,uid,gid) if fileTxt: FD.write(fileTxt) FD.close() return True def getDateObjClientConf(self, fileConfig): """Возващает объект времени из конфигурационного файла .calculate/desktop.env""" if os.path.exists(fileConfig): objConfig = iniParser(fileConfig) data = self.getDataInConfig("main", ["date", "date_logout"], objConfig) timeLogout = data["date_logout"] timeConfig = data["date"] timeLogoutObj = False if timeLogout: try: timeLogoutObj = time.strptime(timeLogout, "%Y-%m-%d %H:%M:%S") except: pass timeConfigObj = False if timeConfig: try: timeConfigObj = time.strptime(timeConfig, "%Y-%m-%d %H:%M:%S") except: pass listDataObj = filter(lambda x: x, (timeLogoutObj, timeConfigObj)) if listDataObj: return max(listDataObj) return False def mountUserResAndSync(self, userName, sync=True, progress=False): """Монтирование пользовательских ресурсов и синхронизация настроек""" # Проверяем на root if not self.isRoot(): return False domain = self.clVars.Get("cl_remote_host") if domain: foundMountRemote = isMount("/var/calculate/remote") if not foundMountRemote: self.mountRemote(); # Проверка на повторный вход пользователя if self.isTwoSessionsUser(userName): return False domain = self.clVars.Get("cl_remote_host") hostAuth = self.clVars.Get("os_remote_auth") # В случае компьютера вне домена if not hostAuth or not domain: self.printSUCCESS(_("The local profile will be used")) return True try: passwdUsers = map(lambda x: x[0], map(lambda x: x.split(':'), map(lambda x: x.strip(), open("/etc/passwd").readlines()))) except: self.printERROR(_("Failed to open /etc/passwd")) return False if userName in passwdUsers: try: pwdObj = pwd.getpwnam(userName) except: self.printERROR(_("Failed to found user %s")%userName) self.umountUserRes() return False self.printWARNING(_("User information from /etc/passwd is used")) return True # Проверим что компьютер в домене и смонтирован [remote] connectDomain = self.isDomain() if not connectDomain: # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes() return False # Информация о пользователе из LDAP userLdapInfo = self.ldapDataObj.getUserLdapInfo(userName) if userLdapInfo: uid = int(userLdapInfo['uid']) gid = int(userLdapInfo['gid']) homeDir = userLdapInfo['home'] else: self.printERROR(_("Failed to found user %s in LDAP") %userName) self.umountUserRes() return False # Файл хранения настроек пакета configFileName = os.path.join(homeDir, self.configFileDesktop) # объект времени для текущего профиля currentTimeObj = self.getDateObjClientConf(configFileName) # При отсуствии создаем домашнюю директорию if not os.path.exists(homeDir): os.makedirs(homeDir) os.chown(homeDir,uid,gid) os.chmod(homeDir,0700) # записываем в конфигурационный файл статус "в процессе" self.setVarToConfig("main", {"status_sync":"process"}, configFileName, uid, gid) # Получаем пароль пользователя из ключей ядра userPwd = getKey(userName) if not userPwd: self.printERROR(_("User password not found")) # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes(homeDir) return False # Кеширование пользователя if not self.cAddUserToCache(userName, userPwd): return False # Флаг ошибки flagError = False # Имя удаленного сервера remoteServer = "" if self.clVars.Get("cl_profile_all_set") == "on": osLinuxShort = "all" else: osLinuxShort = self.clVars.Get("os_linux_shortname") # В случае включения репликации на сервере True replOn = self.ldapDataObj.isRepl() if replOn: remoteServer = self.ldapDataObj.getNameRemoteServer(userName, osLinuxShort, domain) # Получаем монтируемые директории names, dictRes = self.getUserMountResources(userName, homeDir, remoteServer) # Путь к профилю пользователя по умолчанию defaultPath = "" # Хост пользователя по умолчанию defaultHost = domain # Флаг - будет использован инкрементальный архив flagIncrArch = True # Ошибка при монтировании удаленного сервера flagErrorMountRemote = False # Ошибка при cинхронизации с удаленного сервера flagErrorSyncRemote = False # Статус синхронизации syncStatus = True # Флаг копирования приватных файлов с сервера в случае # устаревшей даты профиля flagCopyPrivateFiles = False # Если профиль на удаленном сервере if remoteServer and replOn: # имя файла архива в процессе архивации archPathProcess = "" # имя файла архива - после архивации archPathSuccess = "" # Создаем инкрементальный архив в случае репликации prevHost = "" # Дата профиля на текущем сервере dateDefaultTemplate = "" # Текущее время currTime = str(float(time.time())) archPathSrc = "" archPathDst = "" # Создаваемые пути при монтировании for name in names: # Пропускаем монтирование директории если нет синхронизации if not sync and name == "remote_profile": continue path = dictRes[name]["path"] res = dictRes[name]["resource"] # Создаем пользовательскую директории для профиля 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 the directory")) self.printERROR(_("Permission denied: '%s'")%path) flagError = True break #Проверяем на монтирование директории if isMount(path): continue # Имя хоста для монтирования ресурса hostConnect = defaultHost if name == "remote_profile": hostConnect = remoteServer # Монтируем Samba ресурс textLine = self.mountSleepRes(hostConnect, userName, userPwd, uid, gid, res, path) if textLine is False: if name == "remote_profile": flagErrorMountRemote = True break else: self.printERROR( _("Failed to mount Samba resource [%s]")\ %res + " ...") flagError = True break # Если нет синхронизации отменяем обработку if not sync: continue if name == "profile" or name == "remote_profile": # Находим директорию профиля homeTemplate = os.path.join(path, osLinuxShort) if not os.path.exists(homeTemplate): homeTemplate = os.path.join(path, "." + osLinuxShort) # Проверка на дату профиля на удаленном сервере if name == "remote_profile": fileConfigRemote = os.path.join(homeTemplate, self.configFileDesktop) # объект времени для удаленного сервера remoteTimeObj = self.getDateObjClientConf(fileConfigRemote) if remoteTimeObj and currentTimeObj: if currentTimeObj > remoteTimeObj: sync = False flagCopyPrivateFiles = True # Если нет синхронизации отменяем обработку if not sync: continue # Примонтирована директория профиля с текущего сервера if name == "profile": # Перенос профиля на сервере в директорию без точки if not self.upgradeTemplateClient(userName, homeDir, path): flagError = True break fileConfig = os.path.join(homeTemplate, self.configFileDesktop) if os.path.exists(fileConfig): objConfig = iniParser(fileConfig) data = self.getDataInConfig("main", ["status_sync", "date"], objConfig) if data: status = data.get("status_sync") date = data.get("date") if date and status=="success": dateDefaultTemplate = date # Примонтирована директория профиля с удаленного сервера # и есть дата профиля текущего сервера if name == "remote_profile" and dateDefaultTemplate: # Даем команду на создание инкрементального архива проф. fileConfig = \ os.path.join(homeTemplate,self.configFileServer) varsConfig = {"arch_date":dateDefaultTemplate, "curr_time":currTime} self.setServerCommand(["pack"], varsConfig, fileConfig) # Отмонтируем директорию профиля с удаленного сервера textLine = self.umountSleepPath(path) if not textLine: flagError = True break pathTemplateCurr = dictRes["profile"]["path"] homeTemplateCurr = os.path.join(pathTemplateCurr, osLinuxShort) # Синхронизация профиля с текущего сервера hostConnect = defaultHost if not self.syncLoginTemplate(hostConnect, userName,homeDir, homeTemplateCurr, uid, gid, progress): flagError = True break # Монтируем директорию профиля с удаленного сервера hostConnect = remoteServer textLine = self.mountSleepRes(hostConnect,userName,userPwd, uid,gid,res,path) if textLine is False: self.printERROR( _("Failed to mount Samba resource [%s]") %res + " ...") flagError = True break extSuccess = "gz" extProcess = "process" strCurrentTime = currTime.replace(".","_") archPathTmp = os.path.join(path, "profile.%s.%s.tar"\ %(osLinuxShort, strCurrentTime)) # имя файла архива в процессе архивации archPathSuccess = "%s.%s"%(archPathTmp,extSuccess) # имя файла архива - после архивации archPathProcess = "%s.%s"%(archPathTmp,extProcess) # Ищем файл архива if not self.foundArchFile(strCurrentTime, archPathProcess, archPathSuccess, progress, remoteServer): flagIncrArch = False break # Если нет архива применяем rsync синхронизацию if not os.path.exists(archPathSuccess): flagIncrArch = False break else: archPathSrc = archPathSuccess archPathDst = ".".join(filter(lambda x: x!="/", archPathSuccess.rpartition("/"))) # Копируем архив if not self.moveArch(archPathSrc, archPathDst, progress, remoteServer): flagError = True if archPathSrc: # Удаление архивного файла if os.path.exists(archPathSrc): # Удаляем файл архива os.remove(archPathSrc) if archPathDst: # Удаление архивного файла if os.path.exists(archPathDst): # Удаляем файл архива os.remove(archPathDst) break # Распаковываем архив в домашнюю директорию if not self.unpackArch(homeDir, archPathDst, progress, remoteServer): flagError = True if archPathDst: # Удаление архивного файла if os.path.exists(archPathDst): # Удаляем файл архива os.remove(archPathDst) break # Cканирование домашней директории и получение списка # файлов pathListFile = os.path.join(homeTemplate, self.listTemplFile) if not self.removeFilesInTemplate(homeDir,pathListFile): # Отмонтируем пользовательские ресурсы в # случае ошибки self.umountUserRes(homeDir) return False else: if name == "remote_profile": flagIncrArch = False if archPathProcess or archPathSuccess or archPathSrc or archPathDst: # Удаление архивных файлов rmFiles = [archPathProcess, archPathSuccess, archPathSrc, archPathDst] rmFiles = filter(lambda x: x, list(set(rmFiles))) for rmFile in rmFiles: if os.path.exists(rmFile): # Удаляем файл архива os.remove(rmFile) # Если не удалось замонтировать удаленный профиль if flagErrorMountRemote: syncStatus = False # Если не удалось использовать инкрементальный архив elif flagIncrArch is False: # Синхронизируем удаленный профиль pathTemplate = dictRes["remote_profile"]["path"] homeTemplate = os.path.join(pathTemplate, osLinuxShort) if not self.upgradeTemplateClient(userName, homeDir, pathTemplate): # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes(homeDir) return False # Синхронизация профиля с удаленного сервера hostConnect = remoteServer ret = self.syncLoginTemplate(hostConnect, userName, homeDir, homeTemplate, uid, gid, progress, False) if not ret: flagErrorSyncRemote = True syncStatus = False # Если не удалось синхронизировать удаленный синхронизируем # текущий homeTemplate = os.path.join(dictRes["profile"]["path"], osLinuxShort) # Синхронизация профиля пользователя с текущего сервера hostConnect = defaultHost if not self.syncLoginTemplate(hostConnect,userName,homeDir, homeTemplate,uid,gid,progress): # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes(homeDir) return False if not flagError and flagCopyPrivateFiles: configFileName = os.path.join(homeDir, self.configFileDesktop) # записываем в конфигурационный файл статус # Write to the configuration file status self.setVarToConfig("main", {"status_sync":"success"}, configFileName, uid, gid) # Копируем приватные файлы с сервера # Copy private files from the server self.copyPrivateFiles(homeTemplate, homeDir) # Отмонтируем директорию профиля пользователя на удаленном сервере if sync and "remote_profile" in dictRes: umountPath = dictRes["remote_profile"]["path"] if isMount(umountPath): textLine = self.umountSleepPath(umountPath) if not textLine: # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes(homeDir) return False # Удаляем директорию удаленного профиля у клиента if os.path.exists(umountPath) and not os.listdir(umountPath): os.rmdir(umountPath) # Если профиль на текущем сервере elif not remoteServer and replOn or not replOn: # Создаваемые пути при монтировании for name in names: path = dictRes[name]["path"] res = dictRes[name]["resource"] # Создаем пользовательскую директории для профиля 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 the directory")) self.printERROR(_("Permission denied: '%s'")%path) flagError = True break # Проверяем на монтирование директории if isMount(path): continue # Имя хоста для монтирования ресурса hostConnect = defaultHost textLine = self.mountSleepRes(hostConnect, userName, userPwd, uid, gid, res, path) if textLine is False: self.printERROR( _("Failed to mount Samba resource [%s]") %res + " ...") flagError = True break # Если нет синхронизации отменяем обработку if not sync: continue if name == "profile": pathTemplate = path # Перенос профиля на сервере в директорию без точки if not self.upgradeTemplateClient(userName, homeDir, pathTemplate): flagError = True break # Директория профиля homeTemplate = os.path.join(path, osLinuxShort) # Проверка на дату профиля на текущем сервере fileConfigThis = os.path.join(homeTemplate, self.configFileDesktop) thisTimeObj = self.getDateObjClientConf(fileConfigThis) if thisTimeObj and currentTimeObj: if currentTimeObj > thisTimeObj: sync = False flagCopyPrivateFiles = True # Если нет синхронизации отменяем обработку if not sync: continue # Синхронизация профиля пользователя с текущего сервера hostConnect = defaultHost if not self.syncLoginTemplate(hostConnect, userName,homeDir, homeTemplate, uid, gid, progress): # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes(homeDir) return False if not flagError and flagCopyPrivateFiles: configFileName = os.path.join(homeDir, self.configFileDesktop) # записываем в конфигурационный файл статус # Write to the configuration file status self.setVarToConfig("main", {"status_sync":"success"}, configFileName, uid, gid) # Копируем приватные файлы с сервера # Copy private files from the server self.copyPrivateFiles(homeTemplate, homeDir) if flagError: # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes(homeDir) return False self.unpackLinks(homeDir) if sync: logOutFile = os.path.join(homeDir,self.logOutFile) if syncStatus: self.setVarToConfig("main", {"status_sync":"success"}, configFileName, uid, gid) self.createUserFile(logOutFile,"SUCCESS", uid, gid) else: self.createUserFile(logOutFile,"ERROR", uid, gid) self.setVarToConfig("main", {"status_sync":"error"}, configFileName, uid, gid) self.printWARNING( _("Error synchronizing with the remote server %s") %remoteServer) self.printSUCCESS(_("Mounted user resource of the domain")) if sync: self.printOK(_("Get a user profile from the domain") + " ...") return True def isSessionUser(self, userName): """Проверка на вход пользователя в X""" xSession = False reFoundUser = re.compile("%s\s+:\d+\s+"%(userName)) resWho = self.execProg("who") if resWho: for string in resWho: if reFoundUser.search(string): xSession = True break return xSession def tarSymLinks(self,userHome,uid,gid): """Create tar archive of symlinks""" linkArch = pathJoin(userHome,".calculate/links.tar.bz2") try: for filename in tarLinks(userHome,linkArch, skip=self.clVars.Get("cl_sync_del_path")+ self.clVars.Get("cl_sync_skip_path")): try: os.unlink(filename) except OSError: self.printWARNING(_("Failed to remove %s")%filename) except: self.printWARNING(_("Failed to make links archive")) try: if os.path.exists(linkArch): os.chown(linkArch,uid,gid) except: self.printWARNING(_("Failed to make links archive")) def unpackLinks(self,userHome): """Unpack archive of symlinks""" linksArch = pathJoin(userHome,".calculate/links.tar.bz2") try: if os.path.exists(linksArch): tf = tarfile.open(linksArch) tf.extractall(userHome) tf.close() except: self.printWARNING(_("Failed to unpack links archive")) def moveHomeDir(self, userHome): """Переносим файлы пользователя в Home/Moved""" # Находим директории и файлы в домашней директории pathProg = os.getcwd() os.chdir(userHome) dirs = [] files = [] movedLink = os.path.join('Moved') movedPath = os.path.join('Home',"Moved") skipPaths = self.clVars.Get("cl_moved_skip_path") if not skipPaths: skipPaths = ['Disks','Share','Home','Moved','FTP','Desktop'] filesAndDir = filter(lambda x: not ('.' in x[0] or x in\ skipPaths or os.path.islink(x)), os.listdir('.')) filesDir = [] for fd in filesAndDir: if os.path.islink(fd): os.unlink(fd) else: filesDir.append(fd) # Нахождение файлов отличных ссылок и .* в Desktop pathDesktop = './Desktop' filesDirDesk = [] if os.path.exists(pathDesktop): filesDirDesk = filter(lambda x: not os.path.islink(x) and\ not os.path.split(x)[1].startswith('.') and\ not x.rpartition('.')[2]=='desktop', map(lambda x: os.path.join(pathDesktop,x),\ os.listdir(pathDesktop))) movedPathDesk = os.path.join(movedPath, pathDesktop) filesDir += filesDirDesk if not filesDir or not os.path.exists("Home"): # Удаляем пустую папку Moved if os.path.exists(movedPath) and not os.listdir(movedPath): os.rmdir(movedPath) # Удалям ссылку на Moved в домашней директории if os.path.islink(movedLink) and not os.path.exists(movedPath): os.unlink(movedLink) return True if not os.path.exists(movedPath): os.mkdir(movedPath) directFile = os.path.join(movedPath,".directory") if not os.path.exists(directFile): txt = "[Desktop Entry]\nIcon=folder-development" fd = os.open(directFile, os.O_CREAT) os.close(fd) FD = open (directFile, "r+") FD.write(txt) FD.close() if not os.path.exists(movedLink): os.symlink(movedPath, movedLink) for fd in filesDir: execStr = "cp -r '%s' '%s'" %(fd, movedPath) textLine = self.execProg(execStr) if textLine is False: self.printERROR(_("Failed to execute") + " " + str(execStr) +\ " ...") return False execStr = "rm -rf '%s'" %fd textLine = self.execProg(execStr) if textLine is False: self.printERROR(_("Failed to execute") + " " + str(execStr) +\ " ...") return False os.chdir(pathProg) return True def removeNoiseFiles(self, userHome): """Удаление файлов, создающих помехи работе dm""" noiseFiles = [] for nsFile in noiseFiles: rmFile = os.path.join(userHome, nsFile) if os.path.exists(rmFile): os.remove(rmFile) return True def getPrivateFiles(self, userHome): """Все приватные файлы пользователя относительно директории userHome""" privateFiles = [] for privateHomeDir in self.privateDirs: privateDir = os.path.join(userHome,privateHomeDir) if os.path.isdir(privateDir): # .ssh файлы относительно домашней директории пользователя privateFiles += map(lambda x:os.path.join(privateHomeDir,x), filter(lambda x:\ os.path.isfile(os.path.join(privateDir,x)), os.listdir(privateDir))) return self.privateFiles + privateFiles def removePrivateFiles(self, userHome): """Удаление приватных файлов""" privateFiles = self.getPrivateFiles(userHome) for prFile in privateFiles: rmFile = os.path.join(userHome, prFile) if os.path.exists(rmFile): os.remove(rmFile) return True def copyPrivateFiles(self, serverProfileDir, userHomeDir): """Копирование приватных файлов c сервера в домашнюю директорию""" privateFiles = self.getPrivateFiles(serverProfileDir) for prFile in privateFiles: src = os.path.join(serverProfileDir, prFile) dst = os.path.join(userHomeDir, prFile) if os.path.exists(src): dstPath = os.path.dirname(dst) if not os.path.exists(dstPath): listElPath = [] for el in filter(lambda x: x, dstPath.partition(userHomeDir)[2].split("/")): listElPath.append(el) joinPath = "/".join(listElPath) dPath = os.path.join(userHomeDir, joinPath) if not os.path.exists(dPath): sPath = os.path.join(serverProfileDir, joinPath) sMode, sUid, sGid = getModeFile(sPath) os.mkdir(dPath,sMode) os.chown(dPath, sUid, sGid) copy2(src, dst) sUid, sGid = getModeFile(src, mode="owner") os.chown(dst, sUid, sGid) return True def scanDirectory(self, scanDir, listFiles, skipPath=[], prefix=False, flagDir=False): """Генерация списка файлов и директорий""" if not prefix: prefix = os.path.join(scanDir,"") if flagDir or stat.S_ISDIR(os.lstat(scanDir)[stat.ST_MODE]): for fileOrDir in os.listdir(scanDir): absPath = os.path.join(scanDir,fileOrDir) relPath = absPath.split(prefix)[1] if relPath in skipPath: continue listFiles.append(relPath) stInfo = os.lstat(absPath) statInfo = stInfo[stat.ST_MODE] if stat.S_ISDIR(statInfo): self.scanDirectory(absPath, listFiles, skipPath, prefix, True) def getListFilesTemplate(self, homeDir): """Генерация списка файлов в домашней директориии Исключая монтируемые директории """ home = os.path.join(homeDir, "") execStr = "mount" textLines = self.execProg(execStr) # Пропускаемые директории для сканирования skipPaths = [] if textLines: for line in textLines: if home in line: skipPath =\ line.partition(home)[2].rpartition(" type")[0] skipPaths.append(skipPath) # Список файлов в профиле пользователя listFiles = [] if not skipPaths: self.printERROR(_("Mounting point for server resources not found")) return False self.scanDirectory(homeDir, listFiles, skipPaths) return listFiles def removeFilesInTemplate(self, homeDir, pathListFile): """Удаляем файлы которых нет в профиле пользователя""" # Получаем файлы профиля на сервере try: filesTemplateTxt = open(pathListFile).read() except: self.printERROR(_("Failed to open %s")%pathListFile) return False listFilesTemplate = filter(lambda x: x.strip(), filesTemplateTxt.split("\n")) filesTemplate = set(listFilesTemplate) # Получаем файлы в домашней директории listFilesHome = self.getListFilesTemplate(homeDir) if listFilesHome is False: return False filesHome = set(listFilesHome) filesRemove = list(filesHome - filesTemplate) filesRemove.sort(lambda x, y: cmp(len(y), len(x))) rmPath = "" try: for rmFile in filesRemove: rmPath = os.path.join(homeDir, rmFile) if os.path.islink(rmPath): os.unlink(rmPath) elif os.path.isfile(rmPath): os.remove(rmPath) elif os.path.isdir(rmPath): os.rmdir(rmPath) else: os.remove(rmPath) except: self.printERROR(_("Failed to remove %s")%rmPath) return False return True def getRunCommandsWithEnv(self): """List run program""" def getCmd(procNum): cmdLineFile = '/proc/%s/cmdline'%procNum environFile = '/proc/%s/environ'%procNum try: if os.path.exists(cmdLineFile) and \ os.path.exists(environFile): return (open(cmdLineFile,'r').read().strip(), open(environFile,'r').read().strip()) except: pass return ("","") if not os.access('/proc',os.R_OK): return [] return map(getCmd, filter(lambda x:x.isdigit(), os.listdir('/proc'))) def clearUserKey(self, userName): """Очищает пользовательский ключ ядра""" # Ищем ключ в ядре и проверяем не выполняет ли он повторный вход if getKey(userName) and not \ filter(lambda x:"xdm/xdm\x00--login" in x[0] and \ ("USER=%s"%userName) in x[1],self.getRunCommandsWithEnv()): # очищаем ret = clearKey(userName) if ret == 0: return True else: self.printERROR(_("Failed to clear the kernel key for user %s")\ %userName) return False return True def umountUserResAndSync(self, userName, progress=False, sync=True): """Отмонтирование пользовательских ресурсов и синхронизация настроек""" # Проверяем на root if not self.isRoot(): return False try: passwdUsers = map(lambda x: x[0], map(lambda x: x.split(':'), map(lambda x: x.strip(), open("/etc/passwd").readlines()))) except: self.printERROR(_("Failed to open /etc/passwd")) return False currentDateStr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) if userName in passwdUsers: try: pwdObj = pwd.getpwnam(userName) except: self.printERROR(_("Failed to found user %s")%userName) self.umountUserRes() return False homeDir = pwdObj.pw_dir uid = pwdObj.pw_uid gid = pwdObj.pw_gid configFileName = os.path.join(homeDir, self.configFileDesktop) self.setVarToConfig("main", {"date_logout":currentDateStr}, configFileName, uid, gid) self.printWARNING(_("User information from /etc/passwd is used")) self.printSUCCESS(_("The local profile will be used")) return True domain = self.clVars.Get("cl_remote_host") hostAuth = self.clVars.Get("os_remote_auth") # В случае компьютера вне домена if not hostAuth or not domain: self.printSUCCESS(_("The local profile will be used")) return True connectDomain = self.isDomain() if not connectDomain: # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes() return False # Если пользователь в X сессии тогда не будем отмонтировать ресурсы if self.isSessionUser(userName): self.printERROR(_("User %s is already in X session")%userName) self.printERROR(_("Failed to unmount user %s resource")%userName) return False # Информация о пользователе из LDAP userLdapInfo = self.ldapDataObj.getUserLdapInfo(userName) if userLdapInfo: uid = int(userLdapInfo['uid']) gid = int(userLdapInfo['gid']) homeDir = userLdapInfo['home'] else: self.printERROR(_("Failed to found user %s in LDAP")%userName) self.umountUserRes() return False # Файл хранения настроек пакета configFileName = os.path.join(homeDir, self.configFileDesktop) self.setVarToConfig("main", {"date_logout":currentDateStr}, configFileName, uid, gid) if os.path.exists(homeDir): self.moveHomeDir(homeDir) if sync: self.tarSymLinks(homeDir,uid,gid) else: # Отмонтируем пользовательские ресурсы в случае ошибки self.printERROR(_("Directory %s not found") % homeDir) self.umountUserRes(homeDir) return False # необходима ли синхронизация с локальным сервером needSync = sync # проверяем произведен ли корректный вход в систему - # в этом случае закачиваем профиль с локальной машины на сервер exitStr = iniParser(configFileName).getVar('main','status_sync') # проверяем cтатус синхронизации: не содержит ли он "process" if exitStr == "process" or exitStr == "error": needSync = False if self.clVars.Get("cl_profile_all_set") == "on": osLinuxShort = "all" else: osLinuxShort = self.clVars.Get("os_linux_shortname") # Получаем монтируемые директории names, dictRes = self.getUserMountResources(userName, homeDir, False) pathUnix = dictRes["profile"]["path"] homeTemplate = os.path.join(pathUnix, osLinuxShort) if needSync: # Синхронизация профиля пользователя на сервер if not self.syncUser(userName, homeDir, "logout", uid, gid, homeTemplate): # Удаляем файлы, мешающие работе dm self.removeNoiseFiles(homeDir) # Удаляем приватные файлы self.removePrivateFiles(homeDir) # Очищаем ключ в ядре self.clearUserKey(userName) # Отмонтируем пользовательские ресурсы в случае ошибки self.umountUserRes(homeDir) return False # Удаляем файлы, мешающие работе dm self.removeNoiseFiles(homeDir) # Удаляем приватные файлы self.removePrivateFiles(homeDir) # Очищаем ключ в ядре self.clearUserKey(userName) # Отмонтируем директории if not self.umountUserRes(homeDir): return False # Удаляем пустые директории for name in names: path = dictRes[name]["path"] if os.path.exists(path) and not os.listdir(path): try: os.rmdir(path) except: self.printERROR(_("Failed to remove dir %s")% path) return False # Удаляем Disks директорию если она пуста pathDisks = os.path.join(homeDir,"Disks") if os.path.exists(pathDisks) and not os.listdir(pathDisks): try: os.rmdir(pathDisks) except: self.printERROR(_("Failed to remove dir %s")% pathDisks) return False if needSync: self.printSUCCESS(_("User profile saved in the domain")) self.printOK(_("Domain user resource unmounted") + " ...") return True def getMountUserPaths(self, homeDir=False): """Находит пользовательские примонтированные пути""" # Имя пользователя if not homeDir: userName = self.clVars.Get("ur_login") try: homeDir = pwd.getpwnam(userName).pw_dir except: homeDir = os.path.join("/home",userName) dirStart, dirEnd = os.path.split(homeDir) mountTemplateDir = os.path.join(dirStart, ".%s" %dirEnd) mountRemoteTemplateDir = os.path.join(dirStart, ".%s.remote" %dirEnd) return filter(lambda x: x.startswith(homeDir) or\ x.startswith(mountTemplateDir) or\ x.startswith(mountRemoteTemplateDir), map(lambda x: x.split(" ")[1],\ open("/proc/mounts").readlines())) def execProg(self, cmdStrProg, inStr=False, envProg={}): """Выполняет внешнюю программу Параметры: cmdStrProg внешняя программа inStr данные передаваемые программе на страндартный вход. Возвращаемые параметры: строки которые выведет внешняя программа или False в случае ошибки """ env_path = {"PATH":getpathenv()} env = {} env.update(os.environ.items() + env_path.items() + envProg.items()) retCode,programOut = runOsCommand(cmdStrProg,in_str=inStr,env_dict=env) if not retCode: return programOut return False def umountSleepPath(self, path): """Отмонтирует путь при неудаче задержка потом повтор""" # Задержки при отмонтированиии директории sleeps = [0.5, 1, 2] # Проверяем на монтирование директорию if isMount(path): textLines = self.execProg("umount %s"%path) if textLines is False: i = 0 flagError = False while (i/dev/null') if not pwDialog: pwDialog = _("Password") userPwd = getpass.getpass(pwDialog+":") return userPwd def addDaemonAutostart(self, daemon): """Прописывает демона в автозагрузку""" defaultDaemons = self.getDefaultRunlevelDaemons() if defaultDaemons is False: return False if daemon in defaultDaemons: return True execStr = "rc-update add %s default" %daemon textLine = self.execProg(execStr) if textLine is False: self.printERROR(_("ERROR") + ": " + execStr) self.printERROR(_("Failed to add to the default runlevel")) return False else: return True def restartDBus(self): """Перезапускаем службу D-Bus""" dbusDaemon = 'rc-service -i dbus' existsDaemon = 'rc-service -e dbus' # если установлена и запущена if os.system(existsDaemon) == 0 and \ os.system(dbusDaemon + ' status &>/dev/null') == 0: # пезапустить if os.system(dbusDaemon + ' restart -- -s &>/dev/null') != 0: self.printWARNING(_("Error restarting")+" "+dbusDaemon+" ...") return False return True def getInfoService(self, service, option, envFile=False): """Получить параметр сервиса из env""" if not envFile: if self.convObj == None: # файл /var/calculate/remote/server.env envFile = self.clVars.Get("cl_env_server_path") if os.access(envFile, os.R_OK): self.convObj = False elif os.access("/var/calculate/remote/calculate.env", os.R_OK): self.convObj = convertEnv() if self.convObj: return self.convObj.getVar(service, option) if not self.optionsInfo: if not envFile: # файл /var/calculate/remote/server.env envFile = self.clVars.Get("cl_env_server_path") optInfo = self.clVars.GetRemoteInfo(envFile) if optInfo is False: return False if optInfo: self.optionsInfo = optInfo value = '' if service in self.optionsInfo and option in self.optionsInfo[service]: value = self.optionsInfo[service][option] return value def addDomain(self, domainName): """Вводим в домен""" # Проверяем на root if not self.isRoot(): return False 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) if resPing is False: self.printERROR(_("Failed to execute \"%s\"")%execStr) return False foudHost = False foundHostSamba = False foundMountRemote = False reFoundHost = re.compile("(\d+)\% packet loss") if resPing 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(_("Domain %s not found")%domain) return False reFoundHostSamba = re.compile("Server=\[Samba.+\]") resSmbClient = self.execProg("smbclient -N -L %s" %domain) if not resSmbClient is False: for string in resSmbClient: if reFoundHostSamba.search(string): foundHostSamba = True break if not foundHostSamba: self.printERROR(_("Samba server not found in %s")%domain) return False remoteHost = self.clVars.Get("cl_remote_host") if remoteHost: self.printERROR(_("The computer is already in the domain %s")\ %remoteHost) self.printWARNING(_("Before joining the domain, " "you need to remove it from the previous domain")) self.printWARNING(_("Run 'cl-client -r'")) return False foundMountRemote = isMount("/var/calculate/remote") if foundMountRemote: self.printERROR(_("Samba resource [%s] mounted")%\ "remote" + " ...") return False else: def passwdQueue(): remotePw = self.clVars.Get('cl_remote_pw') if remotePw: yield remotePw yield self.getUserPassword(\ _("Domain password for the desktop")) pathRemote = "/var/calculate/remote" for pwdRemote in passwdQueue(): 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, envProg={"PASSWD":pwdRemote}) if not textLine is False: self.printSUCCESS(_("Samba resource [%s] mounted")%"remote" + \ " ...") self.clVars.Write("cl_remote_host", domain, True, "local") self.clVars.Write("cl_remote_pw", pwdRemote, True, "local") break else: self.printERROR(_("Failed to mount Samba resource [%s]")%\ "remote" + " ...") return False servDn = self.getInfoService("ldap", "services_dn") unixDn = self.getInfoService("unix", "dn") bindDn = self.getInfoService("unix", "bind_dn") bindPw = self.getInfoService("unix", "bind_pw") # запишем их if not (servDn and unixDn and bindDn and bindPw): self.printERROR(_("Info not found on the server") + ": " +\ _("services DN or unix DN or bind DN or bind password")) return False # Наложим шаблоны - domain # Действие - ввод в домен self.clVars.Set("cl_action", "domain", True) # Доменная авторизация self.clVars.Set("os_remote_auth", domain) if not self.applyTemplatesFromSystem(): self.printERROR( _("Failed to apply install templates or domain templates")) return False # Рестартуем dbus self.restartDBus() if not self.addDaemonAutostart("client"): return False # Записываем переменную self.clVars.Write("os_remote_auth", domain, True) # Записываем текущую версию программы currentVersion = self.clVars.Get("cl_ver") self.clVars.Write("os_remote_client", currentVersion, True) self.printOK(_("Computer added to domain %s")%domain + " ...") return True def relevanceTemplates(self, hostAuth): """Определяем актуальность наложенных шаблонов в зависимости от версии программы Перед запуском обязательно должен быть определен объект переменных self.clVars Если актуальны - True, если нет False """ # '' или имя хоста if hostAuth != self.clVars.Get("os_remote_auth"): return False clTempl = template(self.clVars) # Текущая версия программы currentVersion = self.clVars.Get("cl_ver") # Версия программы которая ранее работала с шаблонами previousVersion = self.clVars.Get("os_remote_client") cVersion,pVersion = clTempl._convertVers(currentVersion,previousVersion) # Если версии программ не равны то шаблоны не актуальные if cVersion != pVersion: return False return True def applyRelevanceTemplates(self, hostAuth=""): """Накладывает релевантные шаблоны Перед запуском обязательно должен быть определен объект переменных self.clVars """ if not self.relevanceTemplates(hostAuth): if hostAuth: # Устанавливаем действие вход в домен self.clVars.Set("cl_action","domain",True) else: # Устанавливаем действие выход из домена self.clVars.Set("cl_action","undomain",True) self.clVars.Set("os_remote_auth", hostAuth) # Наложим шаблоны dirsAndFiles = self.applyTemplatesFromSystem() if not dirsAndFiles: if hostAuth: self.printERROR(_("Failed to apply domain templates")) else: self.printERROR(_("Failed to apply undomain templates")) return False if hostAuth: self.printOK(_("Templates set for network mode")) currentVersion = self.clVars.Get("cl_ver") self.clVars.Write("os_remote_client", currentVersion, True) self.clVars.Write("os_remote_auth", hostAuth, True) else: self.printOK(_("Templates set for local mode")) self.clVars.Delete("os_remote_auth") self.clVars.Delete("os_remote_client") return True def cDelLdapSysUsersAndSyncCache(self): """Delete LDAP users from system and synchronize cache""" cacheObj = userCache() if not cacheObj.deleteCacheUsersFromSystem(): return False if not cacheObj.syncCacheToLdap(): return False return True def cAddCacheUsersFromSystem(self): """Add cache users from system""" cacheObj = userCache() if not cacheObj.addCacheUsersFromSystem(): return False return True def cAddUserToCache(self, userName, userPwd): """Add user to cache""" cacheObj = userCache() pwdHash = self.getHashPasswd(userPwd, "shadow_ssha256") if not cacheObj.addUserToCache(userName, pwdHash): return False return True def cDelLdapSysUsersAndClearCache(self): """Delete LDAP users from system and clear cache""" cacheObj = userCache() if not cacheObj.deleteCacheUsersFromSystem(): return False if not cacheObj.clearCache(): return False return True def moveHomeDirs(self): """Move home dirs /var/calculate/client-home -> /home if user in cache """ cacheObj = userCache() loginUsersData = cacheObj.getLoginDomainUsers() if loginUsersData is False: return False previousHome = "/var/calculate/client-home" if isMount(previousHome): return True if os.path.exists(previousHome): flagMovedUsers = False for userName,x,uid,gid,gecos,directory,shell in loginUsersData: homeDir = directory pathUserList = filter(lambda x: x, directory.split('/')) if not pathUserList: continue pathUser = "/".join(pathUserList[1:]) srcDir = pathJoin(previousHome, pathUser) if os.path.exists(srcDir) and not os.path.exists(homeDir): flagMovedUsers = True destDir = os.path.dirname(homeDir) self.printWARNING(_("Moved %(src)s to %(dest)s")\ %{"src":srcDir,"dest":homeDir}) if not self.copyTemplateDir(srcDir, destDir): return False if flagMovedUsers and not os.listdir(previousHome): os.rmdir(previousHome) return True def mountRemote(self): """Монтирование remote если компьютер в домене а так-же ввод в домен если найдено имя хоста и пароль для подключения """ # Проверяем на root if not self.isRoot(): return False domain = self.clVars.Get("cl_remote_host") if domain: foundMountRemote = isMount("/var/calculate/remote") else: self.printWARNING(_("This computer is not in the domain")) # Если шаблоны не актуальны накладываем новую версию шаблонов if not self.applyRelevanceTemplates(): return False return True if foundMountRemote: self.printWARNING(_("Samba resource [%s] mounted")%"remote" + \ " ...") # Накладываем сетевые шаблоны if domain: self.clVars.flIniFile() if not self.applyRelevanceTemplates(domain): return False # Delete LDAP users from system and synchronize cache if not self.cDelLdapSysUsersAndSyncCache(): return False return True else: pathRemote = "/var/calculate/remote" pwdRemote = self.clVars.Get("cl_remote_pw") if not (domain and pwdRemote): self.printERROR(_("Variable not found")+\ ": 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, envProg={"PASSWD":pwdRemote}) if textLine is False: self.printWARNING(_("Failed to mount Samba resource [%s]")%\ "remote" + " ...") beforeRemoteAuth = self.clVars.Get('os_remote_auth') # Если шаблоны не актуальны накладываем новую версию шаблонов if not self.applyRelevanceTemplates(): return False if not self.cAddCacheUsersFromSystem(): return False if not self.moveHomeDirs(): return False if beforeRemoteAuth != self.clVars.Get('os_remote_auth'): self.restartDBus() return True self.printSUCCESS(_("Samba resource [%s] mounted") % "remote" +\ " ...") # Накладываем сетевые шаблоны if domain: self.clVars.flIniFile() beforeRemoteAuth = self.clVars.Get('os_remote_auth') if not self.applyRelevanceTemplates(domain): return False # Delete LDAP users from system and synchronize cache if not self.cDelLdapSysUsersAndSyncCache(): return False if beforeRemoteAuth != self.clVars.Get('os_remote_auth'): self.restartDBus() return True def updateEnvFiles(self): """Апдейт env файлов до версии 2.2""" previousVersion = self.clVars.Get("os_remote_client") flagUpdate = False if not previousVersion: # Апдейт envFile="/var/calculate/calculate.env" remoteHost = self.getInfoService("client", "cl_remote_host", envFile=envFile) remotePw = self.getInfoService("client", "cl_remote_pw", envFile=envFile) if remoteHost and remotePw: self.clVars.Write("cl_remote_host", remoteHost, True, "local") self.clVars.Write("cl_remote_pw", remotePw, True, "local") envFile="/etc/calculate/calculate.env" previousVersion = self.getInfoService("client", "os_remote_client", envFile=envFile) workHost = self.getInfoService("client", "os_remote_auth", envFile=envFile) if previousVersion and workHost: self.clVars.Write("os_remote_client", previousVersion, True) if workHost != "local": self.clVars.Write("os_remote_auth", workHost, True) self.printOK(_("Env files updated") + " ...") flagUpdate = True return flagUpdate def setUserPasswordToServer(self): """Установка пароля пользователя на сервере""" # Проверяем на root if self.isRoot(False): self.printERROR(_("The user is root")) self.printWARNING(\ _("The program can be executed by a non-root user only")) return False # DNS имя хоста server = self.ldapDataObj.getHost() # DN пользователей usersDN = self.ldapDataObj.getUsersDN() if not (server and usersDN): self.printERROR(_("The computer is not in the domain")) self.printWARNING(_("Use passwd")) return False count = 2 # Получаем старый пароль пользователя curPassword = self.getUserPassword(_("Enter the current password")) if not curPassword: self.printERROR(_("The current password is empty")) for i in range(count): count -= 1 # Получаем старый пароль пользователя curPassword = self.getUserPassword( _("Enter the current password")) if curPassword: break self.printERROR(_("The current password is empty")) if not curPassword: return False userDN = self.ldapDataObj.addDN("uid=%s"%os.environ["USER"], usersDN) # Проверяем в LDAP сервере текущий пароль пользователя ret, err = self.checkUserPwdLDAP(server, userDN, curPassword) if not ret: self.printERROR(err) for i in range(count): # Получаем старый пароль пользователя curPassword = self.getUserPassword( _("Enter the current password")) if not curPassword: self.printERROR(_("The current password is empty")) continue # Проверяем в LDAP сервере текущий пароль пользователя ret, err = self.checkUserPwdLDAP(server, userDN, curPassword) if ret: break self.printERROR(err) if not ret: return False password = self.getUserPwd({"p":""}, "p", False) if password is False: for i in range(2): password = self.getUserPwd({"p":""}, "p", False) if password: break if password is False: return False # Переменные для записи в env файл varsConfig = {"unix_hash":self.getHashPasswd(password,"ssha"), "samba_lm_hash":self.getHashPasswd(password,"lm"), "samba_nt_hash":self.getHashPasswd(password,"nt"), "samba_nt_hash_old":self.getHashPasswd(curPassword,"nt")} if filter(lambda x: not x, varsConfig.values()): return False # ~/.calculate/server.env fileConfig = os.path.join(os.environ["HOME"], self.configFileServer) if not self.setServerCommand(["passwd_samba"], varsConfig, fileConfig): return False self.printOK(_("%s's password changed")%os.environ["USER"] + \ " ...") self.printWARNING(_("The password will be changed when you log " "out from the X session")) return True