You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-desktop/pym/desktop/desktop.py

322 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- 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
from os import path
import sys
import time
from calculate.core.server.core_interfaces import MethodsInterface
from calculate.desktop._cl_keys import getKey
from calculate.lib.utils.files import (isMount, process,
getRunCommands, STDOUT, childMounts,
getLoopFromPath,
getMdRaidDevices, listDirectory,
removeDir,
makeDirectory, getProgPath)
from calculate.lib.utils.common import (mountEcryptfs,
CommonError, isBootstrapDataOnly)
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
_ = lambda x: x
setLocalTranslate('cl_desktop3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
import tarfile
import tempfile
import shutil
class DesktopError(Exception):
"""Desktop Error"""
class Desktop(MethodsInterface):
"""
Модуль для настройки пользовательского сеанса и выполнения
принудительного выхода из 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:
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 umountSleepPath(self, rpath):
"""
Отмонтировать указанный путь, а также отключить используемые
в этом пути loop устройства и raid
"""
# check for mount
umount_cmd = getProgPath('/bin/umount')
fuser_cmd = getProgPath("/bin/fuser")
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)
process(umount_cmd, rpath).success()
if not isMount(rpath):
return True
process(fuser_cmd, "-km", rpath).success()
for waittime in [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]:
time.sleep(waittime)
if not isMount(rpath):
return True
process(umount_cmd, "-l", rpath).success()
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 IOError:
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 urLogin not 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