|
|
|
|
# -*- 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,
|
|
|
|
|
isMount, pathJoin, tarLinks,
|
|
|
|
|
listDirectory, process, find, STDOUT,
|
|
|
|
|
sambaPasswordCheck, checkUtils)
|
|
|
|
|
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.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 = ["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 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')
|
|
|
|
|
if cmpVersion(self.clVars.Get('cl_cifs_ver'), "2.05") < 0:
|
|
|
|
|
nomapposix = ""
|
|
|
|
|
else:
|
|
|
|
|
nomapposix = ",nomapposix"
|
|
|
|
|
if not uid is None:
|
|
|
|
|
# mount by uid
|
|
|
|
|
p = process(mountCmd, "-t", "cifs", "-o",
|
|
|
|
|
"cache=loose,user=%s,uid=%d,gid=%d,noperm%s" % (
|
|
|
|
|
userName, uid, gid, nomapposix),
|
|
|
|
|
"//%s/%s" % (host, res), rpath,
|
|
|
|
|
envdict={"PASSWD": userPwd}, stderr=STDOUT)
|
|
|
|
|
return p.success()
|
|
|
|
|
else:
|
|
|
|
|
p = process(mountCmd, "-t", "cifs", "-o",
|
|
|
|
|
"cache=loose,user=%s%s" % (userName, nomapposix),
|
|
|
|
|
"//%s/%s" % (host, res), rpath,
|
|
|
|
|
envdict={"PASSWD": userPwd}, stderr=STDOUT)
|
|
|
|
|
return p.success()
|
|
|
|
|
|
|
|
|
|
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 = "<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, 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):
|
|
|
|
|
"""
|
|
|
|
|
Очистить домашнюю директорию
|
|
|
|
|
"""
|
|
|
|
|
rmFiles = list(set(os.listdir(homeDir)) - \
|
|
|
|
|
set(self.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:
|
|
|
|
|
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 = ['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 = open(pathListFile).read()
|
|
|
|
|
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 (open(cmdLineFile, 'r').read().strip(),
|
|
|
|
|
open(environFile, 'r').read().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)
|
|
|
|
|
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
|
|
|
|
|
reFoundHostSamba = re.compile("Server=\[Samba.+\]")
|
|
|
|
|
smbClientCmd = checkUtils('/usr/bin/smbclient')
|
|
|
|
|
p = process(smbClientCmd, "-N", "-L", domain, stderr=STDOUT)
|
|
|
|
|
if p.success() and any(reFoundHostSamba.search(x) for x in p):
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
self.printERROR(_("Samba server not found in %s") % domain)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def getDomainPassword(self, domain):
|
|
|
|
|
"""
|
|
|
|
|
Получить пароль для ввода в домен
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
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"))
|
|
|
|
|
|
|
|
|
|
for pwdRemote in passwdQueue():
|
|
|
|
|
pwdRemote = pwdRemote or ""
|
|
|
|
|
if sambaPasswordCheck("client", pwdRemote, domain, "remote"):
|
|
|
|
|
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")
|
|
|
|
|
# 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)
|
|
|
|
|
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
|