# -*- coding: utf-8 -*- # Copyright 2010-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, sys, re, time from calculate.lib.encrypt import encrypt from os import path from calculate.lib.utils.files import pathJoin, FilePermission from calculate.lib.cl_lang import setLocalTranslate, _ from functools import reduce setLocalTranslate('cl_install3', sys.modules[__name__]) class MigrationError(Exception): pass class _shareData(): """Share class""" _reNumb = re.compile("^\d+$") def getDataInFile(self, fileName='', lenData=7): """Get data list from file""" with open(fileName) as f: return [x[:lenData] for x in (y.rstrip().split(":") for y in f) if len(x) >= lenData] class migrateGroups(_shareData): """Migrate group to new system""" maxGid = 65000 minGid = 1000 fileGroups = "/etc/group" def __init__(self, prefixNewSystem): self.prefixNewSystem = prefixNewSystem def getData(self, fileName=''): if not fileName: fileName = self.fileGroups return self.getDataInFile(fileName=fileName, lenData=4) def getThisData(self): """Get data migrate groups in this system""" return [x for x in self.getData() if self._reNumb.match(x[2]) and self.minGid <= int(x[2]) <= self.maxGid] def getNewData(self): """Get data migrate groups in new system""" fileName = pathJoin(self.prefixNewSystem, self.fileGroups) return [x for x in self.getData(fileName=fileName) if self._reNumb.match(x[2]) and self.minGid <= int(x[2]) <= self.maxGid] def getNewDataSystemGroups(self): """Get data system groups in new system""" fileName = pathJoin(self.prefixNewSystem, self.fileGroups) return [x for x in self.getData(fileName=fileName) if self._reNumb.match(x[2]) and (int(x[2]) > self.maxGid or int(x[2]) < self.minGid)] def getNewProcessedData(self): """Get processed data migrate groups in new system""" # data this Group no users dataThisGroupsNoUsers = [x[:3] + [""] for x in self.getThisData()] dataNewGroups = self.getNewData() namesNewGroups = [x[0] for x in dataNewGroups] gidsNewGroups = [x[2] for x in dataNewGroups] for data in dataThisGroupsNoUsers: nameGroup = data[0] gid = data[2] if nameGroup in namesNewGroups: dataNewGroups = [x for x in dataNewGroups if x[0] != nameGroup] namesNewGroups = [x[0] for x in dataNewGroups] gidsNewGroups = [x[2] for x in dataNewGroups] if gid in gidsNewGroups: dataNewGroups = [x for x in dataNewGroups if x[2] != gid] namesNewGroups = [x[0] for x in dataNewGroups] gidsNewGroups = [x[2] for x in dataNewGroups] systemGroupsNewData = self.getNewDataSystemGroups() return systemGroupsNewData, dataNewGroups, dataThisGroupsNoUsers class migrateUsers(_shareData): """Migrate users to new system""" maxId = 65000 minId = 1000 filePasswd = "/etc/passwd" def __init__(self, prefixNewSystem): self.prefixNewSystem = prefixNewSystem def getData(self, fileName=''): if not fileName: fileName = self.filePasswd return self.getDataInFile(fileName=fileName, lenData=7) def getThisData(self): """Get data migrate users in this system""" return [x for x in self.getData() if self._reNumb.match(x[2]) and self.minId <= int(x[2]) <= self.maxId] def getNewData(self): """Get data migrate users in new system""" fileName = pathJoin(self.prefixNewSystem, self.filePasswd) return [x for x in self.getData(fileName=fileName) if self._reNumb.match(x[2]) and self.minId <= int(x[2]) <= self.maxId] def getNewDataSystemUsers(self): """Get data system users in new system""" fileName = pathJoin(self.prefixNewSystem, self.filePasswd) return [x for x in self.getData(fileName=fileName) if self._reNumb.match(x[2]) and (int(x[2]) > self.maxId or int(x[2]) < self.minId)] def getThisDataSystemUsers(self): """Get data system users in this system""" fileName = self.filePasswd return [x for x in self.getData(fileName=fileName) if self._reNumb.match(x[2]) and (int(x[2]) > self.maxId or int(x[2]) < self.minId)] def getNewProcessedData(self, migrateUsers=()): """Get processed data migrate users in new system""" dataThisUsers = self.getThisData() if migrateUsers: dataThisUsers = [x for x in dataThisUsers if x[0] in migrateUsers] dataNewUsers = self.getNewData() namesNewUsers = [x[0] for x in dataNewUsers] uidsNewUsers = [x[2] for x in dataNewUsers] for data in dataThisUsers: nameUser = data[0] uid = data[2] if nameUser in namesNewUsers: dataNewUsers = [x for x in dataNewUsers if x[0] != nameUser] namesNewUsers = [x[0] for x in dataNewUsers] uidsNewUsers = [x[2] for x in dataNewUsers] if uid in uidsNewUsers: dataNewUsers = [x for x in dataNewUsers if x[2] != uid] namesNewUsers = [x[0] for x in dataNewUsers] uidsNewUsers = [x[2] for x in dataNewUsers] systemUsersNewData = self.getNewDataSystemUsers() systemUsersNewNames = [x[0] for x in systemUsersNewData] systemUsersNewUids = [x[2] for x in systemUsersNewData] systemUsersThisData = [] if migrateUsers: # this users < minId systemUsersThisData = [x for x in self.getThisDataSystemUsers() if int(x[2]) < self.minId and x[0] in migrateUsers] for data in systemUsersThisData: nameUser = data[0] uid = data[2] if nameUser in systemUsersNewNames: systemUsersNewData = [x for x in systemUsersNewData if x[0] != nameUser] systemUsersNewNames = [x[0] for x in systemUsersNewData] systemUsersNewUids = [x[2] for x in systemUsersNewData] if uid in systemUsersNewUids: systemUsersNewData = [x for x in systemUsersNewData if x[2] != uid] systemUsersNewNames = [x[0] for x in systemUsersNewData] systemUsersNewUids = [x[2] for x in systemUsersNewData] return (systemUsersThisData, systemUsersNewData, dataNewUsers, dataThisUsers) class migrateShadow(_shareData): """Migrate users to new system""" fileShadow = "/etc/shadow" def __init__(self, sysThisMigrateUsers, sysNewMigrateUsers, newMigrateUsers, thisMigrateUsers, prefixNewSystem): self.prefixNewSystem = prefixNewSystem self.sysThisMigrateUsers = sysThisMigrateUsers self.sysNewMigrateUsers = sysNewMigrateUsers self.newMigrateUsers = newMigrateUsers self.thisMigrateUsers = thisMigrateUsers self.newFileName = pathJoin(self.prefixNewSystem, self.fileShadow) def getData(self, fileName=''): if not fileName: fileName = self.fileShadow return self.getDataInFile(fileName=fileName, lenData=9) def getThisData(self): """Get data migrate users in this system""" return [x for x in self.getData() if x[0] in self.thisMigrateUsers] def getNewData(self): """Get data migrate users in new system""" return [x for x in self.getData(fileName=self.newFileName) if x[0] in self.newMigrateUsers] def getNewDataSystemShadow(self): """Get data system users in new system""" return [x for x in self.getData(fileName=self.newFileName) if x[0] in self.sysNewMigrateUsers] def getThisDataSystemShadow(self): """Get data system users in this system""" return [x for x in self.getData() if x[0] in self.sysThisMigrateUsers] def getNewProcessedData(self): """Get processed data migrate shadow in new system""" dataThisShadow = self.getThisData() dataNewShadow = self.getNewData() namesNewShadow = [x[0] for x in dataNewShadow] for data in dataThisShadow: nameUser = data[0] if nameUser in namesNewShadow: dataNewShadow = [x for x in dataNewShadow if x[0] != nameUser] namesNewShadow = [x[0] for x in dataNewShadow] systemShadowNewData = self.getNewDataSystemShadow() systemShadowThisData = self.getThisDataSystemShadow() systemShadowNewNames = [x[0] for x in systemShadowNewData] for data in systemShadowThisData: nameUser = data[0] if nameUser in systemShadowNewNames: systemShadowNewData = [x for x in systemShadowNewData if x[0] != nameUser] systemShadowNewNames = [x[0] for x in systemShadowNewData] return (systemShadowThisData, systemShadowNewData, dataNewShadow, dataThisShadow) class migrate(): """Migrate users ang groups to new system""" templateShadow = "%(user)s:%(hash)s:%(days)s:0:%(maxDays)s:%(warnDays)s:::" templateUser = "%(user)s:x:%(id)s:%(gid)s::/home/%(user)s:/bin/bash" templateGroup = "%(group)s:x:%(gid)s:" dataUsers = [] dataGroups = [] dataShadow = [] maxId = 65000 minId = 1000 maxGid = 65000 minGid = 1000 minSysId = 1000 newUserGroups = ["audio", "cdrom", "cdrw", "games", "lp", "lpadmin", "plugdev", "scanner" "usb", "users", "video", "wheel"] def __init__(self, prefixNewSystem): self.prefixNewSystem = prefixNewSystem self.objGroups = migrateGroups(self.prefixNewSystem) self.objUsers = migrateUsers(self.prefixNewSystem) def addThisUsersToGroups(self, users): """Add users to groups""" thisGroupsData = self.objGroups.getData() thisGroupsData = [(x[0], x[3].split(',')) for x in thisGroupsData] dataGroups = [] for data in self.dataGroups: groupName = data[0] thisUsersInGroup = [x[1] for x in thisGroupsData if x[0] == groupName] #??? whats the point of this? thisUsersInGroup = reduce(lambda x, y: x + y, thisUsersInGroup, []) addUsers = list(set(thisUsersInGroup) & set(users)) if addUsers: newUsersInGroup = data[3].split(',') for user in addUsers: if not user in newUsersInGroup: newUsersInGroup.append(user) data[3] = ','.join((x for x in newUsersInGroup if x)) dataGroups.append(data) self.dataGroups = dataGroups return self.dataGroups def getNextUid(self): """get next uid""" listUid = [int(x[2]) for x in self.dataUsers if self.objUsers._reNumb.match(x[2]) and self.minId <= int(x[2]) <= self.maxId] if listUid: return max(listUid) + 1 return self.minId def getNextGid(self): """get next gid""" listGid = [int(x[2]) for x in self.dataGroups if self.objUsers._reNumb.match(x[2]) and self.minGid <= int(x[2]) <= self.maxGid] if listGid: return max(listGid) + 1 return self.minGid def isSystemUser(self, userName): if [x for x in self.dataUsers if (x[0] == userName and int(x[2]) <= self.minSysId)]: return True return False def addUserToGroups(self, userName, userGroups): """Add users to groups""" dataGroups = [] for data in self.dataGroups: groupName = data[0] if groupName in userGroups: usersInGroup = data[3].split(',') if not userName in usersInGroup: usersInGroup.append(userName) data[3] = ','.join((x for x in usersInGroup if x)) dataGroups.append(data) self.dataGroups = dataGroups return self.dataGroups def addUserToDefaultGroups(self, userName): """Add users to default groups""" return self.addUserToGroups(userName, self.newUserGroups) def changePassword(self, userName, pwdHash, maxDays="99999", warnDays="7"): if not [x for x in self.dataUsers if x[0] == userName]: raise MigrationError(_("User %s not found") % userName) indexFoundUser = False for i, data in enumerate(self.dataShadow): if data[0] == userName: indexFoundUser = i break if callable(pwdHash): pwdHash = pwdHash(userName) if pwdHash is False: return False shadowDict = {"user": userName, "hash": pwdHash, "days": str(int(time.time() / 86400)), "maxDays": maxDays, "warnDays": warnDays} shadowLine = self.templateShadow % shadowDict shadowList = shadowLine.split(":") if indexFoundUser is False: self.dataShadow.append(shadowList) else: self.dataShadow[indexFoundUser] = shadowList return True def addUser(self, userName, userGroups, pwdHash): """Add user""" # find user if [x for x in self.dataUsers if x[0] == userName]: return "EXISTS" else: strUid = str(self.getNextUid()) strGid = str(self.getNextGid()) groupName = userName dataExistGroup = [x for x in self.dataGroups if x[0] == groupName] if dataExistGroup: strGid = dataExistGroup[0][2] else: # add group groupDict = {"group": groupName, "gid": strGid} groupLine = self.templateGroup % groupDict groupList = groupLine.split(":") self.dataGroups.append(groupList) # add user userDict = {"user": userName, "id": strUid, "gid": strGid} userline = self.templateUser % userDict userList = userline.split(":") self.dataUsers.append(userList) # add shadow if not self.changePassword(userName, pwdHash): return False # add user to default groups self.addUserToGroups(userName, userGroups) return True def checkPermFiles(self): """Check permission files""" checkThisFiles = [migrateGroups.fileGroups, migrateUsers.filePasswd, migrateShadow.fileShadow] checkNewFiles = [pathJoin(self.prefixNewSystem, x) for x in checkThisFiles] parentDir = lambda x: "".join(os.path.split(x)[:-1]) notRead = lambda x: not os.access(x, os.R_OK) notWrite = lambda x: not os.access(x, os.W_OK) filesNotRead = [x for x in checkThisFiles if notRead(x)] if filesNotRead: raise MigrationError(_("Failed to read files") + _(": ") + ", ".join(filesNotRead)) filesNotWrite = [x for x in checkNewFiles if notWrite(x)] if filesNotWrite: raise MigrationError(_("Failed to write to files") + _(": ") + ", ".join(filesNotWrite)) # Check permissions backup files checkNewBackupFiles = (pathJoin(self.prefixNewSystem, x + "-") for x in checkThisFiles) notWriteBackup = lambda x: not os.access(x, os.W_OK) and \ (os.path.exists(x) or not os.access(os.path.dirname(x), os.W_OK)) filesNotWrite = [x for x in checkNewBackupFiles if notWriteBackup(x)] if filesNotWrite: raise MigrationError(_("Failed to write to files") + _(": ") + ", ".join(filesNotWrite)) return True def saveNewFiles(self): """Save /etc/passwd /etc/group /etc/shadow to new system""" listFilesThisSystem = [migrateGroups.fileGroups, migrateUsers.filePasswd, migrateShadow.fileShadow] listFiles = [(pathJoin(self.prefixNewSystem, x), pathJoin(self.prefixNewSystem, x + "-")) for x in listFilesThisSystem] listData = [self.dataGroups, self.dataUsers, self.dataShadow] allData = zip(listFiles, listData) for fileNames, data in allData: buff = "\n".join((":".join(x) for x in data)) + "\n" for fileName in fileNames: FD = open(fileName, "w+") FD.write(buff) FD.close() def createUserGuest(self): if [x for x in self.dataUsers if int(x[2]) >= self.minSysId]: return True else: # add user guest pwd = "guest" encryptObj = encrypt() pwdHash = encryptObj.getHashPasswd(pwd, "shadow_ssha256") if pwdHash is False: return False if not self.addUser("guest", "guest", pwdHash): return False return True def createHomeDirs(self, addUsersList, existsMigrateUsers): """Create home directories for all migreate users""" def createHome(userdata): perms = FilePermission.UserAll if not userdata[5].startswith('/dev/'): homedir = pathJoin(self.prefixNewSystem, userdata[5]) if not path.exists(homedir): os.mkdir(homedir) os.chown(homedir, int(userdata[2]), int(userdata[3])) os.chmod(homedir, perms) users = list(set([x[0] for x in addUsersList] + existsMigrateUsers) - {"root"}) try: # map(createHome, (x for x in self.dataUsers if x[0] in users)) [createHome(x) for x in self.dataUsers if x[0] in users] except Exception as e: raise MigrationError( _("Failed to create the user's home directory")) def migrate(self, addUsersList=None, rootPwd="", pwdUsersList=None, existsMigrateUsers=None): """Migrate users ang groups to new system""" if addUsersList is None: addUsersList = [] elif not any(addUsersList): addUsersList = [] if pwdUsersList is None: pwdUsersList = [] if existsMigrateUsers is None: existsMigrateUsers = [] if not self.checkPermFiles(): return False migrateUsers = (["root"] + [x[0] for x in addUsersList + pwdUsersList]) for existMigrUser in existsMigrateUsers: if existMigrUser not in migrateUsers: migrateUsers.append(existMigrUser) # add root to migrate users dataUsers = self.objUsers.getNewProcessedData(migrateUsers) dataGroups = self.objGroups.getNewProcessedData() thisSystemUsers, newSystemUsers, newUsers, thisUsers = \ [[y[0] for y in x] for x in dataUsers] objShadow = migrateShadow(thisSystemUsers, newSystemUsers, newUsers, thisUsers, self.prefixNewSystem) dataShadow = objShadow.getNewProcessedData() self.dataGroups = reduce(lambda x, y: x + y, dataGroups, []) self.dataUsers = reduce(lambda x, y: x + y, dataUsers, []) self.dataShadow = reduce(lambda x, y: x + y, dataShadow, []) self.addThisUsersToGroups(thisUsers) for userName, pwdHash, maxDays, warnDays in pwdUsersList: if not self.changePassword(userName, pwdHash, maxDays=maxDays, warnDays=warnDays): return False for userName, userGroups, pwdHash in [ ["root", [], rootPwd]] + addUsersList: # if self.isSystemUser(userName): # raise MigrationError(_("%s is a system user") %userName) ret = self.addUser(userName, userGroups, pwdHash) if not ret: return False elif ret == "EXISTS": if not self.changePassword(userName, pwdHash): return False if not newUsers or not thisUsers: # add user guest if not self.createUserGuest(): return False self.saveNewFiles() self.createHomeDirs(addUsersList, existsMigrateUsers) return True class currentUsers(migrate): """Current users""" def __init__(self): super().__init__('/') def addUsers(self, *users_passwd): """Added users and groups to current system""" if not self.checkPermFiles(): return False getDataInFile = _shareData().getDataInFile self.dataUsers = getDataInFile(fileName=migrateUsers.filePasswd, lenData=7) self.dataGroups = getDataInFile(fileName=migrateGroups.fileGroups, lenData=4) self.dataShadow = getDataInFile(fileName=migrateShadow.fileShadow, lenData=9) getHash = encrypt().getHashPasswd for userName, pwd in zip(users_passwd[0::2], users_passwd[1::2]): pwdHash = getHash(pwd, "shadow_ssha256") if not self.addUser(userName, userName, pwdHash): return False self.saveNewFiles() return True def hasUsers(self, *users): """Is users in system""" if not self.checkPermFiles(): return False getDataInFile = _shareData().getDataInFile self.dataUsers = [x[0] for x in getDataInFile(fileName=migrateUsers.filePasswd,lenData=7)] return set(self.dataUsers) >= set(users)