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

394 lines
17 KiB

9 years ago
# -*- coding: utf-8 -*-
12 years ago
# Copyright 2010-2016 Mir Calculate. http://www.calculate-linux.org
12 years ago
#
# 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
9 years ago
from calculate.core.server.core_interfaces import MethodsInterface
12 years ago
9 years ago
from calculate.desktop._cl_keys import getKey
12 years ago
from calculate.lib.utils.mount import isMount, childMounts
from calculate.lib.utils.files import (process,
getRunCommands, STDOUT,
9 years ago
getLoopFromPath,
getMdRaidDevices, listDirectory,
removeDir, rsync_files, RsyncOptions,
makeDirectory, getProgPath, FilesError)
9 years ago
from calculate.lib.utils.common import (mountEcryptfs,
CommonError, isBootstrapDataOnly)
12 years ago
9 years ago
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
_ = lambda x: x
setLocalTranslate('cl_desktop3', sys.modules[__name__])
12 years ago
__ = getLazyLocalTranslate(_)
9 years ago
import tarfile
import tempfile
import shutil
12 years ago
12 years ago
class DesktopError(Exception):
"""Desktop Error"""
9 years ago
class Desktop(MethodsInterface):
12 years ago
"""
Модуль для настройки пользовательского сеанса и выполнения
принудительного выхода из X сессии пользователя
12 years ago
"""
9 years ago
12 years ago
def __init__(self):
self.homeDir = ""
self.clTempl = None
self.clVars = None
9 years ago
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"))
9 years ago
ecryptfsPath = path.join('/home/.ecryptfs', userName)
# если шифрование уже используется
if path.exists(ecryptfsPath):
9 years ago
for d in (".ecryptfs", ".Private"):
source, target = path.join(ecryptfsPath, d), path.join(userDir,
d)
if not path.lexists(target):
9 years ago
os.symlink(source, target)
# попытаться подключить шифрованные данные
try:
9 years ago
if not mountEcryptfs(userName, userPwd, userDir):
error = _("Failed to mount ecrypted data")
except CommonError as e:
9 years ago
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"))
9 years ago
for source in (userDir, ecryptfsPath):
if path.exists(source):
if listDirectory(source):
9 years ago
target = source + ".bak"
newtarget = target
if path.exists(target):
removeDir(target)
9 years ago
os.rename(source, newtarget)
else:
os.rmdir(source)
9 years ago
self.createUserDir(userName, uid, gid, userDir)
# ошибка создания шифрования
else:
raise DesktopError(error)
# если нет шифрованных данных
if not path.exists(ecryptfsPath):
tf = None
remove_files = None
try:
# если профиль содержит только данные от бутстрапа core
if isBootstrapDataOnly(userDir):
if childMounts(userDir):
raise DesktopError(
9 years ago
_("Failed to create an encrypted user profile") +
_(": ") +
_("The home directory contains mount points"))
# поместить данные во временный tarfile
remove_files = []
tf = tempfile.TemporaryFile()
with tarfile.open(fileobj=tf, mode='w:') as tarf:
for fn in listDirectory(userDir, fullPath=False):
full_fn = path.join(userDir, fn)
if path.lexists(full_fn):
tarf.add(full_fn, fn)
remove_files.append(full_fn)
tf.flush()
tf.seek(0)
# удалить эти данные
for fn in remove_files:
if not path.islink(fn) and path.isdir(fn):
shutil.rmtree(fn)
else:
os.unlink(fn)
# создать шифрованные данные
9 years ago
e = process('/usr/bin/ecryptfs-setup-private', '-u', userName,
'-b', '-l', userPwd, stderr=STDOUT)
if e.failed():
raise DesktopError(e.read())
# если были данные от бутстрапа, то распаковать их
if tf and remove_files:
9 years ago
with tarfile.open(fileobj=tf, mode='r:') as tarf:
tarf.extractall(userDir)
except Exception as e:
# в случае ошибки сохраняем архив (с данными bootstrap)
# из памяти в файловую систему
if tf:
tf.seek(0)
9 years ago
bakArchName = path.join(userDir, ".calculate.tar.bz2")
with open(bakArchName, 'w') as f:
f.write(tf.read())
raise DesktopError(
"%s\n%s" %
(str(e),
_("Failed to create an encrypted user profile")))
finally:
if tf:
tf.close()
return True
3 years ago
def createUserDir(self, userName, uid, gid, userDir, mode=0o700):
12 years ago
"""
Create user directory with need uid and gid
"""
if not path.exists(userDir):
os.makedirs(userDir)
if mode:
9 years ago
os.chmod(userDir, mode)
os.chown(userDir, uid, gid)
12 years ago
return True
else:
9 years ago
raise DesktopError(_("Path %s exists") % userDir)
12 years ago
def umountUserRes(self, *umountPaths):
12 years ago
"""
Отключить пользовательские ресурсы
12 years ago
"""
if umountPaths and type(umountPaths[0]) == list:
umountPaths = umountPaths[0]
for umountPath in umountPaths:
if not self.umountSleepPath(umountPath):
return False
12 years ago
return True
def getMountUserPaths(self, homeDir=False):
12 years ago
"""
Found user resources
"""
12 years ago
if not homeDir:
9 years ago
self.clVars.Get("ur_login")
12 years ago
homeDir = self.clVars.Get("ur_home_path")
if not homeDir:
raise DesktopError(_("Failed to determine the home directory"))
12 years ago
dirStart, dirEnd = path.split(homeDir)
9 years ago
mountProfileDir = path.join(dirStart, ".%s" % dirEnd)
mountRemoteProfileDir = path.join(dirStart, ".%s.remote" % dirEnd)
with open("/proc/mounts") as f:
3 years ago
return list(filter(lambda x: x.startswith(homeDir) or
x.startswith(mountProfileDir) or
x.startswith(mountRemoteProfileDir),
3 years ago
map(lambda x: x.split(" ")[1], f)))
12 years ago
12 years ago
def umountSleepPath(self, rpath):
"""
Отмонтировать указанный путь, а также отключить используемые
в этом пути loop устройства и raid
12 years ago
"""
# check for mount
9 years ago
umount_cmd = getProgPath('/bin/umount')
fuser_cmd = getProgPath("/bin/fuser")
loops = getLoopFromPath(rpath)
if loops:
9 years ago
setLoops = set(map(lambda x: x.partition('/dev/')[2], loops))
mdInfo = getMdRaidDevices()
9 years ago
for k, v in mdInfo.items():
if setLoops & set(v):
9 years ago
self.umountSleepPath('/dev/%s' % k)
process('/sbin/mdadm', '--stop', '/dev/%s' % k).success()
for loop in loops:
self.umountSleepPath(loop)
9 years ago
process('/sbin/losetup', '-d', loop).success()
12 years ago
if isMount(rpath):
9 years ago
for waittime in [0, 0.5, 1, 2]:
12 years ago
time.sleep(waittime)
9 years ago
process(umount_cmd, rpath).success()
if not isMount(rpath):
return True
process(fuser_cmd, "-km", rpath).success()
12 years ago
for waittime in [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]:
time.sleep(waittime)
9 years ago
if not isMount(rpath):
return True
process(umount_cmd, "-l", rpath).success()
12 years ago
else:
if isMount(rpath):
9 years ago
self.printERROR(_("Failed to unmount directory %s") % rpath)
12 years ago
return False
12 years ago
return True
12 years ago
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:
9 years ago
open(fastlogin_user, 'w').close()
return True
9 years ago
except IOError:
self.printWARNING(_("Failed to create the fastlogin mark file"))
return False
def userLogout(self, urLogin):
# Используется завершение сессии, так loginctl прибивает
# сессию без завершающих скриптов
return self.userLogoutBySession(urLogin)
def getElogindSessionId(self, urLogin):
loginctl = getProgPath("/bin/loginctl")
p = process(loginctl, "--no-legend")
try:
for line in p:
cols = [x.strip() for x in line.split()]
if len(cols) >= 3:
sessionid, uid, username = cols[:3]
if username == urLogin:
return sessionid
finally:
p.close()
return None
def terminateUserSession(self, session_id):
loginctl = getProgPath("/bin/loginctl")
p = process(loginctl, "terminate-session", session_id)
return p.success()
def userLogoutByElogind(self, urLogin):
elogin_session_id = self.getElogindSessionId(urLogin)
if not elogin_session_id:
raise DesktopError(_("Unable to detect user session id"))
return self.terminateUserSession(elogin_session_id)
def userLogoutBySession(self, urLogin):
"""
Выполнить logout пользователя через dbus
"""
display = self.clVars.Select('cl_desktop_online_display',
9 years ago
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 " \
"org.xfce.Session.Manager.Logout False False"
elif session == 'kde':
logoutCommand = "/usr/bin/kquitapp ksmserver"
elif session == 'plasma':
logoutCommand = "/usr/bin/qdbus org.kde.ksmserver /KSMServer " \
"org.kde.KSMServerInterface.logout 0 0 0"
elif session in ('gnome', 'mate', 'cinnamon-session'):
logoutCommand = "/usr/bin/qdbus org.gnome.SessionManager " \
"/org/gnome/SessionManager " \
"org.gnome.SessionManager.Logout 1"
elif session == 'lxqt':
logoutCommand = "/usr/bin/qdbus org.lxqt.session " \
"/LXQtSession org.lxqt.session.logout"
else:
raise DesktopError(_("Unable to detect the X session"))
9 years ago
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=20):
"""
Ожидать завершения пользовательского сеанса
Args:
urLogin: логин пользователя
waitTime: время ожидания завершения сеанса
"""
session = self.clVars.Get('cl_desktop_xsession')
if session == "plasma":
uid = self.clVars.Get('ur_uid')
if uid:
uid = int(uid)
for i in range(0, postWaitTime):
3 years ago
if not list(filter(lambda x: "ksmserver" in x,
getRunCommands(uid=uid))):
break
time.sleep(1)
time.sleep(1)
3 years ago
if list(filter(lambda x: "xdm/xdm\x00--logout" in x,
getRunCommands())):
9 years ago
for i in range(0, waitTime):
3 years ago
if not list(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, postWaitTime):
self.clVars.Invalidate('cl_desktop_online_data')
9 years ago
if urLogin not in self.clVars.Get('cl_desktop_online_user'):
return True
time.sleep(1)
else:
return False
9 years ago
def prepareFace(self, ur_home_path):
"""Подготовить каталог пользователя с шифрованием для работы с .face
Для шифрованных профилей в корне домашней директории
создается симлинк на .ecryptfs/.face. В зашифрованном профиле
такой симлинк будет создаваться шаблонами
Args:
ur_home_path: домашняя директория пользователя
Returns:
True/False в зависимости от успешности
"""
if path.exists(ur_home_path):
9 years ago
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
def syncSkel(self, ur_home_path, uid, gid):
"""
Скопировать содержимое /etc/skel в каталог пользователя
"""
try:
rsync_files('/etc/skel', ur_home_path,
opts=(RsyncOptions.Archive,
RsyncOptions.Chown(uid, gid),))
except FilesError as e:
raise DesktopError(str(e))
return True