def _getInstalledPrintersOfUser(username): logging.info(f"Getting installed printers of {username}") command = f"lpstat -U {username} -p" #logging.debug("running '{}'".format(command)) result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) if not result.returncode == 0: logging.info("No Printers installed.") return True, [] rawInstalledPrinters = list(filter(None, result.stdout.split("\n"))) installedPrinters = [] for rawInstalledPrinter in rawInstalledPrinters: rawInstalledPrinterList = list( filter(None, rawInstalledPrinter.split(" "))) if len(rawInstalledPrinterList) < 2: continue installedPrinter = rawInstalledPrinterList[1] installedPrinters.append(installedPrinter) return True, installedPrinters
def writeNetworkConfig(newNetworkConfig): """ Write the network configuration in `/etc/linuxmuster-linuxclient7/network.conf` :param newNetworkConfig: The new config :type newNetworkConfig: dict :return: True or False :rtype: bool """ networkConfig = configparser.ConfigParser(interpolation=None) try: networkConfig["network"] = {} networkConfig["network"]["version"] = str(_networkConfigVersion()) networkConfig["network"]["serverHostname"] = newNetworkConfig[ "serverHostname"] networkConfig["network"]["domain"] = newNetworkConfig["domain"] networkConfig["network"]["realm"] = newNetworkConfig["realm"] except Exception as e: logging.error("Error when preprocessing new network configuration!") logging.exception(e) return False try: logging.info("Writing new network Configuration") with open(constants.networkConfigFilePath, 'w') as networkConfigFile: networkConfig.write(networkConfigFile) except Exception as e: logging.error("Failed!") logging.exception(e) return False return True
def _cleanOldDomainJoins(): # stop sssd logging.info("Stopping sssd") if subprocess.call(["service", "sssd", "stop"]) != 0: logging.error("Failed!") return False # Clean old domain join data logging.info("Deleting old kerberos tickets.") subprocess.call(["kdestroy"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if not realm.leaveAll(): return False # delete krb5.keytab file, if existent logging.info('Deleting krb5.keytab if it exists ... ') if not fileHelper.deleteFile("/etc/krb5.keytab"): return False # delete old CA Certificate logging.info('Deleting old CA certificate if it exists ... ') if not fileHelper.deleteFilesWithExtension("/var/lib/samba/private/tls", ".pem"): return False # remove network.conf logging.info(f"Deleting {constants.networkConfigFilePath} if exists ...") if not fileHelper.deleteFile(constants.networkConfigFilePath): return False return True
def writeNetworkConfig(newNetworkConfig): networkConfig = configparser.ConfigParser(interpolation=None) try: networkConfig["network"] = {} networkConfig["network"]["version"] = str(_networkConfigVersion()) networkConfig["network"]["serverHostname"] = newNetworkConfig["serverHostname"] networkConfig["network"]["domain"] = newNetworkConfig["domain"] networkConfig["network"]["realm"] = newNetworkConfig["realm"] except Exception as e: logging.error("Error when preprocessing new network configuration!") logging.exception(e) return False try: logging.info("Writing new network Configuration") with open(constants.networkConfigFilePath, 'w') as networkConfigFile: networkConfig.write(networkConfigFile) except Exception as e: logging.error("Failed!") logging.exception(e) return False return True
def deleteFilesWithExtension(directory, extension): """ Delete all files with a given extension in a given directory. :param directory: The path of the directory :type directory: str :param extension: The file extension :type extension: str :return: True on success, False otherwise :rtype: bool """ if directory.endswith("/"): directory = directory[:-1] if not os.path.exists(directory): return True existingFiles = os.listdir(directory) for file in existingFiles: if file.endswith(extension): logging.info("* Deleting {}".format(file)) if not deleteFile("{}/{}".format(directory, file)): logging.error("Failed!") return False return True
def _installCaCertificate(domain, user): logging.info('Installing server ca certificate ... ') # try to mount the share rc, sysvolMountpoint = shares.getLocalSysvolPath() if not rc: logging.error("Failed to mount sysvol!") return False cacertPath = f"{sysvolMountpoint}/{domain}/tls/cacert.pem" cacertTargetPath = f"/var/lib/samba/private/tls/{domain}.pem" logging.info("Copying CA certificate from server to client!") try: Path(Path(cacertTargetPath).parent.absolute()).mkdir(parents=True, exist_ok=True) shutil.copyfile(cacertPath, cacertTargetPath) except Exception as e: logging.error("Failed!") logging.exception(e) return False # make sure the file was successfully copied if not os.path.isfile(cacertTargetPath): logging.error('Failed to copy over CA certificate!') return False # unmount sysvol shares.unmountAllSharesOfUser(computer.krbHostName()) return True
def _clearUserHomes(unattended=False): print("\nCAUTION! This will delete all userhomes of AD users!") if not unattended and not _askStep("clear all user homes now", False): return True if not _checkLoggedInUsers(): return False if not _unmountAllCifsMounts(): logging.info("Aborting deletion of user homes to prevent deleting data on the server.") return False userHomes = os.listdir("/home") logging.info("Deleting all user homes now!") for userHome in userHomes: if not user.isUserInAD(userHome): logging.info("* {} [SKIPPED]".format(userHome)) continue logging.info("* {}".format(userHome)) try: shutil.rmtree("/home/{}".format(userHome)) except Exception as e: logging.error("* FAILED!") logging.exception(e) try: shutil.rmtree(constants.hiddenShareMountBasepath.format(userHome)) except: pass logging.info("Done.") return True
def cleanTemplateUserGtkBookmarks(): logging.info("Cleaning {} gtk bookmarks".format(constants.templateUser)) gtkBookmarksFile = "/home/{0}/.config/gtk-3.0/bookmarks".format(user.username()) if not os.path.isfile(gtkBookmarksFile): logging.warning("Gtk bookmarks file not found, skipping!") return fileHelper.removeLinesInFileContainingString(gtkBookmarksFile, constants.templateUser)
def runLocalHook(hookType): logging.info("=== Running local hook on{0} ===".format(hookType.name)) hookDir = _getLocalHookDir(hookType) if os.path.exists(hookDir): _prepareEnvironment() for fileName in os.listdir(hookDir): filePath = hookDir + "/" + fileName _runHookScript(filePath) logging.info("===> Finished running local hook on{0} ===".format( hookType.name))
def _uninstallPrinter(name): logging.info("* Uninstalling Printer {}".format(name)) uninstallCommand = ["lpadmin", "-x", name] #logging.debug("* running '{}'".format(installCommand)) if not subprocess.call(uninstallCommand) == 0: logging.error("* Error uninstalling printer!") return False return True
def join(domain, user): # join the domain using the kerberos ticket joinCommand = ["realm", "join", "-v", domain, "-U", user] if subprocess.call(joinCommand) != 0: print() logging.error('Failed! Did you enter the correct password?') return False logging.info("It looks like the domain was joined successfully.") return True
def runRemoteHook(hookType): logging.info("=== Running remote hook on{0} ===".format(hookType.name)) rc, hookScripts = _getRemoteHookScripts(hookType) if rc: _prepareEnvironment() _runHookScript(hookScripts[0]) _runHookScript(hookScripts[1]) logging.info("===> Finished running remote hook on{0} ===".format( hookType.name))
def _clearUserCache(unattended=False): if not unattended and not _askStep("clear all cached users now"): return True if not _checkLoggedInUsers(): return False realm.clearUserCache() logging.info("Done.") return realm.clearUserCache()
def _readTextfile(filePath): if not os.path.isfile(filePath): return False, None try: infile = codecs.open(filePath, 'r', encoding='utf-8', errors='ignore') content = infile.read() infile.close() return True, content except Exception as e: logging.info('Cannot read ' + filePath + '!') logging.exception(e) return False, None
def patchKeytab(): """ Patches the `/etc/krb5.keytab` file. It inserts the correct hostname of the current computer. :return: True on success, False otherwise :rtype: bool """ krb5KeytabFilePath = "/etc/krb5.keytab" logging.info("Patching {}".format(krb5KeytabFilePath)) krb5KeytabUtil = Krb5KeytabUtil(krb5KeytabFilePath) try: krb5KeytabUtil.read() except: logging.error("Error reading {}".format(krb5KeytabFilePath)) return False for entry in krb5KeytabUtil.keytab.entries: oldData = entry.principal.components[-1].data if len(entry.principal.components) == 1: newData = computer.hostname().upper() + "$" entry.principal.components[0].data = newData elif len(entry.principal.components) == 2 and ( entry.principal.components[0].data == "host" or entry.principal.components[0].data == "RestrictedKrbHost"): rc, networkConfig = config.network() if not rc: continue newData = "" domain = networkConfig["domain"] if domain in entry.principal.components[1].data: newData = computer.hostname().lower() + "." + domain else: newData = computer.hostname().upper() entry.principal.components[1].data = newData logging.debug("{} was changed to {}".format( oldData, entry.principal.components[-1].data)) logging.info("Trying to overwrite {}".format(krb5KeytabFilePath)) try: result = krb5KeytabUtil.write() except: result = False if not result: logging.error("Error overwriting {}".format(krb5KeytabFilePath)) return result
def uninstallAllPrintersOfUser(username): logging.info("Uninstalling all printers of {}".format(username)) rc, installedPrinters = _getInstalledPrintersOfUser(username) if not rc: logging.error("Error getting printers!") return False for installedPrinter in installedPrinters: if not _uninstallPrinter(installedPrinter): return False return True
def unmountAllSharesOfUser(username): logging.info( "=== Trying to unmount all shares of user {0} ===".format(username)) for basedir in [ constants.shareMountBasepath, constants.hiddenShareMountBasepath ]: shareMountBasedir = basedir.format(username) try: mountedShares = os.listdir(shareMountBasedir) except FileNotFoundError: logging.info( "Mount basedir {} does not exist -> nothing to unmount".format( shareMountBasedir)) continue for share in mountedShares: _unmountShare("{0}/{1}".format(shareMountBasedir, share)) if len(os.listdir(shareMountBasedir)) > 0: logging.warning( "* Mount basedir {} is not empty so not removed!".format( shareMountBasedir)) return else: # Delete the directory logging.info("Deleting {0}...".format(shareMountBasedir)) try: shutil.rmtree(shareMountBasedir) except Exception as e: logging.error("FAILED!") logging.exception(e) logging.info( "===> Finished unmounting all shares of user {0} ===".format(username))
def _preparePam(): # enable necessary pam modules logging.info('Updating pam configuration ... ') subprocess.call([ 'pam-auth-update', '--package', '--enable', 'libpam-mount', 'pwquality', 'sss', '--force' ]) ## mkhomedir was injected in template not using pam-auth-update subprocess.call([ 'pam-auth-update', '--package', '--remove', 'krb5', 'mkhomedir', '--force' ]) return True
def _prepareNetworkConfiguration(domain): logging.info("Preparing network configuration") rc, domainConfig = realm.getDomainConfig(domain) if not rc: logging.error("Could not read domain configuration") return False newNetworkConfig = {} newNetworkConfig["serverHostname"] = domainConfig["domain-controller"] newNetworkConfig["domain"] = domainConfig["domain-name"] newNetworkConfig["realm"] = domainConfig["domain-name"].upper() config.writeNetworkConfig(newNetworkConfig) return True
def runLocalHook(hookType): """ Run all scripts in a local hookdir :param hookType: The type of hook to run :type hookType: hooks.Type """ logging.info("=== Running local hook on{0} ===".format(hookType.name)) hookDir = _getLocalHookDir(hookType) if os.path.exists(hookDir): _prepareEnvironment() for fileName in os.listdir(hookDir): filePath = hookDir + "/" + fileName _runHookScript(filePath) logging.info("===> Finished running local hook on{0} ===".format( hookType.name))
def runRemoteHook(hookType): """ Run hookscript from sysvol :param hookType: The type of hook to run :type hookType: hooks.Type """ logging.info("=== Running remote hook on{0} ===".format(hookType.name)) rc, hookScripts = _getRemoteHookScripts(hookType) if rc: _prepareEnvironment() _runHookScript(hookScripts[0]) _runHookScript(hookScripts[1]) logging.info("===> Finished running remote hook on{0} ===".format( hookType.name))
def _runHookScript(filePath): if not os.path.isfile(filePath): logging.warning( "* File {0} should be executed as hook but does not exist!".format( filePath)) return if not os.access(filePath, os.X_OK): logging.warning( "* File {0} is in hook dir but not executable!".format(filePath)) return logging.info("== Executing script {0} ==".format(filePath)) result = subprocess.call([filePath]) logging.info("==> Script {0} finished with exit code {1} ==".format( filePath, result))
def _processDrivesPolicy(policyBasepath): logging.info("== Parsing a drive policy! ==") policyFile = "{}/User/Preferences/Drives/Drives.xml".format(policyBasepath) shareList = [] rc, tree = _parseXmlPolicy(policyFile) if not rc: logging.error( "==> Error while reading Drives policy file, skipping! ==") return xmlDrives = tree.getroot() if not xmlDrives.tag == "Drives": logging.warning( "==> Drive policy xml File is of invalid format, skipping! ==") return for xmlDrive in xmlDrives: if xmlDrive.tag != "Drive" or ("disabled" in xmlDrive.attrib and xmlDrive.attrib["disabled"] == "1"): continue drive = {} drive["filters"] = [] for xmlDriveProperty in xmlDrive: if xmlDriveProperty.tag == "Properties": try: drive["label"] = xmlDriveProperty.attrib["label"] drive["letter"] = xmlDriveProperty.attrib["letter"] drive["path"] = xmlDriveProperty.attrib["path"] except Exception as e: logging.warning( "Exception when parsing a drive policy XML file") logging.exception(e) continue if xmlDriveProperty.tag == "Filters": drive["filters"] = _parseXmlFilters(xmlDriveProperty) shareList.append(drive) shareList = _processFilters(shareList) logging.info("Found shares:") for drive in shareList: logging.info("* {0}\t\t| {1}\t| {2}\t| {3}".format( drive["label"], drive["letter"], drive["path"], drive["filters"])) shares.mountShare(drive["path"], shareName="{0} ({1}:)".format( drive["label"], drive["letter"])) logging.info("==> Successfully parsed a drive policy! ==")
def _clearCaches(unattended=False): if not unattended and not _askStep("clear journalctl and apt caches now"): return True logging.info("Cleaning caches..") logging.info("* apt") fileHelper.deleteAllInDirectory("/var/lib/apt/lists/") logging.info("* journalctl") subprocess.call(["journalctl", "--flush", "--rotate"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.call(["journalctl", "--vacuum-time=1s"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) logging.info("Done.") return True
def uninstallAllPrintersOfUser(username): """ Uninstalls all printers of a given user :param username: The username of the user :type username: str :return: True on success, False otherwise :rtype: bool """ logging.info("Uninstalling all printers of {}".format(username)) rc, installedPrinters = _getInstalledPrintersOfUser(username) if not rc: logging.error("Error getting printers!") return False for installedPrinter in installedPrinters: if not _uninstallPrinter(installedPrinter): return False return True
def _prepareServices(): logging.info("Raloading systctl daemon") subprocess.call(["systemctl", "daemon-reload"]) logging.info('Enabling services:') services = ['linuxmuster-linuxclient7', 'smbd', 'nmbd', 'sssd'] for service in services: logging.info('* %s' % service) subprocess.call(['systemctl', 'enable', service + '.service'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) logging.info('Restarting services:') services = ['smbd', 'nmbd', 'systemd-timesyncd'] for service in services: logging.info('* %s' % service) subprocess.call(['systemctl', 'restart', service + '.service'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) return True
def _parsePolicy(policyDn): logging.info("=== Parsing policy [{0};{1}] ===".format( policyDn[0], policyDn[1])) # Check if the policy is disabled if policyDn[1] == 1: logging.info("===> Policy is disabled! ===") return True # Find policy in AD rc, policyAdObject = ldapHelper.searchOne("(distinguishedName={})".format( policyDn[0])) if not rc: logging.error("===> Could not find poilcy in AD! ===") return False, None # mount the share the policy is on (probaply already mounted, just to be sure) rc, localPolicyPath = shares.getMountpointOfRemotePath( policyAdObject["gPCFileSysPath"], hiddenShare=True, autoMount=True) if not rc: logging.error("===> Could not mount path of poilcy! ===") return False, None try: # parse drives _processDrivesPolicy(localPolicyPath) # parse printers _processPrintersPolicy(localPolicyPath) except Exception as e: logging.error("An error occured when parsing policy!") logging.exception(e) logging.info("===> Parsed policy [{0};{1}] ===".format( policyDn[0], policyDn[1]))
def _upgradeNetworkConfig(): logging.info("Upgrading network config.") rc, rawNetworkConfig = _readNetworkConfig() if not rc: return False rc, networkConfigVersion = _checkNetworkConfigVersion(rawNetworkConfig) if rc: logging.info("No need to upgrade, already up-to-date.") return True logging.info("Upgrading network config from {0} to {1}".format(networkConfigVersion, _networkConfigVersion())) if networkConfigVersion > _networkConfigVersion(): logging.error("Cannot upgrade from a newer version to an older one!") return False try: if networkConfigVersion == 0: newNetworkConfig = {} newNetworkConfig["serverHostname"] = rawNetworkConfig["serverHostname"] + "." + rawNetworkConfig["domain"] newNetworkConfig["domain"] = rawNetworkConfig["domain"] newNetworkConfig["realm"] = rawNetworkConfig["domain"].upper() return writeNetworkConfig(newNetworkConfig) except Exception as e: logging.error("Error when upgrading network config!") logging.exception(e) return False return True
def upgrade(): """ Performs an upgrade of the linuxmuster-linuxclient7. This is executed after the package is updated. :return: True on success, False otherwise :rtype: bool """ if not isSetup(): logging.info( "linuxmuster-linuxclient7 does not seem to be setup -> no upgrade is needed" ) return True logging.info('#### linuxmuster-linuxclient7 upgrade ####') if not config.upgrade(): return False if not _deleteObsoleteFiles(): return False if not templates.applyAll(): return False if not _prepareServices(): return False rc, joinedDomains = realm.getJoinedDomains() if not rc: return False for domain in joinedDomains: _adjustSssdConfiguration(domain) logging.info('#### linuxmuster-linuxclient7 upgrade SUCCESSFULL ####') return True
def _unmountShare(mountpoint): # check if mountpoint exists if (not os.path.exists(mountpoint)) or (not os.path.isdir(mountpoint)): logging.warning( f"* Could not unmount {mountpoint}, it does not exist.") # Try to unmount share logging.info("* Trying to unmount {0}...".format(mountpoint)) if not subprocess.call(["umount", mountpoint]) == 0: logging.warning("* Failed!") if _directoryIsMountpoint(mountpoint): logging.warning("* It is still mounted! Exiting!") # Do not delete in this case! We might delete userdata! return logging.info("* It is not mounted! Continuing!") # check if the mountpoint is empty if len(os.listdir(mountpoint)) > 0: logging.warning( "* mountpoint {} is not empty so not removed!".format(mountpoint)) return # Delete the directory logging.info("* Deleting {0}...".format(mountpoint)) try: shutil.rmtree(mountpoint) except Exception as e: logging.error("* FAILED!") logging.exception(e)