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

2187 lines
87 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 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import stat
import re
import sys
import pwd
import subprocess
import ldap
import time
import types
import getpass
from datavars import DataVarsClient, DataVars, __version__,__app__
from calculate.lib.cl_template import (Template, iniParser,TemplatesError,
ProgressTemplate)
from calculate.lib.cl_print import color_print
from calculate.lib.cl_ldap import ldapUser
from calculate.lib.utils.ip import Pinger, isOpenPort, IPError
from calculate.lib.utils.files import (runOsCommand, getModeFile, removeDir,
isMount, readFile, pathJoin, tarLinks)
from calculate.lib.utils.common import (getpathenv, appendProgramToEnvFile,
removeProgramToEnvFile)
from _cl_keys import getKey, clearKey
from calculate.lib.convertenv import convertEnv
from calculate.lib.encrypt import encrypt
from cl_client_cache import userCache
from shutil import copy2
from socket import gethostbyname
import tarfile
from collections import OrderedDict
from calculate.core.server.func import safetyWrapper
from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
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):
self.title = title
self.secondtitle = secondtitle
self.maximum = maximum
self.rsyncstr = rsyncstr
self.parent = parent
self.value = 0
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:
return self.pipe.stderr.read()
return _("RsyncProgressBar: Wrong pipe")
def runsilent(self):
"""
Run rsync without progressbar
"""
self.pipe = subprocess.Popen(self.rsyncstr, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True, shell=True)
while True:
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, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True, shell=True)
oldpercent = 0
while True:
s = self.pipe.stdout.readline()
if len(s) == 0:
break
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 ldapData(ldapUser):
"""LDAP methods"""
def getReplDN(self):
"""
Get from LDAP last domain server
Branch has ((username,systemname,host))
"""
usersDN = self.getUsersDN()
if usersDN:
partDN = "ou=Worked,ou=Replication,ou=LDAP"
servicesDN = "ou=Services"
baseDN = usersDN.rpartition(servicesDN+",")[2]
replDN = self.addDN(partDN, servicesDN, baseDN)
return replDN
return False
def searchPrevHost(self, userName, osLinuxShort):
"""Find server which user use"""
connectData = self.getBindConnectData()
if connectData:
bindDn, bindPw, host = connectData
replDN = self.getReplDN()
# find string for service replication branch
userAndOsName = "%s@%s"%(userName,osLinuxShort)
findAttr = "uid=%s"%userAndOsName
# connect to LDAP
if not self.ldapConnect(bindDn, bindPw, host):
return False
resSearch = self.ldapObj.ldapSearch(replDN, ldap.SCOPE_ONELEVEL,
findAttr, ["host"])
return resSearch
return False
def _gethostbyname(self,hostname):
try:
return gethostbyname(hostname)
except:
pass
return None
def getNameRemoteServer(self,userName, osLinuxShort, curHost):
"""
Get remote domain hostname or empty if profile is keeped on
current server
"""
searchPrevHost = self.searchPrevHost(userName, osLinuxShort)
if searchPrevHost and searchPrevHost[0][0][1].has_key('host'):
prevHost = searchPrevHost[0][0][1]['host'][0]
else:
prevHost = None
# get ip address of previous server and current server
prevIp = self._gethostbyname(prevHost)
curIp = self._gethostbyname(curHost)
# if actual profile not found or on local
if not prevHost or curIp and prevIp == curIp:
return False
else:
return prevHost
def isRepl(self):
"""
Is on or off replication on server
"""
connectData = self.getBindConnectData()
if connectData:
bindDn, bindPw, host = connectData
usersDN = self.getUsersDN()
partDN = "ou=Replication,ou=LDAP"
servicesDN = "ou=Services"
baseDN = usersDN.rpartition(servicesDN+",")[2]
replDN = self.addDN(partDN, servicesDN, baseDN)
findAttr = "ou=Worked"
# connect to LDAP
if not self.ldapConnect(bindDn, bindPw, host):
return False
resSearch = self.ldapObj.ldapSearch(replDN, ldap.SCOPE_ONELEVEL,
findAttr,
[findAttr.partition("=")[0]])
if resSearch:
return True
return False
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 - структура для вывода приглашения в режиме диалога
"""
userPwd = ""
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]+":")
if (optStdIn and options.has_key(optStdIn)) or\
(optDialog and options.has_key(optDialog)):
if not pwdA or not (pwdA == pwdB):
self.printERROR (_("ERROR") + ": " +\
_("passwords do not match"))
return False
userPwd = pwdA
return userPwd
class Client(commandServer, encrypt):
"""
Client logic object
Has fundamental methods:
mountUserResAndSync - mount user resources and sync profile from server
umountUserResAndSync - umount user resources and sync profile to server
mountRemote - mount remote domain resource
delDomain - remove workstation from domain
addDomain - add workstation to domain
changePasswd - change password on server
"""
# object for user data in LDAP
ldapDataObj = ldapData()
# 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 __init__(self):
self.clVars = None
self.clTempl = None
def initVars(self,datavars=None):
"""Primary variables initialization"""
if not datavars:
self.clVars = DataVarsClient()
self.clVars.importClient()
self.clVars.flIniFile()
else:
self.clVars = datavars
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.Delete("os_remote_client")
self.clVars.Set("cl_remote_host", "", True)
self.clVars.Set("cl_remote_pw", "", True)
self.clVars.Set("os_remote_auth", "", True)
self.clVars.Set("os_remote_client", "", True)
def applyTemplatesFromSystem(self):
"""Apply templates for system"""
if self.clVars.Get('cl_action') == 'domain':
self.startTask(_("Appling domain templates"),progress=True)
else:
self.startTask(_("Appling undomain templates"),progress=True)
if self.clTempl:
self.closeClTemplate()
self.clTempl = ProgressTemplate(self.setProgress,self.clVars,
cltObj=True)
dirsFiles = self.clTempl.applyTemplates()
if self.clTempl.getError():
self.printERROR(self.clTempl.getError().strip())
return False
else:
self.endTask()
return dirsFiles
def isTwoSessionsUser(self, userName):
"""
Check on second user login
"""
xSession = False
foundTwoSession = False
resWho = self.execProg("who")
if resWho:
for pattern in ("%s\s+\d+\s+","%s\s+tty\d+.*:\d+"):
reFoundUser = re.compile(pattern%(userName))
for string in resWho:
if reFoundUser.search(string):
if xSession:
self.printERROR(\
_("Second X session for user %s cannot be opened.")\
%userName)
return True
else:
xSession = True
return False
def isDomain(self):
"""
Check domain or not workstation
"""
foundMountRemote = isMount("/var/calculate/remote")
remoteHost = self.clVars.Get("cl_remote_host")
if remoteHost and foundMountRemote:
return True
return False
def getSyncStatus(self,rpath):
"""
Get local config 'date' and 'status_sync' or None
"""
fileConfig = os.path.join(rpath, self.configFileDesktop)
# get data from desktop config on success run get by archive
if os.path.exists(fileConfig):
objConfig = iniParser(fileConfig)
data = self.getDataInConfig("main", ["status_sync"],
objConfig)
if data:
return data.get("status_sync",None)
return None
def getUserMountResources(self, userName, homeDir, flagRemoteServer):
"""
Get information about remote profile resource
"""
home = os.path.split(homeDir)[0]
dictResources = OrderedDict(# share resource
share={"resource":"share",
"path":os.path.join(homeDir,"Share")},
profile={"resource":"unix",
"path":os.path.join(home,"."+userName)},
# resource - home directory
home={"resource":"homes",
"path":os.path.join(homeDir,"Home")})
if self.getInfoService("ftp", "host"):
# resource ftp
dictResources["ftp"] = {"resource":"ftp",
"path":os.path.join(homeDir,"FTP")}
if flagRemoteServer:
# remote user profile
dictResources["remote_profile"] = {"resource":"unix",
"path":os.path.join(home,"."+userName+"."+"remote")}
return dictResources
def mountSambaRes(self,host,userName,userPwd,uid,gid,res,rpath,
mountUidList=['ftp','homes','share']):
"""Mount samba resource"""
if res in mountUidList:
# mount by uid
mountStr = "mount -t cifs -o user=%s,uid=%s,gid=%s,noperm"\
%(userName,uid,gid) +\
" //%s/%s %s" %(host, res, rpath)
else:
# mount by root
mountStr = "mount -t cifs -o user=%s"%(userName)+\
" //%s/%s %s" %(host, res, rpath)
textLine = self.execProg(mountStr, envProg={"PASSWD":userPwd})
return textLine
def mountSleepRes(self,host,userPwd,res,rpath):
"""
Mount user resource with waiting if need
"""
for waittime in [0.5,2,5]:
if self.mountSambaRes(host,self.userName,userPwd,
self.uid,self.gid,res,rpath) is False:
# check on mount path
if isMount(rpath):
if not self.umountSleepPath(rpath):
return False
# wait
time.sleep(waittime)
else:
return True
return False
def copyTemplateDir(self, srcDir, destDir):
"""
Move directory srcDir to destDir
"""
if os.system("mv %s %s &>/dev/null"%(srcDir, destDir)) != 0:
raise ClientError(_("Failed to move %s")%srcDir + " " +\
_("to %s")%destDir)
def upgradeUserProfile(self, userName, userHome, pathTemplates):
"""
Update user profile (example /home/.user/.CLD to /home/.user/CLD)
"""
# directory which keop old profile
home = os.path.split(userHome)[0]
if os.path.exists(pathTemplates):
if self.clVars.Get("cl_profile_all_set") == "on":
osLinuxShort = "all"
else:
osLinuxShort = self.clVars.Get("os_linux_shortname")
pathNewTemplate = os.path.join(pathTemplates, osLinuxShort)
pathOldTemplate = os.path.join(pathTemplates, "."+osLinuxShort)
if not os.path.exists(pathNewTemplate) and\
os.path.exists(pathOldTemplate) and\
os.listdir(pathOldTemplate):
# move profile
self.copyTemplateDir(pathOldTemplate,pathNewTemplate)
if not os.path.exists(pathNewTemplate):
# create directory for profile keeping
os.mkdir(pathNewTemplate)
os.chmod(pathNewTemplate, 0700)
if os.path.exists(pathOldTemplate) and\
not os.listdir(pathOldTemplate):
os.rmdir(pathOldTemplate)
return True
def syncUser(self, userName, userHome, sync, homeTemplate, \
host="default"):
"""
Sync user profile from server or to server
"""
flagError = False
execStr = ""
skipPaths = self.clVars.Get("cl_sync_skip_path")
if not skipPaths:
self.printERROR(
_("The variable 'cl_sync_skip_path' is 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))
if sync == "login":
if os.path.exists(userHome) and\
os.path.exists(homeTemplate):
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, homeTemplate, userHome)
elif sync == "logout":
if os.path.exists(userHome) and os.listdir(userHome) and\
os.path.exists(homeTemplate):
execStr = '/usr/bin/rsync --delete-excluded --delete %s \
-rlptgo -x -v -v -v %s/ %s/'%(excludePaths, userHome, homeTemplate)
else:
raise ClientError(
_("Method syncUser: option sync=%s incorrect")%str(sync))
if execStr:
host = "<i>" + host +"</i>"
if sync == "login":
rsync = RsyncProgressBar(\
_("Receiving the file list from %s") % host,
_("Downloading the user profile from %s") % host,
execStr,self)
else:
rsync = RsyncProgressBar(\
_("Sending the file list to %s") % host,
_("Uploading the user profile to %s") % host,
execStr,self)
pathConfig = os.path.join(homeTemplate,
self.pathConfig)
# remove old ini file
prevIniFile = os.path.join(homeTemplate,".calculate.ini")
if os.path.exists(prevIniFile):
os.remove(prevIniFile)
# create home directory
if not os.path.exists(pathConfig):
self.createUserDirectory(pathConfig)
numfiles = 0
configFileName = os.path.join(homeTemplate, 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:
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,self.uid,self.gid)
except:
pass
rsync.close()
if rsync.getExitCode() != 0:
try:
if iniParser(configFileName).setVar(\
'rsync',{'exitcode':rsync.getExitCode()}):
os.chmod(configFileName, 0600)
os.chown(configFileName,self.uid,self.gid)
except:
pass
self.printERROR(rsync.getErrMessage())
self.printERROR(_("Failed to execute rsync") + " " + \
str(sync) + " ...")
return False
# change permissions
changeDirs = [userHome, homeTemplate]
for changeDir in 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):
"""Clear home directory"""
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)
return True
def syncLoginProfile(self, host, homeDir, homeProfile,
flagClearHomeDir=True):
"""
Get user profile from server
"""
# if currect server has any files then sync it
if filter(lambda x: not x in ('.calculate',), os.listdir(homeProfile)):
if not self.syncUser(self.userName, homeDir, "login",
homeProfile, host=host):
return False
# remove home directory on workstation
else:
if flagClearHomeDir:
# clean home directory
if not self.clearHomeDir(homeDir):
return False
return True
def getDataInConfig(self, section, listVars, objConfig):
"""
Read variables listVars from section in configuration file
"""
varsConfig = {}
for varName in listVars:
varsConfig[varName] = objConfig.getVar(section,varName)
if objConfig.getError():
return False
return varsConfig
def foundArchFile(self,strCurrentTime,archPathProcess,
archPathSuccess, remoteServer=False):
"""Found user profile archive"""
self.startTask(_("Packing the archive on the server"),
progress=True)
for sleeptime in [0.1,0.2,0.5]:
# archive in packing process found
if os.path.exists(archPathProcess) or \
os.path.exists(archPathSuccess):
break
time.sleep(sleeptime)
else:
# archive not found
self.endTask(False)
return False
# wait finish packing
if os.path.exists(archPathProcess) or \
os.path.exists(archPathSuccess):
while os.path.exists(archPathProcess) or \
os.path.exists(archPathSuccess):
for waittime in [0.5,1,2]:
if os.path.exists(archPathSuccess):
self.endTask()
return True
try:
startSize = os.stat(archPathProcess).st_size
time.sleep(waittime)
if startSize != os.stat(archPathProcess).st_size:
break
except OSError as e:
if os.path.exists(archPathSuccess):
self.endTask()
return True
# run out of time size change
else:
# archive size was not change above 4s => archive not found
self.endTask(False)
return False
self.endTask(False)
return False
def fileReader(self, fileName, stdin, maxSize):
"""
Read file by block
"""
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)
def moveArch(self, srcArchFile, dstArchFile, remoteServer=False, mode=0600):
"""
Move archive from one directory to other
"""
class copyTo(color_print):
def __init__(self, destFile):
self.FD = open(destFile, "w")
os.chmod(destFile, mode)
def write(self, data):
self.FD.write(data)
def close(self):
self.FD.close()
try:
copyToObj = copyTo(dstArchFile)
except:
raise ClientError(_("Failed to create file '%s'")%dstArchFile)
archiveSize = os.stat(srcArchFile).st_size or 1
try:
self.fileReader(srcArchFile, copyToObj, archiveSize)
except:
raise ClientError(_("Failed to copy '%(from)s' -> '%(to)s'")\
%{'from':srcArchFile,
'to':dstArchFile})
def unpackArch(self, homeDir, archFile, remoteServer=False):
"""
Unpack archive into user home directory
"""
archiveSize = os.stat(archFile).st_size
execStr = "tar -C '%s' -xzf -" %homeDir
# run unpacking process
pipe = subprocess.Popen(execStr,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
env=os.environ, shell=True)
# read file into pipe
self.fileReader(archFile, pipe.stdin,archiveSize)
ret = pipe.wait()
pipe.stdout.close()
pipe.stderr.close()
if ret:
self.printWARNING(_("Failed to execute %s") %execStr)
self.printWARNING(_("Failed to unpack %s") %archFile)
return False
return True
def setVarToConfig(self, nameSection, varsDict, configFileName):
"""Записывает переменную в файл конфигурации"""
# Создаем директорию для конфигурационных файлов
pathConfig = os.path.split(configFileName)[0]
if not os.path.exists(pathConfig):
self.createUserDirectory(pathConfig)
try:
if iniParser(configFileName).setVar(nameSection, varsDict):
os.chmod(configFileName, 0600)
os.chown(configFileName,self.uid,self.gid)
except:
return False
return True
def convertDate(self,strdate,dateformat="%Y-%m-%d %H:%M:%S"):
"""
Convert date from string format (dateformat) to stuct or None
"""
if strdate:
try:
return time.strptime(strdate,dateformat)
except ValueError:
pass
return None
def getDateObjClientConf(self, fileConfig):
"""
Return time object from config file .calculate/desktop.env
"""
if os.path.exists(fileConfig):
objConfig = iniParser(fileConfig)
data = self.getDataInConfig("main", ["date", "date_logout"],
objConfig)
timeLogout = data["date_logout"]
timeConfig = data["date"]
dates = filter(None,
[self.convertDate(timeLogout),
self.convertDate(timeConfig)])
if dates:
return dates[0]
return False
def createUserDirectory(self,userdir):
"""
Create directory with user permissions
"""
if not os.path.exists(userdir):
try:
os.mkdir(userdir)
os.chown(userdir, self.uid, self.gid)
os.chmod(userdir,0700)
except OSError:
raise ClientError(_("Error creating the directory")+
_("Permission denied: '%s'")%userdir)
def checkNeedSync(self,rpath,curTimeObj,curStatusSync,osLinuxShort):
"""
Check about need sync with domain profile by profile time
"""
# upgrade user profile or create need profile directory
self.upgradeUserProfile(self.userName, self.homeDir, rpath)
# profile directory
fileConfig = os.path.join(self.homeDir, self.configFileServer)
pathProfile = os.path.join(rpath, osLinuxShort)
if not readFile(fileConfig).strip():
return True,pathProfile
fileSoftConfigThis = os.path.join(pathProfile,
self.configFileSoft)
fileSoftConfigCur = os.path.join(self.homeDir,
self.configFileSoft)
xSessionCur = iniParser(fileSoftConfigCur).getVar('main','xsession')
xSessionThis = iniParser(fileSoftConfigThis).getVar('main','xsession')
# check profile date on current server
fileConfigThis = os.path.join(pathProfile,
self.configFileDesktop)
if iniParser(fileConfigThis).getVar('main','status_sync') == "success":
self.setVarToConfig("main", {"status_sync":"success_mount"},
fileConfigThis)
thisTimeObj = self.getDateObjClientConf(fileConfigThis)
if curStatusSync == "success_logout" and \
xSessionCur == xSessionThis and \
thisTimeObj and curTimeObj and \
curTimeObj >= thisTimeObj:
return False,pathProfile
return True,pathProfile
def mountLocalDomainRes(self,userPwd,dictRes):
"""
Mount all local samba resource
"""
res = True
configFileName = os.path.join(self.homeDir, self.configFileDesktop)
for name in dictRes.keys():
if name == "remote_profile":
continue
rpath = dictRes[name]["path"]
res = dictRes[name]["resource"]
# create user directory
self.createUserDirectory(rpath)
# is directory already mount
if isMount(rpath):
continue
else:
if self.mountSleepRes(self.domain, userPwd,
res, rpath) is False:
if name == "profile":
self.printWARNING(_("Failed to mount user profile"))
res = False
continue
else:
raise ClientError(
_("Failed to mount Samba resource [%s]")%res)
elif name == "profile":
# upgrade user profile or create need profile directory
self.upgradeUserProfile(self.userName, self.homeDir, rpath)
else:
return res
return False
def syncLocalDomainRes(self,userPwd,dictRes,curTimeObj,curStatusSync,
osLinuxShort,fromRemote=False):
"""
Sync profile with current server
fromRemote - method called by remote sync method
"""
if self.mountLocalDomainRes(userPwd,dictRes):
if not self.sync: return True
if "profile" in dictRes:
rpath = dictRes["profile"]["path"]
needSync,lDomainProfile = self.checkNeedSync(rpath, curTimeObj,
curStatusSync,osLinuxShort)
if not needSync:
if fromRemote:
self.printSUCCESS(_("Local user profile does not need "
"to update with local domain"))
else:
self.printSUCCESS(_("Local user profile will be used"))
# Copy private files from the server
self.copyPrivateFiles(lDomainProfile, self.homeDir)
return True
else:
# sync profile with current server
return self.syncLoginProfile(self.domain,self.homeDir,
lDomainProfile)
return False
def _getRemoteServerProfileByArch(self,dictRes,rHomeProfile,
dateDefaultTemplate,userPwd,curTimeObj,
curStatusSync,remoteServer,
osLinuxShort):
"""
Try get profile from remote server
"""
# current time
currTime = str(float(time.time()))
archPathSuccess,archPathProcess,archPathSrc,archPathDst = \
(None,None,None,None)
try:
rpath = dictRes["remote_profile"]["path"]
res = dictRes["remote_profile"]["resource"]
# create command for create incremental arch
fileConfig = \
os.path.join(rHomeProfile,self.configFileServer)
varsConfig = {"arch_date":dateDefaultTemplate,
"curr_time":currTime}
try:
self.setServerCommand(["pack"], varsConfig, fileConfig)
except ClientError as e:
return False
rFileConfig = os.path.join(rHomeProfile, self.configFileDesktop)
self.setVarToConfig("main", {"status_sync":"success"},
rFileConfig)
self.umountSleepPath(rpath)
if not self.syncLocalDomainRes(userPwd,dictRes,curTimeObj,
curStatusSync,osLinuxShort,
fromRemote=True):
return False
if self.mountSleepRes(remoteServer, userPwd, res, rpath) is False:
return False
self.setVarToConfig("main", {"status_sync":"success_mount"},
rFileConfig)
extSuccess = "gz"
extProcess = "process"
strCurrentTime = currTime.replace(".","_")
archPathTmp = os.path.join(rpath, "profile.%s.%s.tar"\
%(osLinuxShort, strCurrentTime))
# result archive file name
archPathSuccess = "%s.%s"%(archPathTmp,extSuccess)
# archive in pack process
archPathProcess = "%s.%s"%(archPathTmp,extProcess)
# find archive file
if not self.foundArchFile(strCurrentTime, archPathProcess,
archPathSuccess,
remoteServer):
self.printWARNING(_("Failed to find profile archive from %s")%
remoteServer)
return False
if os.path.exists(archPathSuccess):
archPathSrc = archPathSuccess
archPathDst = ".".join(filter(lambda x: x!="/",
archPathSuccess.rpartition("/")))
# get archive
self.startTask(_("Copying archive from the server"),
progress=True)
try:
self.moveArch(archPathSrc, archPathDst,
remoteServer)
except ClientError as e:
self.printWARNING(str(e))
return False
self.endTask()
# unpack archive into home directory
self.startTask(_("Unpack profile archive"),progress=True)
if not self.unpackArch(self.homeDir, archPathDst,
remoteServer):
self.printERROR(_("Failed to unpack archive"))
return False
# get removed files list
pathListFile = os.path.join(rHomeProfile,
self.listTemplFile)
if not self.removeFilesInProfile(self.homeDir,pathListFile):
self.printWARNING(_("Unable to remove needless files"))
return False
self.endTask()
return True
return False
finally:
for rmFile in (archPathSuccess,archPathProcess,
archPathSrc,archPathDst):
if rmFile and os.path.exists(rmFile):
os.remove(rmFile)
def syncRemoteDomainRes(self,userPwd,dictRes,remoteServer,curTimeObj,
curStatusSync,osLinuxShort):
"""
Sync if actual profile is kept on a remote server
"""
# mount directories path
if not "remote_profile" in dictRes:
return False
if not self.sync:
self.mountLocalDomainRes(userPwd,dictRes)
return True
rpath = dictRes["remote_profile"]["path"]
res = dictRes["remote_profile"]["resource"]
# create user directory
self.createUserDirectory(rpath)
# mount remote profile
if isMount(rpath):
self.printWARNING(_("Remote profile already mounted"))
return False
else:
if self.mountSleepRes(remoteServer, userPwd, res, rpath) is False:
return False
try:
rpath = dictRes["remote_profile"]["path"]
needSync,pathRProfile = self.checkNeedSync(rpath,curTimeObj,
curStatusSync,osLinuxShort)
if not needSync:
# Copy private files from the server
self.printSUCCESS(_("Local user profile will be used"))
if not self.mountLocalDomainRes(userPwd,dictRes):
return False
self.copyPrivateFiles(pathRProfile, self.homeDir)
return True
localServer = self.domain
# mount local samba resources
if self.mountLocalDomainRes(userPwd,dictRes):
lpath = dictRes["profile"]["path"]
homeLProfile = os.path.join(lpath, osLinuxShort)
if not os.path.exists(homeLProfile):
homeLProfile = os.path.join(lpath, "." + osLinuxShort)
lFileConfig = os.path.join(homeLProfile, self.configFileDesktop)
# get data from desktop config on success run get by archive
if os.path.exists(lFileConfig):
objConfig = iniParser(lFileConfig)
data = self.getDataInConfig("main", ["status_sync",
"date","date_logout"],
objConfig)
if data:
status = data.get("status_sync")
date = data.get("date")
date_logout = data.get("date_logout")
if date and date_logout:
diffdate = \
abs(time.mktime(self.convertDate(date))-
time.mktime(self.convertDate(date_logout)))
else:
diffdate = None
# if diff date less 10 min
if diffdate is None or diffdate < 600:
if date and "success" in status:
if self._getRemoteServerProfileByArch(dictRes,
pathRProfile,date,userPwd,
curTimeObj, curStatusSync,
remoteServer, osLinuxShort):
return True
else:
self.printWARNING(
_("Difference time between server and "
"client more 10 minute"))
self.printWARNING(_("Profile synchronization "
"will be used without archiving"))
# sync local before remote sync
self.syncLocalDomainRes(userPwd,dictRes,curTimeObj,
curStatusSync,osLinuxShort,
fromRemote=True)
# get user profile from remote domain
return self.syncLoginProfile(remoteServer, self.homeDir,
pathRProfile, False)
finally:
# unmount remote profile
if self.sync and "remote_profile" in dictRes:
if isMount(rpath):
if not self.umountSleepPath(rpath):
raise ClientError(_("Failed to unmount remote profile"))
# remote directory for remote
if os.path.exists(rpath) and not os.listdir(rpath):
os.rmdir(rpath)
def errorUmountUserRes(self,error):
"""
Post action for umount user res and write status_sync=error
"""
self.closeClTemplate()
if error and self.homeDir:
umountResult = self.umountUserRes(self.homeDir)
configFileName = os.path.join(self.homeDir, self.configFileDesktop)
uid = self.clVars.Get('ur_uid')
gid = self.clVars.Get('ur_gid')
self.setVarToConfig("main", {"status_sync":"error"},
configFileName)
self.printERROR(_("Failed to get user profile from domain"))
return umountResult
return True
def initEnv(self):
"""
Init object variables
"""
try:
self.uid = int(self.clVars.Get('ur_uid'))
self.gid = int(self.clVars.Get('ur_gid'))
except ValueError as e:
raise ClientError(_("Failed to determinate UID and GID"))
self.sync = self.clVars.Get('cl_client_sync') == 'on'
self.userName = self.clVars.Get("ur_login")
self.homeDir = self.clVars.Get('ur_home_path')
self.domain = self.clVars.Get("cl_remote_host")
@safetyWrapper(native_errors=(TemplatesError,ClientError),
man_int=__("Manually interrupted"),
post_action=errorUmountUserRes)
def mountUserResAndSync(self,dv):
"""Mount user resources and sync profile"""
self.initVars(dv)
self.initEnv()
if self.domain:
foundMountRemote = isMount("/var/calculate/remote")
if not foundMountRemote:
self.mountRemote();
# check on two session user login
if self.isTwoSessionsUser(self.userName):
return False
hostAuth = self.clVars.Get("os_remote_auth")
# if not domain
if not hostAuth or not self.domain:
self.printSUCCESS(_("The local profile will be used"))
return True
try:
passwdUsers = map(lambda x: x[0],
map(lambda x: x.split(':'),
map(lambda x: x.strip(),
open("/etc/passwd").readlines())))
except:
self.printERROR(_("Failed to open /etc/passwd"))
return False
if self.userName in passwdUsers:
try:
pwdObj = pwd.getpwnam(self.userName)
except:
raise ClientError(_("Failed to found user %s")%self.userName)
self.printWARNING(_("User information from /etc/passwd is used"))
return True
# check for domain workstation and [remote] was mounted
if not self.isDomain():
raise ClientError(_("The computer is not in the domain"))
# user config filename
configFileName = os.path.join(self.homeDir, self.configFileDesktop)
# user time object from config file
currentTimeObj = self.getDateObjClientConf(configFileName)
currentStatusSync = self.getSyncStatus(self.homeDir)
# create home directory if it is not exists
if not os.path.exists(self.homeDir):
os.makedirs(self.homeDir)
os.chown(self.homeDir,self.uid,self.gid)
os.chmod(self.homeDir,0700)
# get local date and statusSync
# write into config status "process"
self.setVarToConfig("main", {"status_sync":"process"}, configFileName)
# get user password from key kernel
userPwd = getKey(self.userName)
if not userPwd or userPwd == "XXXXXXXX":
raise ClientError(_("User password not found"))
# user cache
if not self.cAddUserToCache(self.userName, userPwd):
self.printWARNING(_("Unable cache user info"))
# profile name
if self.clVars.Get("cl_profile_all_set") == "on":
osLinuxShort = "all"
else:
osLinuxShort = self.clVars.Get("os_linux_shortname")
# if replication is on
replOn = self.ldapDataObj.isRepl()
# remote domain name
if replOn:
remoteServer = self.ldapDataObj.getNameRemoteServer(
self.userName, osLinuxShort, self.domain)
else:
remoteServer = ""
# get mount directory
dictRes = self.getUserMountResources(self.userName, self.homeDir,
remoteServer)
if isMount(dictRes["profile"]["path"]):
self.sync = False
useRemoteProfile = remoteServer and replOn
profileServer = self.domain
# if profile on remote server
if useRemoteProfile:
if not self.syncRemoteDomainRes(userPwd,dictRes,
remoteServer,currentTimeObj,
currentStatusSync,osLinuxShort):
syncStatus = "error"
useRemoteProfile = False
self.printWARNING(
_("Error synchronizing with the remote server %s")
%remoteServer)
else:
syncStatus = "success"
profileServer = remoteServer
# if profile not remote or remote sync is failed
if not useRemoteProfile:
if self.syncLocalDomainRes(userPwd,dictRes,currentTimeObj,
currentStatusSync,osLinuxShort):
# if not fallback for remote sync
if not(remoteServer and replOn):
syncStatus = "success"
else:
syncStatus = "error"
self.printWARNING(
_("Error synchronizing with the remote server %s")
%self.domain)
if self.sync:
self.setVarToConfig("main", {"status_sync":syncStatus},
configFileName)
# unpack link from link arch
self.unpackLinks(self.homeDir)
self.printSUCCESS(_("Mounted user resource of the domain"))
if self.sync:
if syncStatus == "error":
self.printWARNING(
_("Get a user fallback profile from the %s domain")
%profileServer)
else:
self.printSUCCESS(_("Get a user profile from the %s domain")%
profileServer)
return True
def isSessionUser(self, userName):
"""
Check for user in X session
"""
reFoundUser = re.compile("%s\s+:\d+\s+"%(userName))
resWho = self.execProg("who")
if resWho:
if any(reFoundUser.search(x) for x in resWho):
return True
return False
def tarSymLinks(self,userHome,uid,gid):
"""Create tar archive of symlinks"""
linkArch = pathJoin(userHome,".calculate/links.tar.bz2")
try:
for filename in tarLinks(userHome,linkArch,
skip=self.clVars.Get("cl_sync_del_path")+
self.clVars.Get("cl_sync_skip_path")):
try:
os.unlink(filename)
except OSError:
self.printWARNING(_("Failed to remove %s")%filename)
except:
self.printWARNING(_("Failed to make links archive"))
try:
if os.path.exists(linkArch):
os.chown(linkArch,uid,gid)
except:
self.printWARNING(_("Failed to make links archive"))
def unpackLinks(self,userHome):
"""Unpack archive of symlinks"""
linksArch = pathJoin(userHome,".calculate/links.tar.bz2")
try:
if os.path.exists(linksArch):
tf = tarfile.open(linksArch)
tf.extractall(userHome)
tf.close()
except:
self.printWARNING(_("Failed to unpack links archive"))
def moveHomeDir(self, userHome):
"""
Move non profile files in root of home profile to Home/Moved
"""
pathProg = os.getcwd()
os.chdir(userHome)
dirs = []
files = []
movedLink = os.path.join('Moved')
movedPath = os.path.join('Home',"Moved")
skipPaths = self.clVars.Get("cl_moved_skip_path")
if not skipPaths:
skipPaths = ['Disks','Share','Home','Moved','FTP','Desktop']
filesAndDir = filter(lambda x: not ('.' in x[0] or x in\
skipPaths or os.path.islink(x)),
os.listdir('.'))
filesDir = []
for fd in filesAndDir:
if os.path.islink(fd):
os.unlink(fd)
else:
filesDir.append(fd)
# find files in Desktop
pathDesktop = './Desktop'
filesDirDesk = []
if os.path.exists(pathDesktop):
filesDirDesk = filter(lambda x: not os.path.islink(x) and\
not os.path.split(x)[1].startswith('.') and\
not x.rpartition('.')[2]=='desktop',
map(lambda x: os.path.join(pathDesktop,x),\
os.listdir(pathDesktop)))
movedPathDesk = os.path.join(movedPath, pathDesktop)
filesDir += filesDirDesk
if not filesDir or not os.path.exists("Home"):
# remote empty Moved folder
if os.path.exists(movedPath) and not os.listdir(movedPath):
os.rmdir(movedPath)
# remote link to Moved in home directory
if os.path.islink(movedLink) and not os.path.exists(movedPath):
os.unlink(movedLink)
return True
if not os.path.exists(movedPath):
os.mkdir(movedPath)
directFile = os.path.join(movedPath,".directory")
if not os.path.exists(directFile):
txt = "[Desktop Entry]\nIcon=folder-development"
fd = os.open(directFile, os.O_CREAT)
os.close(fd)
FD = open (directFile, "r+")
FD.write(txt)
FD.close()
if not os.path.exists(movedLink):
os.symlink(movedPath, movedLink)
for fd in filesDir:
execStr = "cp -r '%s' '%s'" %(fd, movedPath)
textLine = self.execProg(execStr)
if textLine is False:
self.printERROR(_("Failed to execute") + " " + str(execStr))
return False
execStr = "rm -rf '%s'" %fd
textLine = self.execProg(execStr)
if textLine is False:
self.printERROR(_("Failed to execute") + " " + str(execStr))
return False
os.chdir(pathProg)
return True
def removeNoiseFiles(self, userHome):
"""
Remove files which hinder dm
"""
noiseFiles = []
for nsFile in noiseFiles:
rmFile = os.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 scanDirectory(self, scanDir, listFiles, skipPath=[], prefix=False,
flagDir=False):
"""
Generate file/directory list
"""
if not prefix:
prefix = os.path.join(scanDir,"")
if flagDir or stat.S_ISDIR(os.lstat(scanDir)[stat.ST_MODE]):
for fileOrDir in os.listdir(scanDir):
absPath = os.path.join(scanDir,fileOrDir)
relPath = absPath.split(prefix)[1]
if relPath in skipPath:
continue
listFiles.append(relPath)
stInfo = os.lstat(absPath)
statInfo = stInfo[stat.ST_MODE]
if stat.S_ISDIR(statInfo):
self.scanDirectory(absPath, listFiles,
skipPath, prefix, True)
def getListFilesTemplate(self, homeDir):
"""
Generation file list in user home directory, exclude mount dirs
"""
home = os.path.join(homeDir, "")
execStr = "mount"
textLines = self.execProg(execStr)
# skip directories in scanning
skipPaths = []
if textLines:
for line in textLines:
if home in line:
skipPath =\
line.partition(home)[2].rpartition(" type")[0]
skipPaths.append(skipPath)
# file list in user profile
listFiles = []
if not skipPaths:
self.printERROR(_("Mounting point for server resources not found"))
return False
self.scanDirectory(homeDir, listFiles, skipPaths)
return listFiles
def removeFilesInProfile(self, homeDir, pathListFile):
"""
Remove files which is absend in user profile
"""
# read file list from config
try:
filesTemplateTxt = open(pathListFile).read()
except:
return False
listFilesTemplate = filter(lambda x: x.strip(),
filesTemplateTxt.split("\n"))
filesTemplate = set(listFilesTemplate)
# get files in home directory
listFilesHome = self.getListFilesTemplate(homeDir)
if listFilesHome is False:
return False
filesHome = set(listFilesHome)
filesRemove = list(filesHome - filesTemplate)
filesRemove.sort(lambda x, y: cmp(len(y), len(x)))
rmPath = ""
try:
for rmFile in filesRemove:
rmPath = os.path.join(homeDir, rmFile)
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:
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:
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
@safetyWrapper(native_errors=(TemplatesError,ClientError),
man_int=__("Manually interrupted"),
post_action=errorUmountUserRes)
def umountUserResAndSync(self, dv):
"""
Umount user resouces and synchronizatoin user profile
"""
self.initVars(dv)
self.initEnv()
try:
passwdUsers = map(lambda x: x[0],
map(lambda x: x.split(':'),
map(lambda x: x.strip(),
open("/etc/passwd").readlines())))
except:
self.printERROR(_("Failed to open /etc/passwd"))
return False
if not os.path.exists(self.homeDir):
self.printERROR(_("User home directory %s not found")%self.homeDir)
return False
currentDateStr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
# domain workstation work in local mode
if self.userName in passwdUsers:
configFileName = os.path.join(self.homeDir, self.configFileDesktop)
self.setVarToConfig("main", {"date_logout":currentDateStr},
configFileName)
self.printWARNING(_("User information from /etc/passwd is used"))
self.printSUCCESS(_("The local profile will be used"))
return True
hostAuth = self.clVars.Get("os_remote_auth")
# local mode
if not hostAuth or not self.domain:
self.printSUCCESS(_("The local profile will be used"))
return True
# check for domain workstation and [remote] was mounted
if not self.isDomain():
raise ClientError(_("The computer is not in the domain"))
# check is user in X session, do not umount user resources
if self.isSessionUser(self.userName):
self.printERROR(_("User %s is already in X session")%self.userName)
self.printERROR(
_("Failed to unmount user %s resource")%self.userName)
return False
# user package config
configFileName = os.path.join(self.homeDir, self.configFileDesktop)
self.setVarToConfig("main", {"date_logout":currentDateStr},
configFileName)
if os.path.exists(self.homeDir):
self.moveHomeDir(self.homeDir)
if self.sync:
self.tarSymLinks(self.homeDir,self.uid,self.gid)
else:
raise ClientError(_("Directory %s not found") % self.homeDir)
# get status sync
exitStr = iniParser(configFileName).getVar('main','status_sync')
# check on error or net full executed -> process
if exitStr == "process" or exitStr == "error":
self.sync = False
if self.clVars.Get("cl_profile_all_set") == "on":
osLinuxShort = "all"
else:
osLinuxShort = self.clVars.Get("os_linux_shortname")
# get mount resources
dictRes = self.getUserMountResources(self.userName, self.homeDir,False)
pathUnix = dictRes["profile"]["path"]
homeProfile = os.path.join(pathUnix, osLinuxShort)
if not isMount(pathUnix):
self.printERROR(_("Mounted remote resources for user %s not found")%
self.userName)
return False
try:
if self.sync:
# sync profiles local to domain
if not self.syncUser(self.userName, self.homeDir, "logout",
homeProfile,host=self.domain):
return False
finally:
# remove files, which hinder correct dm work
self.removeNoiseFiles(self.homeDir)
# remove private files
self.removePrivateFiles(self.homeDir)
# clean user password from root keys
self.clearUserKey(self.userName)
# umount user res
if not self.umountUserRes(self.homeDir):
return False
# remove empty directories
for name in dictRes.keys():
rpath = dictRes[name]["path"]
if os.path.exists(rpath) and not os.listdir(rpath):
try:
os.rmdir(rpath)
except:
self.printERROR(_("Failed to remove dir %s")% path)
return False
# remove Disks directory if it empty
pathDisks = os.path.join(self.homeDir,"Disks")
if os.path.exists(pathDisks) and not os.listdir(pathDisks):
try:
os.rmdir(pathDisks)
except:
self.printERROR(_("Failed to remove dir %s")% pathDisks)
return False
if self.sync:
self.printSUCCESS(_("User profile saved in the domain"))
self.setVarToConfig("main", {"status_sync":"success_logout"},
configFileName)
self.printSUCCESS(_("Domain user resource unmounted"))
return True
def getMountUserPaths(self, homeDir=False):
"""
Get users mount paths
"""
if not homeDir:
userName = self.clVars.Get("ur_login")
try:
homeDir = pwd.getpwnam(userName).pw_dir
except:
homeDir = os.path.join("/home",userName)
dirStart, dirEnd = os.path.split(homeDir)
mountTemplateDir = os.path.join(dirStart, ".%s" %dirEnd)
mountRemoteTemplateDir = os.path.join(dirStart, ".%s.remote" %dirEnd)
return filter(lambda x: x.startswith(homeDir) or\
x.startswith(mountTemplateDir) or\
x.startswith(mountRemoteTemplateDir),
map(lambda x: x.split(" ")[1],\
open("/proc/mounts").readlines()))
def execProg(self, cmdStrProg, inStr=False, envProg={}):
"""
Execute external program
"""
env_path = {"PATH":getpathenv()}
env = {}
env.update(os.environ.items() + env_path.items() + envProg.items())
retCode,programOut = runOsCommand(cmdStrProg,in_str=inStr,env_dict=env)
if not retCode:
return programOut
return False
def umountSleepPath(self, rpath):
"""
Unmount path, sleep by failed and repeat
"""
# check for mount
if isMount(rpath):
for waittime in [0,0.5,1,2]:
time.sleep(waittime)
if not self.execProg("umount %s"%rpath) is False \
or not isMount(rpath):
if not isMount(rpath):
return True
self.execProg("fuser -km %s"%rpath)
for waittime in [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]:
time.sleep(waittime)
if not self.execProg("umount %s"%rpath) is False \
or not isMount(rpath):
if not isMount(rpath):
return True
else:
if isMount(rpath):
raise ClientError(_("Failed to unmount path %s")%rpath)
return True
def umountUserRes(self, homeDir=False, removeEmpty=False):
"""
Unmount user directories
"""
for umountPath in self.getMountUserPaths(homeDir):
if not self.umountSleepPath(umountPath):
return False
if os.path.exists(umountPath) and not os.listdir(umountPath):
os.rmdir(umountPath)
return True
def getDefaultRunlevelDaemons(self):
"""
Get all daemons from default runlevel
"""
execStr = "rc-update show"
textLine = self.execProg(execStr)
if textLine is False:
self.printERROR(_("ERROR") + ": " + execStr)
return False
else:
splLines = filter(lambda x: len(x)==2 and "default" in x[1],\
map(lambda x: x.split("|"),textLine))
splLines = map(lambda x: x[0].strip(), splLines)
return splLines
def delDaemonAutostart(self, daemon):
"""
Remove daemon from autorun
"""
defaultDaemons = self.getDefaultRunlevelDaemons()
if defaultDaemons is False:
return False
if daemon in defaultDaemons:
execStr = "rc-update del %s default" %daemon
textLine = self.execProg(execStr)
if textLine is False:
self.printERROR(_("ERROR") + ": " + execStr)
self.printERROR(_("Failed to delete from the default runlevel"))
return False
else:
return True
return True
def delDomain(self):
"""
Remove from domain
"""
pathRemote = "/var/calculate/remote"
foundMountRemote = isMount(pathRemote)
domain = self.clVars.Get("cl_remote_host")
if foundMountRemote:
textLineUmount = self.umountSleepPath(pathRemote)
if not textLineUmount:
return False
if not domain:
self.printWARNING(_("The computer is not in the domain"))
return True
# remove domain vars from env files
self.removeVars()
# apply templates for undomain
self.clVars.Set("cl_action", "undomain", True)
if not self.applyTemplatesFromSystem():
self.printERROR(_("Failed to apply undomain templates"))
return False
# Delete LDAP users from system and clear cache
if not self.cDelLdapSysUsersAndClearCache():
self.printERROR(_("Failed to clear user cache"))
return False
# restart dbus
self.restartDBus()
if not self.delDaemonAutostart("client"):
self.printERROR(
_("Failed to remove client service from autostart"))
return False
self.printSUCCESS(_("Computer removed from domain %s")%domain + " ...")
return True
def getUserPassword(self, pwDialog=False):
"""Получить пароль у пользователя
pwDialog - приглашение ввода пароля
"""
if os.readlink('/proc/self/fd/0') == '/dev/console':
os.system('chvt 1 &>/dev/null')
if not pwDialog:
pwDialog = _("Password")
userPwd = getpass.getpass(pwDialog+":")
return userPwd
def addDaemonAutostart(self, daemon):
"""Прописывает демона в автозагрузку"""
defaultDaemons = self.getDefaultRunlevelDaemons()
if defaultDaemons is False:
return False
if daemon in defaultDaemons:
return True
execStr = "rc-update add %s default" %daemon
textLine = self.execProg(execStr)
if textLine is False:
self.printERROR(_("ERROR") + ": " + execStr)
self.printERROR(_("Failed to add to the default runlevel"))
return False
else:
return True
def restartDBus(self):
"""Restart D-Bus service"""
dbusDaemon = 'rc-service -i dbus'
existsDaemon = 'rc-service -e dbus'
# if exists dbus and running
if os.system(existsDaemon) == 0 and \
os.system(dbusDaemon + ' status &>/dev/null') == 0:
# restart
if os.system(dbusDaemon + ' restart -- -s &>/dev/null') != 0:
self.printWARNING(_("Error restarting")+" "+dbusDaemon+" ...")
return False
return True
def getInfoService(self, service, option, envFile=False):
"""
Get service parameter from env
"""
if not envFile:
if self.convObj == 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
@safetyWrapper(native_errors=(TemplatesError,ClientError),
man_int=__("Manually interrupted"))
def domain(self,dv):
self.initVars(dv)
if self.clVars.Get('cl_client_mount_set') == 'on':
return self.mountRemote()
else:
if self.clVars.Get('cl_localhost_set') == "on":
return self.delDomain()
else:
return self.addDomain()
def addDomain(self):
"""
Add to domain
"""
domainName = self.clVars.Get('cl_remote_host_new')
remoteHost = self.clVars.Get("cl_remote_host")
netDomain = self.clVars.Get("os_net_domain")
# get domain name
if "." in domainName:
domain = domainName
else:
domain = "%s.%s" %(domainName,netDomain)
# 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.+\]")
resSmbClient = self.execProg("smbclient -N -L %s" %domain)
if not resSmbClient is False:
for string in resSmbClient:
if reFoundHostSamba.search(string):
foundHostSamba = True
break
if not foundHostSamba:
self.printERROR(_("Samba server not found in %s")%domain)
return False
if remoteHost and self.clVars.Get('cl_localhost_set') != 'on':
self.printERROR(_("The computer is already in the domain %s")\
%remoteHost)
self.printWARNING(_("Before joining the domain, "
"you need to remove it from the previous domain"))
self.printWARNING(_("Run 'cl-client -r'"))
return False
foundMountRemote = isMount("/var/calculate/remote")
if foundMountRemote:
self.printERROR(_("Samba resource [%s] mounted")%\
"remote" + " ...")
return False
else:
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)
pathRemote = "/var/calculate/remote"
for pwdRemote in passwdQueue():
if not os.path.exists(pathRemote):
os.makedirs(pathRemote)
if not self.mountSambaRes(domain,"client",pwdRemote,
0,0,"remote",pathRemote) is False:
self.printSUCCESS(
_("Samba resource [%s] mounted")%"remote")
self.clVars.Write("cl_remote_host", domain, True, "local")
self.clVars.Write("cl_remote_pw", pwdRemote, True, "local")
break
else:
self.printERROR(
_("Failed to mount Samba resource [%s]")%"remote")
return False
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):
self.printERROR(_("Info not found on the server") + ": " +\
_("services DN or unix DN or bind DN or bind password"))
return False
self.clVars.Set("cl_action", "domain", True)
self.clVars.Set("os_remote_auth", domain)
if not self.applyTemplatesFromSystem():
self.printERROR(
_("Failed to apply install templates or domain templates"))
return False
# restart dbus
self.restartDBus()
if not self.addDaemonAutostart("client"):
return False
# write domain to config
self.clVars.Write("os_remote_auth", domain, True)
# write current program version
currentVersion = self.clVars.Get("cl_ver")
self.clVars.Write("os_remote_client", currentVersion, True)
self.printSUCCESS(_("Computer added to domain %s")%domain)
return True
def relevanceTemplates(self, hostAuth):
"""
Determine relevance appling templates by program version
"""
# '' or hostname
if hostAuth != self.clVars.Get("os_remote_auth"):
return False
if self.clTempl:
self.closeClTemplate()
self.clTempl = ProgressTemplate(self.setProgress,self.clVars,
cltObj=True)
# current program version
currentVersion = self.clVars.Get("cl_ver")
# version of program which appled templates
previousVersion = self.clVars.Get("os_remote_client")
cVersion,pVersion = self.clTempl._convertVers(currentVersion,previousVersion)
if cVersion != pVersion:
return False
return True
def closeClTemplate(self):
if self.clTempl:
if self.clTempl.cltObj:
self.clTempl.cltObj.closeFiles()
self.clTempl.closeFiles()
self.clTempl = None
def applyRelevanceTemplates(self, hostAuth=""):
"""
Appling relevance templates
"""
if not self.relevanceTemplates(hostAuth):
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
dirsAndFiles = self.applyTemplatesFromSystem()
if not dirsAndFiles:
if hostAuth:
self.printERROR(_("Failed to apply domain templates"))
else:
self.printERROR(_("Failed to apply undomain templates"))
return False
if hostAuth:
self.printSUCCESS(_("Templates set for network mode"))
currentVersion = self.clVars.Get("cl_ver")
self.clVars.Write("os_remote_client", currentVersion, True)
self.clVars.Write("os_remote_auth", hostAuth, True)
else:
self.printSUCCESS(_("Templates set for local mode"))
self.clVars.Delete("os_remote_auth")
self.clVars.Delete("os_remote_client")
return True
def cDelLdapSysUsersAndSyncCache(self):
"""Delete LDAP users from system and synchronize cache"""
cacheObj = userCache()
if not cacheObj.deleteCacheUsersFromSystem():
return False
if not cacheObj.syncCacheToLdap():
return False
return True
def cAddCacheUsersFromSystem(self):
"""Add cache users from system"""
cacheObj = userCache()
if not cacheObj.addCacheUsersFromSystem():
return False
return True
def cAddUserToCache(self, userName, userPwd):
"""Add user to cache"""
cacheObj = userCache()
pwdHash = self.getHashPasswd(userPwd, "shadow_ssha256")
if not cacheObj.addUserToCache(userName, pwdHash):
return False
return True
def cDelLdapSysUsersAndClearCache(self):
"""Delete LDAP users from system and clear cache"""
cacheObj = userCache()
if not cacheObj.deleteCacheUsersFromSystem():
return False
if not cacheObj.clearCache():
return False
return True
def moveHomeDirs(self):
"""Move home dirs /var/calculate/client-home -> /home
if user in cache
"""
cacheObj = userCache()
loginUsersData = cacheObj.getLoginDomainUsers()
if loginUsersData is False:
return False
previousHome = "/var/calculate/client-home"
if isMount(previousHome):
return True
if os.path.exists(previousHome):
flagMovedUsers = False
for userName,x,uid,gid,gecos,directory,shell in loginUsersData:
homeDir = directory
pathUserList = filter(lambda x: x, directory.split('/'))
if not pathUserList:
continue
pathUser = "/".join(pathUserList[1:])
srcDir = pathJoin(previousHome, pathUser)
if os.path.exists(srcDir) and not os.path.exists(homeDir):
flagMovedUsers = True
destDir = os.path.dirname(homeDir)
self.printWARNING(_("Moved %(src)s to %(dest)s")\
%{"src":srcDir,"dest":homeDir})
if not self.copyTemplateDir(srcDir, destDir):
return False
if flagMovedUsers and not os.listdir(previousHome):
os.rmdir(previousHome)
return True
def mountRemote(self):
"""
Mount remote domain resource for domain workstation.
Alsa add to domain if found hostname and password
"""
domain = self.clVars.Get("cl_remote_host")
if domain:
foundMountRemote = isMount("/var/calculate/remote")
else:
self.printWARNING(_("This computer is not in the domain"))
if not self.applyRelevanceTemplates():
return False
return True
if foundMountRemote:
self.printWARNING(_("Samba resource [%s] mounted")%"remote" + \
" ...")
# apply domain templates
if domain:
self.clVars.flIniFile()
if not self.applyRelevanceTemplates(domain):
return False
# Delete LDAP users from system and synchronize cache
if not self.cDelLdapSysUsersAndSyncCache():
return False
return True
else:
pathRemote = "/var/calculate/remote"
pwdRemote = self.clVars.Get("cl_remote_pw")
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 self.mountSambaRes(domain,"client",pwdRemote,
0,0,"remote",pathRemote) is False:
self.printWARNING(_("Failed to mount Samba resource [%s]")%\
"remote" + " ...")
beforeRemoteAuth = self.clVars.Get('os_remote_auth')
# apply templates if current up-to-date
if not self.applyRelevanceTemplates():
return False
if not self.cAddCacheUsersFromSystem():
return False
if not self.moveHomeDirs():
return False
if beforeRemoteAuth != self.clVars.Get('os_remote_auth'):
self.restartDBus()
return True
self.printSUCCESS(_("Samba resource [%s] mounted") % "remote" +\
" ...")
# apply domain templates
if domain:
self.clVars.flIniFile()
beforeRemoteAuth = self.clVars.Get('os_remote_auth')
if not self.applyRelevanceTemplates(domain):
return False
# Delete LDAP users from system and synchronize cache
if not self.cDelLdapSysUsersAndSyncCache():
return False
if beforeRemoteAuth != self.clVars.Get('os_remote_auth'):
self.restartDBus()
return True
@safetyWrapper(native_errors=(TemplatesError,ClientError),
man_int=__("Manually interrupted"))
def clientPasswd(self,dv):
"""
Change user password on server
"""
self.initVars(dv)
self.initEnv()
# check on root
if self.uid == 0:
self.printERROR(_("The user is root"))
self.printWARNING(\
_("The program can be executed for a non-root user only"))
return False
password = self.clVars.Get('ur_user_new_pw')
curPassword = self.clVars.Get('ur_user_pw')
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()):
raise ClientError(_("Failed to change password"))
# ~/.calculate/server.env
fileConfig = os.path.join(self.homeDir, self.configFileServer)
if not self.setServerCommand(["passwd_samba"], varsConfig, fileConfig,
self.uid,self.gid):
raise ClientError(_("Failed to change password"))
self.printSUCCESS(_("%s's password changed")%
self.clVars.Get('ur_login'))
self.printSUCCESS(_("The password will be changed when you log "
"out from the X session"))
return True