samba-tool: Make samba-tool user getpassword support a ';previous=1' option
authorAndrew Bartlett <abartlet@samba.org>
Mon, 11 Dec 2023 07:56:16 +0000 (20:56 +1300)
committerDouglas Bagnall <dbagnall@samba.org>
Thu, 21 Dec 2023 02:05:38 +0000 (02:05 +0000)
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
python/samba/netcmd/user/readpasswords/common.py

index 267c8242b8e67bf09eafd352c0565bf4204b7a57..6d44881823db5ad97683ba145254597e896423c7 100644 (file)
@@ -371,6 +371,8 @@ class GetPasswordCommand(Command):
                                                    managed_password)
             calculated["Primary:CLEARTEXT"] = \
                 unpacked_managed_password.passwords.current
+            calculated["OLDCLEARTEXT"] = \
+                unpacked_managed_password.passwords.previous
 
         account_name = str(obj["sAMAccountName"][0])
         if "userPrincipalName" in obj:
@@ -397,6 +399,17 @@ class GetPasswordCommand(Command):
                 return binascii.a2b_hex(p.data)
             return None
 
+        def get_cleartext(attr_opts):
+            param = get_option(attr_opts, "previous")
+            if param:
+                if param != "1":
+                    raise CommandError(
+                        f"Invalid attribute parameter ;previous={param}, "
+                        "only supported value is previous=1")
+                return calculated.get("OLDCLEARTEXT")
+            else:
+                return get_package("Primary:CLEARTEXT")
+
         def get_kerberos_ctr():
             primary_krb5 = get_package("Primary:Kerberos-Newer-Keys")
             if primary_krb5 is None:
@@ -596,7 +609,7 @@ class GetPasswordCommand(Command):
             if sv is None:
                 # No exact match on algorithm and number of rounds
                 # try and calculate one from the Primary:CLEARTEXT
-                b = get_package("Primary:CLEARTEXT")
+                b = get_cleartext(attr_opts)
                 if b is not None:
                     u8 = get_utf8(a, b, username or account_name)
                     if u8 is not None:
@@ -664,6 +677,14 @@ class GetPasswordCommand(Command):
             except ValueError:
                 return 0
 
+        def get_unicode_pwd_hash(pwd):
+            # We can't read unicodePwd directly, but we can regenerate
+            # it from msDS-ManagedPassword
+            tmp = credentials.Credentials()
+            tmp.set_anonymous()
+            tmp.set_utf16_password(pwd)
+            return tmp.get_nt_hash()
+
         # We use sort here in order to have a predictable processing order
         for a in sorted(virtual_attributes.keys()):
             vattr = None
@@ -679,7 +700,7 @@ class GetPasswordCommand(Command):
             attr_opts = vattr["opts"]
 
             if a == "virtualClearTextUTF8":
-                b = get_package("Primary:CLEARTEXT")
+                b = get_cleartext(attr_opts)
                 if b is None:
                     continue
                 u8 = get_utf8(a, b, username or account_name)
@@ -687,11 +708,11 @@ class GetPasswordCommand(Command):
                     continue
                 v = u8
             elif a == "virtualClearTextUTF16":
-                v = get_package("Primary:CLEARTEXT")
+                v = get_cleartext(attr_opts)
                 if v is None:
                     continue
             elif a == "virtualSSHA":
-                b = get_package("Primary:CLEARTEXT")
+                b = get_cleartext(attr_opts)
                 if b is None:
                     continue
                 u8 = get_utf8(a, b, username or account_name)
@@ -728,13 +749,13 @@ class GetPasswordCommand(Command):
                 v = kerberos_salt
                 if v is None:
                     continue
-            elif a == "unicodePwd" and "Primary:CLEARTEXT" in calculated and unicodePwd is None:
-                # We can't read unicodePwd directly, but we can regenerate
-                # it from msDS-ManagedPassword
-                tmp = credentials.Credentials()
-                tmp.set_anonymous()
-                tmp.set_utf16_password(calculated["Primary:CLEARTEXT"])
-                v = tmp.get_nt_hash()
+            elif a == "unicodePwd" and unicodePwd is None:
+                if "Primary:CLEARTEXT" in calculated and not get_option(attr_opts, "previous"):
+                    v = get_unicode_pwd_hash(calculated["Primary:CLEARTEXT"])
+                elif "OLDCLEARTEXT" in calculated and get_option(attr_opts, "previous"):
+                    v = get_unicode_pwd_hash(calculated["OLDCLEARTEXT"])
+                else:
+                    continue
             elif a.startswith("virtualWDigest"):
                 primary_wdigest = get_package("Primary:WDigest")
                 if primary_wdigest is None: