# -*- coding: utf-8 -*- # Copyright 2012-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 import time from calculate.lib.utils.files import getModeFile from calculate.lib.cl_ldap import ldapUser from calculate.lib.cl_lang import setLocalTranslate _ = lambda x: x setLocalTranslate('cl_client3', sys.modules[__name__]) class Printable(object): def __init__(self, parent): if isinstance(parent, Printable): self.parent = parent.parent self.parent = parent def printERROR(self, s): self.parent.printERROR(s) class _shareData(Printable): """Share class""" fileName = "" template = "" lenData = 0 data = [] def getDataInFile(self, fileName='', lenData=0): """Get data list from file""" return filter(lambda x: len(x) == lenData, map(lambda x: x.rstrip().split(":"), open(fileName))) def getFileAccess(self, perm="READ"): if perm == "READ": if os.access(self.fileName, os.R_OK): return True else: self.printERROR(_("Failed to read the file") + _(": ") + self.fileName) return False elif perm == "WRITE": if os.access(self.fileName, os.W_OK): return True else: self.printERROR(_("Failed to write to file") + _(": ") + self.fileName) return False def getData(self): if id(_shareData.data) == id(self.data): self.data = self.data[:] if self.data: return self.data elif self.getFileAccess(perm="READ"): self.data = self.getDataInFile(fileName=self.fileName, lenData=self.lenData) return self.data else: return False def save(self): if self.getFileAccess(perm="WRITE"): buff = "\n".join(map(lambda x: ":".join(x), self.data)) + "\n" FD = open(self.fileName, "w+") FD.write(buff) FD.close() return True else: return False def delete(self, name): if self.getData() is False: return False else: self.data = filter(lambda x: x[0] != name, self.data) return True def replace(self, name, listData): if self.getData() is False: return False else: flagFound = False for index, listDataOld in enumerate(self.data): if name == listDataOld[0]: flagFound = True if not self.equally(listData, self.data[index]): self.data[index] = listData if flagFound: return True else: return None def equally(self, listDataA, listDataB): if set(listDataA) == set(listDataB): return True else: return False def get(self, name): if self.getData() is False: return False else: listData = filter(lambda x: x[0] == name, self.data) if listData: return listData[0] else: return [] class passwd(_shareData): '''Class for working with the file /etc/passwd''' fileName = "/etc/passwd" template = "%(login)s:x:%(uid)s:%(gid)s:%(gecos)s:%(directory)s:%(shell)s" lenData = 7 def add(self, name, uid, gid, gecos="", directory="", shell="/bin/bash"): if not directory: directory = "/home/%s" % name userData = self.template % {'login': name, 'uid': uid, 'gid': gid, 'gecos': gecos, 'directory': directory, 'shell': shell} userList = userData.split(":") if self.getData() is False: return False else: ret = self.replace(name, userList) if ret is False: return False elif ret is None: self.data.append(userList) return True class group(_shareData): '''Class for working with the file /etc/group''' fileName = "/etc/group" template = "%(group_name)s:x:%(gid)s:%(user_list)s" lenData = 4 def add(self, name, gid, userList=()): groupData = self.template % {'group_name': name, 'gid': gid, 'user_list': ','.join(userList)} groupList = groupData.split(":") if self.getData() is False: return False else: ret = self.replace(name, groupList) if ret is False: return False elif ret is None: self.data.append(groupList) return True def replace(self, name, listData): if self.getData() is False: return False else: delEmpty = lambda y: filter(lambda x: x.strip(), y) flagFound = False for index, listDataOld in enumerate(self.data): if name == listDataOld[0]: flagFound = True if not self.equally(listData, self.data[index]): listDataWork = listData[:] listDataWork[1] = self.data[index][1] # Constant gid listDataWork[2] = self.data[index][2] # Join user list userList = delEmpty(self.data[index][3].split(',') + listDataWork[3].split(',')) # unique list userList = reduce(lambda x, y: \ (y in x and x) or x + [y], userList, []) listDataWork[3] = ','.join(userList) self.data[index] = listDataWork if flagFound: return True else: return None def equally(self, listDataA, listDataB): getData = lambda x: x[:1] + x[3:] return _shareData.equally(self, getData(listDataA), getData(listDataB)) def getSecondUserGroups(self, userName): """get all second user groups""" if self.getData() is False: return False else: userGroups = [] for group, x, gid, userList in self.data: usersInGroup = userList.split(",") if userName in usersInGroup: userGroups.append(group) return userGroups def getUsersInGroup(self, name): if self.getData() is False: return False else: dataGroup = map(lambda x: x[3].split(","), filter(lambda x: x[0] == name, self.data)) if dataGroup: return dataGroup[0] else: return [] def deleteUserInGroups(self, userName, groups): if self.getData() is False: return False else: data = [] for dataList in self.data: groupName, x, gid, userList = dataList if groupName in groups: usersList = ",".join(filter(lambda x: x != userName, userList.split(","))) dataList[3] = usersList data.append(dataList) self.data = data return self.data class shadow(_shareData): '''Class for working with the file /etc/shadow''' fileName = "/etc/shadow" template = ("%(login)s:%(hash)s:%(shadowLastChange)s:" "%(shadowMin)s:%(shadowMax)s:%(shadowWarning)s:::") lenData = 9 def add(self, name, pwdHash, shadowLastChange=str(int(time.time() / 86400)), shadowMin="0", shadowMax="99999", shadowWarning="7"): shadowData = self.template % {'login': name, 'hash': pwdHash, 'shadowLastChange': shadowLastChange, 'shadowMin': shadowMin, 'shadowMax': shadowMax, 'shadowWarning': shadowWarning} shadowList = shadowData.split(":") if self.getData() is False: return False else: ret = self.replace(name, shadowList) if ret is False: return False elif ret is None: self.data.append(shadowList) return True def equally(self, listDataA, listDataB): getData = lambda x: x[:1] + x[2:] return _shareData.equally(self, getData(listDataA), getData(listDataB)) class _shareCache(_shareData): def save(self): path = os.path.dirname(self.fileName) if not os.path.exists(path): try: os.makedirs(path) except OSError: self.printERROR(_("Failed to create directory %s") % path) return False if not os.path.exists(self.fileName): try: open(self.fileName, "w").close() except IOError: self.printERROR(_("Failed to create file %s") % self.fileName) return False if self.getFileAccess(perm="WRITE"): modeFile = 0600 if getModeFile(self.fileName, mode="mode") != modeFile: os.chmod(self.fileName, modeFile) buff = "\n".join(map(lambda x: ":".join(x), self.data)) + "\n" FD = open(self.fileName, "w+") FD.write(buff) FD.close() return True else: return False def getData(self): if id(_shareData.data) == id(self.data): self.data = self.data[:] if self.data: return self.data elif not os.path.exists(self.fileName): return self.data elif self.getFileAccess(perm="READ"): self.data = self.getDataInFile(fileName=self.fileName, lenData=self.lenData) return self.data else: return False class cachePasswd(_shareCache, passwd): fileName = "/var/lib/calculate/calculate-client/cache/passwd" class cacheGroup(_shareCache, group): fileName = "/var/lib/calculate/calculate-client/cache/group" def replace(self, name, listData): return _shareData.replace(self, name, listData) def equally(self, listDataA, listDataB): return _shareData.equally(self, listDataA, listDataB) class cacheShadow(_shareCache, shadow): fileName = "/var/lib/calculate/calculate-client/cache/shadow" class cacheCreateGroup(cacheGroup): fileName = "/var/lib/calculate/calculate-client/cache/create_group" class cacheCreatePasswd(cachePasswd): fileName = "/var/lib/calculate/calculate-client/cache/create_passwd" class userCache(Printable): ldapObj = ldapUser() def addUserToCache(self, userName, pwdHash): '''Add LDAP user to cache''' ldapData = self.ldapObj.getUserLdapInfo(userName, shadowAttr=True) if not ldapData: self.printERROR(_("User %s not found in LDAP") % userName) return False groupName = ldapData['group'] # Add user cachePasswdObj = cachePasswd(self) if not cachePasswdObj.add(userName, ldapData['uid'], ldapData['gid'], gecos=ldapData['fullName'], directory=ldapData['home'], shell=ldapData['loginShell']): return False if not cachePasswdObj.save(): return False cacheGroupObj = cacheGroup(self) # Add primary group if not cacheGroupObj.add(groupName, ldapData['gid']): return False # Add second groups secondGroupsData = ldapData['groups'] cacheSecondUserGroups = cacheGroupObj.getSecondUserGroups(userName) deleteSecondGroups = cacheSecondUserGroups if secondGroupsData: allUsersSecondGroups = [] for groupName, gid in secondGroupsData: usersInGroup = cacheGroupObj.getUsersInGroup(groupName) if usersInGroup is False: return False if not userName in usersInGroup: usersInGroup.append(userName) if not cacheGroupObj.add(groupName, gid, usersInGroup): return False allUsersSecondGroups.append(groupName) deleteSecondGroups = list(set(cacheSecondUserGroups) - \ set(allUsersSecondGroups)) if not cacheGroupObj.deleteUserInGroups(userName, deleteSecondGroups): return False if not cacheGroupObj.save(): return False # Add shadow user cacheShadowObj = cacheShadow(self) if not cacheShadowObj.add(userName, pwdHash, shadowLastChange=ldapData['shadowLastChange'], shadowMin=ldapData['shadowMin'], shadowMax=ldapData['shadowMax'], shadowWarning=ldapData['shadowWarning']): return False if not cacheShadowObj.save(): return False return True def delUserFromCacheCreate(self, userName): '''Delete LDAP user from createCache''' cacheCreatePasswdObj = cacheCreatePasswd(self) cacheUserData = cacheCreatePasswdObj.get(userName) if cacheUserData is False: return False if not cacheUserData: return True gid = cacheUserData[3] cacheCreateGroupObj = cacheCreateGroup(self) cacheSecondGroups = cacheCreateGroupObj.getSecondUserGroups(userName) if cacheSecondGroups is False: return False # delete user in second groups if not cacheCreateGroupObj.deleteUserInGroups(userName, cacheSecondGroups): return False # delete user if not cacheCreatePasswdObj.delete(userName): return False if not cacheCreatePasswdObj.save(): return False # delete groups usersGids = map(lambda x: x[3], cacheCreatePasswdObj.data) deleteGroups = map(lambda x: x[0], filter(lambda x: not x[2] in usersGids and not x[3], cacheCreateGroupObj.data)) for delGroupName in deleteGroups: if not cacheCreateGroupObj.delete(delGroupName): return False if not cacheCreateGroupObj.save(): return False return True def delUserFromCache(self, userName): '''Delete LDAP user from cache''' cachePasswdObj = cachePasswd(self) cacheUserData = cachePasswdObj.get(userName) if cacheUserData is False: return False if not cacheUserData: return True gid = cacheUserData[3] cacheGroupObj = cacheGroup(self) cacheSecondGroups = cacheGroupObj.getSecondUserGroups(userName) if cacheSecondGroups is False: return False # delete user in second groups if not cacheGroupObj.deleteUserInGroups(userName, cacheSecondGroups): return False # delete user if not cachePasswdObj.delete(userName): return False if not cachePasswdObj.save(): return False # delete groups usersGids = map(lambda x: x[3], cachePasswdObj.data) deleteGroups = map(lambda x: x[0], filter(lambda x: not x[2] in usersGids and not x[3], cacheGroupObj.data)) for delGroupName in deleteGroups: if not cacheGroupObj.delete(delGroupName): return False if not cacheGroupObj.save(): return False # delete shadow user cacheShadowObj = cacheShadow(self) if not cacheShadowObj.delete(userName): return False if not cacheShadowObj.save(): return False return True def delUserFromSystem(self, userName): '''Delete LDAP user from system files ( passwd, group, shadow )''' cacheCreatePasswdObj = cacheCreatePasswd(self) cacheCreatePasswdData = cacheCreatePasswdObj.get(userName) if cacheCreatePasswdData is False: return False if not cacheCreatePasswdData: return True passwdObj = passwd(self) userData = passwdObj.get(userName) if userData is False: return False # delete user if not userData: return True if not passwdObj.delete(userName): return False if not passwdObj.save(): return False # delete user group groupObj = group(self) listGroupData = groupObj.getData() if listGroupData is False: return False cacheCreateGroupObj = cacheCreateGroup(self) secondUsersGroups = groupObj.getSecondUserGroups(userName) usersGids = map(lambda x: x[3], passwdObj.data) listGroupDataWork = [] for index, groupData in enumerate(listGroupData): groupName, x, gid, listUsers = groupData listUsers = filter(lambda x: x.strip(), listUsers.split(',')) listUsers = ",".join(filter(lambda x: x != userName, listUsers)) cacheCreateGroupData = cacheCreateGroupObj.get(groupName) if cacheCreateGroupData is False: return False if groupName in cacheCreateGroupData: if not gid in usersGids and not listUsers: continue listGroupDataWork.append([groupName, x, gid, listUsers]) groupObj.data = listGroupDataWork if not groupObj.save(): return False # delete user shadow shadowObj = shadow(self) shadowData = shadowObj.get(userName) if shadowData is False: return False if not shadowData: return True if not shadowObj.delete(userName): return False if not shadowObj.save(): return False if not self.delUserFromCacheCreate(userName): return False return True def isConnectToLdap(self): connectData = self.ldapObj.getBindConnectData() if not connectData: return {} bindDn, bindPw, host = connectData usersDN = self.ldapObj.getUsersDN() groupsDNs = self.ldapObj.getGroupsDN() # Соединяемся с LDAP if not self.ldapObj.ldapConnect(bindDn, bindPw, host): return False return True def deleteCacheUsersFromSystem(self): '''Delete cache users from system''' cacheCreatePasswdObj = cacheCreatePasswd(self) cacheCreateListPasswdData = cacheCreatePasswdObj.getData() if cacheCreateListPasswdData is False: return False delUsersPasswd = map(lambda x: x[0], cacheCreateListPasswdData) for delUser in delUsersPasswd: if not self.delUserFromSystem(delUser): return False return True def getLoginDomainUsers(self): '''Get all domain login users''' cacheCreatePasswdObj = cacheCreatePasswd(self) cacheListCreatePasswdData = cacheCreatePasswdObj.getData() if cacheListCreatePasswdData is False: return False return cacheListCreatePasswdData def addCacheUsersFromSystem(self): '''Add cache users from system''' cachePasswdObj = cachePasswd(self) cacheListPasswdData = cachePasswdObj.getData() if not isinstance(cacheListPasswdData, (list, tuple)): return False # Add cache passwd users to system passwdObj = passwd(self) cacheCreatePasswdObj = cacheCreatePasswd(self) cacheListCreatePasswdData = cacheCreatePasswdObj.getData() if cacheListCreatePasswdData is False: return False # remove deleted users cacheUsers = map(lambda x: x[0], cacheListPasswdData) createUsers = map(lambda x: x[0], cacheListCreatePasswdData) deletedUsers = list(set(createUsers) - set(cacheUsers)) for delUser in deletedUsers: if not self.delUserFromSystem(delUser): return False if not self.delUserFromCache(delUser): return False addUsers = [] addUsersGid = [] notAddUsers = [] for cachePasswdData in cacheListPasswdData: userName, x, uid, gid, gecos, directory, shell = cachePasswdData retPasswd = passwdObj.get(userName) if retPasswd is False: return False if not retPasswd: if not cacheCreatePasswdObj.add(userName, uid, gid, gecos=gecos, directory=directory, shell=shell): return False retCacheCreate = cacheCreatePasswdObj.get(userName) if retCacheCreate is False: return False if retCacheCreate: if not passwdObj.add(userName, uid, gid, gecos=gecos, directory=directory, shell=shell): return False addUsers.append(userName) addUsersGid.append(gid) else: notAddUsers.append(userName) if passwdObj.data: if not passwdObj.save(): return False if not cacheCreatePasswdObj.save(): return False cacheShadowObj = cacheShadow(self) cacheListShadowData = cacheShadowObj.getData() if not isinstance(cacheListShadowData, (list, tuple)): return False # Add cache shadow users to system shadowObj = shadow(self) for cacheShadowData in cacheListShadowData: userName, pwdHash, shadowLastChange, shadowMin, shadowMax, \ shadowWarning, x, x, x = cacheShadowData if userName in addUsers: if not shadowObj.add(userName, pwdHash, shadowLastChange=shadowLastChange, shadowMin=shadowMin, shadowMax=shadowMax, shadowWarning=shadowWarning): return False if shadowObj.data: if not shadowObj.save(): return False cacheGroupObj = cacheGroup(self) cacheListGroupData = cacheGroupObj.getData() if not isinstance(cacheListGroupData, (list, tuple)): return False cacheCreateGroupObj = cacheCreateGroup(self) # Add cache group users to system groupObj = group(self) setAddUsers = set(addUsers) for cacheGroupData in cacheListGroupData: groupName, x, gid, listUsers = cacheGroupData retGroup = groupObj.get(groupName) if retGroup is False: return False listUsers = filter(lambda x: x.strip(), listUsers.split(',')) if setAddUsers & set(listUsers) or gid in addUsersGid: listUsers = filter(lambda x: not x in notAddUsers, listUsers) if not retGroup: if not cacheCreateGroupObj.add(groupName, gid, listUsers): return False if not groupObj.add(groupName, gid, listUsers): return False if groupObj.data: if not groupObj.save(): return False if cacheCreateGroupObj.data: if not cacheCreateGroupObj.save(): return False return True def syncCacheToLdap(self): '''Compare cache users to LDAP users. Deleting a user from the cache when the differences''' if not self.isConnectToLdap(): self.printERROR(_("Failed to connect to the LDAP server")) return False cachePasswdObj = cachePasswd(self) cacheListPasswdData = cachePasswdObj.getData() if cacheListPasswdData is False: return False if (not isinstance(cacheListPasswdData, (tuple, list)) or not cacheListPasswdData): return True cacheGroupObj = cacheGroup(self) cacheListGroupData = cacheGroupObj.getData() if cacheListGroupData is False: return False cacheShadowObj = cacheShadow(self) deletedCacheUsers = [] for cachePasswdData in cacheListPasswdData: userName, x, uid, gid, gecos, directory, shell = cachePasswdData ldapData = self.ldapObj.getUserLdapInfo(userName, shadowAttr=True) if not ldapData: deletedCacheUsers.append(userName) continue cacheGroupData = map(lambda x: x[0], filter(lambda x: x[2] == gid, cacheListGroupData)) if not cacheGroupData: deletedCacheUsers.append(userName) continue groupName = cacheGroupData[0] cacheShadowData = cacheShadowObj.get(userName) if cacheShadowData is False: return False if not cacheShadowData: deletedCacheUsers.append(userName) continue x, x, shadowLastChange, shadowMin, shadowMax, shadowWarning, \ x, x, x = cacheShadowData groups = cacheGroupObj.getSecondUserGroups(userName) gidsGroups = map(lambda x: x[2], filter(lambda x: x[0] in groups, cacheGroupObj.data)) userShadowDict = {'uid': uid, 'gid': gid, 'fullName': gecos, 'home': directory, 'group': groupName, 'groups': (groups, gidsGroups), 'loginShell': shell, 'shadowLastChange': shadowLastChange, 'shadowMin': shadowMin, 'shadowMax': shadowMax, 'shadowWarning': shadowWarning} flagDeleteUser = False for attr, value in userShadowDict.items(): if attr == "groups": for index, val in enumerate(value): if set(map(lambda x: x[index], ldapData[attr])) != set(val): flagDeleteUser = True break else: if ldapData[attr] != value: flagDeleteUser = True break if flagDeleteUser: deletedCacheUsers.append(userName) continue # Deleted cache users for delUserName in deletedCacheUsers: if not self.delUserFromCache(delUserName): return False return True def clearCache(self): '''Clear cache files''' cacheObjs = (cachePasswd(self), cacheShadow(self), cacheGroup(self), cacheCreateGroup(self), cacheCreatePasswd(self)) for cacheObj in cacheObjs: if not cacheObj.save(): return False return True