#-*- 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 from os import path import re import sys import pwd import time import traceback from calculate.desktop._cl_keys import getKey, clearKey from datavars import DataVarsDesktop, DataVars, __version__,__app__ from calculate.lib.cl_template import (Template, ProgressTemplate, TemplatesError,templateFunction,iniParser) from calculate.lib.utils.files import (runOsCommand, isMount,process, getRunCommands,STDOUT,childMounts,getLoopFromPath, getMdRaidDevices,listDirectory,removeDir, makeDirectory) from calculate.lib.utils.common import (getpathenv,appendProgramToEnvFile, removeProgramToEnvFile,mountEcryptfs, CommonError, isBootstrapDataOnly) from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate setLocalTranslate('cl_desktop3',sys.modules[__name__]) __ = getLazyLocalTranslate(_) from itertools import ifilter import tarfile import tempfile import shutil from itertools import count class DesktopError(Exception): """Desktop Error""" class Desktop: """ Модуль для настройки пользовательского сеанса и выполнения принудительного выхода из X сессии пользователя """ def __init__(self): self.homeDir = "" self.clTempl = None self.clVars = None def createCryptDir(self,userName,uid,gid,userDir,recreateOnError=False): """ Создать шифрование домашней директории, или подключить существующую userName,uid,gid,userDir: параметры пользовательской учётной записи recreateOnError: пересоздать профиль при ошбиках (используется при доменной ученой записи, так пользователь при этом ничего не теряет - профиль на сервере) """ userPwd = getKey(userName) error = "" # проверить наличие пароля в ключах ядра if not userPwd or userPwd == "XXXXXXXX": raise DesktopError(_("User password not found")) ecryptfsPath = path.join('/home/.ecryptfs',userName) # если шифрование уже используется if path.exists(ecryptfsPath): for d in (".ecryptfs",".Private"): source,target = path.join(ecryptfsPath,d),path.join(userDir,d) if not path.lexists(target): os.symlink(source,target) # попытаться подключить шифрованные данные try: if not mountEcryptfs(userName,userPwd,userDir): error = _("Failed to mount ecrypted data") except CommonError as e: error = (_("Failed to mount ecrypted data")+ _(": ")+'":%s"'%str(e)) # если при подключении произошли ошибки if error: # заархивировать текущий профиль и удалить его if recreateOnError: self.printSUCCESS(_("Recovering encrypted data")) if self.getMountUserPaths(userDir): raise DesktopError(_("Failed to encrypt the directory")) for source in (userDir,ecryptfsPath): if path.exists(source): if listDirectory(source): target = source+".bak" newtarget = target if path.exists(target): removeDir(target) os.rename(source,newtarget) else: os.rmdir(source) self.createUserDir(userName,uid,gid,userDir) # ошибка создания шифрования else: raise DesktopError(error) # если нет шифрованных данных if not path.exists(ecryptfsPath): tf = None try: # если профиль содержит только данные от бутстрапа core if isBootstrapDataOnly(userDir): if childMounts(userDir): raise DesktopError( _("Failed to create an encrypted user profile")+ _(": ")+ _("The home directory contains mount points")) # поместить данные во временный tarfile calculateName = ".calculate" calculatePath = path.join(userDir,calculateName) tf = tempfile.TemporaryFile() with tarfile.open(fileobj=tf,mode='w:') as tarf: tarf.add(calculatePath,calculateName) tf.flush() tf.seek(0) # удалить эти данные shutil.rmtree(calculatePath) # создать шифрованные данные e = process('/usr/bin/ecryptfs-setup-private','-u',userName, '-b','-l',userPwd,stderr=STDOUT) if e.failed(): raise DesktopError(e.read()) # если были данные от бутстрапа, то распаковать их if tf: with tarfile.open(fileobj=tf,mode='r:') as tarf: tarf.extractall(userDir) except Exception as e: # в случае ошибки сохраняем архив (с данными bootstrap) # из памяти в файловую систему if tf: tf.seek(0) bakArchName = path.join(userDir,".calculate.tar.bz2") with open(bakArchName,'w') as f: f.write(tf.read()) raise DesktopError(str(e)+ _("Failed to create an encrypted user profile")) finally: if tf: tf.close() return True def createUserDir(self, userName, uid, gid, userDir, mode=0700): """ Create user directory with need uid and gid """ if not path.exists(userDir): os.makedirs(userDir) if mode: os.chmod(userDir,mode) os.chown(userDir,uid,gid) return True else: raise DesktopError(_("Path %s exists") %userDir) def umountUserRes(self, *umountPaths): """ Отключить пользовательские ресурсы """ if umountPaths and type(umountPaths[0]) == list: umountPaths = umountPaths[0] for umountPath in umountPaths: if not self.umountSleepPath(umountPath): return False return True def getMountUserPaths(self, homeDir=False): """ Found user resources """ if not homeDir: userName = self.clVars.Get("ur_login") homeDir = self.clVars.Get("ur_home_path") if not homeDir: raise DesktopError(_("Failed to determine the home directory")) dirStart, dirEnd = path.split(homeDir) mountProfileDir = path.join(dirStart, ".%s" %dirEnd) mountRemoteProfileDir = path.join(dirStart, ".%s.remote" %dirEnd) return filter(lambda x: x.startswith(homeDir) or\ x.startswith(mountProfileDir) or\ x.startswith(mountRemoteProfileDir), map(lambda x: x.split(" ")[1],\ open("/proc/mounts").readlines())) def execProg(self, cmdStrProg, inStr=False, envProg={}): """ Exec external program """ 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, rpath): """ Отмонтировать указанный путь, а также отключить используемые в этом пути loop устройства и raid """ # check for mount loops = getLoopFromPath(rpath) if loops: setLoops = set(map(lambda x:x.partition('/dev/')[2],loops)) mdInfo = getMdRaidDevices() for k,v in mdInfo.items(): if setLoops & set(v): self.umountSleepPath('/dev/%s'%k) process('/sbin/mdadm','--stop','/dev/%s'%k).success() for loop in loops: self.umountSleepPath(loop) process('/sbin/losetup','-d',loop).success() if isMount(rpath): for waittime in [0,0.5,1,2]: time.sleep(waittime) if not self.execProg("umount %s"%rpath) is False \ or not isMount(rpath): if not isMount(rpath): return True self.execProg("fuser -km %s"%rpath) for waittime in [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]: time.sleep(waittime) if not self.execProg("umount %s"%rpath) is False \ or not isMount(rpath): if not isMount(rpath): return True self.execProg("umount -l %s"%rpath) else: if isMount(rpath): self.printERROR(_("Failed to unmount directory %s")%rpath) return False return True def setFastlogin(self, urLogin): """ Отметить пользователя, что для него может быть использовать "быстрой логин" """ fastlogin = self.clVars.Get('cl_desktop_fastlogin_path') if not path.exists(fastlogin): makeDirectory(fastlogin) fastlogin_user = path.join(fastlogin, urLogin) if not path.exists(fastlogin_user): try: open(fastlogin_user,'w').close() return True except: self.printWARNING(_("Failed to create the fastlogin mark file")) return False def userLogout(self, urLogin): """ Выполнить logout пользователя через dbus """ display = self.clVars.Select('cl_desktop_online_display', where='cl_desktop_online_user',eq=urLogin,limit=1) session = self.clVars.Get('cl_desktop_xsession') if session == 'xfce': logoutCommand = "/usr/bin/qdbus org.xfce.SessionManager " \ "/org/xfce/SessionManager Logout False False" elif session == 'kde': logoutCommand = "/usr/bin/kquitapp ksmserver" elif session == 'gnome': logoutCommand = "/usr/bin/qdbus org.gnome.SessionManager " \ "/org/gnome/SessionManager Logout 1" else: raise DesktopError(_("Unable to detect the X session")) if process("su",urLogin,"-c", ("DISPLAY=:%s "%display)+logoutCommand).failed(): raise DesktopError(_("Unable to send the logout command")) return True def waitLogout(self,urLogin,waitTime,postWaitTime=5): """ Ожидать завершения пользовательского сеанса Args: urLogin: логин пользователя waitTime: время ожидания завершения сеанса """ if filter(lambda x: "xdm/xdm\x00--logout" in x, getRunCommands()): for i in range(0,waitTime): if not filter(lambda x: "xdm/xdm\x00--logout" in x, getRunCommands()): return True time.sleep(1) else: raise DesktopError(_("Unable to wait for completion " "of the user logout")) for wait in range(0,5): self.clVars.Invalidate('cl_desktop_online_data') if not urLogin in self.clVars.Get('cl_desktop_online_user'): return True time.sleep(1) else: return False def prepareFace(self,ur_home_path): """Подготовить каталог пользователя с шифрованием для работы с .face Для шифрованных профилей в корне домашней директории создается симлинк на .ecryptfs/.face. В зашифрованном профиле такой симлинк будет создаваться шаблонами Args: ur_home_path: домашняя директория пользователя Returns: True/False в зависимости от успешности """ if path.exists(ur_home_path): symlink_path = path.join(ur_home_path,'.face') if not path.lexists(symlink_path): os.symlink('.ecryptfs/.face', symlink_path) return True else: return False