printing role without lmn-linuxmusterclient7
This commit is contained in:
		
							parent
							
								
									5f088511c4
								
							
						
					
					
						commit
						0e4073336a
					
				
					 32 changed files with 80 additions and 3246 deletions
				
			
		|  | @ -52,6 +52,7 @@ | ||||||
|     ntp_serv: "{{ vault_ntp_serv }}"  ## ntp.example.org |     ntp_serv: "{{ vault_ntp_serv }}"  ## ntp.example.org | ||||||
|     proxy: "{{ vault_proxy }}"        ## http://firewall.example.org:3128 |     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 |     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: |     ## PAM mount nextcloud, remove or leave empty to skip: | ||||||
|     web_dav: "{{ vault_web_dav }}"    ## https://nc.example.org/remote.php/dav/files/%(USER) |     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::Update-Package-Lists "1"; | ||||||
|           APT::Periodic::Unattended-Upgrade "1"; |           APT::Periodic::Unattended-Upgrade "1"; | ||||||
| 
 | 
 | ||||||
|  |     - name: Remove pam_mount sysvol mount | ||||||
|  |       blockinfile: | ||||||
|  |         dest: /etc/security/pam_mount.conf.xml | ||||||
|  |         marker: "<!-- {mark} ANSIBLE MANAGED BLOCK (SysVol) -->" | ||||||
|  |         block: | | ||||||
|  |           <volume | ||||||
|  |             fstype="cifs" | ||||||
|  |             server="{{ smb_server }}" | ||||||
|  |             path="sysvol/" | ||||||
|  |             mountpoint="/srv/samba/%(USER)/sysvol" | ||||||
|  |             options="sec=krb5i,cruid=%(USERUID),user=%(USER),gid=1010,file_mode=0770,dir_mode=0770,mfsymlinks" | ||||||
|  |             ><not><or><user>root</user><user>ansible</user><user>Debian-gdm</user><user>sddm</user><user>{{ localuser }}</user></or></not> | ||||||
|  |           </volume> | ||||||
|  |         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: | ## bookworm fixes/hacks: | ||||||
|     - name: Work around sddm hang on shutdown |     - name: Work around sddm hang on shutdown | ||||||
|       ansible.builtin.lineinfile: |       ansible.builtin.lineinfile: | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								roles/lmn_printer/files/90-lmn-install-printers
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								roles/lmn_printer/files/90-lmn-install-printers
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  | 
 | ||||||
|  | @ -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 |  | ||||||
| 
 |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| # |  | ||||||
| # linuxmuster-linuxclient7 is a library for use with Linuxmuster.net |  | ||||||
| # |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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" |  | ||||||
| ] |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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://<link>;<status>][LDAP://<link>;<status>][...] |  | ||||||
|     # The ragex matches <link> and <status> 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:<school name> |  | ||||||
|     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! ==") |  | ||||||
|  | @ -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://<link>;<status>][LDAP://<link>;<status>][...] |  | ||||||
|     # The ragex matches <link> and <status> 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:<school name> |  | ||||||
|     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! ==") |  | ||||||
|  | @ -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]) |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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}"]) |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -1 +1 @@ | ||||||
| [[ "${UID}" -gt 10000 ]] && /usr/local/bin/onLogin | [[ "${UID}" -gt 10000 ]] && sudo /usr/local/bin/install-printers.sh | ||||||
|  |  | ||||||
|  | @ -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) |  | ||||||
|  | @ -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) |  | ||||||
| 
 |  | ||||||
|  | @ -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.' |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| [Unit] |  | ||||||
| Description=Remove all printers on boot |  | ||||||
| 
 |  | ||||||
| [Timer] |  | ||||||
| OnBootSec=10 |  | ||||||
| 
 |  | ||||||
| [Install] |  | ||||||
| WantedBy=timers.target |  | ||||||
|  | @ -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="<task>", 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) |  | ||||||
|  | @ -3,7 +3,6 @@ | ||||||
|   apt: |   apt: | ||||||
|     name: |     name: | ||||||
|       - cups |       - cups | ||||||
|       - python3-ldap |  | ||||||
|     state: latest |     state: latest | ||||||
| 
 | 
 | ||||||
