From 0bc93500a8bfb268085b02379375741903961f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Baumbach?= Date: Thu, 22 Oct 2020 17:29:56 +0200 Subject: [PATCH] samba-tool: add new "user unlock" command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Can be used to unlock a user when the badPwdCount has been reached. Introduces SamDB error classes, as suggested by Douglas Bagnall - thanks! This helps to handle expected failures. Tracebacks of really unexpected failures will not be hidden. Signed-off-by: Björn Baumbach Reviewed-by: Douglas Bagnall --- docs-xml/manpages/samba-tool.8.xml | 6 +++ python/samba/netcmd/user.py | 74 +++++++++++++++++++++++++++++- python/samba/samdb.py | 30 ++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/docs-xml/manpages/samba-tool.8.xml b/docs-xml/manpages/samba-tool.8.xml index ccaaa8b432a..a7e8a7c9d1a 100644 --- a/docs-xml/manpages/samba-tool.8.xml +++ b/docs-xml/manpages/samba-tool.8.xml @@ -1465,6 +1465,12 @@ Sets or resets the password of a user account. + + user unlock <replaceable>username</replaceable> [options] + This command unlocks a user account in the Active Directory + domain. + + user getpassword <replaceable>username</replaceable> [options] Gets the password of a user account. diff --git a/python/samba/netcmd/user.py b/python/samba/netcmd/user.py index f9762e761ea..b483dcf5591 100644 --- a/python/samba/netcmd/user.py +++ b/python/samba/netcmd/user.py @@ -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 (|--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() diff --git a/python/samba/samdb.py b/python/samba/samdb.py index 0ec91ed3970..e8aee496352 100644 --- a/python/samba/samdb.py +++ b/python/samba/samdb.py @@ -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)