]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
samba-tool: add new "user unlock" command
authorBjörn Baumbach <bb@sernet.de>
Thu, 22 Oct 2020 15:29:56 +0000 (17:29 +0200)
committerDouglas Bagnall <dbagnall@samba.org>
Tue, 3 Nov 2020 22:55:37 +0000 (22:55 +0000)
Can be used to unlock a user when the badPwdCount has been reached.

Introduces SamDB error classes, as suggested by
Douglas Bagnall <douglas.bagnall@catalyst.net.nz> - thanks!
This helps to handle expected failures.
Tracebacks of really unexpected failures will not be hidden.

Signed-off-by: Björn Baumbach <bb@sernet.de>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
docs-xml/manpages/samba-tool.8.xml
python/samba/netcmd/user.py
python/samba/samdb.py

index ccaaa8b432aa08229901e4eec4a404455cef58d6..a7e8a7c9d1a3f1e7a4f343fff4fd6cd502715342 100644 (file)
        <para>Sets or resets the password of a user account.</para>
 </refsect3>
 
+<refsect3>
+       <title>user unlock <replaceable>username</replaceable> [options]</title>
+       <para>This command unlocks a user account in the Active Directory
+       domain.</para>
+</refsect3>
+
 <refsect3>
        <title>user getpassword <replaceable>username</replaceable> [options]</title>
        <para>Gets the password of a user account.</para>
index f9762e761eabaabca3a90718ea0e7f0a3cb37539..b483dcf55916229f53972f216098676c13f7cfbe 100644 (file)
@@ -33,7 +33,7 @@ import binascii
 from subprocess import Popen, PIPE, STDOUT, check_call, CalledProcessError
 from getpass import getpass
 from samba.auth import system_session
-from samba.samdb import SamDB
+from samba.samdb import SamDB, SamDBError, SamDBNotFoundError
 from samba.dcerpc import misc
 from samba.dcerpc import security
 from samba.dcerpc import drsblobs
@@ -3257,6 +3257,77 @@ unixHomeDirectory: {6}
             self.outf.write("Modified User '{}' successfully\n"
                             .format(username))
 
+class cmd_user_unlock(Command):
+    """Unlock a user account.
+
+    This command unlocks a user account in the Active Directory domain. The
+    username specified on the command is the sAMAccountName. The username may
+    also be specified using the --filter option.
+
+    The command may be run from the root userid or another authorized userid.
+    The -H or --URL= option can be used to execute the command against a remote
+    server.
+
+    Example:
+    samba-tool user unlock user1 -H ldap://samba.samdom.example.com \\
+        --username=Administrator --password=Passw0rd
+
+    The example shows how to unlock a user account in the domain against a
+    remote LDAP server. The -H parameter is used to specify the remote target
+    server. The --username= and --password= options are used to pass the
+    username and password of a user that exists on the remote server and is
+    authorized to issue the command on that server.
+"""
+
+    synopsis = "%prog (<username>|--filter <filter>) [options]"
+
+    takes_options = [
+        Option("-H",
+               "--URL",
+               help="LDB URL for database or target server",
+               type=str,
+               metavar="URL",
+               dest="H"),
+        Option("--filter",
+               help="LDAP Filter to set password on",
+               type=str),
+    ]
+
+    takes_args = ["username?"]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+    }
+
+    def run(self,
+            username=None,
+            sambaopts=None,
+            credopts=None,
+            versionopts=None,
+            filter=None,
+            H=None):
+        if username is None and filter is None:
+            raise CommandError("Either the username or '--filter' must be "
+                               "specified!")
+
+        if filter is None:
+            filter = ("(&(objectClass=user)(sAMAccountName=%s))" % (
+                ldb.binary_encode(username)))
+
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+
+        samdb = SamDB(url=H,
+                      session_info=system_session(),
+                      credentials=creds,
+                      lp=lp)
+        try:
+            samdb.unlock_account(filter)
+        except (SamDBError, ldb.LdbError) as msg:
+            raise CommandError("Failed to unlock user '%s': %s" % (
+                               username or filter, msg))
 
 class cmd_user_sensitive(Command):
     """Set/unset or show UF_NOT_DELEGATED for an account."""
@@ -3336,5 +3407,6 @@ class cmd_user(SuperCommand):
     subcommands["show"] = cmd_user_show()
     subcommands["move"] = cmd_user_move()
     subcommands["rename"] = cmd_user_rename()
+    subcommands["unlock"] = cmd_user_unlock()
     subcommands["addunixattrs"] = cmd_user_add_unix_attrs()
     subcommands["sensitive"] = cmd_user_sensitive()
index 0ec91ed3970281d97cdd7e3076ce300991f976d9..e8aee496352ba08533be304d821dc41393352678 100644 (file)
@@ -42,6 +42,11 @@ __docformat__ = "restructuredText"
 def get_default_backend_store():
     return "tdb"
 
+class SamDBError(Exception):
+    pass
+
+class SamDBNotFoundError(SamDBError):
+    pass
 
 class SamDB(samba.Ldb):
     """The SAM database."""
@@ -179,6 +184,31 @@ dn: %s
 changetype: modify
 replace: pwdLastSet
 pwdLastSet: 0
+""" % (user_dn)
+        self.modify_ldif(mod)
+
+    def unlock_account(self, search_filter):
+        """Unlock a user account by resetting lockoutTime to 0.
+        This does also reset the badPwdCount to 0.
+
+        :param search_filter: LDAP filter to find the user (e.g.
+            sAMAccountName=username)
+        """
+        res = self.search(base=self.domain_dn(),
+                          scope=ldb.SCOPE_SUBTREE,
+                          expression=search_filter,
+                          attrs=[])
+        if len(res) == 0:
+            raise SamDBNotFoundError('Unable to find user "%s"' % search_filter)
+        if len(res) != 1:
+            raise SamDBError('User "%s" is not unique' % search_filter)
+        user_dn = res[0].dn
+
+        mod = """
+dn: %s
+changetype: modify
+replace: lockoutTime
+lockoutTime: 0
 """ % (user_dn)
         self.modify_ldif(mod)