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

2351 lines
95 KiB

12 years ago
#-*- 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,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 addDN(self, *arg):
"""
Append text DN elements
"""
DNs = []
for dn in arg:
if dn:
DNs.append(dn)
return ','.join(DNs)
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
"""
# 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
pathProfile = os.path.join(rpath, osLinuxShort)
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 postAct(self,error):
# """
# Post action for umount user res and write status_sync=error
# """
# self.printSUCCESS("OK")
# return True
#
# @safetyWrapper(native_errors=(TemplatesError,ClientError),
# man_int=__("Manually interrupted"),
# post_action=postAct)
# def clienttest(self,dv):
# import time
# self.initVars(dv)
# self.startTask("Hello",progress=True)
# time.sleep(2)
# self.endTask()
# self.startTask("ZXC",progress=True)
# time.sleep(1)
# self.setProgress(33)
# time.sleep(1)
# self.setProgress(66)
# self.printWARNING("BAD")
# self.printWARNING("BAD")
# #raise ClientError("Hello world")
# #z = 5 / 0
# self.printWARNING("BAD")
# self.printWARNING("BAD")
# time.sleep(1)
# self.setProgress(100)
# self.endTask()
# self.printSUCCESS("HELLO")
# return True
@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)
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 determnate 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")
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)
# Получаем файлы в домашней директории
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):
"""Очищает пользовательский ключ ядра"""
# Ищем ключ в ядре и проверяем не выполняет ли он повторный вход
return True
if getKey(userName) and not \
filter(lambda x:"xdm/xdm\x00--login" in x[0] and \
("USER=%s"%userName) in x[1],self.getRunCommandsWithEnv()):
# очищаем
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)
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 determnate 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")
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):
"""Находит пользовательские примонтированные пути"""
# Имя пользователя
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 installProg(self,onlyEnv=False):
"""Наложение шаблонов на систему при инсталяции
onlyEnv - выполнить только добавление в calculate2.env
и client в автозапуск
"""
# Действие выход из домена
self.clVars.Set("cl_action", "install", True)
domain = self.clVars.Get("cl_remote_host")
domainLive = self.clVars.Get("cl_remote_host_live")
self.clVars.AppendToList("cl_merges", __app__, force=True)
# Добавление программы в инсталяционную переменную
if not appendProgramToEnvFile(__app__, self.clVars):
self.printERROR(_("Failed to save '%s'") %__app__ + " " +\
_("to %s") %self.clVars.Get("cl_env_path")[0])
return False
if domain or domainLive:
if not self.addDaemonAutostart("client"):
return False
if onlyEnv:
# apply templates (this appling client templates need
# for safety executing cl-install --startup for
# already domained computers
if self.clVars.Get("os_remote_auth") and \
not self.applyTemplatesFromSystem():
self.printERROR(_("Failed to apply install templates"))
return False
return True
if domain and not self.mountRemote():
return False
if not self.applyTemplatesFromSystem():
self.printERROR(_("Failed to apply install templates"))
return False
if domain:
self.printOK(_("The domain profile will be used") + " ...")
else:
self.printOK(_("The local profile will be used") + " ...")
return True
def getDefaultRunlevelDaemons(self):
"""Получаем всех демонов в default уровне"""
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 uninstallProg(self):
"""Наложение шаблонов на систему при деинсталяции"""
# Проверяем на root
if not self.isRoot():
return False
# Действие удаление
self.clVars.Set("cl_action", "uninstall", True)
# Удаляем переменные из env файлов
self.removeVars()
# Удаление программы из инсталяционной переменной
if not removeProgramToEnvFile(__app__, self.clVars):
self.printERROR(_("Failed to remove '%(app)s' from %(path)s")%
{'app':__app__,
'path':self.clVars.Get("cl_env_path")[0]})
return False
if not self.applyTemplatesFromSystem():
self.printERROR(_("Failed to apply uninstall templates"))
return False
if not self.delDaemonAutostart("client"):
return False
self.printOK(_("Apply uninstall templates"))
return True
def delDaemonAutostart(self, daemon):
"""Удаляет демона из автозагрузки"""
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"""
return True
dbusDaemon = 'rc-service -i dbus'
existsDaemon = 'rc-service -e dbus'
# если установлена и запущена
if os.system(existsDaemon) == 0 and \
os.system(dbusDaemon + ' status &>/dev/null') == 0:
# пезапустить
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):
"""Получить параметр сервиса из env"""
if not envFile:
if self.convObj == None:
# файл /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:
# файл /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
# Рестартуем dbus
self.restartDBus()
if not self.addDaemonAutostart("client"):
return False
# Записываем переменную
self.clVars.Write("os_remote_auth", domain, True)
# Записываем текущую версию программы
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
def updateEnvFiles(self):
"""Апдейт env файлов до версии 2.2"""
previousVersion = self.clVars.Get("os_remote_client")
flagUpdate = False
if not previousVersion:
# Апдейт
envFile="/var/calculate/calculate.env"
remoteHost = self.getInfoService("client", "cl_remote_host",
envFile=envFile)
remotePw = self.getInfoService("client", "cl_remote_pw",
envFile=envFile)
if remoteHost and remotePw:
self.clVars.Write("cl_remote_host", remoteHost, True, "local")
self.clVars.Write("cl_remote_pw", remotePw, True, "local")
envFile="/etc/calculate/calculate.env"
previousVersion = self.getInfoService("client", "os_remote_client",
envFile=envFile)
workHost = self.getInfoService("client", "os_remote_auth",
envFile=envFile)
if previousVersion and workHost:
self.clVars.Write("os_remote_client", previousVersion, True)
if workHost != "local":
self.clVars.Write("os_remote_auth", workHost, True)
self.printOK(_("Env files updated") + " ...")
flagUpdate = True
return flagUpdate
def setUserPasswordToServer(self):
"""Установка пароля пользователя на сервере"""
# Проверяем на root
if self.isRoot(False):
self.printERROR(_("The user is root"))
self.printWARNING(\
_("The program can be executed by a non-root user only"))
return False
# DNS имя хоста
server = self.ldapDataObj.getHost()
# DN пользователей
usersDN = self.ldapDataObj.getUsersDN()
if not (server and usersDN):
self.printERROR(_("The computer is not in the domain"))
self.printWARNING(_("Use passwd"))
return False
count = 2
# Получаем старый пароль пользователя
curPassword = self.getUserPassword(_("Enter the current password"))
if not curPassword:
self.printERROR(_("The current password is empty"))
for i in range(count):
count -= 1
# Получаем старый пароль пользователя
curPassword = self.getUserPassword(
_("Enter the current password"))
if curPassword:
break
self.printERROR(_("The current password is empty"))
if not curPassword:
return False
userDN = self.ldapDataObj.addDN("uid=%s"%os.environ["USER"], usersDN)
# Проверяем в LDAP сервере текущий пароль пользователя
ret, err = self.checkUserPwdLDAP(server, userDN, curPassword)
if not ret:
self.printERROR(err)
for i in range(count):
# Получаем старый пароль пользователя
curPassword = self.getUserPassword(
_("Enter the current password"))
if not curPassword:
self.printERROR(_("The current password is empty"))
continue
# Проверяем в LDAP сервере текущий пароль пользователя
ret, err = self.checkUserPwdLDAP(server, userDN, curPassword)
if ret:
break
self.printERROR(err)
if not ret:
return False
password = self.getUserPwd({"p":""}, "p", False)
if password is False:
for i in range(2):
password = self.getUserPwd({"p":""}, "p", False)
if password:
break
if password is False:
return False
# Переменные для записи в env файл
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(os.environ["HOME"], self.configFileServer)
if not self.setServerCommand(["passwd_samba"], varsConfig, fileConfig):
return False
self.printOK(_("%s's password changed")%os.environ["USER"] + \
" ...")
self.printWARNING(_("The password will be changed when you log "
"out from the X session"))
return True