def _connect(): global _currentLdapConnection if not user.isInAD() and not (user.isRoot() or not computer.isInAD()): logging.warning("Cannot perform LDAP search: User is not in AD!") _currentLdapConnection = None return False if not _currentLdapConnection == None: return True try: sasl_auth = ldap.sasl.sasl({}, 'GSSAPI') _currentLdapConnection = ldap.initialize(serverUrl(), trace_level=0) # TODO: # conn.set_option(ldap.OPT_X_TLS_CACERTFILE, '/path/to/ca.pem') # conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0) # conn.start_tls_s() _currentLdapConnection.set_option(ldap.OPT_REFERRALS, 0) _currentLdapConnection.protocol_version = ldap.VERSION3 _currentLdapConnection.sasl_interactive_bind_s("", sasl_auth) except Exception as e: _currentLdapConnection = None logging.error("Cloud not bind to ldap!") logging.exception(e) 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 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 _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 getGroupsOfLocalUser(username): try: groups = subprocess.check_output(["id", "-Gnz", username]) stringList = [x.decode('utf-8') for x in groups.split(b"\x00")] return True, stringList except Exception as e: logging.warning( "Exception when querying groups of user {}, it probaply does not exist" .format(username)) return False, None
def _checkNetworkConfigVersion(rawNetworkConfig): try: networkConfigVersion = int(rawNetworkConfig["version"]) except KeyError as e: logging.warning("The network.conf version could not be identified, assuming 0") networkConfigVersion = 0 if networkConfigVersion != _networkConfigVersion(): logging.warning("The network.conf Version is a mismatch!") return False, networkConfigVersion return True, networkConfigVersion
def _parseXmlPolicy(policyFile): if not os.path.isfile(policyFile): logging.warning("==> XML policy file not found! ==") return False, None try: tree = ElementTree.parse(policyFile) return True, tree except Exception as e: logging.exception(e) logging.error("==> Error while reading XML policy file! ==") return False, None
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)
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 _parseXmlFilters(filtersXmlNode): if not filtersXmlNode.tag == "Filters": logging.warning("Tried to parse a non-filter node as a filter!") return [] filters = [] for xmlFilter in filtersXmlNode: if xmlFilter.tag == "FilterGroup": filters.append({ "name": xmlFilter.attrib["name"].split("\\")[1], "bool": xmlFilter.attrib["bool"], "userContext": xmlFilter.attrib["userContext"], # userContext defines if the filter applies in user or computer context "type": xmlFilter.tag }) return filters
def getGroupsOfLocalUser(username): """ Get all groups of a local user :param username: The username of the user :type username: str :return: Tuple (success, list of groups) :rtype: tuple """ try: groups = subprocess.check_output(["id", "-Gnz", username]) stringList = [x.decode('utf-8') for x in groups.split(b"\x00")] return True, stringList except Exception as e: logging.warning( "Exception when querying groups of user {}, it probaply does not exist" .format(username)) return False, None
def _apply(templatePath): try: # read template file rc, fileData = _readTextfile(templatePath) if not rc: logging.error('Failed!') return False fileData = _resolveVariables(fileData) # get target path firstLine = fileData.split('\n')[0] targetFilePath = firstLine.partition(' ')[2] # remove first line (the target file path) fileData = fileData[fileData.find('\n'):] # never ever overwrite sssd.conf, this will lead to issues! # sssd.conf is written by `realm join`! if targetFilePath in constants.notTemplatableFiles: logging.warning( "Skipping forbidden file {}".format(targetFilePath)) return True # create target directory Path(Path(targetFilePath).parent.absolute()).mkdir(parents=True, exist_ok=True) # remove comment lines beginning with # from .xml files if targetFilePath.endswith('.xml'): fileData = _stripComment(fileData) # write config file logging.debug("-> to {}".format(targetFilePath)) with open(targetFilePath, 'w') as targetFile: targetFile.write(fileData) return True except Exception as e: logging.error('Failed!') logging.exception(e) return False
def removeLinesInFileContainingString(filePath, forbiddenStrings): """ Remove all lines containing a given string form a file. :param filePath: The path to the file :type filePath: str :param forbiddenStrings: The string to search for :type forbiddenStrings: str :return: True on success, False otherwise :rtype: bool """ if not isinstance(forbiddenStrings, list): forbiddenStrings = [forbiddenStrings] try: with open(filePath, "r") as originalFile: originalContents = originalFile.read() except Exception as e: logging.exception(e) logging.warning("Could not read contents of original file") return False newContents = "" for line in originalContents.split("\n"): lineIsClean = True for forbiddenString in forbiddenStrings: lineIsClean = lineIsClean and not forbiddenString in line if lineIsClean: newContents += line + "\n" try: with open(filePath, "w") as originalFile: originalFile.write(newContents) except Exception as e: logging.exception(e) logging.warning("Could not write new contents to original file") return False return True
def _processFilters(policies): filteredPolicies = [] for policy in policies: if not len(policy["filters"]) > 0: filteredPolicies.append(policy) else: filtersPassed = True for filter in policy["filters"]: logging.debug("Testing filter: {}".format(filter)) # TODO: check for AND and OR if filter["bool"] == "AND": filtersPassed = filtersPassed and _processFilter(filter) elif filter["bool"] == "OR": filtersPassed = filtersPassed or _processFilter(filter) else: logging.warning( "Unknown boolean operation: {}! Cannot process filter!" .format(filter["bool"])) if filtersPassed: filteredPolicies.append(policy) return filteredPolicies
def _processPrintersPolicy(policyBasepath): logging.info("== Parsing a printer policy! ==") policyFile = "{}/User/Preferences/Printers/Printers.xml".format( policyBasepath) printerList = [] # test rc, tree = _parseXmlPolicy(policyFile) if not rc: logging.error( "==> Error while reading Printer policy file, skipping! ==") return xmlPrinters = tree.getroot() if not xmlPrinters.tag == "Printers": logging.warning( "==> Printer policy xml File is of invalid format, skipping! ==") return for xmlPrinter in xmlPrinters: if xmlPrinter.tag != "SharedPrinter" or ( "disabled" in xmlPrinter.attrib and xmlPrinter.attrib["disabled"] == "1"): continue printer = {} printer["filters"] = [] try: printer["name"] = xmlPrinter.attrib["name"] except Exception as e: logging.warning( "Exception when reading a printer name from a printer policy XML file" ) logging.exception(e) for xmlPrinterProperty in xmlPrinter: if xmlPrinterProperty.tag == "Properties": try: rc, printerUrl = printers.translateSambaToIpp( xmlPrinterProperty.attrib["path"]) if rc: printer["path"] = printerUrl except Exception as e: logging.warning( "Exception when parsing a printer policy XML file") logging.exception(e) continue if xmlPrinterProperty.tag == "Filters": printer["filters"] = _parseXmlFilters(xmlPrinterProperty) printerList.append(printer) printerList = _processFilters(printerList) logging.info("Found printers:") for printer in printerList: logging.info("* {0}\t\t| {1}\t| {2}".format(printer["name"], printer["path"], printer["filters"])) printers.installPrinter(printer["path"], printer["name"]) logging.info("==> Successfully parsed a printer policy! ==")
def _mountShare(username, networkPath, shareName, hiddenShare, useCruidOfExecutingUser=False): mountpoint = _getShareMountpoint(networkPath, username, hiddenShare, shareName) mountCommandOptions = f"file_mode=0700,dir_mode=0700,sec=krb5,nodev,nosuid,mfsymlinks,nobrl,vers=3.0,user={username}" rc, networkConfig = config.network() domain = None if rc: domain = networkConfig["domain"] mountCommandOptions += f",domain={domain.upper()}" try: pwdInfo = pwd.getpwnam(username) uid = pwdInfo.pw_uid gid = pwdInfo.pw_gid mountCommandOptions += f",gid={gid},uid={uid}" if not useCruidOfExecutingUser: mountCommandOptions += f",cruid={uid}" except KeyError: uid = -1 gid = -1 logging.warning("Uid could not be found! Continuing anyway!") mountCommand = [ "/usr/sbin/mount.cifs", "-o", mountCommandOptions, networkPath, mountpoint ] logging.debug(f"Trying to mount '{networkPath}' to '{mountpoint}'") logging.debug("* Creating directory...") try: Path(mountpoint).mkdir(parents=True, exist_ok=False) except FileExistsError: # Test if a share is already mounted there if _directoryIsMountpoint(mountpoint): logging.debug("* The mountpoint is already mounted.") return True, mountpoint else: logging.warning( "* The target directory already exists, proceeding anyway!") logging.debug("* Executing '{}' ".format(" ".join(mountCommand))) logging.debug("* Trying to mount...") if not subprocess.call( mountCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0: logging.fatal( f"* Error mounting share {networkPath} to {mountpoint}!\n") return False, None logging.debug("* Success!") # hide the shares parent dir (/home/%user/media) in case it is not a hidden share if not hiddenShare: try: hiddenFilePath = f"{mountpoint}/../../.hidden" logging.debug(f"* hiding parent dir {hiddenFilePath}") hiddenFile = open(hiddenFilePath, "w+") hiddenFile.write(mountpoint.split("/")[-2]) hiddenFile.close() except: logging.warning(f"Could not hide parent dir of share {mountpoint}") return True, mountpoint