diff --git a/debian/changelog b/debian/changelog index 82d59e5..c4439e8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,17 @@ +linuxmuster-linuxclient7 (7.4.0) lmn74; urgency=high + + * Escape values in LDAP search filters (#94) + * Prevent path traversal in sudoTools mount-share (#94) + * Validate printer path and name in sudoTools install-printer (#94) + * Create user homes with umask 077 (#44) + * Fix printers not being uninstalled on logout (#93) + * Fix onBoot hook blocking the boot process if the DC is unreachable (#61) + * Remove deprecated "client use spnego" option from smb.conf (#94) + * Fix GDM login screen not showing on Ubuntu 26.04 (#94) + * Improve login performance (#98, #99, #100) + + -- Lukas Lukic-Spitznagel Fri, 16 Oct 2026 11:00:06 +0200 + linuxmuster-linuxclient7 (1.1.4) lmn73; urgency=medium * Fix: regressions in setup and prepareImage (#91) diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/computer.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/computer.py index dd71347..09dde64 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/computer.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/computer.py @@ -26,7 +26,7 @@ def readAttributes(): :return: Tuple (success, dict of attributes) :rtype: tuple """ - return ldapHelper.searchOne(f"(sAMAccountName={hostname()}$)") + return ldapHelper.searchOne(f"(sAMAccountName={ldapHelper.escape(hostname())}$)") def isInGroup(groupName): """ diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/gpo.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/gpo.py index 1a845b2..4463331 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/gpo.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/gpo.py @@ -80,7 +80,7 @@ def _findApplicablePolicies(): policyName = f"sophomorix:school:{schoolName}" # find policy - rc, policyAdObject = ldapHelper.searchOne(f"(displayName={policyName})") + rc, policyAdObject = ldapHelper.searchOne(f"(displayName={ldapHelper.escape(policyName)})") if not rc: return False, None @@ -99,7 +99,7 @@ def _parsePolicy(policyDn): """ # Find policy in AD - rc, policyAdObject = ldapHelper.searchOne(f"(distinguishedName={policyDn[0]})") + rc, policyAdObject = ldapHelper.searchOne(f"(distinguishedName={ldapHelper.escape(policyDn[0])})") if not rc: logging.error("===> Could not find poilcy in AD! ===") return False diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/ldapHelper.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/ldapHelper.py index 9d8c124..f61c916 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/ldapHelper.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/ldapHelper.py @@ -3,6 +3,18 @@ _currentLdapConnection = None +def escape(value): + """ + Escapes a value so it can be safely used in an LDAP search filter + + :param value: The value to escape + :type value: str + :return: The escaped value + :rtype: str + """ + import ldap.filter + return ldap.filter.escape_filter_chars(str(value)) + def serverUrl(): """ Returns the server URL @@ -109,7 +121,7 @@ def isObjectInGroup(objectDn, groupName): :rtype: bool """ logging.debug(f"= Testing if object {objectDn} is a member of group {groupName} =") - rc, groupAdObject = searchOne(f"(&(member:1.2.840.113556.1.4.1941:={objectDn})(sAMAccountName={groupName}))") + rc, groupAdObject = searchOne(f"(&(member:1.2.840.113556.1.4.1941:={escape(objectDn)})(sAMAccountName={escape(groupName)}))") logging.debug(f"=> Result: {rc} =") return rc diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/printers.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/printers.py index 8665058..5af0e32 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/printers.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/printers.py @@ -1,6 +1,8 @@ import os, subprocess, re from linuxmusterLinuxclient7 import logging, user +_allowedPrinterUriSchemes = ["ipp", "ipps", "http", "https", "smb", "lpd", "socket", "dnssd"] + def installPrinter(networkPath, name=None, username=None): """ Installs a networked printer for a user @@ -70,8 +72,16 @@ def translateSambaToIpp(networkPath): # -------------------- def _installPrinter(username, networkPath, name): + if not _printerNetworkPathIsValid(networkPath): + logging.error(f"Refusing to install printer: invalid network path '{networkPath}'") + return False + + if not _printerNameIsValid(name): + logging.error(f"Refusing to install printer: invalid printer name '{name}'") + return False + logging.info(f"Install Printer {name} on {networkPath}") - installCommand = ["timeout", "10", "lpadmin", "-p", name, "-E", "-v", networkPath, "-m", "everywhere", "-u", f"allow:{username}"] + installCommand = ["timeout", "10", "/usr/sbin/lpadmin", "-p", name, "-E", "-v", networkPath, "-m", "everywhere", "-u", f"allow:{username}"] logging.debug(f"* running '{' '.join(installCommand)}'") p = subprocess.call(installCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if p == 0: @@ -88,7 +98,7 @@ def _installPrinterWithoutRoot(networkPath, name): def _getInstalledPrintersOfUser(username): logging.info(f"Getting installed printers of {username}") - command = ["lpstat", "-U", username, "-p"] + command = ["/usr/bin/lpstat", "-U", username, "-p"] #logging.debug(f"running '{command}'") result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) @@ -113,7 +123,7 @@ def _getInstalledPrintersOfUser(username): def _uninstallPrinter(name): logging.info(f"Uninstall Printer {name}") - uninstallCommand = ["timeout", "10", "lpadmin", "-x", name] + uninstallCommand = ["timeout", "10", "/usr/sbin/lpadmin", "-x", name] logging.debug(f"* running '{' '.join(uninstallCommand)}'") p = subprocess.call(uninstallCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if p == 0: @@ -124,3 +134,32 @@ def _uninstallPrinter(name): else: logging.fatal(f"* Error Uninstalling Printer {name}!") return False + +def _printerNetworkPathIsValid(networkPath): + """ + Checks if a printer network path is an URI with an allowed scheme (e.g. `ipp://`) + + :param networkPath: The network path of the printer + :type networkPath: str + :return: True if valid, False otherwise + :rtype: bool + """ + if not networkPath or not networkPath.isprintable(): + return False + match = re.match(r"^([a-zA-Z][a-zA-Z0-9+.-]*)://", networkPath) + if not match: + return False + return match.group(1).lower() in _allowedPrinterUriSchemes + +def _printerNameIsValid(name): + """ + Checks if a printer name is valid for CUPS (printable characters except space, tab, `/`, `\\`, `#` and quotes) + + :param name: The name of the printer + :type name: str + :return: True if valid, False otherwise + :rtype: bool + """ + if not name or not name.isprintable() or name.startswith("-"): + return False + return not any(c in name for c in " \t/\\#'\"") diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/realm.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/realm.py index f878586..9b39f38 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/realm.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/realm.py @@ -69,14 +69,19 @@ def isJoined(): else: return len(joinedDomains) > 0 -def pullKerberosTicketForComputerAccount(): +def pullKerberosTicketForComputerAccount(timeout=None): """ Pulls a kerberos ticket using the computer account from `/etc/krb5.keytab` + :param timeout: Timeout in seconds, defaults to None (no timeout) + :type timeout: int, optional :return: True on success, False otherwise :rtype: bool """ - return subprocess.call(["kinit", "-k", computer.krbHostName()]) == 0 + command = ["kinit", "-k", computer.krbHostName()] + if timeout != None: + command = ["timeout", str(timeout)] + command + return subprocess.call(command) == 0 def verifyDomainJoin(): """ diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/shares.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/shares.py index 2759759..fffec11 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/shares.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/shares.py @@ -138,8 +138,22 @@ def getLocalSysvolPath(): # This parameter influences the `cruid` mount option. def _mountShare(username, networkPath, shareName, hiddenShare, useCruidOfExecutingUser=False): + networkPath = networkPath.replace("\\", "/") + + if not _networkPathIsValid(networkPath): + logging.error(f"Refusing to mount: invalid network path '{networkPath}'") + return False, None + + if not _shareNameIsValid(shareName): + logging.error(f"Refusing to mount: invalid share name '{shareName}'") + return False, None + mountpoint = _getShareMountpoint(networkPath, username, hiddenShare, shareName) + if not _mountpointIsWithinBase(mountpoint, username, hiddenShare): + logging.error(f"Refusing to mount: mountpoint '{mountpoint}' is outside of the share mount basepath") + return False, None + 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 @@ -162,7 +176,7 @@ def _mountShare(username, networkPath, shareName, hiddenShare, useCruidOfExecuti gid = -1 logging.warning("Uid could not be found! Continuing anyway!") - mountCommand = [shutil.which("mount.cifs"), "-o", mountCommandOptions, networkPath, mountpoint] + mountCommand = ["timeout", "10", shutil.which("mount.cifs") or "/usr/sbin/mount.cifs", "-o", mountCommandOptions, networkPath, mountpoint] logging.debug(f"Trying to mount '{networkPath}' to '{mountpoint}'") logging.debug("* Creating directory...") @@ -259,3 +273,54 @@ def _getShareMountpoint(networkPath, username, hidden, shareName = None): def _directoryIsMountpoint(dir): return subprocess.call(["mountpoint", "-q", dir]) == 0 + +def _networkPathIsValid(networkPath): + """ + Checks if a network path has the format `//server/share[/subdir]` and does not contain `..` or control characters + + :param networkPath: The network path + :type networkPath: str + :return: True if valid, False otherwise + :rtype: bool + """ + if not networkPath or "\x00" in networkPath or "\n" in networkPath: + return False + if ".." in networkPath.split("/"): + return False + return re.fullmatch(r"//[^/]+/.+", networkPath) is not None + +def _shareNameIsValid(shareName): + """ + Checks if a share name is a single path component, as it is used as the name of the mountpoint + + :param shareName: The name of the share + :type shareName: str + :return: True if valid, False otherwise + :rtype: bool + """ + if not shareName or shareName in (".", ".."): + return False + if "/" in shareName or "\\" in shareName or "\x00" in shareName: + return False + return True + +def _mountpointIsWithinBase(mountpoint, username, hidden): + """ + Checks if a mountpoint is located inside the share mount basepath of a user + + :param mountpoint: The mountpoint + :type mountpoint: str + :param username: The username of the user + :type username: str + :param hidden: If the share is a hidden share + :type hidden: bool + :return: True if the mountpoint is inside the basepath, False otherwise + :rtype: bool + """ + if hidden: + base = constants.hiddenShareMountBasepath.format(username) + else: + base = constants.shareMountBasepath.format(username) + + base = os.path.normpath(base) + return os.path.normpath(mountpoint).startswith(base + "/") diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_ldapHelper.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_ldapHelper.py new file mode 100644 index 0000000..cf57ad6 --- /dev/null +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_ldapHelper.py @@ -0,0 +1,17 @@ +from unittest import mock +from .. import ldapHelper + +def test_escape(): + assert ldapHelper.escape("user1") == "user1" + assert ldapHelper.escape("*") == r"\2a" + assert ldapHelper.escape("a)(b") == r"a\29\28b" + assert ldapHelper.escape("a\\b") == r"a\5cb" + assert ldapHelper.escape(123) == "123" + +@mock.patch("linuxmusterLinuxclient7.ldapHelper.searchOne") +def test_isObjectInGroupEscapesFilter(mockSearchOne): + mockSearchOne.return_value = (True, {}) + assert ldapHelper.isObjectInGroup("cn=user,dc=x", "domain users)(cn=*") + usedFilter = mockSearchOne.call_args.args[0] + assert "*" not in usedFilter + assert r"\2a" in usedFilter diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_printers.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_printers.py index 5a86cee..a31d3cf 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_printers.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_printers.py @@ -2,6 +2,39 @@ from unittest import mock from .. import printers +def test_printerNetworkPathIsValid(): + assert printers._printerNetworkPathIsValid("ipp://server/printers/P1") + assert printers._printerNetworkPathIsValid("smb://server/P1") + assert printers._printerNetworkPathIsValid("https://server/printers/P1") + assert not printers._printerNetworkPathIsValid("file:///etc/passwd") + assert not printers._printerNetworkPathIsValid("/etc/passwd") + assert not printers._printerNetworkPathIsValid("") + assert not printers._printerNetworkPathIsValid(None) + assert not printers._printerNetworkPathIsValid("ipp://server/\nP1") + +def test_printerNameIsValid(): + assert printers._printerNameIsValid("EW-farblaser") + assert printers._printerNameIsValid("printer1") + assert printers._printerNameIsValid("Drucker-Büro_1.OG") + assert not printers._printerNameIsValid("") + assert not printers._printerNameIsValid(None) + assert not printers._printerNameIsValid("-flag") + assert not printers._printerNameIsValid("a/b") + assert not printers._printerNameIsValid("a b") + assert not printers._printerNameIsValid("a#b") + assert not printers._printerNameIsValid("a\nb") + +@mock.patch("subprocess.call") +@mock.patch("linuxmusterLinuxclient7.hooks.user.isRoot") +@mock.patch("linuxmusterLinuxclient7.hooks.user.username") +def test_installPrinterRejectsInvalidInput(mockUserUsername, mockUserIsRoot, mockSubprocessCall): + mockUserIsRoot.return_value = True + mockUserUsername.return_value = "user1" + + assert not printers.installPrinter("file:///etc/passwd", "printer1") + assert not printers.installPrinter("ipp://server/printers/P1", "../evil") + assert _getCallsTo(mockSubprocessCall, "timeout") == [] + def test_translateSambaToIpp(): assert printers.translateSambaToIpp("\\\\linuxmuster.lan\\printer1") == (True, "ipp://linuxmuster.lan/printers/printer1") assert printers.translateSambaToIpp("\\\\linuxmuster.lan\\") == (False, None) @@ -18,13 +51,13 @@ def test_installPrinter(mockUserUsername, mockUserIsRoot, mockSubprocessCall): mockUserUsername.return_value = "user1" assert printers.installPrinter("ipp://linuxmuster.lan/printers/printer1") - assert _getCallsTo(mockSubprocessCall, "timeout")[-1] == ["timeout", "10", "lpadmin", "-p", "printer1", "-E", "-v", "ipp://linuxmuster.lan/printers/printer1", "-m", "everywhere", "-u", "allow:user1"] + assert _getCallsTo(mockSubprocessCall, "timeout")[-1] == ["timeout", "10", "/usr/sbin/lpadmin", "-p", "printer1", "-E", "-v", "ipp://linuxmuster.lan/printers/printer1", "-m", "everywhere", "-u", "allow:user1"] assert printers.installPrinter("ipp://linuxmuster.lan/printers/printer1", "printer2") - assert _getCallsTo(mockSubprocessCall, "timeout")[-1] == ["timeout", "10", "lpadmin", "-p", "printer2", "-E", "-v", "ipp://linuxmuster.lan/printers/printer1", "-m", "everywhere", "-u", "allow:user1"] + assert _getCallsTo(mockSubprocessCall, "timeout")[-1] == ["timeout", "10", "/usr/sbin/lpadmin", "-p", "printer2", "-E", "-v", "ipp://linuxmuster.lan/printers/printer1", "-m", "everywhere", "-u", "allow:user1"] assert printers.installPrinter("ipp://linuxmuster.lan/printers/printer1", "printer2", "user2") - assert _getCallsTo(mockSubprocessCall, "timeout")[-1] == ["timeout", "10", "lpadmin", "-p", "printer2", "-E", "-v", "ipp://linuxmuster.lan/printers/printer1", "-m", "everywhere", "-u", "allow:user2"] + assert _getCallsTo(mockSubprocessCall, "timeout")[-1] == ["timeout", "10", "/usr/sbin/lpadmin", "-p", "printer2", "-E", "-v", "ipp://linuxmuster.lan/printers/printer1", "-m", "everywhere", "-u", "allow:user2"] mockUserIsRoot.return_value = False assert printers.installPrinter("ipp://linuxmuster.lan/printers/printer1") @@ -57,8 +90,8 @@ def test_uninstallAllPrintersOfUser(mockSubprocessRun, mockSubprocessCall): mockSubprocessRun.return_value = CompletedProcess(args=["lpstat", "-U", "user1", "-p"], returncode=0, stdout=lpstatStdout) assert printers.uninstallAllPrintersOfUser("user1") - assert _getCallsTo(mockSubprocessRun, "lpstat")[-1] == ['lpstat', '-U', 'user1', '-p'] - assert _getCallsTo(mockSubprocessCall, "timeout") == [["timeout", "10", "lpadmin", "-x", "printer1"], ["timeout", "10", "lpadmin", "-x", "printer2"]] + assert _getCallsTo(mockSubprocessRun, "/usr/bin/lpstat")[-1] == ['/usr/bin/lpstat', '-U', 'user1', '-p'] + assert _getCallsTo(mockSubprocessCall, "timeout") == [["timeout", "10", "/usr/sbin/lpadmin", "-x", "printer1"], ["timeout", "10", "/usr/sbin/lpadmin", "-x", "printer2"]] mockSubprocessRun.return_value = CompletedProcess(args=["lpstat", "-U", "user1", "-p"], returncode=1) assert printers.uninstallAllPrintersOfUser("user1") diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_realm.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_realm.py index 979876d..1573e2b 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_realm.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_realm.py @@ -80,6 +80,14 @@ def test_pullKerberosTicketForComputerAccount(mockSubprocessCall): mockSubprocessCall.return_value = 1 assert not realm.pullKerberosTicketForComputerAccount() +@mock.patch("subprocess.call") +@mock.patch("linuxmusterLinuxclient7.computer.krbHostName", lambda : "linuxmuster.lan") +def test_pullKerberosTicketForComputerAccountWithTimeout(mockSubprocessCall): + mockSubprocessCall.return_value = 0 + assert realm.pullKerberosTicketForComputerAccount(timeout=5) + calls = _getCallsTo(mockSubprocessCall, "timeout") + assert ["timeout", "5", "kinit", "-k", "linuxmuster.lan"] in calls + @mock.patch("subprocess.call") @mock.patch("linuxmusterLinuxclient7.realm.isJoined") @mock.patch("linuxmusterLinuxclient7.realm.pullKerberosTicketForComputerAccount") diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_shares.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_shares.py index 9b6a44e..5434c13 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_shares.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/tests/test_shares.py @@ -2,6 +2,70 @@ from unittest import mock from .. import shares +def test_networkPathIsValid(): + assert shares._networkPathIsValid("//server/sysvol") + assert shares._networkPathIsValid("//server/share/subdir") + assert not shares._networkPathIsValid("") + assert not shares._networkPathIsValid("/server/share") + assert not shares._networkPathIsValid("//server") + assert not shares._networkPathIsValid("//server/") + assert not shares._networkPathIsValid("//server/../etc") + assert not shares._networkPathIsValid("//server/share\nfoo") + +def test_shareNameIsValid(): + assert shares._shareNameIsValid("sysvol") + assert shares._shareNameIsValid("My Share (H:)") + assert not shares._shareNameIsValid("") + assert not shares._shareNameIsValid(None) + assert not shares._shareNameIsValid(".") + assert not shares._shareNameIsValid("..") + assert not shares._shareNameIsValid("../../etc/cron.d") + assert not shares._shareNameIsValid("a/b") + assert not shares._shareNameIsValid("a\\b") + +def test_mountpointIsWithinBase(): + assert shares._mountpointIsWithinBase("/home/user1/media/share", "user1", False) + assert shares._mountpointIsWithinBase("/srv/samba/user1/share", "user1", True) + assert not shares._mountpointIsWithinBase("/home/user1/media", "user1", False) + assert not shares._mountpointIsWithinBase("/home/user1/media/../../../etc/cron.d", "user1", False) + assert not shares._mountpointIsWithinBase("/etc/cron.d", "user1", False) + +@mock.patch("subprocess.call") +def test_mountShareRejectsInvalidInput(mockSubprocessCall): + assert shares._mountShare("user1", "//server/share", "../../../../etc/cron.d", False, False) == (False, None) + assert shares._mountShare("user1", "//server/../etc", "share", False, False) == (False, None) + for callArgs in mockSubprocessCall.call_args_list: + assert callArgs.args[0][0] == "logger" + +@mock.patch("subprocess.call") +@mock.patch("linuxmusterLinuxclient7.shares._getShareMountpoint") +def test_mountShareRejectsMountpointOutsideBase(mockGetShareMountpoint, mockSubprocessCall): + mockGetShareMountpoint.return_value = "/etc/cron.d" + assert shares._mountShare("user1", "//server/share", "share", False, False) == (False, None) + for callArgs in mockSubprocessCall.call_args_list: + assert callArgs.args[0][0] == "logger" + +@mock.patch("builtins.open", new_callable=mock.mock_open) +@mock.patch("subprocess.call") +@mock.patch("linuxmusterLinuxclient7.shares._directoryIsMountpoint") +@mock.patch("linuxmusterLinuxclient7.shares.Path") +@mock.patch("linuxmusterLinuxclient7.shares.pwd.getpwnam") +@mock.patch("linuxmusterLinuxclient7.shares.config.network") +def test_mountShare(mockNetwork, mockGetpwnam, mockPath, mockIsMountpoint, mockSubprocessCall, mockOpen): + mockNetwork.return_value = (True, {"domain": "linuxmuster.lan"}) + mockGetpwnam.return_value = mock.Mock(pw_uid=1001, pw_gid=1001) + mockIsMountpoint.return_value = False + mockSubprocessCall.return_value = 0 + + assert shares._mountShare("user1", "//server/share", "myshare", True, False) == (True, "/srv/samba/user1/myshare") + calls = _getCallsTo(mockSubprocessCall, "timeout") + assert len(calls) == 1 + assert calls[0][0:2] == ["timeout", "10"] + assert calls[0][-2:] == ["//server/share", "/srv/samba/user1/myshare"] + + mockSubprocessCall.return_value = 1 + assert shares._mountShare("user1", "//server/share", "myshare", True, False) == (False, None) + @mock.patch("linuxmusterLinuxclient7.shares.mountShare") @mock.patch("linuxmusterLinuxclient7.shares.computer.hostname") @mock.patch("linuxmusterLinuxclient7.shares.user.isRoot") @@ -52,4 +116,17 @@ def test_getMountpointOfRemotePath(mockUserUsername, mockUserIsRoot, mockCompute res = shares.getMountpointOfRemotePath("//server/sysvol/linuxmuster.lan/Policies", hiddenShare=False, username="user1", autoMount=True) assert res == (False, None) assert mockSharesMountShare.call_count == 3 - assert mockSharesMountShare.call_args == mock.call('//server/sysvol', hiddenShare=False, username='user1') \ No newline at end of file + assert mockSharesMountShare.call_args == mock.call('//server/sysvol', hiddenShare=False, username='user1') + +# -------------------- +# - Helper functions - +# -------------------- + +def _getCallsTo(mockSubprocessCall, program): + calls = [] + for call_args in mockSubprocessCall.call_args_list: + args = call_args.args[0] + print(args) + if (type(args) == list and args[0] == program) or (type(args) == str and args.startswith(f"{program} ")): + calls.append(args) + return calls \ No newline at end of file diff --git a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/user.py b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/user.py index 6ed989b..f2050db 100644 --- a/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/user.py +++ b/usr/lib/python3/dist-packages/linuxmusterLinuxclient7/user.py @@ -12,7 +12,7 @@ def readAttributes(): if not user.isInAD(): return False, None - return ldapHelper.searchOne(f"(sAMAccountName={user.username()})") + return ldapHelper.searchOne(f"(sAMAccountName={ldapHelper.escape(user.username())})") def school(): """ diff --git a/usr/share/linuxmuster-linuxclient7/scripts/onBoot b/usr/share/linuxmuster-linuxclient7/scripts/onBoot index b91ffca..3da5029 100755 --- a/usr/share/linuxmuster-linuxclient7/scripts/onBoot +++ b/usr/share/linuxmuster-linuxclient7/scripts/onBoot @@ -11,7 +11,7 @@ try: logging.info("====== onBoot started ======") keytab.patchKeytab() - realm.pullKerberosTicketForComputerAccount() + realm.pullKerberosTicketForComputerAccount(timeout=5) # mount sysvol rc, sysvolPath = shares.getLocalSysvolPath() diff --git a/usr/share/linuxmuster-linuxclient7/scripts/sudoTools b/usr/share/linuxmuster-linuxclient7/scripts/sudoTools index 7459135..83dcece 100755 --- a/usr/share/linuxmuster-linuxclient7/scripts/sudoTools +++ b/usr/share/linuxmuster-linuxclient7/scripts/sudoTools @@ -42,7 +42,8 @@ if task == "install-printer": sys.exit(1) pass elif task == "mount-share": - if shares._mountShare(username, args.path, args.name, args.hidden, False): + rc, mountpoint = shares._mountShare(username, args.path, args.name, args.hidden, False) + if rc: sys.exit(0) else: sys.exit(1) diff --git a/usr/share/linuxmuster-linuxclient7/templates/nsswitch.conf b/usr/share/linuxmuster-linuxclient7/templates/nsswitch.conf index aa7fbdf..1a08d45 100644 --- a/usr/share/linuxmuster-linuxclient7/templates/nsswitch.conf +++ b/usr/share/linuxmuster-linuxclient7/templates/nsswitch.conf @@ -5,8 +5,8 @@ passwd: files systemd sss group: files systemd sss -shadow: files sss -gshadow: files +shadow: files systemd sss +gshadow: files systemd hosts: files mdns4_minimal [NOTFOUND=return] dns networks: files diff --git a/usr/share/linuxmuster-linuxclient7/templates/smb.conf b/usr/share/linuxmuster-linuxclient7/templates/smb.conf index aac5bec..e3c1aff 100644 --- a/usr/share/linuxmuster-linuxclient7/templates/smb.conf +++ b/usr/share/linuxmuster-linuxclient7/templates/smb.conf @@ -5,7 +5,6 @@ [global] client signing = yes -client use spnego = yes kerberos method = secrets and keytab security = user tls verify peer = ca_and_name