# -*- 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 ''