diff --git a/lmn-client.yml b/lmn-client.yml
index e4ece7c..b38d67a 100644
--- a/lmn-client.yml
+++ b/lmn-client.yml
@@ -52,6 +52,7 @@
ntp_serv: "{{ vault_ntp_serv }}" ## ntp.example.org
proxy: "{{ vault_proxy }}" ## http://firewall.example.org:3128
no_proxy: "{{ vault_no_proxy }}" ## firewall.example.org,server.example.org,idam.example.org,dw.example.org
+ printservers: ['10.190.1.1'] ## list of printservers
## PAM mount nextcloud, remove or leave empty to skip:
web_dav: "{{ vault_web_dav }}" ## https://nc.example.org/remote.php/dav/files/%(USER)
@@ -139,6 +140,39 @@
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
+ - name: Remove pam_mount sysvol mount
+ blockinfile:
+ dest: /etc/security/pam_mount.conf.xml
+ marker: ""
+ block: |
+ rootansibleDebian-gdmsddm{{ localuser }}
+
+ state: absent
+
+ - name: disable rmlpr.timer
+ systemd:
+ name: rmlpr.timer
+ enabled: false
+
+ - name: Remove deprecated files and directories
+ file:
+ path: "{{ item }}"
+ state: absent
+ with_items:
+ - /etc/linuxmuster-linuxclient7
+ - /usr/lib/python3/dist-packages/linuxmusterLinuxclient7
+ - /usr/share/linuxmuster-linuxclient7
+ - /usr/local/bin/onLogin
+ - /etc/sudoers.d/90-lmn-sudotools
+ - /etc/systemd/system/rmlpr.service
+ - /etc/systemd/system/rmlpr.timer
+
## bookworm fixes/hacks:
- name: Work around sddm hang on shutdown
ansible.builtin.lineinfile:
diff --git a/roles/lmn_printer/files/90-lmn-install-printers b/roles/lmn_printer/files/90-lmn-install-printers
new file mode 100644
index 0000000..8f0763f
--- /dev/null
+++ b/roles/lmn_printer/files/90-lmn-install-printers
@@ -0,0 +1,4 @@
+%examusers ALL=(root) NOPASSWD: /usr/local/bin/install-printers.sh
+%role-student ALL=(root) NOPASSWD: /usr/local/bin/install-printers.sh
+%role-teacher ALL=(root) NOPASSWD: /usr/local/bin/install-printers.sh
+
diff --git a/roles/lmn_printer/files/90-lmn-sudotools b/roles/lmn_printer/files/90-lmn-sudotools
deleted file mode 100644
index 1c82b4d..0000000
--- a/roles/lmn_printer/files/90-lmn-sudotools
+++ /dev/null
@@ -1,4 +0,0 @@
-%examusers ALL=(root) NOPASSWD: /usr/share/linuxmuster-linuxclient7/scripts/sudoTools
-%role-student ALL=(root) NOPASSWD: /usr/share/linuxmuster-linuxclient7/scripts/sudoTools
-%role-teacher ALL=(root) NOPASSWD: /usr/share/linuxmuster-linuxclient7/scripts/sudoTools
-
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/__init__.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/__init__.py
deleted file mode 100644
index bf319b7..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-#
-# linuxmuster-linuxclient7 is a library for use with Linuxmuster.net
-#
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/computer.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/computer.py
deleted file mode 100644
index 701ab81..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/computer.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import socket
-from linuxmusterLinuxclient7 import logging, ldapHelper, realm, localUserHelper
-
-def hostname():
- """
- Get the hostname of the computer
-
- :return: The hostname
- :rtype: str
- """
- return socket.gethostname().split('.', 1)[0]
-
-def krbHostName():
- """
- Get the krb hostname, eg. `COMPUTER01$`
-
- :return: The krb hostname
- :rtype: str
- """
- return hostname().upper() + "$"
-
-def readAttributes():
- """
- Read all ldap attributes of the cumputer
-
- :return: Tuple (success, dict of attributes)
- :rtype: tuple
- """
- return ldapHelper.searchOne("(sAMAccountName={}$)".format(hostname()))
-
-def isInGroup(groupName):
- """
- Check if the computer is part of an ldap group
-
- :param groupName: The name of the group to check
- :type grouName: str
- :return: True or False
- :rtype: bool
- """
- rc, groups = localUserHelper.getGroupsOfLocalUser(krbHostName())
- if not rc:
- return False
-
- return groupName in groups
-
-def isInAD():
- """
- Check if the computer is joined to an AD
-
- :return: True or False
- :rtype: bool
- """
- rc, groups = localUserHelper.getGroupsOfLocalUser(krbHostName())
- if not rc:
- return False
-
- return "domain computers" in groups
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/config.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/config.py
deleted file mode 100644
index bbfbaf9..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/config.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import configparser, re
-from linuxmusterLinuxclient7 import logging, constants
-
-def network():
- """
- Get the network configuration in `/etc/linuxmuster-linuxclient7/network.conf`
-
- :return: Tuple (success, dict of keys)
- :rtype: tuple
- """
- rc, rawNetworkConfig = _readNetworkConfig()
- if not rc:
- return False, None
-
- if not _checkNetworkConfigVersion(rawNetworkConfig)[0]:
- return False, None
-
- networkConfig = {}
-
- try:
- networkConfig["serverHostname"] = rawNetworkConfig["serverHostname"]
- networkConfig["domain"] = rawNetworkConfig["domain"]
- networkConfig["realm"] = rawNetworkConfig["realm"]
- except KeyError as e:
- logging.error("Error when reading network.conf (2)")
- logging.exception(e)
- return False, None
-
- return True, networkConfig
-
-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 upgrade():
- """
- Upgrade the format of the network configuration in `/etc/linuxmuster-linuxclient7/network.conf`
- This is done automatically on package upgrades.
-
- :return: True or False
- :rtype: bool
- """
- return _upgradeNetworkConfig()
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _readNetworkConfig():
- configParser = configparser.ConfigParser()
- configParser.read(constants.networkConfigFilePath)
- try:
- rawNetworkConfig = configParser["network"]
- return True, rawNetworkConfig
- except KeyError as e:
- logging.error("Error when reading network.conf (1)")
- logging.exception(e)
- return False, None
- return configParser
-
-def _networkConfigVersion():
- return 1
-
-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 _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
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/constants.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/constants.py
deleted file mode 100644
index 34d8243..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/constants.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/python3
-
-templateUser = "linuxadmin"
-userTemplateDir = "/home/" + templateUser
-defaultDomainAdminUser = "global-admin"
-
-# {} will be substituted for the username
-shareMountBasepath = "/home/{}/media"
-hiddenShareMountBasepath = "/srv/samba/{}"
-machineAccountSysvolMountPath = "/var/lib/samba/sysvol"
-
-etcBaseDir = "/etc/linuxmuster-linuxclient7"
-shareBaseDir = "/usr/share/linuxmuster-linuxclient7"
-configFileTemplateDir = shareBaseDir + "/templates"
-scriptDir = shareBaseDir + "/scripts"
-
-networkConfigFilePath = etcBaseDir + "/network.conf"
-# {} will be substituted for the username
-tmpEnvironmentFilePath = "/home/{}/.linuxmuster-linuxclient7-environment.sh"
-
-notTemplatableFiles = ["/etc/sssd/sssd.conf", "/etc/linuxmuster-linuxclient7/network.conf"]
-
-# cleanup
-obsoleteFiles = [
- "/etc/profile.d/99-linuxmuster.sh",
- "/etc/sudoers.d/linuxmuster",
- "/etc/profile.d/linuxmuster-proxy.sh",
- "/etc/bash_completion.d/99-linuxmuster-client-adsso.sh",
- "/etc/profile.d/99-linuxmuster-client-adsso.sh",
- "/etc/sudoers.d/linuxmuster-client-adsso",
- "/usr/sbin/linuxmuster-client-adsso",
- "/usr/sbin/linuxmuster-client-adsso-print-logs",
- "/etc/systemd/system/linuxmuster-client-adsso.service",
- "{}/.config/autostart/linuxmuster-client-adsso-autostart.desktop".format(userTemplateDir),
- "/etc/cups/client.conf",
- "/usr/share/linuxmuster-linuxclient7/templates/linuxmuster-client-adsso.service",
- "/usr/share/linuxmuster-linuxclient7/templates/linuxmuster-client-adsso-autostart.desktop",
- "/etc/security/pam_mount.conf.xml",
- "{}/pam_mount.conf.xml".format(configFileTemplateDir)
-]
-
-obsoleteDirectories = [
- "/etc/linuxmuster-client",
- "/etc/linuxmuster-client-adsso",
- "/usr/share/linuxmuster-client-adsso"
-]
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/environment.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/environment.py
deleted file mode 100644
index e25b622..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/environment.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import os
-from linuxmusterLinuxclient7 import constants, user, logging
-
-def export(keyValuePair):
- """
- Export an environment variable
-
- :param keyValuePair: Key value pair in format `key=value`
- :type keyValuePait: str
- :return: True or False
- :rtype: bool
- """
- logging.debug("Saving export '{}' to tmp file".format(keyValuePair))
-
- envList = keyValuePair.split("=")
- if len(envList) == 2:
- os.putenv(envList[0], envList[1])
-
- return _appendToTmpEnvFile("export", keyValuePair)
-
-def unset(key):
- """
- Unset a previously exported environment variable
-
- :param key: The key to unset
- :type key: str
- :return: True or False
- :rtype: bool
- """
- logging.debug("Saving unset '{}' to tmp file".format(key))
- return _appendToTmpEnvFile("unset", key)
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _isApplicable():
- if not user.isInAD():
- logging.error("Modifying environment variables of non-AD users is not supported by lmn-export and lmn-unset!")
- return False
- elif "LinuxmusterLinuxclient7EnvFixActive" not in os.environ or os.environ["LinuxmusterLinuxclient7EnvFixActive"] != "1":
- logging.error("lmn-export and lmn-unset may only be used inside of linuxmuster-linuxclient7 hooks!")
- return False
- else:
- return True
-
-def _appendToTmpEnvFile(mode, keyValuePair):
- if not _isApplicable():
- return False
-
- tmpEnvironmentFilePath = constants.tmpEnvironmentFilePath.format(user.username())
- fileOpenMode = "a" if os.path.exists(tmpEnvironmentFilePath) else "w"
-
- try:
- with open(tmpEnvironmentFilePath, fileOpenMode) as tmpEnvironmentFile:
- tmpEnvironmentFile.write("\n{0} '{1}'".format(mode, keyValuePair))
- return True
- except Exception as e:
- logging.exception(e)
- return False
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/fileHelper.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/fileHelper.py
deleted file mode 100644
index ad9b55e..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/fileHelper.py
+++ /dev/null
@@ -1,133 +0,0 @@
-import os, shutil
-from linuxmusterLinuxclient7 import logging
-
-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 deleteFile(filePath):
- """
- Delete a file
-
- :param filePath: The path of the file
- :type filePath: str
- :return: True on success, False otherwise
- :rtype: bool
- """
- try:
- if os.path.exists(filePath):
- os.unlink(filePath)
- return True
- except Exception as e:
- logging.error("Failed!")
- logging.exception(e)
- return False
-
-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 deleteDirectory(directory):
- """
- Recoursively delete a directory.
-
- :param directory: The path of the directory
- :type directory: bool
- :return: True on success, False otherwise
- :rtype: bool
- """
- try:
- shutil.rmtree(directory)
- except:
- return False
- return True
-
-def deleteAllInDirectory(directory):
- """
- Delete all files in a given directory
-
- :param directory: The path of the directory
- :type directory: 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:
- fullFilePath = "{}/{}".format(directory, file)
- if os.path.isdir(fullFilePath):
- rc = deleteDirectory(fullFilePath)
- else:
- rc = deleteFile(fullFilePath)
- if not rc:
- logging.error("Failed!")
- return False
-
- return True
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/gpo.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/gpo.py
deleted file mode 100644
index 43d08a8..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/gpo.py
+++ /dev/null
@@ -1,291 +0,0 @@
- # Order of parsing: (overwriting each other)
- # 1. Local (does not apply)
- # 2. Site (does not apply)
- # 3. Domain
- # 4. OUs from top to bottom
-import ldap, ldap.sasl, re, os.path
-import xml.etree.ElementTree as ElementTree
-from linuxmusterLinuxclient7 import logging, constants, config, user, ldapHelper, shares, computer, printers
-
-def processAllPolicies():
- """
- Process all applicable policies (equivalent to gpupdate on windows)
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- rc, policyDnList = _findApplicablePolicies()
- if not rc:
- logging.fatal("* Error when loading applicable GPOs! Shares and printers will not work.")
- return False
-
- for policyDn in policyDnList:
- _parsePolicy(policyDn)
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _parseGplinkSring(string):
- # a gPLink strink looks like this:
- # [LDAP://;][LDAP://;][...]
- # The ragex matches and in two separate groups
- # Note: "LDAP://" is matched as .*:// to prevent issues when the capitalization changes
- pattern = re.compile("\\[.*:\\/\\/([^\\]]+)\\;([0-9]+)\\]")
-
- return pattern.findall(string)
-
-def _extractOUsFromDN(dn):
- # NOT finished!
- pattern = re.compile("OU=([^,]+),")
-
- ouList = pattern.findall(dn)
- # We need to parse from top to bottom
- ouList.reverse()
- return ouList
-
-def _findApplicablePolicies():
-
- policyDnList = []
-
- """ Do this later!
- # 1. Domain
- rc, domainAdObject = ldapHelper.searchOne("(distinguishedName={})".format(ldapHelper.baseDn()))
-
- if not rc:
- return False, None
-
- policyDNs.extend(_parseGplinkSring(domainAdObject["gPLink"]))
-
- # 2. OU policies from top to bottom
- rc, userAdObject = ldapHelper.searchOne("(sAMAccountName={})".format(user.username()))
-
- if not rc:
- return False, None
-
- print(userAdObject["distinguishedName"])
- """
-
- # For now, just parse policy sophomorix:school:
- rc, schoolName = user.school()
- if not rc:
- return False, None
-
- policyName = "sophomorix:school:{}".format(schoolName)
-
- # find policy
- rc, policyAdObject = ldapHelper.searchOne("(displayName={})".format(policyName))
- if not rc:
- return False, None
-
- policyDnList.append((policyAdObject["distinguishedName"], 0))
-
- return True, policyDnList
-
-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
- # Skip Drive Policys (fvs change)
- #_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 _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 _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 _processFilter(filter):
- if filter["type"] == "FilterGroup":
- if filter["userContext"] == "1":
- return user.isInGroup(filter["name"])
- elif filter["userContext"] == "0":
- return computer.isInGroup(filter["name"])
-
- return False
-
-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 _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"]
- drive["useLetter"] = xmlDriveProperty.attrib["useLetter"]
- 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("* {:10}| {:5}| {:40}| {:5}".format(drive["label"], drive["letter"], drive["path"], drive["useLetter"]))
-
- for drive in shareList:
- if drive["useLetter"] == "1":
- shareName = f"{drive['label']} ({drive['letter']}:)"
- else:
- shareName = drive["label"]
- shares.mountShare(drive["path"], shareName=shareName)
-
- logging.info("==> Successfully parsed a drive policy! ==")
-
-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! ==")
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/gpo.py.orig b/roles/lmn_printer/files/linuxmusterLinuxclient7/gpo.py.orig
deleted file mode 100644
index 20f10ba..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/gpo.py.orig
+++ /dev/null
@@ -1,290 +0,0 @@
- # Order of parsing: (overwriting each other)
- # 1. Local (does not apply)
- # 2. Site (does not apply)
- # 3. Domain
- # 4. OUs from top to bottom
-import ldap, ldap.sasl, re, os.path
-import xml.etree.ElementTree as ElementTree
-from linuxmusterLinuxclient7 import logging, constants, config, user, ldapHelper, shares, computer, printers
-
-def processAllPolicies():
- """
- Process all applicable policies (equivalent to gpupdate on windows)
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- rc, policyDnList = _findApplicablePolicies()
- if not rc:
- logging.fatal("* Error when loading applicable GPOs! Shares and printers will not work.")
- return False
-
- for policyDn in policyDnList:
- _parsePolicy(policyDn)
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _parseGplinkSring(string):
- # a gPLink strink looks like this:
- # [LDAP://;][LDAP://;][...]
- # The ragex matches and in two separate groups
- # Note: "LDAP://" is matched as .*:// to prevent issues when the capitalization changes
- pattern = re.compile("\\[.*:\\/\\/([^\\]]+)\\;([0-9]+)\\]")
-
- return pattern.findall(string)
-
-def _extractOUsFromDN(dn):
- # NOT finished!
- pattern = re.compile("OU=([^,]+),")
-
- ouList = pattern.findall(dn)
- # We need to parse from top to bottom
- ouList.reverse()
- return ouList
-
-def _findApplicablePolicies():
-
- policyDnList = []
-
- """ Do this later!
- # 1. Domain
- rc, domainAdObject = ldapHelper.searchOne("(distinguishedName={})".format(ldapHelper.baseDn()))
-
- if not rc:
- return False, None
-
- policyDNs.extend(_parseGplinkSring(domainAdObject["gPLink"]))
-
- # 2. OU policies from top to bottom
- rc, userAdObject = ldapHelper.searchOne("(sAMAccountName={})".format(user.username()))
-
- if not rc:
- return False, None
-
- print(userAdObject["distinguishedName"])
- """
-
- # For now, just parse policy sophomorix:school:
- rc, schoolName = user.school()
- if not rc:
- return False, None
-
- policyName = "sophomorix:school:{}".format(schoolName)
-
- # find policy
- rc, policyAdObject = ldapHelper.searchOne("(displayName={})".format(policyName))
- if not rc:
- return False, None
-
- policyDnList.append((policyAdObject["distinguishedName"], 0))
-
- return True, policyDnList
-
-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 _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 _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 _processFilter(filter):
- if filter["type"] == "FilterGroup":
- if filter["userContext"] == "1":
- return user.isInGroup(filter["name"])
- elif filter["userContext"] == "0":
- return computer.isInGroup(filter["name"])
-
- return False
-
-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 _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"]
- drive["useLetter"] = xmlDriveProperty.attrib["useLetter"]
- 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("* {:10}| {:5}| {:40}| {:5}".format(drive["label"], drive["letter"], drive["path"], drive["useLetter"]))
-
- for drive in shareList:
- if drive["useLetter"] == "1":
- shareName = f"{drive['label']} ({drive['letter']}:)"
- else:
- shareName = drive["label"]
- shares.mountShare(drive["path"], shareName=shareName)
-
- logging.info("==> Successfully parsed a drive policy! ==")
-
-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! ==")
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/hooks.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/hooks.py
deleted file mode 100644
index 52430c3..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/hooks.py
+++ /dev/null
@@ -1,219 +0,0 @@
-#
-# This is used to run hooks
-#
-
-from enum import Enum
-import os, subprocess
-from linuxmusterLinuxclient7 import logging, constants, user, config, computer, environment, setup, shares
-
-class Type(Enum):
- """
- Enum containing all hook types
- """
-
- Boot = 0
- """The onBoot hook
- """
- Shutdown = 1
- """The on Shutdown hook
- """
- LoginAsRoot = 2
- """The onLoginAsRoot hook
- """
- Login = 3
- """The onLogin hook
- """
- SessionStarted = 4
- """The onSession started hook
- """
- LogoutAsRoot = 5
- LoginLogoutAsRoot = 6
-
-remoteScriptNames = {
- Type.Boot: "sysstart.sh",
- Type.Login: "logon.sh",
- Type.SessionStarted: "sessionstart.sh",
- Type.Shutdown: "sysstop.sh"
-}
-
-_remoteScriptInUserContext = {
- Type.Boot: False,
- Type.Login: True,
- Type.SessionStarted: True,
- Type.Shutdown: False
-}
-
-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 sorted(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 runHook(hookType):
- """
- Executes hooks.runLocalHook() and hooks.runRemoteHook()
-
- :param hookType: The type of hook to run
- :type hookType: hooks.Type
- """
- runLocalHook(hookType)
- runRemoteHook(hookType)
-
-def getLocalHookScript(hookType):
- """Get the path of a local hookscript
-
- :param hookType: The type of hook script to get the path for
- :type hookType: hooks.Type
- :return: The path
- :rtype: str
- """
- return "{0}/on{1}".format(constants.scriptDir,hookType.name)
-
-def shouldHooksBeExecuted(overrideUsername=None):
- """Check if hooks should be executed
-
- :param overrideUsername: Override the username to check, defaults to None
- :type overrideUsername: str, optional
- :return: True if hooks should be executed, fale otherwise
- :rtype: bool
- """
- # check if linuxmuster-linuxclient7 is setup
- if not setup.isSetup():
- logging.info("==== Linuxmuster-linuxclient7 is not setup, exiting ====")
- return False
-
- # check if the computer is joined
- if not computer.isInAD():
- logging.info("==== This Client is not joined to any domain, exiting ====")
- return False
-
- # Check if the user is an AD user
- if overrideUsername == None:
- overrideUsername = user.username()
-
- if not user.isUserInAD(overrideUsername):
- logging.info("==== {0} is not an AD user, exiting ====".format(user.username()))
- return False
-
- return True
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _prepareEnvironment():
- dictsAndPrefixes = {}
-
- rc, networkConfig = config.network()
- if rc:
- dictsAndPrefixes["Network"] = networkConfig
-
- rc, userConfig = user.readAttributes()
- if rc:
- dictsAndPrefixes["User"] = userConfig
-
- rc, computerConfig = computer.readAttributes()
- if rc:
- dictsAndPrefixes["Computer"] = computerConfig
-
- environment = _dictsToEnv(dictsAndPrefixes)
- _writeEnvironment(environment)
-
-def _getLocalHookDir(hookType):
- return "{0}/on{1}.d".format(constants.etcBaseDir,hookType.name)
-
-def _getRemoteHookScripts(hookType):
- if not hookType in remoteScriptNames:
- return False, None
-
- rc, networkConfig = config.network()
-
- if not rc:
- logging.error("Could not execute server hooks because the network config could not be read")
- return False, None
-
- if _remoteScriptInUserContext[hookType]:
- rc, attributes = user.readAttributes()
- if not rc:
- logging.error("Could not execute server hooks because the user config could not be read")
- return False, None
- else:
- rc, attributes = computer.readAttributes()
- if not rc:
- logging.error("Could not execute server hooks because the computer config could not be read")
- return False, None
-
- try:
- domain = networkConfig["domain"]
- school = attributes["sophomorixSchoolname"]
- scriptName = remoteScriptNames[hookType]
- except:
- logging.error("Could not execute server hooks because the computer/user config is missing attributes")
- return False, None
-
- rc, sysvolPath = shares.getLocalSysvolPath()
- if not rc:
- logging.error("Could not execute server hook {} because the sysvol could not be mounted!\n")
- return False, None
-
- hookScriptPathTemplate = "{0}/{1}/scripts/{2}/{3}/linux/{4}".format(sysvolPath, domain, school, "{}", scriptName)
-
- return True, [hookScriptPathTemplate.format("lmn"), hookScriptPathTemplate.format("custom")]
-
-# parameter must be a dict of {"prefix": dict}
-def _dictsToEnv(dictsAndPrefixes):
- environmentDict = {}
- for prefix in dictsAndPrefixes:
- for key in dictsAndPrefixes[prefix]:
- if type(dictsAndPrefixes[prefix][key]) is list:
- environmentDict[prefix + "_" + key] = "\n".join(dictsAndPrefixes[prefix][key])
- else:
- environmentDict[prefix + "_" + key] = dictsAndPrefixes[prefix][key]
-
- return environmentDict
-
-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 _writeEnvironment(environment):
- for key in environment:
- os.putenv(key, environment[key])
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/imageHelper.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/imageHelper.py
deleted file mode 100644
index e0924b7..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/imageHelper.py
+++ /dev/null
@@ -1,220 +0,0 @@
-import os, subprocess, shutil
-from linuxmusterLinuxclient7 import logging, setup, realm, user, constants, printers, fileHelper
-
-def prepareForImage(unattended=False):
- """Prepare the computer for creating an image
-
- :param unattended: If set to True, all questions will be answered with yes, defaults to False
- :type unattended: bool, optional
- :return: True on success, False otherwise
- :rtype: bool
- """
- logging.info("#### Image preparation ####")
-
- try:
- if not _testDomainJoin(unattended):
- return False
-
- if not _upgradeSystem(unattended):
- return False
-
- if not _clearCaches(unattended):
- return False
-
- if not _clearUserHomes(unattended):
- return False
-
- if not _clearUserCache(unattended):
- return False
-
- if not _clearPrinters(unattended):
- return False
-
- if not _clearLogs(unattended):
- return False
-
- if not _emptyTrash(unattended):
- return False
-
- except KeyboardInterrupt:
- print()
- logging.info("Cancelled.")
- return False
-
- print()
- logging.info("#### Image preparation done ####")
- logging.info("#### You may create an Image now :) ####")
- print()
- return True
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _askStep(step, printPlaceholder=True):
- if printPlaceholder:
- print()
- response = input("Do you want to {}? (y/n): ".format(step))
- result = response in ["y", "Y", "j", "J"]
- if result:
- print()
- return result
-
-def _testDomainJoin(unattended=False):
- if not unattended and not _askStep("test if the domain join works"):
- return True
-
- return setup.status()
-
-def _upgradeSystem(unattended=False):
- if not unattended and not _askStep("update this computer now"):
- return True
-
- # Perform an update
- logging.info("Updating this computer now...")
-
- if subprocess.call(["apt", "update"]) != 0:
- logging.error("apt update failed!")
- return False
-
- if subprocess.call(["apt", "dist-upgrade", "-y"]) != 0:
- logging.error("apt dist-upgrade failed!")
- return False
-
- if subprocess.call(["apt", "autoremove", "-y"]) != 0:
- logging.error("apt autoremove failed!")
- return False
-
- if subprocess.call(["apt", "clean", "-y"]) != 0:
- logging.error("apt clean failed!")
- return False
-
- return True
-
-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 _checkLoggedInUsers():
- result = subprocess.run("who -s | awk '{ print $1 }'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
-
- if result.returncode != 0:
- logging.error("Failed to get logged in users!")
- return False, None
-
- loggedInUsers = list(filter(None, result.stdout.split("\n")))
-
- for loggedInUser in loggedInUsers:
- if user.isUserInAD(loggedInUser):
- logging.error("User {} is still logged in, please log out first! Aborting!".format(loggedInUser))
- return False
-
- return True
-
-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 _unmountAllCifsMounts():
- logging.info("Unmounting all CIFS mounts!")
- if subprocess.call(["umount", "-a", "-t", "cifs", "-l"]) != 0:
- logging.info("Failed!")
- return False
-
- # double check (just to be sure)
- result = subprocess.run("mount", stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
-
- if result.returncode != 0:
- logging.error("Failed to get mounts!")
- return False
-
- if ("cifs" in result.stdout) or ("CIFS" in result.stdout):
- logging.error("There are still shares mounted!")
- logging.info("Use \"mount | grep cifs\" to view them.")
- return False
-
- 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 _clearPrinters(unattended=False):
- print("\nCAUTION! This will delete all printers of {}!".format(constants.templateUser))
- print("This makes sure that local printers do not conflict with remote printers defined by GPOs.")
- if not unattended and not _askStep("remove all local printers of {}".format(constants.templateUser), False):
- return True
-
- if not printers.uninstallAllPrintersOfUser(constants.templateUser):
- return False
-
- return True
-
-def _clearLogs(unattended=False):
- if not unattended and not _askStep("clear the syslog"):
- return True
-
- if not fileHelper.deleteFile("/var/log/syslog"):
- return False
-
- subprocess.call(["sudo", "service", "rsyslog", "restart"])
-
- return True
-
-def _emptyTrash(unattended=False):
- if not unattended and not _askStep("clear the Trash of linuxadmin"):
- return True
-
- if not fileHelper.deleteAllInDirectory("/home/{}/.local/share/Trash".format(constants.templateUser)):
- return False
-
- return True
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/keytab.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/keytab.py
deleted file mode 100644
index ce8556a..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/keytab.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from krb5KeytabUtil import Krb5KeytabUtil
-from linuxmusterLinuxclient7 import computer, config, logging
-
-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
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/ldapHelper.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/ldapHelper.py
deleted file mode 100644
index c9856f4..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/ldapHelper.py
+++ /dev/null
@@ -1,148 +0,0 @@
-import ldap, ldap.sasl, sys, getpass, subprocess
-from linuxmusterLinuxclient7 import logging, constants, config, user, computer
-
-_currentLdapConnection = None
-
-def serverUrl():
- """
- Returns the server URL
-
- :return: The server URL
- :rtype: str
- """
- rc, networkConfig = config.network()
-
- if not rc:
- return False, None
-
- serverHostname = networkConfig["serverHostname"]
- return 'ldap://{0}'.format(serverHostname)
-
-def baseDn():
- """
- Returns the base DN
-
- :return: The baseDN
- :rtype: str
- """
- rc, networkConfig = config.network()
-
- if not rc:
- return None
-
- domain = networkConfig["domain"]
- return "dc=" + domain.replace(".", ",dc=")
-
-def conn():
- """
- Returns the ldap connection object
-
- :return: The ldap connection object
- :rtype: ldap.ldapobject.LDAPObject
- """
- global _currentLdapConnection
-
- if _connect():
- return _currentLdapConnection
-
- return None
-
-def searchOne(filter):
- """Searches the LDAP with a filter and returns the first found object
-
- :param filter: A valid ldap filter
- :type filter: str
- :return: Tuple (success, ldap object as dict)
- :rtype: tuple
- """
- if conn() == None:
- logging.error("Cannot talk to LDAP")
- return False, None
-
- try:
- rawResult = conn().search_s(
- baseDn(),
- ldap.SCOPE_SUBTREE,
- filter
- )
- except Exception as e:
- logging.error("Error executing LDAP search!")
- logging.exception(e)
- return False, None
-
- try:
- result = {}
-
- if len(rawResult) <= 0 or rawResult[0][0] == None:
- logging.debug(f"Search \"{filter}\" did not return any objects")
- return False, None
-
- for k in rawResult[0][1]:
- if rawResult[0][1][k] != None:
- rawAttribute = rawResult[0][1][k]
- try:
- if len(rawAttribute) == 1:
- result[k] = str(rawAttribute[0].decode())
- elif len(rawAttribute) > 0:
- result[k] = []
- for rawItem in rawAttribute:
- result[k].append(str(rawItem.decode()))
- except UnicodeDecodeError:
- continue
-
- return True, result
-
- except Exception as e:
- logging.error("Error while reading ldap search results!")
- logging.exception(e)
- return False, None
-
-def isObjectInGroup(objectDn, groupName):
- """
- Check if a given object is in a given group
-
- :param objectDn: The DN of the object
- :type objectDn: str
- :param groupName: The name of the group
- :type groupName: str
- :return: True if it is a member, False otherwise
- :rtype: bool
- """
- logging.debug("= Testing if object {0} is a member of group {1} =".format(objectDn, groupName))
- rc, groupAdObject = searchOne("(&(member:1.2.840.113556.1.4.1941:={0})(sAMAccountName={1}))".format(objectDn, groupName))
- logging.debug("=> Result: {} =".format(rc))
- return rc
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-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
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/localUserHelper.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/localUserHelper.py
deleted file mode 100644
index f1adcc8..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/localUserHelper.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import subprocess
-from linuxmusterLinuxclient7 import logging
-
-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
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/logging.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/logging.py
deleted file mode 100644
index f73cb68..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/logging.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import logging, os, traceback, re, sys, subprocess
-from enum import Enum
-from linuxmusterLinuxclient7 import user, config
-
-class Level(Enum):
- DEBUG = 0
- INFO = 1
- WARNING = 2
- ERROR = 3
- FATAL = 4
-
-def debug(message):
- """
- Do a debug log.
-
- :param message: The message to log
- :type message: str
- """
- _log(Level.DEBUG, message)
-
-def info(message):
- """
- Do an info log.
-
- :param message: The message to log
- :type message: str
- """
- _log(Level.INFO, message)
-
-def warning(message):
- """
- Do a warning log.
-
- :param message: The message to log
- :type message: str
- """
- _log(Level.WARNING, message)
-
-def error(message):
- """
- Do an error log.
-
- :param message: The message to log
- :type message: str
- """
- _log(Level.ERROR, message)
-
-def fatal(message):
- """
- Do a fatal log. If used in onLogin hook, this will create a dialog containing the message.
-
- :param message: The message to log
- :type message: str
- """
- _log(Level.FATAL, message)
-
-def exception(exception):
- """
- Log an exception
-
- :param exception: The exception to log
- :type exception: Exception
- """
- error("=== An exception occurred ===")
- error(str(exception))
- # Only use for debugging! This will cause ugly error dialogs in X11
- #traceback.print_tb(exception.__traceback__)
- error("=== end exception ===")
-
-def printLogs(compact=False,anonymize=False):
- """
- Print logs of linuxmuster-linuxclient7 from `/var/log/syslog`.
-
- :param compact: If set to True, some stuff like time and date will be removed. Defaults to False
- :type compact: bool, optional
- :param anonymize: If set to True, domain/realm/serverHostname will be replaced by linuxmuster.lan. Defaults to False
- :type anonymize: bool, optional
- """
- print("===========================================")
- print("=== Linuxmuster-linuxclient7 logs begin ===")
-
- (rc, networkConfig) = config.network()
- if rc:
- domain = networkConfig["domain"]
- serverHostname = networkConfig["serverHostname"]
- realm= networkConfig["realm"]
-
- with open("/var/log/syslog") as logfile:
- startPattern = re.compile("^.*linuxmuster-linuxclient7[^>]+======$")
- endPattern = re.compile("^.*linuxmuster-linuxclient7.*======>.*$")
-
- currentlyInsideOfLinuxmusterLinuxclient7Log = False
-
- for line in logfile:
- line = line.replace("\n", "")
- if startPattern.fullmatch(line):
- currentlyInsideOfLinuxmusterLinuxclient7Log = True
- print("\n")
-
- if currentlyInsideOfLinuxmusterLinuxclient7Log:
- if compact:
- # "^([^ ]+[ ]+){4}" matches "Apr 6 14:39:23 somehostname"
- line = re.sub("^([^ ]+[ ]+){4}", "", line)
- if anonymize and rc:
- line = re.sub(serverHostname, "server.linuxmuster.lan", line)
- line = re.sub(domain, "linuxmuster.lan", line)
- line = re.sub(realm, "LINUXMUSTER.LAN", line)
-
- print(line)
-
- if endPattern.fullmatch(line):
- currentlyInsideOfLinuxmusterLinuxclient7Log = False
- print("\n")
-
- print("=== Linuxmuster-linuxclient7 logs end ===")
- print("=========================================")
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _log(level, message):
- #if level == Level.DEBUG:
- # return
- if level == Level.FATAL:
- sys.stderr.write(message)
-
- print("[{0}] {1}".format(level.name, message))
- message = message.replace("'", "")
- subprocess.call(["logger", "-t", "linuxmuster-linuxclient7", f"[{level.name}] {message}"])
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/printers.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/printers.py
deleted file mode 100644
index d4e42a1..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/printers.py
+++ /dev/null
@@ -1,129 +0,0 @@
-import os, subprocess, re
-from linuxmusterLinuxclient7 import logging, user
-
-def installPrinter(networkPath, name=None, username=None):
- """
- Installs a networked printer for a user
-
- :param networkPath: The network path of the printer
- :type networkPath: str
- :param name: The name for the printer, defaults to None
- :type name: str, optional
- :param username: The username of the user whom the is installed printer for. Defaults to the executing user
- :type username: str, optional
- :return: True on success, False otherwise
- :rtype: bool
- """
- if username == None:
- username = user.username()
-
- if user.isRoot():
- return _installPrinter(username, networkPath, name)
- else:
- # This will call installPrinter() again with root privileges
- return _installPrinterWithoutRoot(networkPath, name)
-
- pass
-
-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 translateSambaToIpp(networkPath):
- """
- Translates a samba url, like `\\server\PRINTER-01`, to an ipp url like `ipp://server/printers/PRINTER-01`.
-
- :param networkPath: The samba url
- :type networkPath: str
- :return: An ipp url
- :rtype: str
- """
- networkPath = networkPath.replace("\\", "/")
- # path has to be translated: \\server\EW-FARBLASER -> ipp://server/printers/EW-farblaser
- pattern = re.compile("\\/\\/([^/]+)\\/(.*)")
-
- result = pattern.findall(networkPath)
- if len(result) != 1 or len(result[0]) != 2:
- logging.error("Cannot convert printer network path from samba to ipp, as it is invalid: {}".format(networkPath))
- return False, None
-
- ippNetworkPath = "ipp://{0}/printers/{1}".format(result[0][0], result[0][1])
- return True, ippNetworkPath
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _installPrinter(username, networkPath, name):
- logging.info("Install Printer {0} on {1}".format(name, networkPath))
- installCommand = ["timeout", "10", "lpadmin", "-p", name, "-E", "-v", networkPath, "-m", "everywhere", "-u", f"allow:{username}"]
- logging.debug("* running '{}'".format(" ".join(installCommand)))
- p = subprocess.call(installCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- if p == 0:
- logging.debug("* Success Install Printer!")
- return True
- elif p == 124:
- logging.fatal(f"* Timeout error while installing printer {name} on {networkPath}")
- else:
- logging.fatal(f"* Error installing printer {name} on {networkPath}!")
- return False
-
-def _installPrinterWithoutRoot(networkPath, name):
- return subprocess.call(["sudo", "/usr/share/linuxmuster-linuxclient7/scripts/sudoTools", "install-printer", "--path", networkPath, "--name", name]) == 0
-
-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 _uninstallPrinter(name):
- logging.info("Uninstall Printer {}".format(name))
- uninstallCommand = ["timeout", "10", "lpadmin", "-x", name]
- logging.debug("* running '{}'".format(" ".join(uninstallCommand)))
- p = subprocess.call(uninstallCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- if p == 0:
- logging.debug("* Success Uninstall Printer!")
- return True
- elif p == 124:
- logging.fatal(f"* Timeout error while installing printer {name}")
- else:
- logging.fatal(f"* Error Uninstalling Printer {name}!")
- return False
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/realm.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/realm.py
deleted file mode 100644
index 3847529..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/realm.py
+++ /dev/null
@@ -1,195 +0,0 @@
-import os, sys, subprocess, configparser
-from linuxmusterLinuxclient7 import logging, computer
-
-def join(domain, user):
- """
- Joins the computer to an AD
-
- :param domain: The domain to join
- :type domain: str
- :param user: The admin user used for joining
- :type user: str
- :return: True on success, False otherwise
- :rtype: bool
- """
- # 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 leave(domain):
- """
- Leave a domain
-
- :param domain: The domain to leave
- :type domain: str
- :return: True on success, False otherwise
- :rtype: bool
- """
- leaveCommand = ["realm", "leave", domain]
- return subprocess.call(leaveCommand) == 0
-
-def leaveAll():
- """
- Leaves all joined domains
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- logging.info("Cleaning / leaving all domain joins")
-
- rc, joinedDomains = getJoinedDomains()
- if not rc:
- return False
-
- for joinedDomain in joinedDomains:
- logging.info("* {}".format(joinedDomain))
- if not leave(joinedDomain):
- logging.error("-> Failed! Aborting!")
- return False
-
- logging.info("-> Done!")
- return True
-
-def isJoined():
- """
- Checks if the computer is joined to a domain
-
- :return: True if it is joined to one or more domains, False otherwise
- :rtype: bool
- """
- rc, joinedDomains = getJoinedDomains()
- if not rc:
- return False
- else:
- return len(joinedDomains) > 0
-
-def pullKerberosTicketForComputerAccount():
- """
- Pulls a kerberos ticket using the computer account from `/etc/krb5.keytab`
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- return subprocess.call(["kinit", "-k", computer.krbHostName()]) == 0
-
-def verifyDomainJoin():
- """
- Checks if the domain join actually works.
-
- :return: True if it does, False otherwise
- :rtype: bool
- """
- logging.info("Testing if the domain join actually works")
- if not isJoined():
- logging.error("No domain is joined!")
- return False
-
- logging.info("* Checking if the group \"domain users\" exists")
- if subprocess.call(["getent", "group", "domain users"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) != 0:
- logging.error("The \"domain users\" group does not exists! Users wont be able to log in!")
- logging.error("This is sometimes related to /etc/nsswitch.conf.")
- return False
-
- # Try to get a kerberos ticket for the computer account
- logging.info("* Trying to get a kerberos ticket for the Computer Account")
- if not pullKerberosTicketForComputerAccount():
- logging.error("Could not get a kerberos ticket for the Computer Account!")
- logging.error("Logins of non-cached users WILL NOT WORK!")
- logging.error("Please try to re-join the Domain.")
- return False
-
-
- logging.info("The domain join is working!")
- return True
-
-def getJoinedDomains():
- """
- Returns all joined domains
-
- :return: Tuple (success, list of joined domians)
- :rtype: tuple
- """
- result = subprocess.run("realm list --name-only", stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
-
- if result.returncode != 0:
- logging.error("Failed to read domain joins!")
- return False, None
-
- return True, list(filter(None, result.stdout.split("\n")))
-
-def discoverDomains():
- """
- Searches for avialable domains on the current network
-
- :return: Tuple (success, list of available domains)
- :rtype: tuple
- """
- result = subprocess.run("realm discover --name-only", stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
-
- if result.returncode != 0:
- logging.error("Failed to discover available domains!")
- return False, None
-
- return True, list(filter(None, result.stdout.split("\n")))
-
-def getDomainConfig(domain):
- """
- Looks up all relevant properties of a domain:
- - domain controller IP
- - domain name
-
- :param domain: The domain to check
- :type domain: str
- :return: Tuple (success, dict with domain config)
- :rtype: tuple
- """
- result = subprocess.run("adcli info '{}'".format(domain), stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
-
- if result.returncode != 0:
- logging.error("Failed to get details of domain {}!".format(domain))
- return False, None
-
- rawConfig = _readConfigFromString(result.stdout)
- try:
- rawDomainConfig = rawConfig["domain"]
- except KeyError:
- logging.error("Error when reading domain details")
- return False, None
-
- domainConfig = {}
-
- try:
- domainConfig["domain-controller"] = rawDomainConfig["domain-controller"]
- domainConfig["domain-name"] = rawDomainConfig["domain-name"]
- except KeyError:
- logging.error("Error when reading domain details (2)")
- return False, None
-
- return True, domainConfig
-
-def clearUserCache():
- """
- Clears the local user cache
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- # clean sssd cache
- logging.info("Cleaning sssd cache.")
- subprocess.call(["sssctl", "cache-remove", "--stop", "--start", "--override"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return True
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _readConfigFromString(string):
- configParser = configparser.ConfigParser()
- configParser.read_string(string)
- return configParser
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/setup.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/setup.py
deleted file mode 100644
index 85994fe..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/setup.py
+++ /dev/null
@@ -1,364 +0,0 @@
-import os, re, sys, configparser, subprocess, shutil
-from pathlib import Path
-from linuxmusterLinuxclient7 import logging, constants, hooks, shares, config, user, templates, realm, fileHelper, printers, computer
-
-def setup(domain=None, user=None):
- """
- Sets up the client to be able to act in a linuxmuster environment
-
- :param domain: The domain to join, defaults to the first discovered domain
- :type domain: str, optional
- :param user: The admin user for the join, defaults to global-admin
- :type user: str, optional
- :return: True on success, False otherwise
- :rtype: bool
- """
- logging.info('#### linuxmuster-linuxclient7 setup ####')
-
- if not realm.clearUserCache():
- return False
-
- if not _cleanOldDomainJoins():
- return False
-
- rc, domain = _findDomain(domain)
- if not rc:
- return False
-
- if user == None:
- user = constants.defaultDomainAdminUser
-
- if not _prepareNetworkConfiguration(domain):
- return False
-
- if not _deleteObsoleteFiles():
- return False
-
- if not templates.applyAll():
- return False
-
- if not _preparePam():
- return False
-
- if not _prepareServices():
- return False
-
- # Actually join domain!
- print()
- logging.info(f"#### Joining domain {domain} ####")
-
- if not realm.join(domain, user):
- return False
-
- # copy server ca certificate in place
- # This will also make sure that the domain join actually worked;
- # mounting the sysvol will fail otherwise.
- if not _installCaCertificate(domain, user):
- return False
-
- if not _adjustSssdConfiguration(domain):
- return False
-
- # run a final test
- if not realm.verifyDomainJoin():
- return False
-
- print("\n\n")
-
- logging.info(f"#### SUCCESSFULLY joined domain {domain} ####")
-
- return True
-
-def status():
- """
- Checks the status of the client
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- logging.info('#### linuxmuster-linuxclient7 status ####')
-
- if not isSetup():
- logging.info("Not setup!")
- return False
-
- logging.info("Linuxmuster-linuxclient7 is setup!")
- logging.info("Testing if domain is joined...")
-
- logging.info("Checking joined domains")
- rc, joinedDomains = realm.getJoinedDomains()
- if not rc:
- return False
-
- print()
- logging.info("Joined domains:")
- for joinedDomain in joinedDomains:
- logging.info(f"* {joinedDomain}")
- print()
-
- if len(joinedDomains) > 0 and not realm.verifyDomainJoin():
- print()
- # Give a little explination to our users :)
- print("\n===============================================================================================")
- print("This Computer is joined to a domain, but it was not possible to authenticate")
- print("to the domain controller. There is an error with your domain join! The login WILL NOT WORK!")
- print("Please try to re-join the domain using 'linuxmuster-linuxclient7 setup' and create a new image.")
- print("===============================================================================================\n")
- return False
- elif len(joinedDomains) <= 0:
- print()
- logging.info('#### This client is not joined to any domain. ####')
- print("#### To join a domain, run \"linuxmuster-linuxclient7 setup\" ####")
-
- print()
-
- logging.info('#### linuxmuster-linuxclient7 is fully setup and working! ####')
-
- 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 clean():
- """Removes all sensitive files like keys and leaves all domain joins.
- """
- logging.info("#### linuxmuster-linuxclient7 clean ####")
-
- realm.clearUserCache()
- _cleanOldDomainJoins()
-
- # clean /etc/pam.d/common-session
- logging.info("Cleaning /etc/pam.d/common-session to prevent logon brick")
- fileHelper.removeLinesInFileContainingString("/etc/pam.d/common-session", ["pam_mkhomedir.so", "pam_exec.so", "pam_mount.so", "linuxmuster.net", "linuxmuster-linuxclient7", "linuxmuster-client-adsso"])
-
- logging.info('#### linuxmuster-linuxclient7 clean SUCCESSFULL ####')
-
-def isSetup():
- """
- Checks if the client is setup.
-
- :return: True if setup, False otherwise
- :rtype: bool
- """
- return os.path.isfile(constants.networkConfigFilePath)
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-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 _findDomain(domain=None):
- logging.info("Trying to discover available domains...")
- rc, availableDomains = realm.discoverDomains()
- if not rc or len(availableDomains) < 1:
- logging.error("Could not discover any domain!")
- return False, None
-
- if domain == None:
- domain = availableDomains[0]
- logging.info(f"Using first discovered domain {domain}")
- elif domain in availableDomains:
- logging.info(f"Using domain {domain}")
- else:
- print("\n")
- logging.error(f"Could not find domain {domain}!")
- return False, None
-
- return True, domain
-
-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 _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 _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 _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 _adjustSssdConfiguration(domain):
- logging.info("Adjusting sssd.conf")
-
- sssdConfigFilePath = '/etc/sssd/sssd.conf'
- sssdConfig = configparser.ConfigParser(interpolation=None)
-
- sssdConfig.read(sssdConfigFilePath)
- # accept usernames without domain
- sssdConfig[f"domain/{domain}"]["use_fully_qualified_names"] = "False"
-
- # override homedir
- sssdConfig[f"domain/{domain}"]["override_homedir"] = "/home/%u"
-
- # Don't validate KVNO! Otherwise the Login will fail when the KVNO stored
- # in /etc/krb5.keytab does not match the one in the AD (msDS-KeyVersionNumber)
- sssdConfig[f"domain/{domain}"]["krb5_validate"] = "False"
-
- sssdConfig[f"domain/{domain}"]["ad_gpo_access_control"] = "permissive"
- sssdConfig[f"domain/{domain}"]["ad_gpo_ignore_unreadable"] = "True"
-
- # Don't renew the machine password, as this will break the domain join
- # See: https://github.com/linuxmuster/linuxmuster-linuxclient7/issues/27
- sssdConfig[f"domain/{domain}"]["ad_maximum_machine_account_password_age"] = "0"
-
- # Make sure usernames are not case sensitive
- sssdConfig[f"domain/{domain}"]["case_sensitive"] = "False"
-
- try:
- logging.info("Writing new Configuration")
- with open(sssdConfigFilePath, 'w') as sssdConfigFile:
- sssdConfig.write(sssdConfigFile)
-
- except Exception as e:
- logging.error("Failed!")
- logging.exception(e)
- return False
-
- logging.info("Restarting sssd")
- if subprocess.call(["service", "sssd", "restart"]) != 0:
- logging.error("Failed!")
- return False
-
- return True
-
-def _deleteObsoleteFiles():
-
- # files
- logging.info("Deleting obsolete files")
-
- for obsoleteFile in constants.obsoleteFiles:
- logging.info(f"* {obsoleteFile}")
- fileHelper.deleteFile(obsoleteFile)
-
- # directories
- logging.info("Deleting obsolete directories")
-
- for obsoleteDirectory in constants.obsoleteDirectories:
- logging.info(f"* {obsoleteDirectory}")
- fileHelper.deleteDirectory(obsoleteDirectory)
-
- return True
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/shares.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/shares.py
deleted file mode 100644
index 64a879f..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/shares.py
+++ /dev/null
@@ -1,256 +0,0 @@
-import os, pwd, sys, shutil, re, subprocess, shutil
-from linuxmusterLinuxclient7 import logging, constants, user, config, computer
-from pathlib import Path
-
-def mountShare(networkPath, shareName = None, hiddenShare = False, username = None):
- """
- Mount a given path of a samba share
-
- :param networkPath: Network path of the share
- :type networkPath: str
- :param shareName: The name of the share (name of the folder the share is being mounted to)
- :type shareName: str
- :param hiddenShare: If the share sould be visible in Nautilus
- :type hiddenShare: bool
- :param username: The user in whoms context the share should be mounted
- :type username: str
- :return: Tuple: (success, mountpoint)
- :rtype: tuple
- """
- networkPath = networkPath.replace("\\", "/")
- username = _getDefaultUsername(username)
- shareName = _getDefaultShareName(networkPath, shareName)
-
- if user.isRoot():
- return _mountShare(username, networkPath, shareName, hiddenShare, True)
- else:
- mountpoint = _getShareMountpoint(networkPath, username, hiddenShare, shareName)
- # This will call _mountShare() directly with root privileges
- return _mountShareWithoutRoot(networkPath, shareName, hiddenShare), mountpoint
-
-def getMountpointOfRemotePath(remoteFilePath, hiddenShare = False, username = None, autoMount = True):
- """
- Get the local path of a remote samba share path.
- This function automatically checks if the shares is already mounted.
- It optionally automatically mounts the top path of the remote share:
- If the remote path is `//server/sysvol/linuxmuster.lan/Policies` it mounts `//server/sysvol`
-
- :param remoteFilePath: Remote path
- :type remoteFilePath: str
- :param hiddenShare: If the share sould be visible in Nautilus
- :type hiddenShare: bool
- :param username: The user in whoms context the share should be mounted
- :type username: str
- :parama autoMount: If the share should be mouted automatically if it is not already mounted
- :type autoMount: bool
- :return: Tuple: (success, mountpoint)
- :rtype: tuple
- """
- remoteFilePath = remoteFilePath.replace("\\", "/")
- username = _getDefaultUsername(username)
-
- # get basepath fo remote file path
- # this turns //server/sysvol/linuxmuster.lan/Policies into //server/sysvol
- pattern = re.compile("(^\\/\\/[^\\/]+\\/[^\\/]+)")
- match = pattern.search(remoteFilePath)
-
- if match is None:
- logging.error("Cannot get local file path of {} beacuse it is not a valid path!".format(remoteFilePath))
- return False, None
-
- shareBasepath = match.group(0)
-
- if autoMount:
- rc, mointpoint = mountShare(shareBasepath, hiddenShare=hiddenShare, username=username)
- if not rc:
- return False, None
-
- # calculate local path
- shareMountpoint = _getShareMountpoint(shareBasepath, username, hiddenShare, shareName=None)
- localFilePath = remoteFilePath.replace(shareBasepath, shareMountpoint)
-
- return True, localFilePath
-
-def unmountAllSharesOfUser(username):
- """
- Unmount all shares of a given user and safely delete the mountpoints and the parent directory.
-
- :param username: The username of the user
- :type username: str
- :return: True or False
- :rtype: bool
- """
- 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 False
- else:
- # Delete the directory
- logging.info("Deleting {0}...".format(shareMountBasedir))
- try:
- os.rmdir(shareMountBasedir)
- except Exception as e:
- logging.error("FAILED!")
- logging.exception(e)
- return False
-
- logging.info("===> Finished unmounting all shares of user {0} ===".format(username))
- return True
-
-def getLocalSysvolPath():
- """
- Get the local mountpoint of the sysvol
-
- :return: Full path of the mountpoint
- :rtype: str
- """
- rc, networkConfig = config.network()
- if not rc:
- return False, None
-
- networkPath = f"//{networkConfig['serverHostname']}/sysvol"
- return getMountpointOfRemotePath(networkPath, True)
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-# useCruidOfExecutingUser:
-# defines if the ticket cache of the user executing the mount command should be used.
-# If set to False, the cache of the user with the given username will be used.
-# This parameter influences the `cruid` mount option.
-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 = [shutil.which("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
-
-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:
- os.rmdir(mountpoint)
- except Exception as e:
- logging.error("* FAILED!")
- logging.exception(e)
-
-def _getDefaultUsername(username=None):
- if username == None:
- if user.isRoot():
- username = computer.hostname().upper() + "$"
- else:
- username = user.username()
- return username
-
-def _getDefaultShareName(networkPath, shareName=None):
- if shareName is None:
- shareName = networkPath.split("/")[-1]
- return shareName
-
-def _mountShareWithoutRoot(networkPath, name, hidden):
- mountCommand = ["sudo", "/usr/share/linuxmuster-linuxclient7/scripts/sudoTools", "mount-share", "--path", networkPath, "--name", name]
-
- if hidden:
- mountCommand.append("--hidden")
-
- return subprocess.call(mountCommand) == 0
-
-def _getShareMountpoint(networkPath, username, hidden, shareName = None):
- logging.debug(f"Calculating mountpoint of {networkPath}")
-
- shareName = _getDefaultShareName(networkPath, shareName)
-
- if hidden:
- return "{0}/{1}".format(constants.hiddenShareMountBasepath.format(username), shareName)
- else:
- return "{0}/{1}".format(constants.shareMountBasepath.format(username), shareName)
-
-def _directoryIsMountpoint(dir):
- return subprocess.call(["mountpoint", "-q", dir]) == 0
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/templates.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/templates.py
deleted file mode 100644
index b634fd2..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/templates.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import os, codecs, sys, shutil, subprocess
-from pathlib import Path
-from linuxmusterLinuxclient7 import logging, constants, hooks, config
-
-
-def applyAll():
- """
- Applies all templates from `/usr/share/linuxmuster-linuxclient7/templates`
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- logging.info('Applying all configuration templates:')
-
- templateDir = constants.configFileTemplateDir
- for templateFile in os.listdir(templateDir):
- templatePath = templateDir + '/' + templateFile
- logging.info('* ' + templateFile + ' ...')
- if not _apply(templatePath):
- logging.error("Aborting!")
- return False
-
- # reload sctemctl
- logging.info('Reloading systemctl ... ')
- if not subprocess.call(["systemctl", "daemon-reload"]) == 0:
- logging.error("Failed!")
- return False
-
- return True
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-
-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 _resolveVariables(fileData):
- # replace placeholders with values
- rc, networkConfig = config.network()
-
- if not rc:
- return False, None
-
- # network
- fileData = fileData.replace('@@serverHostname@@', networkConfig["serverHostname"])
- fileData = fileData.replace('@@domain@@', networkConfig["domain"])
- fileData = fileData.replace('@@realm@@', networkConfig["realm"])
-
- # constants
- fileData = fileData.replace('@@userTemplateDir@@', constants.userTemplateDir)
- fileData = fileData.replace('@@hiddenShareMountBasepath@@', constants.hiddenShareMountBasepath.format("%(USER)"))
-
- # hooks
- fileData = fileData.replace('@@hookScriptBoot@@', hooks.getLocalHookScript(hooks.Type.Boot))
- fileData = fileData.replace('@@hookScriptShutdown@@', hooks.getLocalHookScript(hooks.Type.Shutdown))
- fileData = fileData.replace('@@hookScriptLoginLogoutAsRoot@@', hooks.getLocalHookScript(hooks.Type.LoginLogoutAsRoot))
- fileData = fileData.replace('@@hookScriptSessionStarted@@', hooks.getLocalHookScript(hooks.Type.SessionStarted))
-
- return fileData
-
-# read textfile in variable
-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
-
-# remove lines beginning with #
-def _stripComment(fileData):
- filedata_stripped = ''
- for line in fileData.split('\n'):
- if line[:1] == '#':
- continue
- else:
- if filedata_stripped == '':
- filedata_stripped = line
- else:
- filedata_stripped = filedata_stripped + '\n' + line
- return filedata_stripped
\ No newline at end of file
diff --git a/roles/lmn_printer/files/linuxmusterLinuxclient7/user.py b/roles/lmn_printer/files/linuxmusterLinuxclient7/user.py
deleted file mode 100644
index aa838f0..0000000
--- a/roles/lmn_printer/files/linuxmusterLinuxclient7/user.py
+++ /dev/null
@@ -1,158 +0,0 @@
-import ldap, ldap.sasl, sys, getpass, subprocess, pwd, os, os.path
-from pathlib import Path
-from linuxmusterLinuxclient7 import logging, constants, config, user, ldapHelper, shares, fileHelper, computer, localUserHelper
-
-def readAttributes():
- """
- Reads all attributes of the current user from ldap
-
- :return: Tuple (success, dict of user attributes)
- :rtype: tuple
- """
- if not user.isInAD():
- return False, None
-
- return ldapHelper.searchOne(f"(sAMAccountName={user.username()})")
-
-def school():
- """
- Gets the school of the current user from the AD
-
- :return: The short name of the school
- :rtype: str
- """
- rc, userdata = readAttributes()
-
- if not rc:
- return False, None
-
- return True, userdata["sophomorixSchoolname"]
-
-def username():
- """
- Returns the user of the current user
-
- :return: The username of the current user
- :rtype: str
- """
- return getpass.getuser().lower()
-
-def isUserInAD(user):
- """
- Checks if a given user is an AD user.
-
- :param user: The username of the user to check
- :type user: str
- :return: True if the user is in the AD, False if it is a local user
- :rtype: bool
- """
- if not computer.isInAD():
- return False
-
- rc, groups = localUserHelper.getGroupsOfLocalUser(user)
- if not rc:
- return False
-
- return "domain users" in groups
-
-def isInAD():
- """Checks if the current user is an AD user.
-
- :return: True if the user is in the AD, False if it is a local user
- :rtype: bool
- """
- return isUserInAD(username())
-
-def isRoot():
- """
- Checks if the current user is root
-
- :return: True if the current user is root, False otherwise
- :rtype: bool
- """
- return os.geteuid() == 0
-
-def isInGroup(groupName):
- """
- Checks if the current user is part of a given group
-
- :param groupName: The name of the group
- :type groupName: str
- :return: True if the user is part of the group, False otherwise
- :rtype: bool
- """
- rc, groups = localUserHelper.getGroupsOfLocalUser(username())
- if not rc:
- return False
-
- return groupName in groups
-
-def cleanTemplateUserGtkBookmarks():
- """Remove gtk bookmarks of the template user from the current users `~/.config/gtk-3.0/bookmarks` file.
- """
- 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 getHomeShareMountpoint():
- """
- Returns the mountpoint of the users serverhome.
-
- :return: The monutpoint of the users serverhome
- :rtype: str
- """
- rc, homeShareName = _getHomeShareName()
-
- if rc:
- basePath = constants.shareMountBasepath.format(username())
- return True, f"{basePath}/{homeShareName}"
-
- return False, None
-
-def mountHomeShare():
- """
- Mounts the serverhome of the current user
-
- :return: True on success, False otherwise
- :rtype: bool
- """
- rc1, userAttributes = readAttributes()
- rc2, shareName = _getHomeShareName(userAttributes)
- if rc1 and rc2:
- try:
- homeShareServerPath = userAttributes["homeDirectory"]
- res = shares.mountShare(homeShareServerPath, shareName=shareName, hiddenShare=False, username=username())
- return res
-
- except Exception as e:
- logging.error("Could not mount home dir of user")
- logging.exception(e)
-
- return False, None
-
-# --------------------
-# - Helper functions -
-# --------------------
-
-def _getHomeShareName(userAttributes=None):
- if userAttributes is None:
- rc, userAttributes = readAttributes()
- else:
- rc = True
-
- if rc:
- try:
- usernameString = username()
- shareName = f"{usernameString} ({userAttributes['homeDrive']})"
- return True, shareName
-
- except Exception as e:
- logging.error("Could not mount home dir of user")
- logging.exception(e)
-
- return False, None
\ No newline at end of file
diff --git a/roles/lmn_printer/files/lmn-printer.sh b/roles/lmn_printer/files/lmn-printer.sh
index e103068..b609412 100644
--- a/roles/lmn_printer/files/lmn-printer.sh
+++ b/roles/lmn_printer/files/lmn-printer.sh
@@ -1 +1 @@
-[[ "${UID}" -gt 10000 ]] && /usr/local/bin/onLogin
+[[ "${UID}" -gt 10000 ]] && sudo /usr/local/bin/install-printers.sh
diff --git a/roles/lmn_printer/files/onLogin b/roles/lmn_printer/files/onLogin
deleted file mode 100644
index 565b80f..0000000
--- a/roles/lmn_printer/files/onLogin
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/python3
-
-# DO NOT MODIFY THIS SCRIPT!
-# For custom scripts use the hookdir /etc/linuxmuster-linuxclient7/onLogin.d
-
-# This schript is called in user context when a user logs in
-try:
- import os, sys
- #import traceback
- from linuxmusterLinuxclient7 import logging, hooks, shares, user, constants, gpo, computer, environment
-
- logging.info("====== onLogin started ======")
-
- # mount sysvol
- rc, sysvolPath = shares.getLocalSysvolPath()
- if rc:
- environment.export(f"SYSVOL={sysvolPath}")
-
- # process GPOs
- gpo.processAllPolicies()
-
- logging.info("======> onLogin end ======")
-
-except Exception as e:
- try:
- #traceback.print_exc()
- logging.exception(e)
- except:
- print("A fatal error occured!")
-
-# We need to catch all exceptions and return 0 in any case!
-# If we do not return 0, login will FAIL FOR EVERYONE!
-sys.exit(0)
diff --git a/roles/lmn_printer/files/onLogout b/roles/lmn_printer/files/onLogout
deleted file mode 100644
index 9fe8a00..0000000
--- a/roles/lmn_printer/files/onLogout
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/python3
-
-# DO NOT MODIFY THIS SCRIPT!
-# For custom scripts use the hookdirs
-# /etc/linuxmuster-linuxclient7/onLoginAsRoot.d
-# and /etc/linuxmuster-linuxclient7/onLogoutAsRoot.d
-
-# This schript is called in root context when a user logs in or out
-try:
- import os, sys
- from linuxmusterLinuxclient7 import logging, hooks, constants, user, shares, printers, computer, realm
-
- pamType = os.getenv("PAM_TYPE")
- pamUser = os.getenv("PAM_USER")
- #PAM_RHOST, PAM_RUSER, PAM_SERVICE, PAM_TTY, PAM_USER and PAM_TYPE
- logging.info("====== onLoginLogoutAsRoot started with PAM_TYPE={0} PAM_RHOST={1} PAM_RUSER={2} PAM_SERVICE={3} PAM_TTY={4} PAM_USER={5} ======".format(pamType, os.getenv("PAM_RHOST"), os.getenv("PAM_RUSER"), os.getenv("PAM_SERVICE"), os.getenv("PAM_TTY"), pamUser))
-
- # check if whe should execute
- if not hooks.shouldHooksBeExecuted(pamUser):
- logging.info("======> onLoginLogoutAsRoot end ====")
- sys.exit(0)
-
- elif pamType == "close_session":
- # cleanup
- printers.uninstallAllPrintersOfUser(pamUser)
-
- logging.info("======> onLoginLogoutAsRoot end ======")
-
-except Exception as e:
- try:
- logging.exception(e)
- except:
- print("A fatal error occured!")
-
-# We need to catch all exceptions and return 0 in any case!
-# If we do not return 0, login will FAIL FOR EVERYONE!
-sys.exit(0)
-
diff --git a/roles/lmn_printer/files/rmlpr.service b/roles/lmn_printer/files/rmlpr.service
deleted file mode 100644
index e89b994..0000000
--- a/roles/lmn_printer/files/rmlpr.service
+++ /dev/null
@@ -1,6 +0,0 @@
-[Unit]
-Description=Remove all printers
-
-[Service]
-Type=simple
-ExecStart=sh -c 'lpstat -p && for printer in $(lpstat -p | cut -f 2 -d " "); do lpadmin -x $printer; done || echo no printer found.'
diff --git a/roles/lmn_printer/files/rmlpr.timer b/roles/lmn_printer/files/rmlpr.timer
deleted file mode 100644
index a8097d2..0000000
--- a/roles/lmn_printer/files/rmlpr.timer
+++ /dev/null
@@ -1,8 +0,0 @@
-[Unit]
-Description=Remove all printers on boot
-
-[Timer]
-OnBootSec=10
-
-[Install]
-WantedBy=timers.target
diff --git a/roles/lmn_printer/files/scripts/sudoTools b/roles/lmn_printer/files/scripts/sudoTools
deleted file mode 100755
index 7459135..0000000
--- a/roles/lmn_printer/files/scripts/sudoTools
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/python3
-#
-# Script to do some things that require root permissions as a normal user
-# Currently used for:
-# - mounting shares
-# - installing printers
-#
-
-import os, sys, argparse
-from linuxmusterLinuxclient7 import shares, printers, constants
-
-
-parser = argparse.ArgumentParser(description="Script to do some things that require root permissions as a normal user")
-
-subparsers = parser.add_subparsers(title='Tasks', metavar="", help="The task to execute", dest="task", required=True)
-
-subparserCache = subparsers.add_parser('install-printer', help='install a printer')
-requiredGroupCache = subparserCache.add_argument_group('required arguments')
-requiredGroupCache.add_argument("--path", help="The network path of the printer", required=True)
-requiredGroupCache.add_argument("--name", help="The name of the printer", required=True)
-
-subparserCache = subparsers.add_parser('mount-share', help='mount a network share')
-subparserCache.add_argument("--hidden", help="Hide this share", action='store_true')
-requiredGroupCache = subparserCache.add_argument_group('required arguments')
-requiredGroupCache.add_argument("--path", help="The network path of the share", required=True)
-requiredGroupCache.add_argument("--name", help="The name of the share", required=True)
-
-args = parser.parse_args()
-
-task = args.task
-
-if not os.geteuid() == 0:
- print("This script has to be run using sudo!")
- exit(1)
-
-username = os.getenv("SUDO_USER")
-
-if task == "install-printer":
- if printers.installPrinter(args.path, name=args.name, username=username):
- sys.exit(0)
- else:
- sys.exit(1)
- pass
-elif task == "mount-share":
- if shares._mountShare(username, args.path, args.name, args.hidden, False):
- sys.exit(0)
- else:
- sys.exit(1)
-
-exit(0)
\ No newline at end of file
diff --git a/roles/lmn_printer/tasks/main.yml b/roles/lmn_printer/tasks/main.yml
index 23ef2c2..7023c0d 100644
--- a/roles/lmn_printer/tasks/main.yml
+++ b/roles/lmn_printer/tasks/main.yml
@@ -3,7 +3,6 @@
apt:
name:
- cups
- - python3-ldap
state: latest
- name: Disable cups printer browsing
@@ -19,66 +18,20 @@
state: stopped
enabled: no
-- name: Configure pam_mount sysvol mount
- blockinfile:
- dest: /etc/security/pam_mount.conf.xml
- marker: ""
- block: |
- rootansibleDebian-gdmsddm{{ localuser }}
-
- insertafter: ""
-
-- name: Create /etc/linuxmuster-linuxclient7 Directory
- file:
- path: /etc/linuxmuster-linuxclient7
- state: directory
- mode: 0755
-
-- name: install linuxmuster-linuxclient network.conf
+- name: install install-printers.sh
template:
- src: network.conf.j2
- dest: /etc/linuxmuster-linuxclient7/network.conf
- mode: 0644
-
-- name: install linuxmuster-linuxclient python libs
- copy :
- src: linuxmusterLinuxclient7
- dest: /usr/lib/python3/dist-packages
-
-- name: Create /usr/share/linuxmuster-linuxclient7/scripts Directory
- file:
- path: /usr/share/linuxmuster-linuxclient7/scripts
- state: directory
+ src: install-printers.sh.j2
+ dest: /usr/local/bin/install-printers.sh
mode: 0755
-- name: install linuxmuster-scripts
+- name: install lmn-install-printers sudoers
copy:
- src: scripts/sudoTools
- dest: /usr/share/linuxmuster-linuxclient7/scripts/
- mode: 0755
-
-- name: install lmn-sudotools
- copy:
- src: 90-lmn-sudotools
+ src: 90-lmn-install-printers
dest: /etc/sudoers.d/
mode: 0660
owner: root
group: root
-- name: install onLogin script
- copy :
- src: onLogin
- dest: /usr/local/bin/
- mode: 0755
- owner: root
- group: root
-
- name: install lmn-printer.sh in /etc/profile.d/
copy:
src: lmn-printer.sh
@@ -86,18 +39,3 @@
mode: 0644
owner: root
group: root
-
-- name: Provide service and timer to remove all printers on boot
- copy:
- src: "{{ item }}"
- dest: "/etc/systemd/system/{{ item }}"
- mode: 0644
- with_items:
- - rmlpr.service
- - rmlpr.timer
-
-- name: enable rmlpr.timer
- systemd:
- name: rmlpr.timer
- enabled: true
-
diff --git a/roles/lmn_printer/templates/install-printers.sh.j2 b/roles/lmn_printer/templates/install-printers.sh.j2
new file mode 100644
index 0000000..9c953fd
--- /dev/null
+++ b/roles/lmn_printer/templates/install-printers.sh.j2
@@ -0,0 +1,36 @@
+#!/usr/bin/bash
+
+set -eu
+
+printservers="{{ printservers | join(' ') }}"
+hostname=$(hostname)
+hostgroup=$(id -Gn "${hostname^^}$")
+usergroup=$(id -Gn "${SUDO_USER}")
+installedprinters=$(lpstat -p | cut -f 2 -d" " | sed -z 's/\n/ /g' )
+
+echo "Hostgroups: ${hostgroup}"
+echo "Usergroups: ${usergroup}"
+echo "Installed Printers: ${installedprinters}"
+echo
+
+for printer in $installedprinters; do
+ if ! $(echo "${hostgroup}" | grep -w -q "${printer}") && ! $(echo "${usergroup}" | grep -w -q "${printer}") ; then
+ lpadmin -x "${printer}"
+ fi
+done
+
+for printserver in $printservers; do
+ echo "checking Server: $printserver"
+ printers=$(lpstat -h "${printserver}" -U "${SUDO_USER}" -v | cut -f 3 -d" " | sed 's/:$//g' | sed -z 's/\n/ /g' )
+ echo "Available Printers: $printers"
+ for printer in $printers; do
+ if $(echo "${hostgroup}" | grep -w -q "${printer}") || $(echo "${usergroup}" | grep -w -q "${printer}") ; then
+ if ! $(echo "${installedprinters}" | grep -w -q "${printer}"); then
+ echo "Adding ${printer}"
+ timeout 10 lpadmin -p "${printer}" -E -v "ipp://${printserver}/printers/${printer}" -m everywhere || echo "Printer ${printer} could not be added"
+ installedprinters+=" ${printer}"
+ fi
+ fi
+ done
+done
+
diff --git a/roles/lmn_printer/templates/network.conf.j2 b/roles/lmn_printer/templates/network.conf.j2
deleted file mode 100644
index 5cd39ce..0000000
--- a/roles/lmn_printer/templates/network.conf.j2
+++ /dev/null
@@ -1,5 +0,0 @@
-[network]
-serverHostname = server
-domain = pn.steinbeis.schule
-realm = PN.STEINBEIS.SCHULE
-version = 1