# -*- coding: utf-8 -*- # Copyright 2010-2013 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 stat import re import sys import subprocess import ldap import time import getpass from os import path from calculate.lib.cl_ini_parser import iniParser from calculate.lib.cl_print import color_print from calculate.lib.utils.ip import Pinger, IPError from calculate.lib.utils.files import (getModeFile, removeDir, isMount, pathJoin, tarLinks, listDirectory, process, find, STDOUT, sambaPasswordCheck, checkUtils) from calculate.lib.utils.common import cmpVersion from calculate.desktop._cl_keys import getKey, clearKey from calculate.lib.convertenv import convertEnv from calculate.lib.encrypt import encrypt from client_cache import userCache from shutil import copy2 from socket import gethostbyname import tarfile from calculate.desktop.desktop import Desktop from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate _ = lambda x: x setLocalTranslate('cl_client3', sys.modules[__name__]) __ = getLazyLocalTranslate(_) class ClientError(Exception): pass class RsyncProgressBar: """ Rsync with using setProgress """ # get number of send file from info rsync stream senderre = re.compile("\[sender\] i=(\d+) ", re.S) # get number of receivce file from info rsync rtream receiverre = re.compile("recv_generator\(.+,([0-9]+)\)", re.S) pipe = None maximum = 1 copyStarting = False def __init__(self, title, secondtitle, rsyncstr, parent, maximum=1, rsyncver="3.0.9"): self.title = title self.secondtitle = secondtitle self.maximum = maximum self.rsyncstr = rsyncstr self.parent = parent self.value = 0 self.errors = [] if cmpVersion(rsyncver, "3.1.0") >= 0: self.extraopts = " --msgs2stderr" self.use_stderr = True else: self.extraopts = "" self.use_stderr = False def getFilesNum(self): """ Get files count created by generator """ if self.pipe: return self.value def getExitCode(self): """ Get rsync exitcode """ if self.pipe: return self.pipe.wait() return 255 def getErrMessage(self): """ Rsync error message """ if self.pipe: if self.use_stderr: return "".join(self.errors) else: return self.pipe.stderr.read() return _("RsyncProgressBar: Wrong pipe") def runsilent(self): """ Run rsync without progressbar """ self.pipe = subprocess.Popen(self.rsyncstr + self.extraopts, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True) while True: if self.use_stderr: s = self.pipe.stderr.readline() else: 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): """ Run rsync with progressbar """ self.parent.startTask(self.title, progress=True) self.pipe = subprocess.Popen(self.rsyncstr + self.extraopts, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True) oldpercent = 0 while True: if self.use_stderr: s = self.pipe.stderr.readline() else: s = self.pipe.stdout.readline() if len(s) == 0: break if s.startswith("rsync:"): self.errors.append(s) q = self.receiverre.search(s) if q: if not self.copyStarting: self.parent.endTask() self.parent.startTask(self.secondtitle, progress=True) # self.parent.setProgress(0) self.copyStarting = True self.value = int(q.groups()[0]) if self.maximum: newvalue = int(100 * self.value / self.maximum) newvalue = min(newvalue, 99) if newvalue > oldpercent: self.parent.setProgress(newvalue) oldpercent = newvalue self.parent.setProgress(100) def close(self): self.parent.endTask() class commandServer(color_print): """Server command object""" def setServerCommand(self, command, varsCommand, fileConfig, uid=None, gid=None): """Set server command""" pathConfig = os.path.split(fileConfig)[0] # create user config directory 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): raise ClientError(_("Failed to write variables in file %s") \ % fileConfig) 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 - структура для вывода приглашения в режиме диалога """ 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] + _(":")) else: return "" if not pwdA or not (pwdA == pwdB): self.printERROR(_("ERROR") + _(": ") + \ _("passwords do not match")) return False userPwd = pwdA return userPwd class Client(commandServer, encrypt, Desktop): """ Клиентский модуль работы с доменом """ # user calculate config directory pathConfig = ".calculate" # client soft config file configFileSoft = os.path.join(pathConfig, "ini.env") # client config file configFileDesktop = os.path.join(pathConfig, "desktop.env") # client config for server configFileServer = os.path.join(pathConfig, "server.env") # file list of user profile listTemplFile = os.path.join(pathConfig, "files.txt") # files which not cleaning from user home directory skipHomeFile = ["Home", "Disks", "Share", "FTP", configFileDesktop] # option dict of services from /var/calculate/remote/server.env optionsInfo = {} # convertation object from old remote env convObj = None # private user files privateFiles = [configFileServer] # private user directories privateDirs = [] def removeVars(self): """ Remove domain variables. Useing on remove package and undomain action """ self.clVars.Delete("cl_remote_host", "local") self.clVars.Delete("cl_remote_pw", "local") self.clVars.Delete("os_remote_auth") self.clVars.Set("cl_remote_host", "", True) self.clVars.Set("cl_remote_pw", "", True) self.clVars.Set("os_remote_auth", "", True) return True def mountSambaRes(self, host, userName, userPwd, res, rpath, uid=None, gid=None): """ Подключить samba ресурс """ mountCmd = checkUtils('/bin/mount') if cmpVersion(self.clVars.Get('cl_cifs_ver'), "2.05") < 0: nomapposix = "" else: nomapposix = ",nomapposix" if not uid is None: # mount by uid p = process(mountCmd, "-t", "cifs", "-o", "cache=loose,user=%s,uid=%d,gid=%d,noperm%s" % ( userName, uid, gid, nomapposix), "//%s/%s" % (host, res), rpath, envdict={"PASSWD": userPwd}, stderr=STDOUT) return p.success() else: p = process(mountCmd, "-t", "cifs", "-o", "cache=loose,user=%s%s" % (userName, nomapposix), "//%s/%s" % (host, res), rpath, envdict={"PASSWD": userPwd}, stderr=STDOUT) return p.success() def mountSleepRes(self, host, userName, userPwd, res, rpath, uid=None, gid=None): """ Подключить пользовательский ресурс """ for waittime in [0.5, 2, 5]: if not self.mountSambaRes(host, userName, userPwd, res, rpath, uid, gid): # check on mount path if isMount(rpath): if not self.umountSleepPath(rpath): return False # wait time.sleep(waittime) else: return True return False def syncUser(self, uid, gid, userHome, sync, remoteProfile, host="default", skipList=()): """ Синхронизация профиля пользователя """ execStr = "" skipPaths = self.clVars.Get("cl_sync_skip_path") if not skipPaths: self.printERROR( _("Variable 'cl_sync_skip_path' 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 + skipList)) if sync == "login": if os.path.exists(userHome) and \ os.path.exists(remoteProfile): 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, remoteProfile, userHome)) elif sync == "logout": if os.path.exists(userHome) and os.listdir(userHome) and \ os.path.exists(remoteProfile): execStr = ('/usr/bin/rsync --delete-excluded --delete %s ' '-rlpt -x -v -v -v %s/ %s/' % ( excludePaths, userHome, remoteProfile)) else: raise ClientError( _("Method syncUser: wrong option sync=%s") % str(sync)) if execStr: host = "" + host + "" if sync == "login": rsync = RsyncProgressBar( _("Fetching the file list from %s") % host, _("Fetching the user profile from %s") % host, execStr, self, rsyncver=self.clVars.Get('cl_rsync_ver')) else: rsync = RsyncProgressBar( _("Sending the file list to %s") % host, _("Uploading the user profile to %s") % host, execStr, self, rsyncver=self.clVars.Get('cl_rsync_ver')) pathConfig = os.path.join(remoteProfile, self.pathConfig) # remove old ini file prevIniFile = os.path.join(remoteProfile, ".calculate.ini") if os.path.exists(prevIniFile): os.remove(prevIniFile) # create home directory if not os.path.exists(pathConfig): self.createUserDirectory(pathConfig, uid, gid) numfiles = 0 configFileName = os.path.join(remoteProfile, self.configFileDesktop) if sync == "login": # get rsync files 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 Exception: numfiles = 0 rsync.maximum = numfiles if sync == "login": rsync.run() if sync == "logout": rsync.run() try: if iniParser(configFileName).setVar( 'rsync', {'files': rsync.getFilesNum()}): os.chmod(configFileName, 0600) os.chown(configFileName, uid, gid) except Exception: pass rsync.close() try: if iniParser(configFileName).setVar( 'rsync', {'exitcode': rsync.getExitCode()}): os.chmod(configFileName, 0600) os.chown(configFileName, uid, gid) except Exception: pass if rsync.getExitCode() != 0: self.printERROR(rsync.getErrMessage()) self.printERROR(_("Failed to execute rsync") + " " + \ str(sync)) return False # change permissions changeDirs = [userHome, remoteProfile] for changeDir in filter(path.exists, changeDirs): # get directory permissions mode = getModeFile(changeDir, mode="mode") # if permission wrong( not 0700) then change it 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) self.printSUCCESS(_("Cleaning the local user profile")) return True def syncLoginProfile(self, host, uid, gid, homeDir, resourceName, profileName, clearHomeDir=True): """ Получить профиль пользователя из домена """ homeProfile = path.join( self.clVars.Select('cl_client_user_mount_path', where='cl_client_user_mount_name', eq=resourceName, limit=1), profileName) # if currect server has any files then sync it if any(x not in ('.calculate',) for x in listDirectory(homeProfile)): if not self.syncUser(uid, gid, homeDir, "login", homeProfile, host=host): return False # remove home directory on workstation else: if clearHomeDir: # clean home directory if not self.clearHomeDir(homeDir): return False return True def syncLogoutProfile(self, host, uid, gid, homeDir, resourceName, profileName, skipList): """ Закачать профиль пользователя в домен """ homeProfile = path.join( self.clVars.Select('cl_client_user_mount_path', where='cl_client_user_mount_name', eq=resourceName, limit=1), profileName) # if currect server has any files then sync it if listDirectory(homeDir): if not path.exists(homeProfile): self.createUserDirectory(homeProfile, uid, gid) if not self.syncUser(uid, gid, homeDir, "logout", homeProfile, host=host, skipList=skipList): return False return True def strftime(self, timeobj, dateformat="%Y-%m-%d %H:%M:%S"): return time.strftime(dateformat, timeobj) def packRemote(self, resourceName, localTime, currentTime, profileName, uid, gid): """ Запаковать удаленный профиль """ remoteProfilePath = path.join( self.clVars.Select('cl_client_user_mount_path', where='cl_client_user_mount_name', eq=resourceName, limit=1), profileName) fileServer = path.join(remoteProfilePath, Client.configFileServer) if not localTime: return False varsConfig = {"arch_date": self.strftime(localTime), "curr_time": currentTime} self.setServerCommand(["pack"], varsConfig, fileServer) fileDesktop = path.join(remoteProfilePath, Client.configFileDesktop) self.setVarToConfig(uid, gid, "main", {"status_sync": "success"}, fileDesktop) return self.umountRemoteUserRes(False, resourceName) def waitingArchFile(self, packTime, profileName, resourceName): """ Ожидание архива профиля от домена """ arch_fn_success, arch_fn_process = self._getArchNames( packTime, profileName, resourceName) def arch_exists(): return any(os.path.exists(x) for x in (arch_fn_process, arch_fn_success)) for sleeptime in [0.1, 0.2, 0.5]: # archive in packing process found if arch_exists(): break time.sleep(sleeptime) else: return False # wait finish packing if arch_exists(): while arch_exists(): for waittime in [0.5, 1, 2]: if path.exists(arch_fn_success): return True try: startSize = os.stat(arch_fn_process).st_size time.sleep(waittime) if startSize != os.stat(arch_fn_process).st_size: break except OSError: if path.exists(arch_fn_success): return True else: return False return False def unpackArch(self, homeDir, packTime, profileName, resourceName): """ Распаковать архив с профилем """ def fileReader(fileName, stdin, maxSize): """ Прочитать файл по блокам """ self.addProgress() fd = os.open(fileName, os.O_RDONLY) currSize = 0 # default buffer size buffSize = 131072 dataBlock = os.read(fd, buffSize) while dataBlock: currSize += len(dataBlock) self.setProgress(currSize * 100 / maxSize) stdin.write(dataBlock) dataBlock = os.read(fd, buffSize) stdin.close() os.close(fd) archFile, drop = self._getArchNames(packTime, profileName, resourceName) archiveSize = os.stat(archFile).st_size # os.system('tar tf %s'%archFile) 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) try: # прочитать в конвеер fileReader(archFile, pipe.stdin, archiveSize) self.endTask() finally: ret = pipe.wait() pipe.stdout.close() pipe.stderr.close() if ret: return False removedFileList = path.join( self.clVars.Select('cl_client_user_mount_path', where='cl_client_user_mount_name', eq=resourceName, limit=1), profileName, self.listTemplFile) return self.removeFilesInProfile(homeDir, removedFileList) def cleanArchs(self, packTime, profileName, resourceName): """ Удалить архивы использованные для синхронизации пользовательского профиля """ for rmFile in filter(lambda x: x and path.exists(x), self._getArchNames(packTime, profileName, resourceName)): os.remove(rmFile) return True def _getArchNames(self, packTime, profileName, resourceName): """ Получить имена файлов архивов по времени, профилю и ресурсу """ remoteProfile = self.clVars.Select('cl_client_user_mount_path', where='cl_client_user_mount_name', eq=resourceName, limit=1) extSuccess = "gz" extProcess = "process" packTime = packTime.replace(".", "_") archPathTmp = os.path.join(remoteProfile, "profile.%s.%s.tar" % (profileName, packTime)) # result archive file name archPathSuccess = "%s.%s" % (archPathTmp, extSuccess) # archive in pack process archPathProcess = "%s.%s" % (archPathTmp, extProcess) return archPathSuccess, archPathProcess def setVarToConfig(self, uid, gid, nameSection, varsDict, configFileName): """Записывает переменную в файл конфигурации""" # Создаем директорию для конфигурационных файлов pathConfig = os.path.split(configFileName)[0] if not os.path.exists(pathConfig): self.createUserDirectory(pathConfig, uid, gid) try: if iniParser(configFileName).setVar(nameSection, varsDict): os.chmod(configFileName, 0600) os.chown(configFileName, uid, gid) except Exception: return False return True def createUserDirectory(self, userdir, uid, gid): """ Создать директорию с правами пользователя """ if not os.path.exists(userdir): try: os.mkdir(userdir) os.chown(userdir, uid, gid) os.chmod(userdir, 0700) except OSError: raise ClientError(_("Failed to create the directory") + ". " + _("Permission denied: '%s'") % userdir) return True def mountUserDomainRes(self, userName, userPwd, uid, gid, *resources): """ Mount all local samba resource """ for rpath, res, host in \ self.clVars.Select(['cl_client_user_mount_path', 'cl_client_user_mount_resource', 'cl_client_user_mount_host'], where='cl_client_user_mount_name', _in=resources): if not rpath: continue self.createUserDirectory(rpath, uid, gid) if isMount(rpath): continue else: if not self.mountSleepRes(host, userName, userPwd, res, rpath, uid, gid): raise ClientError( _("Failed to mount the Samba volume [%s]") % res) else: return True def setSyncStatus(self, userDir, uid, gid, status): """ Установить статус синхронизации """ # сохранение текущего статуса перед изменением self.clVars.Get('cl_client_sync_status') fileDesktop = path.join(userDir, Client.configFileDesktop) return self.setVarToConfig(uid, gid, "main", {"status_sync": status}, fileDesktop) def tarSymLinks(self, uid, gid, userHome, delPath, skipPath): """ Создать tar архив с симлинками """ linkArch = pathJoin(userHome, ".calculate/links.tar.bz2") try: tarLinks(userHome, linkArch, skip=delPath + skipPath) if path.exists(linkArch): os.chown(linkArch, uid, gid) except Exception: return False return True def unpackLinks(self, userHome): """ Распаковать архив с симлинками """ linksArch = path.join(userHome, ".calculate/links.tar.bz2") try: if os.path.exists(linksArch): tf = tarfile.open(linksArch) tf.extractall(userHome) tf.close() except Exception: return False return True def moveHomeDir(self, userHome, movedDir, resourceName, skipPaths): """ Переместить непрофильные файлы из корня домашней директории в Home/Moved """ desktopDir = "Desktop" resourcePath = self.clVars.Select('cl_client_user_mount_path', where='cl_client_user_mount_name', eq=resourceName, limit=1) if not isMount(resourcePath): raise ClientError(_("Unable to mount %s") % resourcePath) movedPath = path.join(resourcePath, movedDir) movedLink = path.join(userHome, movedDir) if not skipPaths: skipPaths = ['Disks', 'Share', 'Home', 'Moved', 'FTP', 'Desktop'] filesAndDir = filter(lambda x: not ( path.islink(x) or x[len(userHome) + 1:].startswith('.') or x[len(userHome) + 1:] in skipPaths), listDirectory(userHome, fullPath=True)) filesDir = [] for fd in filesAndDir: if os.path.islink(fd): os.unlink(fd) else: filesDir.append(fd) # find files in Desktop pathDesktop = path.join(userHome, desktopDir) filesDirDesk = filter(lambda x: (not path.islink(x) and not path.split(x)[1].startswith( '.') and not x.rpartition('.')[2] == 'desktop'), listDirectory(pathDesktop, fullPath=True)) filesDir += filesDirDesk if not filesDir or not path.exists(resourcePath): # remove empty Moved folder if path.exists(movedPath) and not listDirectory(movedPath): os.rmdir(movedPath) # remove link to Moved in home directory if path.islink(movedLink) and not path.exists(movedPath): os.unlink(movedLink) return True if not path.exists(movedPath): os.mkdir(movedPath) directFile = path.join(movedPath, ".directory") if not path.exists(directFile): with open(directFile, 'w') as f: f.write("[Desktop Entry]\nIcon=folder-development") if not path.exists(movedLink): os.symlink(movedPath[len(userHome) + 1:], movedLink) cpCmd, rmCmd = checkUtils('/bin/cp', '/bin/rm') for fd in filesDir: if process(cpCmd, '-xr', fd, movedPath).failed(): self.printERROR(_("Failed to copy {ffrom} to {fto}") .format(ffrom=fd, fto=movedPath)) return False if process(rmCmd, '-rf', '--one-file-system', fd).failed(): self.printERROR(_("Failed to remove " % fd)) return False return True def removeNoiseFiles(self, userHome): """ Remove files which hinder dm """ noiseFiles = [] for nsFile in noiseFiles: rmFile = path.join(userHome, nsFile) if os.path.exists(rmFile): os.remove(rmFile) return True def getPrivateFiles(self, userHome): """ Get all private files relative user home directory """ privateFiles = [] for privateHomeDir in self.privateDirs: privateDir = os.path.join(userHome, privateHomeDir) if os.path.isdir(privateDir): # .ssh files relative user home directory 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): """ Remove private files """ 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): """ Copy privates files from server to home directory """ 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 getListFilesProfile(self, homeDir): """ Generation file list in user home directory, exclude mount dirs """ lHomeDir = len(homeDir) + 1 return map(lambda x: x[lHomeDir:], find(homeDir, onefilesystem=True)) def removeFilesInProfile(self, homeDir, pathListFile): """ Удалить файлы которые отсутствуют в пользовательском профиле """ # read file list from config try: filesProfileTxt = open(pathListFile).read() except IOError: return False listFilesProfile = filter(lambda x: x.strip(), filesProfileTxt.split("\n")) filesProfile = set(listFilesProfile) # get files in home directory listFilesHome = self.getListFilesProfile(homeDir) if listFilesHome is False: return False filesHome = set(listFilesHome) filesRemove = list(filesHome - filesProfile) try: for rmFile in sorted(filesRemove, reverse=True): rmPath = os.path.join(homeDir, rmFile) if path.ismount(rmPath): continue 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 Exception: 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 (OSError, IOError): 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): """ Clear user key from kernel """ # find user key in kernel and check on relogout if getKey(userName) and not \ filter(lambda x: "xdm/xdm\x00--login" in x[0] and ("USER=%s" % userName) in x[1], self.getRunCommandsWithEnv()): # clean 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 setLogoutDate(self, homeDir, uid, gid): """ Установить дату выхода из сеанса """ configFileName = path.join(homeDir, self.configFileDesktop) currentDateStr = self.strftime(time.localtime()) return self.setVarToConfig(uid, gid, "main", {"date_logout": currentDateStr}, configFileName) def umountRemoteUserRes(self, removeEmpty, *resourceNames): """ Отключить пользовательский ресурс """ retRes = True for resourceName in resourceNames: resourcePath = self.clVars.Select('cl_client_user_mount_path', where='cl_client_user_mount_name', eq=resourceName, limit=1) if resourcePath: result = self.umountSleepPath(resourcePath) retRes &= result if (result and removeEmpty and path.exists(resourcePath) and not listDirectory(resourcePath)): os.rmdir(resourcePath) return retRes def umountUserRes(self, umountPaths): """ Отключить пользовательские ресурсы """ for umountPath in umountPaths: if not self.umountSleepPath(umountPath): return False self.umountRemoteUserRes(True, *self.clVars.Get('cl_client_user_mount_name')) return True def getDefaultRunlevelDaemons(self): """ Получить все службы из default уровня загрузки """ rcUpdateCmd = checkUtils("/sbin/rc-update") p = process(rcUpdateCmd, "show") if p.success(): # получить список названии служб default уровня return map(lambda x: x[0].strip(), # [\s*<название_службы>\s*,\s*default\s*] filter(lambda x: len(x) > 1 and "default" in x[1], # [\s*<название_службы>\s*,\s*уровень запуска\s*] map(lambda x: x.split('|'), # \s*<название_службы>\s*| # \s*<уроверь запуска>?\s* p.readlines()))) else: raise ClientError(_("ERROR") + _(": ") + p.read()) def delDaemonAutostart(self, daemon): """ Удалить службу из автозапуска """ rcUpdateCmd = checkUtils('/sbin/rc-update') defaultDaemons = self.getDefaultRunlevelDaemons() if daemon in defaultDaemons: p = process(rcUpdateCmd, 'del', daemon, 'default') if p.success(): return True else: self.printERROR(_("ERROR") + _(": ") + p.read()) self.printERROR(_("Failed to delete from the default runlevel")) return False return True def addDaemonAutostart(self, daemon): """ Добавить службу в автозагрузку """ rcUpdateCmd = checkUtils('/sbin/rc-update') if daemon in self.getDefaultRunlevelDaemons(): return True p = process(rcUpdateCmd, 'add', daemon, 'default') if p.success(): return True else: self.printERROR(_("ERROR") + _(": ") + p.read()) self.printERROR(_("Failed to add to the default runlevel")) return False def getInfoService(self, service, option, envFile=False): """ Получить параметр севирса из удаленного env """ if not envFile: if self.convObj is None: # file /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: # file /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 checkDomainServer(self, domainName, netDomain): """ Проверить указанный адрес на то, что он может быть доменом """ # get domain name if "." in domainName: domain = domainName else: import socket domain = "%s.%s" % (domainName, netDomain) try: gethostbyname(domain) except socket.gaierror as e: domain = domainName # check domain by ping for i in range(0, 1): try: Pinger().ping(domain, 1000, 16) break except IPError as e: pass else: self.printERROR(_("Server %s does not respond") % domain) return False reFoundHostSamba = re.compile("Server=\[Samba.+\]") smbClientCmd = checkUtils('/usr/bin/smbclient') p = process(smbClientCmd, "-N", "-L", domain, stderr=STDOUT) if p.success() and any(reFoundHostSamba.search(x) for x in p): return True else: self.printERROR(_("Samba server not found in %s") % domain) return False def getDomainPassword(self, domain): """ Получить пароль для ввода в домен """ def passwdQueue(): remotePw = self.clVars.Get('cl_remote_pw') if remotePw: yield remotePw if remotePw: self.printERROR(_("Wrong password")) yield self.askPassword( _("Domain password for the desktop"), False) self.printERROR(_("Wrong password")) for pwdRemote in passwdQueue(): pwdRemote = pwdRemote or "" if sambaPasswordCheck("client", pwdRemote, domain, "remote"): self.clVars.Set('cl_remote_pw', pwdRemote) return True return False def writeClientVars(self, domain, currentVersion, pwdRemote): """ Записать переменные переменные клиента """ # write domain to config self.clVars.Write("os_remote_auth", domain, True) # write current program version self.clVars.Write("cl_remote_host", domain, True, "local") self.clVars.Write("cl_remote_pw", pwdRemote, True, "local") return True def checkDomainInfo(self, domain): """ Проверить наличие настроек на сервере для конфигурации LDAP """ servDn = self.getInfoService("ldap", "services_dn") unixDn = self.getInfoService("unix", "dn") bindDn = self.getInfoService("unix", "bind_dn") bindPw = self.getInfoService("unix", "bind_pw") # check info from server if not (servDn and unixDn and bindDn and bindPw): raise ClientError(_("Info not found on the server") + _(": ") + _( "services DN or unix DN or bind DN or bind password")) self.clVars.Set("os_remote_auth", domain) return True def applyClientTemplates(self, hostAuth=""): """ Применить шаблоны клиента Установка события домен/не домен по удаленному хосту Установка переменной os_remote_auth, необходимой для корректного выполнения шаблонов Выполнение шаблонов Установка необходимых переменных в calculate.env """ if hostAuth: # add to domain self.clVars.Set("cl_action", "domain", True) else: # del from domain self.clVars.Set("cl_action", "undomain", True) self.clVars.Set("os_remote_auth", hostAuth) # apply system templates self.applyTemplates(None, False) if hostAuth: self.printSUCCESS(_("The workstation was configured for work " "in the domain")) self.clVars.Write("os_remote_auth", hostAuth, True) else: self.printSUCCESS(_("The workstation was configured for work " "outside the domain")) self.clVars.Delete("os_remote_auth") return True def cDelLdapSysUsersAndSyncCache(self): """ Удалить LDAP пользователей из системы (/etc/passwd и т.д.) и синхронизировать кэш """ cacheObj = userCache(self) if not cacheObj.deleteCacheUsersFromSystem(): return False if not cacheObj.syncCacheToLdap(): return False return True def cAddCacheUsersFromSystem(self): """ Добавить кэш пользователей из системы """ cacheObj = userCache(self) if not cacheObj.addCacheUsersFromSystem(): return False return True def cAddUserToCache(self, userName, userPwd): """ Добавить пользователя в кэш """ cacheObj = userCache(self) pwdHash = self.getHashPasswd(userPwd, "shadow_ssha256") if not cacheObj.addUserToCache(userName, pwdHash): return False return True def cDelLdapSysUsersAndClearCache(self): """ Удалить LDAP пользователей из системы и очистить кэш """ cacheObj = userCache(self) if not cacheObj.deleteCacheUsersFromSystem(): return False if not cacheObj.clearCache(): return False return True def mountRemoteRes(self, pwdRemote, pathRemote, *domains): """ Подключить удаленный ресурс remote """ # первый домен из списка domain = reduce(lambda x, y: x if x else y, domains, "") foundMountRemote = isMount(pathRemote) if foundMountRemote: self.printWARNING(_("Samba volume [%s] mounted") % "remote") return True else: if not (domain and pwdRemote): self.printERROR(_("Variable not found") + _(": ") + "cl_remote_pw") return False if not os.path.exists(pathRemote): os.makedirs(pathRemote) if not self.mountSambaRes(domain, "client", pwdRemote, "remote", pathRemote): self.printERROR( _("Failed to mount the Samba volume [%s]") % "remote") return False self.printSUCCESS(_("Samba volume [%s] mounted") % "remote") return True def clientPasswd(self, userLogin, uid, gid, homeDir, password, curPassword): """ Изменить пароль пользователя. Пароль пользователя меняется на сервере после выхода из сеанса """ 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(homeDir, self.configFileServer) try: res = self.setServerCommand(["passwd_samba"], varsConfig, fileConfig, uid, gid) except OSError as e: if e.errno == 13: self.printERROR(_("Permission denied")) return False return False return res