| - name: Disable cups printer browsing | - name: Disable cups printer browsing | ||||||
|  | @ -19,66 +18,20 @@ | ||||||
|     state: stopped |     state: stopped | ||||||
|     enabled: no |     enabled: no | ||||||
| 
 | 
 | ||||||
| - name: Configure pam_mount sysvol mount | - name: install install-printers.sh | ||||||
|   blockinfile: |  | ||||||
|     dest: /etc/security/pam_mount.conf.xml |  | ||||||
|     marker: "<!-- {mark} ANSIBLE MANAGED BLOCK (SysVol) -->" |  | ||||||
|     block: | |  | ||||||
|       <volume |  | ||||||
|         fstype="cifs" |  | ||||||
|         server="{{ smb_server }}" |  | ||||||
|         path="sysvol/" |  | ||||||
|         mountpoint="/srv/samba/%(USER)/sysvol" |  | ||||||
|         options="sec=krb5i,cruid=%(USERUID),user=%(USER),gid=1010,file_mode=0770,dir_mode=0770,mfsymlinks" |  | ||||||
|         ><not><or><user>root</user><user>ansible</user><user>Debian-gdm</user><user>sddm</user><user>{{ localuser }}</user></or></not> |  | ||||||
|       </volume> |  | ||||||
|     insertafter: "<!-- Volume definitions -->" |  | ||||||
| 
 |  | ||||||
| - name: Create /etc/linuxmuster-linuxclient7 Directory |  | ||||||
|   file: |  | ||||||
|     path: /etc/linuxmuster-linuxclient7 |  | ||||||
|     state: directory |  | ||||||
|     mode: 0755 |  | ||||||
| 
 |  | ||||||
| - name: install linuxmuster-linuxclient network.conf |  | ||||||
|   template: |   template: | ||||||
|     src: network.conf.j2 |     src: install-printers.sh.j2 | ||||||
|     dest: /etc/linuxmuster-linuxclient7/network.conf |     dest: /usr/local/bin/install-printers.sh | ||||||
|     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 |  | ||||||
|     mode: 0755 |     mode: 0755 | ||||||
| 
 | 
 | ||||||
| - name: install linuxmuster-scripts | - name: install lmn-install-printers sudoers | ||||||
|   copy: |   copy: | ||||||
|     src: scripts/sudoTools |     src: 90-lmn-install-printers | ||||||
|     dest: /usr/share/linuxmuster-linuxclient7/scripts/ |  | ||||||
|     mode: 0755 |  | ||||||
| 
 |  | ||||||
| - name: install lmn-sudotools |  | ||||||
|   copy: |  | ||||||
|     src: 90-lmn-sudotools |  | ||||||
|     dest: /etc/sudoers.d/ |     dest: /etc/sudoers.d/ | ||||||
|     mode: 0660 |     mode: 0660 | ||||||
|     owner: root |     owner: root | ||||||
|     group: 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/ | - name: install lmn-printer.sh in /etc/profile.d/ | ||||||
|   copy: |   copy: | ||||||
|     src: lmn-printer.sh |     src: lmn-printer.sh | ||||||
|  | @ -86,18 +39,3 @@ | ||||||
|     mode: 0644 |     mode: 0644 | ||||||
|     owner: root |     owner: root | ||||||
|     group: 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 |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
							
								
								
									
										36
									
								
								roles/lmn_printer/templates/install-printers.sh.j2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								roles/lmn_printer/templates/install-printers.sh.j2
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  | 
 | ||||||
|  | @ -1,5 +0,0 @@ | ||||||
| [network] |  | ||||||
| serverHostname = server |  | ||||||
| domain = pn.steinbeis.schule |  | ||||||
| realm = PN.STEINBEIS.SCHULE |  | ||||||
| version = 1 |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Raphael Dannecker
						Raphael Dannecker