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

549 lines
22 KiB

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