You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-client/pym/client/variables/client.py

873 lines
28 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 2008-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from os import path
import re
from calculate.lib.datavars import (Variable, VariableError, ReadonlyVariable,
ReadonlyTableVariable, FieldValue,
HumanReadable)
from calculate.lib.cl_ini_parser import iniParser
from calculate.lib.configparser import ConfigParser
import calculate.lib.utils.device as device
from calculate.lib.utils.files import (readFile, find,
FindFileType)
from calculate.lib.utils.mount import isMount
from calculate.lib.utils.common import getValueFromCmdLine, CmdlineParams
from calculate.lib.utils.portage import isPkgInstalled
from calculate.lib.variables import user
from calculate.lib.convertenv import convertEnv
from calculate.lib.utils.ip import isOpenPort
import time
import ldap
from socket import gethostbyname
from calculate.lib.cl_ldap import ldapUser
from calculate.lib.variables.user import LdapHelper
import pwd
from ..client import Client
from ..rsync import ProfileSyncer, ProfileSyncerError
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
_ = lambda x: x
setLocalTranslate('cl_client3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
class VariableClRemoteHost(Variable):
"""
IP or domain name of CDS
"""
value = ""
class VariableClRemoteDomain(Variable):
"""
Имя домена
"""
value = ""
class VariableClRemoteHostNew(Variable):
"""
IP or domain name of CDS
"""
opt = ["cl_remote_host_new"]
metavalue = "DOMAIN"
type = "choiceedit"
value = ""
untrusted = True
def init(self):
self.label = _("Domain IP")
self.help = _("domain")
def check(self, value):
if self.Get('cl_client_mount_set') == 'off':
if self.Get('cl_localhost_set') == 'off':
if self.Get('cl_remote_host') == '':
if not value:
raise VariableError(_("Please specify the domain"))
elif not isOpenPort(value, 445):
raise VariableError(
_("The specified address is not available"))
class VariableClRemoteHostLive(ReadonlyVariable):
"""
Remote host from /proc/cmdline param domain
"""
def get(self):
return getValueFromCmdLine(CmdlineParams.Calculate,
CmdlineParams.Domain) or ""
class VariableOsRemoteAuth(Variable):
"""
Client work mode (local or hostname)
"""
class VariableOsRemoteClient(Variable):
"""
Version which apply templates
"""
pass
class VariableClRemotePw(Variable):
"""
Client password
"""
type = "onepassword"
opt = ["--domain-password"]
def init(self):
self.label = __("Domain password")
self.help = _("specify the domain password")
def get(self):
return getValueFromCmdLine(CmdlineParams.Calculate,
CmdlineParams.DomainPassword) or ""
class VariableClMovedSkipPath(Variable):
"""
Skip "Moved" path
"""
type = "list"
value = ['Disks', 'Home', 'Moved', 'FTP', 'Desktop', 'Share']
class VariableClSyncSkipPath(Variable):
"""
Skip sync path
"""
type = "list"
value = [".googleearth", "Home", "Disks", "FTP",
'Share', ".local/share/akonadi/db_data", ".VirtualBox",
".mozilla/firefox/calculate.default/urlclassifier3.sqlite",
".local/share/mime/mime.cache", ".gvfs",
".kde4/share/apps/nepomuk/repository/main/data", ".logout",
".Xauthority", ".thumbnails", ".mozilla/firefox/*/Cache",
".kde4/socket-*", ".cache/", ".local/share/Trash"]
class VariableClSyncDelPath(Variable):
"""
Removed path on sync
"""
type = "list"
value = [".kde4/share/config/phonondevicesrc",
".kde4/cache-*", ".kde4/tmp-*"]
class VariableClProfileAllSet(Variable):
type = "bool"
value = "off"
class VariableClClientSync(Variable):
type = "bool"
value = "on"
metavalue = "ON/OFF"
opt = ["--sync"]
def init(self):
self.label = _("Synchronize the user profile")
self.help = _("synchronize user preferences")
class VariableClLocalhostSet(Variable):
"""
Using autopartition
"""
type = "bool"
element = "radio"
value = "off"
opt = ["-r"]
metavalue = "ON/OFF"
untrusted = True
def init(self):
self.label = _("Workstation role")
self.help = _("remove the domain connection settings")
def choice(self):
return [("off", _("Domain workstation")),
("on", _("Local workstation"))]
def check(self, value):
if self.Get('cl_client_mount_set') == 'off':
if self.Get('cl_remote_host') == '' and value == "on":
raise VariableError(_("The workstation is not in the domain"))
if self.Get('cl_remote_host') != '' and value == "off":
raise VariableError(
_("The workstation is already in the domain %s")
% self.Get('cl_remote_host') + "\n" +
_("Before joining the domain, "
"you need to remove it from the previous domain"))
# def get(self):
# if self.Get('cl_remote_host') == '':
# return "on"
# else:
# return "off"
class VariableClClientMountSet(Variable):
"""
Mount remote by configured domain information
"""
type = "bool"
value = "off"
metavalue = "ON/OFF"
opt = ["--mount"]
def init(self):
self.label = _("Only mount the domain resource")
self.help = _("only mount the [remote] domain resource")
class VariableUrUserPw(Variable, LdapHelper):
"""
Current user password
"""
type = "need-onepassword"
opt = ["--old-password"]
metavalue = "OLDPASSWORD"
value = ""
untrusted = True
def init(self):
self.label = _("Current password")
self.help = _("current user password")
def checkUserPwdLDAP(self, server, userDN, password):
"""Check unix user password on server"""
ldapInit = ldap.initialize("ldap://%s" % server)
try:
ldapInit.bind_s(userDN, password)
except ldap.INVALID_CREDENTIALS:
raise VariableError(_("Wrong password"))
except ldap.LDAPError as e:
raise VariableError(f"{e.args[0]['desc']}; {e.args[0]['info']}")
return True
def check(self, value):
if not value:
raise VariableError(_("Empty password"))
# читаем os_remote_auth, так как при смене пароля
# чтение должно выполняться от пользователя,
# cl_remote_host не может быть прочитан пользователем
server = self.Get('os_remote_auth')
ldapObj = self.getLdapUserObject()
if ldapObj:
usersDN = ldapObj.getUsersDN()
userDN = ldapObj.addDN("uid=%s" % self.Get('ur_login'),
usersDN)
self.checkUserPwdLDAP(server, userDN, value)
class VariableUrUserNewPw(Variable):
"""
New user password
"""
type = "need-password"
opt = ["--new-password"]
metavalue = "NEWPASSWORD"
value = ""
untrusted = True
def init(self):
self.label = _("New password")
self.help = _("new user password")
def check(self, value):
if not value:
raise VariableError(_("Empty password"))
class VariableClClientLogin(user.VariableUrLogin):
"""
User Login
"""
opt = ["cl_client_login"]
alias = "ur_login"
def choice(self):
loginChoice = user.VariableUrLogin.choice(self)
if self.Get('cl_action') == 'passwd':
return [x for x in loginChoice if x != "root"]
else:
return loginChoice
def check(self, value):
"""Does user exist"""
if not value in self.choice() and self.Get('cl_action') == 'logout':
raise VariableError(_("X session users not found"))
if value == "":
raise VariableError(_("Please specify the user"))
if value == "root" and self.Get('cl_action') == 'passwd':
raise VariableError(
_("This action can be executed by a non-root user only"))
try:
pwd.getpwnam(value).pw_gid
except (TypeError, KeyError):
raise VariableError(_("User %s does not exist") % value)
def get(self):
if (self.Get('cl_action') == 'passwd' and
self.Get('ur_login') != 'root'):
return self.Get('ur_login')
return ""
class VariableClClientRelevanceSet(ReadonlyVariable):
"""
Актуальны ли сейчас выполненные шаблоны
"""
type = "bool"
def get(self):
# если происходят действия ввода или вывода из домена
if (self.Get('cl_action') in ("domain", "undomain") and
self.Get('cl_client_mount_set') == 'off'):
return "off"
# если изменился домен
if self.Get('cl_remote_host') != self.Get("os_remote_auth"):
return "off"
if (self.Get('cl_remote_host') and
not isMount(self.Get('cl_client_remote_path'))):
return "off"
return "on"
class VariableClClientRemotePath(Variable):
"""
Путь для монитрования //domain/remote
"""
value = "/var/calculate/remote"
class VariableClClientProfileName(Variable):
"""
Название удаленного профиля (CLD,CLDX,all)
"""
def get(self):
return ("all" if self.Get('cl_profile_all_set') == 'on'
else self.Get('os_linux_shortname'))
class VariableClLdapData(ldapUser, ReadonlyVariable):
"""
Внутренняя переменная, содержащая объект для доступа к данным LDAP
"""
type = "object"
def get(self):
return self
def getReplDN(self):
"""
Получить из LDAP домен на котором находится актуальный профиль
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 Exception:
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 'host' in searchPrevHost[0][0][1]:
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 ""
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 VariableClReplicationHost(ReadonlyVariable):
"""
Удаленный сервер при репликации, который содержит актуальный профиль
"""
def get(self):
return self.Get('cl_ldap_data').getNameRemoteServer(
self.Get('ur_login'), self.Get('cl_client_profile_name'),
self.Get('cl_remote_host'))
class VariableClClientUserMountData(ReadonlyTableVariable):
"""
Таблица монтирования ресурсов
"""
source = ['cl_client_user_mount_name',
'cl_client_user_mount_resource',
'cl_client_user_mount_path',
'cl_client_user_mount_host']
def get(self, hr=HumanReadable.No):
home = path.split(self.Get('ur_home_path'))[0]
envFile = self.Get('cl_env_server_path')
samba_host = self.Get('sr_samba_host')
ftp_host = convertEnv().getVar("ftp", "host")
def generate():
yield (
"share", "share", path.join(self.Get('ur_home_path'), "Share"),
samba_host)
yield (
"unix", "unix", path.join(home, ".%s" % self.Get('ur_login'),
"profile"),
samba_host)
yield (
"homes", "homes", path.join(self.Get('ur_home_path'), "Home"),
samba_host)
if ftp_host:
yield ("ftp", "ftp", path.join(self.Get('ur_home_path'), "FTP"),
ftp_host)
else:
yield ("ftp", '', '', '')
if self.Get('cl_replication_host'):
yield ("remote_profile", "unix",
path.join(home, ".%s" % self.Get('ur_login'),
"remote_profile"),
self.Get('cl_replication_host'))
else:
yield ("remote_profile", 'unix', '', '')
return list(generate())
class VariableClClientUserMountUnixPath(ReadonlyVariable):
"""
Путь до подключенного unix ресурса данного пользователя
"""
def get(self):
return self.select('cl_client_user_mount_path',
cl_client_user_mount_name='unix', limit=1) or ""
class VariableClClientUserMountName(FieldValue, ReadonlyVariable):
"""
Название удаленного ресурса
"""
type = "list"
source_variable = "cl_client_user_mount_data"
column = 0
class VariableClClientUserMountResource(FieldValue, ReadonlyVariable):
"""
Название удаленного ресурса
"""
type = "list"
source_variable = "cl_client_user_mount_data"
column = 1
class VariableClClientUserMountPath(FieldValue, ReadonlyVariable):
"""
Путь подключения удаленного ресурса
"""
type = "list"
source_variable = "cl_client_user_mount_data"
column = 2
class VariableClClientUserMountHost(FieldValue, ReadonlyVariable):
"""
Удаленный сервер
"""
type = "list"
source_variable = "cl_client_user_mount_data"
column = 3
class SyncHelper():
"""
Вспомогательный объект для определения статуса синхронизации и времени
по конфигурационным файлам
"""
def getSyncStatus(self, rpath):
"""
Получить status_sync из desktop файла
"""
fileConfig = path.join(rpath, Client.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", "")
return ""
def getDataInConfig(self, section, listVars, objConfig):
"""
Прочитать список переменных из области конфигурационного файла
"""
varsConfig = {}
for varName in listVars:
varsConfig[varName] = objConfig.getVar(section, varName)
if objConfig.getError():
return False
return varsConfig
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 ""
def getDateObjClientConf(self, rpath):
"""
Получить время синхронизации из .calculate/desktop.env
"""
fileConfig = path.join(rpath, Client.configFileDesktop)
if os.path.exists(fileConfig):
objConfig = iniParser(fileConfig)
data = self.getDataInConfig("main", ["date", "date_logout"],
objConfig)
timeLogout = data["date_logout"]
timeConfig = data["date"]
dates = [x for x in [self.convertDate(timeLogout),
self.convertDate(timeConfig)] if x]
if dates:
return dates[0]
return ""
def checkNeedSync(self, homeDir, rpath, curTimeObj, curStatusSync,
osLinuxShort):
"""
Проверить необходимость синхронизации текущего профиля с удаленным
"""
# profile directory
# fileConfig = os.path.join(homeDir, Client.configFileServer)
pathProfile = os.path.join(rpath, osLinuxShort)
# if readFile(fileConfig).strip():
# return True
fileSoftConfigThis = os.path.join(pathProfile,
Client.configFileSoft)
fileSoftConfigCur = os.path.join(homeDir,
Client.configFileSoft)
xSessionCur = iniParser(fileSoftConfigCur).getVar('main', 'xsession')
xSessionThis = iniParser(fileSoftConfigThis).getVar('main', 'xsession')
# check profile date on current server
# fileConfigThis = os.path.join(pathProfile, Client.configFileDesktop)
# if iniParser(fileConfigThis).getVar('main','status_sync') == "success":
# self.setVarToConfig("main", {"status_sync":"success_mount"},
# fileConfigThis)
thisTimeObj = self.getDateObjClientConf(pathProfile)
if curStatusSync == "success_logout" and \
xSessionCur == xSessionThis and \
thisTimeObj and curTimeObj and \
curTimeObj >= thisTimeObj:
return False
return True
def getSyncDate(self, osLinuxShort, ps):
"""
Получить время синхронизации из ::profile/.calculate/desktop.env
"""
desktopEnvRemoteData = ps.readfile("::profile/{}/{}".format(
osLinuxShort, Client.configFileDesktop))
if desktopEnvRemoteData:
cpRemote = ConfigParser(strict=False)
cpRemote.read_string(desktopEnvRemoteData)
timeLogout = cpRemote.get("main", "date_logout", fallback=None)
timeConfig = cpRemote.get("main", "date", fallback=None)
dates = [x for x in [self.convertDate(timeLogout),
self.convertDate(timeConfig)] if x]
if dates:
return dates[0]
return ""
def checkNeedSyncNew(self, homeDir, rpath, curTimeObj, curStatusSync,
osLinuxShort, ps):
"""
Проверить необходимость синхронизации текущего профиля с удаленным
"""
iniEnvRemoteData = ps.readfile("::profile/{}/{}".format(
osLinuxShort, Client.configFileSoft))
fileConfigCur = os.path.join(homeDir, Client.configFileSoft)
iniEnvCurrentData = readFile(fileConfigCur)
cpCur = ConfigParser(strict=False)
cpCur.read_string(iniEnvCurrentData)
cpRemote = ConfigParser(strict=False)
cpRemote.read_string(iniEnvRemoteData)
xSessionCur = cpCur.get('main', 'xsession', fallback=None)
xSessionThis = cpThis.get('main', 'xsession', fallback=None)
if curStatusSync == "success_logout" and \
xSessionCur == xSessionThis:
thisTimeObj = self.getSyncDate(osLinuxShort, ps)
if thisTimeObj and curTimeObj and \
curTimeObj >= thisTimeObj:
return False
return True
class VariableClClientSyncTime(SyncHelper, ReadonlyVariable):
"""
Текущее время синхронизации профиля
"""
def get(self):
return self.getDateObjClientConf(self.Get('ur_home_path'))
class VariableClClientPackTime(SyncHelper, ReadonlyVariable):
"""
Время комады упаковки профиля
"""
def get(self):
return str(float(time.time()))
class VariableClClientSyncStatus(SyncHelper, ReadonlyVariable):
"""
Текущий статус синхронизации профиля
"""
def get(self):
return self.getSyncStatus(self.Get('ur_home_path'))
class VariableClClientLocalSyncTime(SyncHelper, ReadonlyVariable):
"""
Текущий статус синхронизации профиля
"""
def get(self):
return self.getDateObjClientConf(
path.join(
self.Select('cl_client_user_mount_path',
where='cl_client_user_mount_name', eq='unix',
limit=1), self.Get('cl_client_profile_name')))
class VariableClClientRsyncProfileSet(ReadonlyVariable):
"""
Используется rsync через ssh для синхронизации пользовательского профиля
"""
type = "bool"
value = "off"
#TODO ProfileSyncer likely doesn't work in py3
class VariableClClientSyncReplicationSet(SyncHelper, ReadonlyVariable):
"""
Нужно ли синхронизировать текущий профиль с удаленным доменом
"""
type = "bool"
host_varname = "cl_replication_host"
profile_type = "remote_profile"
def old_synchronize(self):
profilePath = self.Select('cl_client_user_mount_path',
where='cl_client_user_mount_name',
eq=self.profile_type, limit=1)
if self.Get('cl_action') == 'login' and not isMount(profilePath):
raise VariableError(_("Remote profile not mounted"))
return "on" if self.checkNeedSync(self.Get('ur_home_path'), profilePath,
self.Get('cl_client_sync_time'),
self.Get('cl_client_sync_status'),
self.Get(
'cl_client_profile_name')) else "off"
def new_synchronize(self):
user = self.Get('ur_login')
pwd = self.Get('desktop.ur_password')
remotehost = self.Get(self.host_varname)
ps = ProfileSyncer(remotehost, 2009, user, pwd)
if ps.check():
return "on" if self.checkNeedSyncNew(
self.Get('ur_home_path'), profilePath,
self.Get('cl_client_sync_time'),
self.Get('cl_client_sync_status'),
self.Get('cl_client_profile_name'),
ps) else "off"
return "off"
def get(self):
if not self.Get(self.host_varname):
return "off"
if self.GetBool('cl_client_rsync_profile_set'):
return self.new_synchronize()
else:
return self.old_synchronize()
class VariableClClientSyncLocalSet(VariableClClientSyncReplicationSet):
"""
Нужно ли синхронизировать текущий профиль с локальным доменом
"""
type = "bool"
host_varname = "cl_remote_host"
profile_type = "unix"
class VariableClClientSymlinks(ReadonlyVariable):
"""
Список симлинков в пользовательском профиле
"""
def get(self):
skipFiles = (self.Get('cl_sync_del_path') +
self.Get('cl_sync_skip_path'))
reSkip = re.compile("|".join((x.replace("*", ".*") for x in skipFiles))).search
return [x for x in find(self.Get('ur_home_path'), onefilesystem=True,
filetype=FindFileType.SymbolicLink)
if not reSkip(x)]
class VariableClClientNscdCache(Variable):
"""
Частота обновления кэша nscd при работе в домене в часах
"""
class VariableClCifsVer(ReadonlyVariable):
"""
Версия модуля CIFS
"""
def get(self):
return device.sysfs.read(device.sysfs.Path.Module, "cifs/version")
class VariableClCifsCache(Variable):
"""
Параметр cache= при монтировании cifs
"""
value = "loose"
class VariableClCifsMountVers(Variable):
"""
Параметр vers= для cifs
"""
value = "1.0"
class VariableClRsyncVer(ReadonlyVariable):
"""
Версия rsync
"""
def get(self):
data = isPkgInstalled('net-misc/rsync')
if data:
return data[0]['PVR']
return ""
class VariableClCifsutilsVer(ReadonlyVariable):
"""
Версия cifs-utils
"""
def get(self):
data = isPkgInstalled('net-fs/cifs-utils')
if data:
return data[0]['PV']
return ""
class VariableClClientIgnoreErrorsSet(Variable):
"""
Параметр, для отключения отмонтирования пользовательских ресурсов,
при ошбиках, возникших во вермя cl-client-sync-login
"""
type = "bool"
value = "on"
metavalue = "ON/OFF"
opt = ["--unmount-on-error"]
def init(self):
self.label = _("Unmount user resources on error")
self.help = _("unmount user resources on error")
class VariableClSyncMovedSet(Variable):
"""
Использовать или нет перенос файлов из домашней директории в Home/Moved при
синхронизации
"""
type = "bool"
value = "on"
class VariableSrSambaHost(Variable):
"""
Хост на котором находятся samba ресурсы
"""
def get(self):
return self.Get('cl_remote_host') or ''