|
|
#-*- coding: utf-8 -*-
|
|
|
|
|
|
#Copyright 2008 Calculate Pack, http://www.calculate-linux.ru
|
|
|
#
|
|
|
# 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 re
|
|
|
import sys
|
|
|
|
|
|
import cl_base
|
|
|
import cl_profile
|
|
|
import cl_utils2
|
|
|
import cl_utils
|
|
|
|
|
|
import ldap
|
|
|
import types
|
|
|
import getpass
|
|
|
import _cl_keys
|
|
|
|
|
|
Version = "calculate-client 0.0.6"
|
|
|
|
|
|
tr = cl_base.lang()
|
|
|
tr.setLanguage(sys.modules[__name__])
|
|
|
|
|
|
pcs = cl_utils.prettyColumnStr
|
|
|
|
|
|
class printNoColor:
|
|
|
def colorPrint(self,attr,fg,bg,string):
|
|
|
sys.stdout.write(string)
|
|
|
|
|
|
# Импортированные классы в cl_ldap
|
|
|
# Запись ошибок
|
|
|
imp_cl_err = cl_profile._error
|
|
|
# Работа с XML
|
|
|
imp_cl_xml = cl_profile.xmlShare
|
|
|
# Обработка параметров командной строки
|
|
|
imp_cl_help = cl_utils2.cl_help
|
|
|
# Форматированный вывод
|
|
|
imp_cl_smcon = cl_utils2.cl_smartcon
|
|
|
|
|
|
class cl_client(imp_cl_err, imp_cl_xml, imp_cl_help, imp_cl_smcon):
|
|
|
"""Основной класс для работы клиентских приложений"""
|
|
|
def __init__(self, cmdName):
|
|
|
# объект для форматированного вывода
|
|
|
imp_cl_help.__init__(self, cmdName)
|
|
|
|
|
|
servName = ""
|
|
|
if "user" in cmdName:
|
|
|
servName = _("user")
|
|
|
|
|
|
self.chapter = [\
|
|
|
# расположение разделов на странице
|
|
|
# имя раздела, видимый или невидимый, кол. "\n" после
|
|
|
# названия раздела, кол. "\n" после раздела
|
|
|
# тип раздела
|
|
|
("Copyright",False,0,2,""),
|
|
|
(_("Usage"),True,0,1,""),
|
|
|
("Function",False,0,2,""),
|
|
|
(_("Examples"),True,1,1,""),
|
|
|
(_("Common options"),True,1,0,"options"),
|
|
|
]
|
|
|
|
|
|
# имена используемых программ и их номера для доступа к переменным
|
|
|
# self.data
|
|
|
self.progName = { 'cl-client':0,
|
|
|
'cl-createhome':1,
|
|
|
'cl-sync':2
|
|
|
}
|
|
|
|
|
|
# Cвязь длинных опций помощи и выводимых разделов помощи с опциями
|
|
|
self.relOptions = {"h":[_("Common options")],}
|
|
|
|
|
|
# список разделов, которые на наличие в ней информации
|
|
|
# используется для автоматического отображения/скрытия
|
|
|
# опций help-имя
|
|
|
# Пример: self.relOption =
|
|
|
# { "help-all":[_("Common options"], _("Unix service options"),
|
|
|
# _("Samba service options"), _("LDAP service options")}]
|
|
|
# self.relChapterPass = (_("Common options"),)
|
|
|
# это означается что опция будет активна, если только в разделах
|
|
|
# кроме Common options есть хоть одна доступная опция.
|
|
|
self.relChapterPass = (_("Common options"),)
|
|
|
|
|
|
self.data = [\
|
|
|
{
|
|
|
#Copyright
|
|
|
#'progAccess':(3,),
|
|
|
'helpChapter':"Copyright",
|
|
|
'help':Version
|
|
|
},
|
|
|
#Usage
|
|
|
{
|
|
|
'progAccess':(0,),
|
|
|
'helpChapter':_("Usage"),
|
|
|
'help': cmdName + " [" + _("options") + "] " + _("domain")
|
|
|
},
|
|
|
{
|
|
|
'progAccess':(1,),
|
|
|
'helpChapter':_("Usage"),
|
|
|
'help': cmdName + " " + _("user")
|
|
|
},
|
|
|
{
|
|
|
'progAccess':(2,),
|
|
|
'helpChapter':_("Usage"),
|
|
|
'help': cmdName + " [" + _("options") + "] " + _("user")
|
|
|
},
|
|
|
# Function
|
|
|
{
|
|
|
'progAccess':(0,),
|
|
|
'helpChapter':"Function",
|
|
|
'help':_("Changes settings for connecting to domain \
|
|
|
(calculate-server)")
|
|
|
},
|
|
|
{
|
|
|
'progAccess':(1,),
|
|
|
'helpChapter':"Function",
|
|
|
'help':_("Create home directory for the new user account")
|
|
|
},
|
|
|
{
|
|
|
'progAccess':(2,),
|
|
|
'helpChapter':"Function",
|
|
|
'help':_("Mounting directories and synchronize the user preferences")
|
|
|
},
|
|
|
# Examples
|
|
|
{
|
|
|
'progAccess':(0,),
|
|
|
'helpChapter':_("Examples"),
|
|
|
'help':pcs( " cl-client 192.168.0.1", self.column_width,
|
|
|
"# " + _("Adds settings for connecting to domain \
|
|
|
(ip 192.168.0.1)"),
|
|
|
self.consolewidth-self.column_width )
|
|
|
},
|
|
|
# Options
|
|
|
{'shortOption':"h",
|
|
|
'longOption':"help",
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("display this help and exit")
|
|
|
},
|
|
|
{'progAccess':(0,),
|
|
|
'shortOption':"r",
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("Removes the settings for connecting to a domain")
|
|
|
},
|
|
|
{'progAccess':(0,1,2),
|
|
|
'longOption':"vars",
|
|
|
'optVal':_("TYPE_VAR"),
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("print variables (TYPE_VAR - all:full var)")
|
|
|
},
|
|
|
{'progAccess':(0,1,2),
|
|
|
'longOption':"color",
|
|
|
'optVal':_("WHEN"),
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("control whether color is used to distinguish file types. \
|
|
|
WHEN may be 'never', 'always', or 'auto'")
|
|
|
},
|
|
|
{'progAccess':(1,),
|
|
|
'shortOption':"f",
|
|
|
'longOption':"force",
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("always join the user profiles and preferences")
|
|
|
},
|
|
|
{'progAccess':(0,),
|
|
|
'longOption':"mount",
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("mount [remote] resource for Samba (calculate-server)")
|
|
|
},
|
|
|
{'progAccess':(2,),
|
|
|
'longOption':"login",
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("mount user resource")+ " " +\
|
|
|
_("and synchronize the user preferences")
|
|
|
},
|
|
|
{'progAccess':(2,),
|
|
|
'longOption':"logout",
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("synchronize the user preferences") + " " +\
|
|
|
_("and umount user resource")
|
|
|
},
|
|
|
{'progAccess':(2,),
|
|
|
'longOption':"nosync",
|
|
|
'helpChapter':_("Common options"),
|
|
|
'help':_("not synchronize the user preferences, is used in \
|
|
|
conjunction with the 'login' or 'logout'")
|
|
|
},
|
|
|
#{'progAccess':(0,),
|
|
|
#'shortOption':"p",
|
|
|
#'longOption':"prvar",
|
|
|
#'optVal':_("TYPES_VAR"),
|
|
|
#'helpChapter':_("Common options"),
|
|
|
#'help':_("print variable (filter type - comma delimited)")
|
|
|
#},
|
|
|
|
|
|
]
|
|
|
|
|
|
self._cl_help__setParamHelp()
|
|
|
|
|
|
# Удаляем ненужный аттрибут класса cl_profile.xmlShare
|
|
|
self._createElement = False
|
|
|
delattr(self, "_createElement")
|
|
|
|
|
|
# Переменная объект Vars
|
|
|
self.clVars = False
|
|
|
# Переменная объект ldapFunction
|
|
|
self.ldapObj = False
|
|
|
# Переменная соединение с LDAP сервером
|
|
|
self.conLdap = False
|
|
|
# Базовый DN LDAP сервера
|
|
|
self.baseDN = False
|
|
|
# DN сервисов относительно базового
|
|
|
self.ServicesDN = "ou=Services"
|
|
|
|
|
|
self.relGrDN = 'ou=Groups'
|
|
|
self.relUsDN = 'ou=Users'
|
|
|
self.relServDN = 'ou=Unix'
|
|
|
self.relDN = self.addDN(self.relServDN,self.ServicesDN)
|
|
|
# DN пользователей, относительно базового DN
|
|
|
self.relUsersDN = self.addDN(self.relUsDN, self.relDN)
|
|
|
# DN групп, относительно базового DN
|
|
|
self.relGroupsDN = self.addDN(self.relGrDN, self.relDN)
|
|
|
# Объект хранения переменных
|
|
|
self.clVars = False
|
|
|
|
|
|
|
|
|
def createClVars(self, clVars=False):
|
|
|
"""Создает объект Vars"""
|
|
|
if not clVars:
|
|
|
clVars = cl_base.DataVars()
|
|
|
clVars.flClient()
|
|
|
clVars.flIniFile()
|
|
|
# Устанавливаем у объекта объект Vars
|
|
|
self.clVars = clVars
|
|
|
return True
|
|
|
|
|
|
def addDN(self, *arg):
|
|
|
"""Складывает текстовые элементы DN"""
|
|
|
DNs = []
|
|
|
for dn in arg:
|
|
|
if dn:
|
|
|
DNs.append(dn)
|
|
|
return ','.join(DNs)
|
|
|
|
|
|
def searchLdapDN(self, name, relDN, attr, retAttr=None):
|
|
|
"""Находит DN в LDAP"""
|
|
|
baseDN = self.clVars.Get("ld_base_dn")
|
|
|
DN = self.addDN(relDN,baseDN)
|
|
|
#searchScope = ldap.SCOPE_SUBTREE
|
|
|
searchScope = ldap.SCOPE_ONELEVEL
|
|
|
searchFilter = "%s=%s" %(attr,name)
|
|
|
retrieveAttributes = retAttr
|
|
|
resSearch = self.ldapObj.ldapSearch(DN, searchScope,
|
|
|
searchFilter, retrieveAttributes)
|
|
|
return resSearch
|
|
|
|
|
|
|
|
|
def searchUnixUser(self, userName):
|
|
|
"""Находит пользователя сервиса Unix"""
|
|
|
resSearch = self.searchLdapDN(userName, self.relUsersDN, "uid")
|
|
|
return resSearch
|
|
|
|
|
|
def searchUnixGid(self, groupId):
|
|
|
"""Находит группу сервиса Unix по ёе id"""
|
|
|
resSearch = self.searchLdapDN(str(groupId), self.relGroupsDN,
|
|
|
"gidNumber")
|
|
|
return resSearch
|
|
|
|
|
|
|
|
|
def getLdapObjBind(self, host, printError=True):
|
|
|
"""Получаем объект ldapFunction
|
|
|
|
|
|
Соединяемся пользователем bind
|
|
|
В выходном объекте есть соединение с LDAP сервером: self.conLdap
|
|
|
"""
|
|
|
self.createClVars(self.clVars)
|
|
|
bindDn = self.clVars.Get("ld_bind_dn")
|
|
|
bindPw = self.clVars.Get("ld_bind_pw")
|
|
|
if not (bindDn or bindPw):
|
|
|
if printError:
|
|
|
self.printERROR(_("Not found LDAP bind DN or password") +\
|
|
|
" ...")
|
|
|
return False
|
|
|
ldapObj = cl_utils2.ldapFun(bindDn, bindPw, host)
|
|
|
if ldapObj.getError():
|
|
|
if printError:
|
|
|
self.printERROR (_("LDAP connect error") + ": " +\
|
|
|
ldapObj.getError().strip())
|
|
|
return False
|
|
|
# Устанавливаем у объекта соединение и объект LDAP функций
|
|
|
self.ldapObj = ldapObj
|
|
|
self.conLdap = ldapObj.conLdap
|
|
|
return True
|
|
|
|
|
|
def searchLineInFile(self, name, fileName, numEl=0):
|
|
|
"""Ищет строку в которой есть название разделенное ':'
|
|
|
|
|
|
в файле похожем на /etc/passwd"""
|
|
|
if os.path.exists(fileName):
|
|
|
FD = open(fileName)
|
|
|
lines = FD.readlines()
|
|
|
FD.close()
|
|
|
lineFound = ""
|
|
|
for line in lines:
|
|
|
if name == line.split(":")[numEl]:
|
|
|
lineFound = line
|
|
|
break
|
|
|
if lineFound:
|
|
|
return lineFound
|
|
|
else:
|
|
|
return False
|
|
|
|
|
|
def searchPasswdUser(self, userName):
|
|
|
"""Ищет пользователей в /etc/passwd"""
|
|
|
filePasswd = "/etc/passwd"
|
|
|
return self.searchLineInFile(userName, filePasswd)
|
|
|
|
|
|
def searchGroupGid(self, groupId):
|
|
|
"""Ищет gid в /etc/group"""
|
|
|
gid = str(groupId)
|
|
|
fileGroup = "/etc/group"
|
|
|
return self.searchLineInFile(gid, fileGroup, 2)
|
|
|
|
|
|
def getUserLdapInfo(self, userName, printError=True):
|
|
|
"""Выдаем uid и gid пользователя"""
|
|
|
searchUser = self.searchUnixUser(userName)
|
|
|
if not searchUser:
|
|
|
if printError:
|
|
|
self.printERROR(_("User %s not found in Unix service")\
|
|
|
%str(userName))
|
|
|
return False
|
|
|
uid = False
|
|
|
gid = False
|
|
|
fullName = ""
|
|
|
mail = ""
|
|
|
if searchUser[0][0][1].has_key('uidNumber'):
|
|
|
uid = searchUser[0][0][1]['uidNumber'][0]
|
|
|
if searchUser[0][0][1].has_key('gidNumber'):
|
|
|
gid = searchUser[0][0][1]['gidNumber'][0]
|
|
|
searchGroup = self.searchUnixGid(gid)
|
|
|
if searchGroup and searchGroup[0][0][1].has_key('cn'):
|
|
|
group = searchGroup[0][0][1]['cn'][0]
|
|
|
if searchUser[0][0][1].has_key('cn'):
|
|
|
fullName = searchUser[0][0][1]['cn'][0]
|
|
|
if searchUser[0][0][1].has_key('mail'):
|
|
|
mail = searchUser[0][0][1]['mail'][0]
|
|
|
if searchUser[0][0][1].has_key('homeDirectory'):
|
|
|
home = searchUser[0][0][1]['homeDirectory'][0]
|
|
|
if uid and gid:
|
|
|
return (uid, gid, fullName, mail, home, group)
|
|
|
else:
|
|
|
return ()
|
|
|
|
|
|
def createUserDir(self, uid, gid, userDir, mode=0700):
|
|
|
"""Создание пользовательской директории"""
|
|
|
if not os.path.exists(userDir):
|
|
|
os.makedirs(userDir)
|
|
|
if mode:
|
|
|
os.chmod(userDir,mode)
|
|
|
os.chown(userDir,uid,gid)
|
|
|
return True
|
|
|
else:
|
|
|
self.printERROR(_("Path %s exists") %userDir)
|
|
|
return False
|
|
|
|
|
|
def applyProfilesFromUser(self):
|
|
|
"""Применяем профили для пользователя"""
|
|
|
# Cоздаем объект профиль
|
|
|
clProf = cl_profile.profile(self.clVars)
|
|
|
# Объединяем профили
|
|
|
dirsFiles = clProf.applyProfiles()
|
|
|
if clProf.getError():
|
|
|
self.printERROR(clProf.getError())
|
|
|
return False
|
|
|
else:
|
|
|
return dirsFiles
|
|
|
|
|
|
def getUserPassword(self, pwDialog=False):
|
|
|
"""Получить пароль у пользователя
|
|
|
|
|
|
pwDialog - приглашение ввода пароля
|
|
|
"""
|
|
|
if not pwDialog:
|
|
|
pwDialog = _("Password")
|
|
|
userPwd = getpass.getpass(pwDialog+":")
|
|
|
userPwd = re.sub("(\W)", r"\\\1",userPwd)
|
|
|
return userPwd
|
|
|
|
|
|
def chownR(self, directory, uid, gid, dirsAndFiles=False):
|
|
|
"""изменяет владельца и группу
|
|
|
|
|
|
для всех файлов и директорий внутри directory
|
|
|
"""
|
|
|
if dirsAndFiles:
|
|
|
dirs, files = dirsAndFiles
|
|
|
# меняем владельца домашней директории
|
|
|
os.chown(directory, uid,gid)
|
|
|
# Меняем владельца директорий
|
|
|
for dirCh in dirs:
|
|
|
if os.path.exists(dirCh):
|
|
|
os.chown(dirCh, uid,gid)
|
|
|
# Меняем владельца файлов
|
|
|
for fileCh in files:
|
|
|
if os.path.exists(fileCh):
|
|
|
if os.path.islink(fileCh):
|
|
|
os.lchown(fileCh, uid, gid)
|
|
|
else:
|
|
|
os.chown(fileCh, uid,gid)
|
|
|
return True
|
|
|
else:
|
|
|
fileObj = cl_profile._file()
|
|
|
scanObjs = fileObj.scanDirs([directory])
|
|
|
# меняем владельца домашней директории
|
|
|
os.chown(directory, uid,gid)
|
|
|
# Меняем владельца директорий
|
|
|
for dirCh in scanObjs[0].dirs:
|
|
|
os.chown(dirCh, uid,gid)
|
|
|
# Меняем владельца файлов
|
|
|
for fileCh in scanObjs[0].files:
|
|
|
os.chown(fileCh, uid,gid)
|
|
|
# Меняем владельца ссылок
|
|
|
for linkCh in scanObjs[0].links:
|
|
|
os.lchown(linkCh[1], uid, gid)
|
|
|
return True
|
|
|
|
|
|
def execProg(self, cmdStrProg, inStr=False, retFull=True):
|
|
|
"""Выполняет внешнюю программу
|
|
|
|
|
|
Параметры:
|
|
|
cmdStrProg внешняя программа
|
|
|
inStr данные передаваемые программе на страндартный вход.
|
|
|
Возвращаемые параметры:
|
|
|
строка которую выведет внешняя программа
|
|
|
"""
|
|
|
return cl_utils.runOsCommand(cmdStrProg, inStr, retFull)
|
|
|
|
|
|
#def getUidAndGidUser(self, userName):
|
|
|
#strRes = self.execProg("id %s" %userName)
|
|
|
#reFind = re.compile("uid=(\d+)\(.+gid=(\d+)\(")
|
|
|
#res = reFind.search(strRes)
|
|
|
#if res:
|
|
|
#return res.group(1), res.group(2)
|
|
|
#else:
|
|
|
#self.printERROR(_("User %s not found")\
|
|
|
#%str(userName))
|
|
|
#return False
|
|
|
|
|
|
def getUserPasswdInfo(self, userName):
|
|
|
"""получаем uid и gid пользователя из /etc/passwd"""
|
|
|
resPasswd = self.searchPasswdUser(userName)
|
|
|
if resPasswd:
|
|
|
uid = resPasswd.split(":")[2]
|
|
|
gid = resPasswd.split(":")[3]
|
|
|
fullName = resPasswd.split(":")[4]
|
|
|
mail = ""
|
|
|
group = ""
|
|
|
home = os.path.join("/home",userName)
|
|
|
resGroup = self.searchGroupGid(gid)
|
|
|
if resGroup:
|
|
|
group = resGroup.split(":")[0]
|
|
|
return (uid, gid, fullName, mail, home, group)
|
|
|
else:
|
|
|
return False
|
|
|
|
|
|
def getAlwaysProfilePath(self):
|
|
|
"""Получаем пути к профилям которые применяются постоянно"""
|
|
|
profilePath = self.clVars.Get('cl_profile_path')
|
|
|
alwProfilePath = []
|
|
|
for prPath in profilePath:
|
|
|
if os.path.split(prPath)[1] == "always":
|
|
|
alwProfilePath.append(prPath)
|
|
|
return alwProfilePath
|
|
|
|
|
|
def createHome(self, userName, applyAlways=False):
|
|
|
"""Создание пользовательской директории с настройками для kde4"""
|
|
|
# Создаем объект переменных
|
|
|
self.createClVars()
|
|
|
uidGid = False
|
|
|
# Подсоединяемся к серверу
|
|
|
domain = self.clVars.Get("cl_remote_host")
|
|
|
connectLdap = False
|
|
|
if domain:
|
|
|
if not self.getLdapObjBind(domain):
|
|
|
return False
|
|
|
connectLdap = True
|
|
|
if connectLdap:
|
|
|
# uid и gid и mail из Ldap
|
|
|
uidGid = self.getUserLdapInfo(userName,False)
|
|
|
if not domain:
|
|
|
# uid и gid и mail из passwd
|
|
|
uidGid = self.getUserPasswdInfo(userName)
|
|
|
if not uidGid:
|
|
|
self.printERROR(_("Not found user uid and gid"))
|
|
|
return False
|
|
|
uid = int(uidGid[0])
|
|
|
gid = int(uidGid[1])
|
|
|
fullName = uidGid[2]
|
|
|
mail = uidGid[3]
|
|
|
homeDir = uidGid[4]
|
|
|
group = uidGid[5]
|
|
|
# Создаем пользовательскую директорию
|
|
|
self.clVars.Set('cl_root_path',homeDir,True)
|
|
|
homeExists = os.path.exists(homeDir)
|
|
|
|
|
|
if homeExists:
|
|
|
self.printWARNING(_("Home dir %s exists")%homeDir)
|
|
|
if set(os.listdir(homeDir))-set(["Home","Disks"]):
|
|
|
if not applyAlways:
|
|
|
# Получаем пути к профилям постоянного наложения
|
|
|
alwProfilePath = self.getAlwaysProfilePath()
|
|
|
if not alwProfilePath:
|
|
|
return True
|
|
|
# Записываем пути к профилям постоянного наложения
|
|
|
#в переменную
|
|
|
self.printSUCCESS(_("Apply always profiles") + " ...")
|
|
|
self.clVars.Set('cl_profile_path',alwProfilePath, True)
|
|
|
|
|
|
if not os.path.exists(homeDir):
|
|
|
self.createUserDir(uid, gid, homeDir)
|
|
|
# Записываем переменные
|
|
|
self.clVars.Set('ur_login',userName)
|
|
|
self.clVars.Set('ur_fullname',fullName)
|
|
|
self.clVars.Set('ur_mail',mail)
|
|
|
self.clVars.Set('ur_group',group)
|
|
|
# Применяем профили для пользователя
|
|
|
dirsAndFiles = self.applyProfilesFromUser()
|
|
|
if not dirsAndFiles:
|
|
|
self.printERROR(_("Not apply user profile"))
|
|
|
return False
|
|
|
self.chownR(homeDir, uid, gid, dirsAndFiles)
|
|
|
if not homeExists:
|
|
|
self.printSUCCESS(_("Created home dir %s")%homeDir + " ...")
|
|
|
self.printSUCCESS(_("User account is configured") + " ...")
|
|
|
return True
|
|
|
|
|
|
def isDomain(self):
|
|
|
"""Находится ли компьютер в домене"""
|
|
|
self.createClVars(self.clVars)
|
|
|
foundMountRemote =self.isMount("/var/calculate/remote" ,"cifs")
|
|
|
foundMountHome =self.isMount("/var/calculate/home" ,"none", False)
|
|
|
if not self.clVars.Get("cl_remote_host") and not foundMountRemote and\
|
|
|
not foundMountHome:
|
|
|
self.printERROR("The computer is not in domain")
|
|
|
return False
|
|
|
return (foundMountRemote,foundMountHome)
|
|
|
|
|
|
def isMount(self, pathMount ,typeMount, secondPath=True):
|
|
|
"""Примонтирована ли директория"""
|
|
|
path = os.path.realpath(pathMount)
|
|
|
foundMount = False
|
|
|
if secondPath:
|
|
|
reFoundMount = re.compile("on\s+%s\s+type\s+%s"%(path,typeMount))
|
|
|
else:
|
|
|
reFoundMount = re.compile("%s\s+.+type\s+%s"%(path,typeMount))
|
|
|
resMount = self.execProg("mount",False,False)
|
|
|
if resMount and type(resMount) == types.ListType:
|
|
|
for string in resMount:
|
|
|
if reFoundMount.search(string):
|
|
|
foundMount = True
|
|
|
break
|
|
|
return foundMount
|
|
|
|
|
|
def mountRemote(self):
|
|
|
self.createClVars(self.clVars)
|
|
|
foundMount = self.isDomain()
|
|
|
if not foundMount:
|
|
|
return False
|
|
|
foundMountRemote = foundMount[0]
|
|
|
foundMountHome = foundMount[1]
|
|
|
pathHome = "/var/calculate/home"
|
|
|
if foundMountRemote:
|
|
|
self.printWARNING(_("Samba resource [remote] is mount") + \
|
|
|
" ...")
|
|
|
if foundMountHome:
|
|
|
self.printWARNING(str(pathHome) + " " +_("is mount")+
|
|
|
" ...")
|
|
|
if foundMountHome and foundMountRemote:
|
|
|
return True
|
|
|
|
|
|
if not foundMountRemote:
|
|
|
pathRemote = "/var/calculate/remote"
|
|
|
domain = self.clVars.Get("cl_remote_host")
|
|
|
pwdRemote = self.clVars.Get("cl_remote_pw")
|
|
|
if not (domain and pwdRemote):
|
|
|
self.printERROR(_("Not found vaiables: cl_remote_host or \
|
|
|
cl_remote_pw") + " ...")
|
|
|
return False
|
|
|
if not os.path.exists(pathRemote):
|
|
|
os.makedirs(pathRemote)
|
|
|
mountStr = "mount -t cifs -o user=client,password=%s \
|
|
|
//%s/remote %s" %(pwdRemote,domain,pathRemote)
|
|
|
textLine = self.execProg(mountStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not mount Samba resource [remote]") +\
|
|
|
" ...")
|
|
|
return False
|
|
|
self.printSUCCESS(_("Mount Samba resource [remote]") +\
|
|
|
" ...")
|
|
|
if not foundMountHome:
|
|
|
if not os.path.exists(pathHome):
|
|
|
os.makedirs(pathHome)
|
|
|
mountStr = "mount -o bind %s /home" %pathHome
|
|
|
textLine = self.execProg(mountStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not mount") + " " + str(pathHome) +\
|
|
|
" ...")
|
|
|
return False
|
|
|
self.printSUCCESS(_("Mount") + " " + str(pathHome) + " " +\
|
|
|
" ...")
|
|
|
return True
|
|
|
|
|
|
def delDomain(self):
|
|
|
"""выводим из домена"""
|
|
|
self.createClVars()
|
|
|
pathRemote = "/var/calculate/remote"
|
|
|
pathHome = "/var/calculate/home"
|
|
|
foundMountRemote =self.isMount(pathRemote ,"cifs")
|
|
|
foundMountHome =self.isMount(pathHome ,"none",False)
|
|
|
domain = self.clVars.Get("cl_remote_host")
|
|
|
if not domain:
|
|
|
self.printWARNING("The computer is not in domain")
|
|
|
return True
|
|
|
if foundMountRemote:
|
|
|
umountStr = "umount %s"%(pathRemote)
|
|
|
textLine = self.execProg(umountStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not umount Samba resource [remote]") + \
|
|
|
" ...")
|
|
|
return False
|
|
|
if foundMountHome:
|
|
|
umountStr = "umount %s"%(pathHome)
|
|
|
textLine = self.execProg(umountStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not umount") + " " + pathHome +\
|
|
|
" ...")
|
|
|
return False
|
|
|
self.execProg("calculate -P install/6intranet")
|
|
|
self.clVars.Delete("cl_remote_host","local")
|
|
|
self.clVars.Delete("cl_remote_pw","local")
|
|
|
self.printOK(_("Computer removed from domain %s")%domain + " ...")
|
|
|
return True
|
|
|
|
|
|
def addDomain(self, domain):
|
|
|
"""Вводим в домен"""
|
|
|
# Создаем объект переменных
|
|
|
self.createClVars()
|
|
|
resPing = self.execProg("ping -c 2 -i 0.3 %s" %domain,False,False)
|
|
|
foudHost = False
|
|
|
foudHostSamba = False
|
|
|
foundMountRemote = False
|
|
|
reFoundHost = re.compile("(\d+)\% packet loss")
|
|
|
if resPing and type(resPing) == types.ListType and len(resPing)>=2:
|
|
|
pingStr = resPing[-2].strip()
|
|
|
reSearch = reFoundHost.search(pingStr)
|
|
|
if reSearch and reSearch.group(1) == "0":
|
|
|
foudHost = True
|
|
|
if not foudHost:
|
|
|
self.printERROR(_("Not found domain %s")%domain)
|
|
|
return False
|
|
|
reFoundHostSamba = re.compile("Server=\[Samba.+\]")
|
|
|
resSmbClient = self.execProg("smbclient -N -L %s" %domain,
|
|
|
False,False)
|
|
|
if resSmbClient and type(resSmbClient) == types.ListType:
|
|
|
for string in resSmbClient:
|
|
|
if reFoundHostSamba.search(string):
|
|
|
foudHostSamba = True
|
|
|
break
|
|
|
if not foudHostSamba:
|
|
|
self.printERROR(_("Not found Samba server in %s")%domain)
|
|
|
return False
|
|
|
if self.clVars.Get("cl_remote_host") and \
|
|
|
self.clVars.Get("cl_remote_host") != domain:
|
|
|
if not self.delDomain():
|
|
|
return False
|
|
|
foundMountRemote =self.isMount("/var/calculate/remote" ,"cifs")
|
|
|
foundMountHome =self.isMount("/var/calculate/home" ,"none", False)
|
|
|
if foundMountRemote:
|
|
|
self.printWARNING(_("Samba resource [remote] mount") + \
|
|
|
" ...")
|
|
|
else:
|
|
|
userPwd = self.getUserPassword("Domain password for the desktop")
|
|
|
pathRemote = "/var/calculate/remote"
|
|
|
pwdRemote = userPwd
|
|
|
if not os.path.exists(pathRemote):
|
|
|
os.makedirs(pathRemote)
|
|
|
mountStr = "mount -t cifs -o user=client,password=%s \
|
|
|
//%s/remote %s" %(pwdRemote,domain,pathRemote)
|
|
|
textLine = self.execProg(mountStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not mount Samba resource [remote]") + \
|
|
|
" ...")
|
|
|
return False
|
|
|
else:
|
|
|
self.printSUCCESS(_("Mount Samba resource [remote]") + \
|
|
|
" ...")
|
|
|
self.clVars.Write("cl_remote_host", domain, False, "local")
|
|
|
self.clVars.Write("cl_remote_pw", userPwd, False, "local")
|
|
|
pathHome = "/var/calculate/home"
|
|
|
if foundMountHome:
|
|
|
self.printWARNING(str(pathHome)+ " " +_("is mount")+
|
|
|
" ...")
|
|
|
else:
|
|
|
if not os.path.exists(pathHome):
|
|
|
os.makedirs(pathHome)
|
|
|
mountStr = "mount -o bind %s /home" %pathHome
|
|
|
textLine = self.execProg(mountStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not mount") + " " + str(pathHome) +\
|
|
|
" ...")
|
|
|
return False
|
|
|
self.printSUCCESS(_("Mount") + " " + str(pathHome) + " " +\
|
|
|
" ...")
|
|
|
self.clVars.flIniFile()
|
|
|
#считаем переменные для клиента
|
|
|
servDn = self.clVars.Get("ld_services_dn")
|
|
|
unixDN = self.clVars.Get("ld_unix_dn")
|
|
|
bindDn = self.clVars.Get("ld_bind_dn")
|
|
|
bindPw = self.clVars.Get("ld_bind_pw")
|
|
|
# запишем их
|
|
|
if not (servDn and unixDN and bindDn and bindPw):
|
|
|
self.printERROR(_("Not found variables:"))
|
|
|
self.printERROR("ld_services_dn or ld_unix_dn \
|
|
|
or ld_bind_dn or ld_bind_pw")
|
|
|
return False
|
|
|
execStr = "calculate --set-server_url=%s --set-ldap_base=%s \
|
|
|
--set-ldap_root=%s --set-ldap_bind=%s --set-ldap_bindpw=%s -P \
|
|
|
install/6intranet" %(domain,servDn,unixDN,bindDn,bindPw)
|
|
|
self.execProg(execStr)
|
|
|
textLine = self.execProg("/etc/init.d/dbus restart")
|
|
|
if not "ok" in textLine:
|
|
|
self.printWARNING(_("Error restarting /etc/init.d/dbus")+ " ...")
|
|
|
return False
|
|
|
self.printOK(_("Computer added to domain %s")%domain + " ...")
|
|
|
return True
|
|
|
|
|
|
def umountUserRes(self, userName):
|
|
|
"""Отмонтирование пользовательских ресурсов и синхронизация настроек"""
|
|
|
self.createClVars()
|
|
|
# В случае компьютера вне домена
|
|
|
if not self.clVars.Get("cl_remote_host"):
|
|
|
self.printSUCCESS(_("To be used by local profile."))
|
|
|
return True
|
|
|
connectDomain = self.isDomain()
|
|
|
if not connectDomain:
|
|
|
return False
|
|
|
elif not connectDomain[0]:
|
|
|
self.printERROR(_("Can not mount Samba resource [remote]") + \
|
|
|
" ...")
|
|
|
return False
|
|
|
# Подсоединяемся к серверу
|
|
|
domain = self.clVars.Get("cl_remote_host")
|
|
|
if not self.getLdapObjBind(domain):
|
|
|
return False
|
|
|
# homeDir из LDAP
|
|
|
resLdap = self.getUserLdapInfo(userName)
|
|
|
if not resLdap:
|
|
|
return False
|
|
|
homeDir = resLdap[4]
|
|
|
home = os.path.split(homeDir)[0]
|
|
|
pathRemote = []
|
|
|
# Удаленный ресурс профилей
|
|
|
pathRemote.append((os.path.join(home,"." + userName), "unix"))
|
|
|
# Удаленный ресурс home
|
|
|
pathRemote.append((os.path.join(homeDir,"Home"), "homes"))
|
|
|
# Удаленный ресурс share
|
|
|
pathRemote.append((os.path.join(homeDir,"Disks"), "share"))
|
|
|
if not self.syncUser(userName, homeDir, "logout"):
|
|
|
return False
|
|
|
flagError = False
|
|
|
for path, res in pathRemote:
|
|
|
if self.isMount(path ,"cifs"):
|
|
|
textLine = self.execProg("umount %s"%path)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not umount Samba resource \
|
|
|
[%s]")%res + " ...")
|
|
|
flagError = True
|
|
|
break
|
|
|
if os.path.exists(path) and not os.listdir(path):
|
|
|
try:
|
|
|
os.rmdir(path)
|
|
|
except:
|
|
|
self.printERROR(_("Can not remove dir %s")% path)
|
|
|
flagError = True
|
|
|
break
|
|
|
if flagError:
|
|
|
self.printERROR(_("Keep a user profile in the domain"))
|
|
|
return False
|
|
|
self.printSUCCESS(_("Keep a user profile in the domain"))
|
|
|
self.printOK(_("Umount user resource in domain") + " ...")
|
|
|
return True
|
|
|
|
|
|
def umountUserResNoSync(self, userName):
|
|
|
"""Отмонтирование пользовательских ресурсов
|
|
|
|
|
|
без синхронизации настроек"""
|
|
|
self.createClVars(self.clVars)
|
|
|
# В случае компьютера вне домена
|
|
|
if not self.clVars.Get("cl_remote_host"):
|
|
|
self.printSUCCESS(_("To be used by local profile."))
|
|
|
return True
|
|
|
# Подсоединяемся к серверу
|
|
|
domain = self.clVars.Get("cl_remote_host")
|
|
|
if not self.getLdapObjBind(domain):
|
|
|
return False
|
|
|
# homeDir из LDAP
|
|
|
resLdap = self.getUserLdapInfo(userName)
|
|
|
if not resLdap:
|
|
|
return False
|
|
|
homeDir = resLdap[4]
|
|
|
home = os.path.split(homeDir)[0]
|
|
|
pathRemote = []
|
|
|
# Удаленный ресурс профилей
|
|
|
pathRemote.append((os.path.join(home,"." + userName), "unix"))
|
|
|
# Удаленный ресурс home
|
|
|
pathRemote.append((os.path.join(homeDir,"Home"), "homes"))
|
|
|
# Удаленный ресурс share
|
|
|
pathRemote.append((os.path.join(homeDir,"Disks"), "share"))
|
|
|
flagError = False
|
|
|
for path, res in pathRemote:
|
|
|
if self.isMount(path ,"cifs"):
|
|
|
textLine = self.execProg("umount %s"%path)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not umount dir %s")%path + " ...")
|
|
|
flagError = True
|
|
|
break
|
|
|
if os.path.exists(path) and not os.listdir(path):
|
|
|
try:
|
|
|
os.rmdir(path)
|
|
|
except:
|
|
|
self.printERROR(_("Can not remove dir %s")% path)
|
|
|
flagError = True
|
|
|
break
|
|
|
if flagError:
|
|
|
self.printERROR(_("Can not umount user %s resource")%userName)
|
|
|
return False
|
|
|
self.printOK(_("Umount user %s resource") %userName + " ...")
|
|
|
return True
|
|
|
|
|
|
def mountUserRes(self, userName, sync=True):
|
|
|
"""Монтирование пользовательских ресурсов и синхронизация настроек"""
|
|
|
self.createClVars()
|
|
|
# В случае компьютера вне домена
|
|
|
if not self.clVars.Get("cl_remote_host"):
|
|
|
self.printSUCCESS(_("To be used by local profile."))
|
|
|
return True
|
|
|
# Проверим что компьютер в домене и смонтирован [remote]
|
|
|
connectDomain = self.isDomain()
|
|
|
if not connectDomain:
|
|
|
return False
|
|
|
elif not connectDomain[0]:
|
|
|
self.printERROR(_("Can not mount Samba resource [remote]") + \
|
|
|
" ...")
|
|
|
return False
|
|
|
# Подсоединяемся к серверу
|
|
|
domain = self.clVars.Get("cl_remote_host")
|
|
|
if not self.getLdapObjBind(domain):
|
|
|
return False
|
|
|
# homeDir из LDAP
|
|
|
resLdap = self.getUserLdapInfo(userName)
|
|
|
if not resLdap:
|
|
|
return False
|
|
|
uid = int(resLdap[0])
|
|
|
gid = int(resLdap[1])
|
|
|
homeDir = resLdap[4]
|
|
|
# При отсуствии создаем домашнюю директорию
|
|
|
if not os.path.exists(homeDir):
|
|
|
os.makedirs(homeDir)
|
|
|
os.chown(homeDir,uid,gid)
|
|
|
os.chmod(homeDir,0700)
|
|
|
# Получаем пароль пользователя из ключей ядра
|
|
|
userPwd = _cl_keys.getKey(userName)
|
|
|
if not userPwd:
|
|
|
self.printERROR(_("Not found user password"))
|
|
|
return False
|
|
|
home = os.path.split(homeDir)[0]
|
|
|
pathRemote = []
|
|
|
# Удаленный ресурс профилей
|
|
|
pathRemote.append((os.path.join(home,"." + userName), "unix"))
|
|
|
# Удаленный ресурс home
|
|
|
pathRemote.append((os.path.join(homeDir,"Home"), "homes"))
|
|
|
# Удаленный ресурс share
|
|
|
pathRemote.append((os.path.join(homeDir,"Disks"), "share"))
|
|
|
flagError = False
|
|
|
i = 0
|
|
|
for path, res in pathRemote:
|
|
|
i += 1
|
|
|
# Создаем директории для монтирования
|
|
|
if not os.path.exists(path):
|
|
|
try:
|
|
|
os.mkdir(path)
|
|
|
os.chown(path, uid, gid)
|
|
|
os.chmod(path,0700)
|
|
|
except OSError:
|
|
|
self.printERROR(_("Error creating directory"))
|
|
|
self.printERROR(_("Permission denied: '%s'")%path)
|
|
|
flagError = True
|
|
|
break
|
|
|
# Проверяем на монтирование директории
|
|
|
if self.isMount(path, 'cifs'):
|
|
|
continue
|
|
|
if i==3:
|
|
|
# Монтируем директории c uid
|
|
|
mountStr="mount -t cifs -o user=%s,password=%s,uid=%s,gid=%s"\
|
|
|
%(userName,userPwd,uid,gid) + " " +\
|
|
|
"//%s/%s %s" %(self.clVars.Get("cl_remote_host"),
|
|
|
res, path)
|
|
|
else:
|
|
|
# Монтируем директории
|
|
|
mountStr="mount -t cifs -o user=%s,password=%s"%(userName,
|
|
|
userPwd) + " " +\
|
|
|
"//%s/%s %s" %(self.clVars.Get("cl_remote_host"),
|
|
|
res, path)
|
|
|
|
|
|
textLine = self.execProg(mountStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not mount Samba resource [%s]")%res + \
|
|
|
" ...")
|
|
|
flagError = True
|
|
|
break
|
|
|
if flagError:
|
|
|
return False
|
|
|
# Синхронизируем настройки
|
|
|
if sync:
|
|
|
if not self.syncUser(userName, homeDir, "login"):
|
|
|
return False
|
|
|
self.printSUCCESS(_("Mount user resource in domain"))
|
|
|
self.printOK(_("Get a user profile in the domain") + " ...")
|
|
|
return True
|
|
|
|
|
|
def syncUser(self, userName, userHome, sync):
|
|
|
"""Синхронизация пользовательских настроек"""
|
|
|
home = os.path.split(userHome)[0]
|
|
|
homeProfile = os.path.join(home,"." + userName)
|
|
|
flagError = False
|
|
|
execStr = ""
|
|
|
if sync == "login":
|
|
|
if os.path.exists(userHome) and\
|
|
|
os.path.exists(homeProfile):
|
|
|
execStr = '/usr/bin/rsync --delete-excluded --delete \
|
|
|
--exclude="/.googleearth" --exclude="/Home" --exclude="/Disks" --exclude="*~" \
|
|
|
--filter="P /.googleearth" --filter="P /Home" --filter="P /Disks" -a -x \
|
|
|
%s/ %s/' %(homeProfile,userHome)
|
|
|
elif sync == "logout":
|
|
|
if os.path.exists(userHome) and os.listdir(userHome) and\
|
|
|
os.path.exists(homeProfile):
|
|
|
execStr = '/usr/bin/rsync --delete-excluded --delete \
|
|
|
--exclude="/.googleearth" --exclude="/Home" --exclude="/Disks" --exclude="*~" \
|
|
|
--exclude="/.kde4/cache-*" --exclude="/.kde4/tmp-*" \
|
|
|
--exclude="/.kde4/socket-*" --filter="P /.googleearth" --filter="P /Home" \
|
|
|
--filter="P /Disks" -a -b -x %s/ %s/'%(userHome,homeProfile)
|
|
|
else:
|
|
|
self.printERROR(_("Method syncUser: option sync=%s incorrect")\
|
|
|
%str(sync))
|
|
|
return False
|
|
|
if execStr:
|
|
|
textLine = self.execProg(execStr)
|
|
|
if not (textLine == None):
|
|
|
self.printERROR(_("Can not rsync") + " " + str(sync) +\
|
|
|
" ...")
|
|
|
flagError = True
|
|
|
else:
|
|
|
if sync == "login":
|
|
|
if not (os.path.exists(userHome)):
|
|
|
self.printERROR(_("Directory %s not exists")%userHome)
|
|
|
else:
|
|
|
self.printERROR(_("Directory %s not exists")%homeProfile)
|
|
|
elif sync == "logout":
|
|
|
if not (os.path.exists(userHome)):
|
|
|
self.printERROR(_("Directory %s is empty or not exists")\
|
|
|
%userHome)
|
|
|
else:
|
|
|
self.printERROR(_("Directory %s not exists")%homeProfile)
|
|
|
flagError = True
|
|
|
if flagError:
|
|
|
return False
|
|
|
else:
|
|
|
return True
|
|
|
|
|
|
class tsOpt(cl_base.opt):
|
|
|
"""Класс для обработки параметров и вывода help
|
|
|
|
|
|
Параметры:
|
|
|
helpObj объект-справка содержащий необходимые опции
|
|
|
notOptError выдавать ошибку при отсутствии опций командной строки
|
|
|
"""
|
|
|
def __init__(self, helpObj, notOptError=False):
|
|
|
# от cl_help получаем короткие и длинные опции
|
|
|
shortOpt,longOpt = helpObj.getAllOpt('all', helpObj.relOptions['h'])
|
|
|
# вызвать конструктор объекта, распознающего опции
|
|
|
cl_base.opt.__init__(self,shortOpt,longOpt)
|
|
|
self.nameParams = ['user']
|
|
|
self.sysArgv = sys.argv[1:]
|
|
|
self.helpObj = helpObj
|
|
|
self.__iter = 0
|
|
|
self.opt = {}
|
|
|
self.params = {}
|
|
|
self.getopt()
|
|
|
# Обработка help
|
|
|
self.flagHelp = False
|
|
|
# определяем есть ли среди опций опции, которые влияют на показ
|
|
|
# опциональных разделов (метод пересечения множеств)
|
|
|
helpopt = \
|
|
|
tuple(set(self.opt.keys()).intersection(helpObj.relOptions.keys()))
|
|
|
#Если есть опции help
|
|
|
if len(helpopt) > 0:
|
|
|
print helpObj.getHelp(helpObj.relOptions[helpopt[0]])
|
|
|
self.flagHelp = True
|
|
|
#Если нет хвостов
|
|
|
#elif not self.params:
|
|
|
#print helpObj.getHelp(helpObj.relOptions['h'])
|
|
|
#self.flagHelp = True
|
|
|
else:
|
|
|
if self.params.has_key('user'):
|
|
|
if len(self.nameParams) != self.__iter:
|
|
|
self.handlerErrOpt()
|
|
|
# В случае остсутствия опций командной строки и имени пользователя
|
|
|
if notOptError:
|
|
|
if not self.opt:
|
|
|
self.printErrorNotOpt()
|
|
|
self.flagHelp = True
|
|
|
elif not self.opt and not self.params.has_key('user'):
|
|
|
print helpObj.getHelp(helpObj.relOptions['h'])
|
|
|
self.flagHelp = True
|
|
|
|
|
|
|
|
|
def printErrorNotOpt(self):
|
|
|
"""Сообщение в случае отсутствия опций"""
|
|
|
print _("Options are absent.")
|
|
|
|
|
|
def handlerOpt(self,option,value):
|
|
|
# Обработчик (опция значение)
|
|
|
#print option, value
|
|
|
shortOpt = self.helpObj.getShortOpt(option)
|
|
|
if not shortOpt:
|
|
|
shortOpt = option
|
|
|
if not shortOpt in self.opt:
|
|
|
self.opt[shortOpt] = value
|
|
|
|
|
|
def handlerErrOpt(self):
|
|
|
# Обработчик ошибок
|
|
|
argv = " ".join(sys.argv[1:])
|
|
|
print _("Unrecognized option") + ' "' + argv + '"\n' + \
|
|
|
_("Try") + ' "' + sys.argv[0].split("/")[-1] + ' --help" ' +\
|
|
|
_("for more information.")
|
|
|
|
|
|
|
|
|
def handlerParam(self,param):
|
|
|
# Обработчик хвостов (значение)
|
|
|
self.__iter += 1
|
|
|
if self.__iter<=len(self.nameParams):
|
|
|
self.params[self.nameParams[self.__iter-1]] = param
|
|
|
|