# -*- coding: utf-8 -*- # Copyright 2010-2016 Mir Calculate. 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, pathJoin, tarLinks, readFile, writeFile, listDirectory, process, find, STDOUT, checkUtils) from calculate.lib.utils.samba import Samba, SambaError from calculate.lib.utils.mount import isMount 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.client.rsync import ProfileSyncer, ProfileSyncerError from calculate.lib.utils.common import get_fastlogin_domain_path 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 = ["Laptop", "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 init(self): self.domainnames = {} 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') addon = "" if cmpVersion(self.clVars.Get('cl_cifs_ver'), "2.05") >= 0: addon = ",nomapposix" cache_method = self.clVars.Get('cl_cifs_cache') domain = self.get_server_domainname(host) cifs_mount_vers = self.clVars.Get('cl_cifs_mount_vers') if not uid is None: # mount by uid p = process(mountCmd, "-t", "cifs", "-o", "cache=%s,domain=%s,user=%s,uid=%d,gid=%d,noperm%s" % ( cache_method, domain, userName, uid, gid, addon), "//%s/%s" % (host, res), rpath, envdict={"PASSWD": userPwd}, stderr=STDOUT) return p.success() else: if cmpVersion(self.clVars.Get('cl_cifsutils_ver'), "6.9_rc73") >= 0: addon += ",forcemandatorylock" if rpath in ("unix", "remote_profile"): versopt = ",vers=%s" % cl_cifs_mount_vers else: versopt = "" p = process(mountCmd, "-t", "cifs", "-o", "cache=%s,domain=%s%s,user=%s%s" % (cache_method, domain, versopt, userName, addon), "//%s/%s" % (host, res), rpath, envdict={"PASSWD": userPwd}, stderr=STDOUT) return p.success() def get_server_domainname(self, host): vardomain = self.clVars.Get('cl_remote_domain') if vardomain: return vardomain if host not in self.domainnames: try: self.domainnames[host] = Samba().get_server_domainname(host) except SambaError as e: raise ClientError(str(e)) return self.domainnames[host] 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 + list(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): """ Очистить домашнюю директорию """ skipHomeFile = list(set(self.skipHomeFile) | set(self.clVars.Get('cl_moved_skip_path'))) rmFiles = list(set(os.listdir(homeDir)) - set(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: pdir = os.path.dirname(userdir) if not os.path.exists(pdir): self.createUserDirectory(pdir, uid, gid) 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 = ['Laptop', '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 = readFile(pathListFile) 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 (readFile(cmdLineFile).strip(), readFile(environFile).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) resourcePath = os.path.dirname(resourcePath) if (resourceName in ("unix", "remote_profile") 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 smbClientCmd = checkUtils('/usr/bin/smbclient') p = process(smbClientCmd, "-N", "//{}/remote".format(domain), stderr=STDOUT) if "NT_STATUS_ACCESS_DENIED" in p.read(): self.get_server_domainname(domain) return True else: self.printERROR(_("Samba server not found in %s") % domain) return False def getDomainPassword(self, host): """ Получить пароль для ввода в домен """ 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")) samba = Samba() for pwdRemote in passwdQueue(): pwdRemote = pwdRemote or "" domain = self.get_server_domainname(host) if samba.password_check("client", pwdRemote, host, "remote", domain): 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") sambaHost = self.getInfoService("samba", "host") # 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) try: self.getDomainPassword(sambaHost) except ClientError: self.printWARNING( _("Failed to authorize on %s") % sambaHost) self.printWARNING( _("Wrong value of variable sr_samba_host in %s") % "/var/calculate/remote/calculate.env") 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 def get_syncer(self, remotehost, user, pwd): return ProfileSyncer(remotehost, 2009, user, pwd) def checkSync(self, remotehost): status = self.get_syncer(remotehost, None, None).check() self.clVars.Set('cl_client_rsync_profile_set', "on" if status else "off", force=True) return status def syncLoginProfileNew(self, host, username, pwd, uid, gid, homeDir, profileName, replHost=False, clearHomeDir=True): """ Получить профиль пользователя из домена """ ps = self.get_syncer(host, username, pwd) # if currect server has any files then sync it remoteProfile = "::profile/{}".format(profileName) if replHost: remoteOpts = ("-c", "--zc=zstd") else: remoteOpts = () if not self.syncUserNew(ps, uid, gid, homeDir, "login", remoteProfile, host=host, rsyncopts=remoteOpts): error_output = ps.output if remoteOpts and re.search(r"(on remote machine.*unknown option|" "unknown compress name:)", error_output): if self.syncUserNew(ps, uid, gid, homeDir, "login", remoteProfile, host=host): return True if re.search(r'change_dir "\?{resPath}" \(in {resName}\) ' 'failed: No such file or directory'.format( resPath=profileName, resName="profile"), error_output) or \ not ps.exists(remoteProfile): if clearHomeDir: # clean home directory if not self.clearHomeDir(homeDir): return False return True else: self.printERROR(error_output) self.printERROR(_("Failed to execute rsync")) return False return True def syncLogoutProfileNew(self, host, username, pwd, uid, gid, homeDir, profileName, skipList): """ Закачать профиль пользователя в домен """ skipList = [os.path.relpath(x, homeDir) for x in skipList] ps = self.get_syncer(host, username, pwd) # if currect server has any files then sync it remoteProfile = "::profile/{}".format(profileName) if listDirectory(homeDir): with writeFile(os.path.join(homeDir,".logout")) as f: f.write("SUCCESS") if not self.syncUserNew(ps, uid, gid, homeDir, "logout", remoteProfile, host=host, skipList=skipList): self.printERROR(ps.output) self.printERROR(_("Failed to execute rsync")) return False return True def syncUserNew(self, ps, uid, gid, userHome, sync, remoteProfile, host="default", skipList=(), rsyncopts=()): """ Синхронизация профиля пользователя """ execStr = "" rsyncopts = list(rsyncopts) 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 = ['--exclude=/%s'% x for x in skipPaths + deletePaths + list(skipList) if x not in {".logout"} ] rsyncParams = [] if sync == "login": if os.path.exists(userHome): filterPath = ['--filter=P /%s'% x for x in skipPaths if x not in {".logout"} ] rsyncParams = ['--delete-excluded', '--delete'] + excludePaths + filterPath + [ '-rlptgo', '-x' ] source, target = remoteProfile, userHome elif sync == "logout": if os.path.exists(userHome) and os.listdir(userHome): rsyncParams = ['--delete-excluded', '--delete'] + excludePaths + [ '-rlpt', '-x' ] source, target = userHome, remoteProfile else: raise ClientError( _("Method syncUser: wrong option sync=%s") % str(sync)) if rsyncParams: host = "" + host + "" if sync == "login": title = _("Fetching the user profile from %s") % host elif sync == "logout": title = _("Uploading the user profile to %s") % host self.startTask(title) self.addProgress() rsyncParams.extend(rsyncopts) for i in ps.sync("%s/"%source, "%s/"%target, *rsyncParams): self.setProgress(i) pathConfig = os.path.join(userHome, self.pathConfig) try: if iniParser(configFileName).setVar( 'rsync', {'exitcode': rsync.getExitCode()}): os.chmod(configFileName, 0600) os.chown(configFileName, uid, gid) except Exception: pass if ps.exitstatus != 0: return False # change permissions changeDirs = [userHome] 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) self.endTask() return True def update_fastlogin_domain_path(self): fn = '/var/lib/calculate/calculate-desktop/fastlogin-domain' try: with writeFile(fn) as f: f.write("{}\n".format( "\n".join(get_fastlogin_domain_path(self.clVars)))) except Exception: if os.path.exists(fn): try: os.unlink(fn) except Exception: pass return True