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-client/pym/client/client.py

1478 lines
59 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.
from __future__ import absolute_import
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 .rsync import ProfileSyncer, ProfileSyncerError
from calculate.lib.utils.common import get_fastlogin_domain_path
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
from functools import reduce
_ = 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)
receiverre_bytes = re.compile(b"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
try:
q = self.receiverre.search(s)
except TypeError:
q = self.receiverre_bytes.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
try:
if s.startswith("rsync:"):
self.errors.append(s)
q = self.receiverre.search(s)
except TypeError:
if s.startswith(b"rsync:"):
self.errors.append(s)
q = self.receiverre_bytes.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 as e:
errMessage = e.args[0]['desc']
return False, errMessage
return True, errMessage
def getUserPwd(self, options, optDialog, optStdIn, pwDialog=False):
"""Получить пароль у пользователя
options - полученные опции командной строки
optDialog - опция командной строки для вывода диалога для получения
пароля
optStdIn - опция командной строки для получения пароля из
стандартного ввода (stdin)
pwDialog - структура для вывода приглашения в режиме диалога
"""
if optStdIn and optStdIn in options:
pwdA = sys.stdin.readline().rstrip()
pwdB = sys.stdin.readline().rstrip()
elif optDialog and optDialog in options:
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)
cl_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(('--exclude="/%s"' % x.replace('"', "").replace("'", "") for x
in skipPaths + deletePaths + list(skipList)))
if sync == "login":
if os.path.exists(userHome) and \
os.path.exists(remoteProfile):
filterPath = " ".join(('--filter="P /%s"' % x.replace(
'"', "").replace("'", "") for x in 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 = "<i>" + host + "</i>"
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, 0o600)
os.chown(configFileName, uid, gid)
except Exception:
pass
rsync.close()
try:
if iniParser(configFileName).setVar(
'rsync', {'exitcode': rsync.getExitCode()}):
os.chmod(configFileName, 0o600)
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 != 0o700:
os.chmod(changeDir, 0o700)
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, 0o600)
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, 0o700)
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):
self.printWARNING(_("Failed to mount the Samba volume [%s]") % res)
continue
# 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))
filesDirDesk = [x for x in listDirectory(pathDesktop, fullPath=True)
if (not path.islink(x) and
not path.split(x)[1].startswith('.') and
not x.rpartition('.')[2] == 'desktop')]
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):
privateFiles += [os.path.join(privateHomeDir, x) for x in os.listdir(privateDir)
if os.path.isfile(os.path.join(privateDir, x))]
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 [x[lHomeDir:] for x in 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 [getCmd(x) for x in os.listdir('/proc') if x.isdigit()]
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()):
if getKey(userName) and not any([x for x in self.getRunCommandsWithEnv()
if "xdm/xdm\x00--login" in x[0] and ("USER=%s" % userName) in x[1]]):
# 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 уровня
# [\s*<название_службы>\s*,\s*default\s*]
# [\s*<название_службы>\s*,\s*уровень запуска\s*]
# \s*<название_службы>\s*|
# \s*<уроверь запуска>?\s*
# 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())))
return [x[0].strip() for x in [z.split('|') for z in p.readlines()] if len(x) > 1 and "default" in x[1]]
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()):
if [x for x in varsConfig.values() if not x]:
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.decode("UTF-8")
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.decode("UTF-8"))
self.printERROR(_("Failed to execute rsync"))
return False
return True
def syncUserNew(self, ps, uid, gid, userHome, sync,
remoteProfile,
host="default", skipList=(), rsyncopts=()):
"""
Синхронизация профиля пользователя
"""
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 = "<i>" + host + "</i>"
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)
configFileName = os.path.join(remoteProfile, self.configFileDesktop)
try:
if iniParser(configFileName).setVar(
'rsync', {'exitcode': ps.exitstatus}):
os.chmod(configFileName, 0o600)
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 != 0o700:
os.chmod(changeDir, 0o700)
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