1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00

samba-tool user: use an implicit_attrs list instead of add_ATTR variables

We'll extent GetPasswordCommand.get_password_attributes() to handle
more virtual formats in future. It'll be much easier to
to maintain a list of attributes we need to filter out again.

sAMAccountName and userPrincipalName are always implicitly
requested in order to keep the existing code sane.

supplementalCredentials and unicodePwd are requested by default
when generating virtual password attributes.

Pair-Programmed-With: Björn Baumbach <bb@sernet.de>

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Björn Baumbach <bb@sernet.de>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
Stefan Metzmacher 2021-01-18 15:51:37 +01:00 committed by Andrew Bartlett
parent 06851084ca
commit 98ee82d4fc

View File

@ -152,27 +152,6 @@ def get_crypt_value(alg, utf8pw, rounds=0):
crypt_salt, len(crypt_value), expected_len))
return crypt_value
# Extract the rounds value from the options of a virtualCrypt attribute
# i.e. options = "rounds=20;other=ignored;" will return 20
# if the rounds option is not found or the value is not a number, 0 is returned
# which indicates that the default number of rounds should be used.
def get_rounds(options):
if not options:
return 0
opts = options.split(';')
for o in opts:
if o.lower().startswith("rounds="):
(key, _, val) = o.partition('=')
try:
return int(val)
except ValueError:
return 0
return 0
try:
import hashlib
h = hashlib.sha1()
@ -1159,44 +1138,78 @@ class GetPasswordCommand(Command):
def get_account_attributes(self, samdb, username, basedn, filter, scope,
attrs, decrypt):
raw_attrs = attrs[:]
search_attrs = []
attr_opts = {}
for a in raw_attrs:
(attr, _, opts) = a.partition(';')
if opts:
attr_opts[attr] = opts
def get_option(opts, name):
if not opts:
return None
for o in opts:
if o.lower().startswith("%s=" % name.lower()):
(key, _, val) = o.partition('=')
return val
return None
def get_virtual_attr_definition(attr):
for van in sorted(virtual_attributes.keys()):
if van.lower() != attr.lower():
continue
return virtual_attributes[van]
return None
def parse_raw_attr(raw_attr, is_hidden=False):
(attr, _, fullopts) = raw_attr.partition(';')
if fullopts:
opts = fullopts.split(';')
else:
attr_opts[attr] = None
search_attrs.append(attr)
lower_attrs = [x.lower() for x in search_attrs]
opts = []
a = {}
a["raw_attr"] = raw_attr
a["attr"] = attr
a["opts"] = opts
a["vattr"] = get_virtual_attr_definition(attr)
a["is_hidden"] = is_hidden
return a
require_supplementalCredentials = False
for a in virtual_attributes.keys():
if a.lower() in lower_attrs:
require_supplementalCredentials = True
add_supplementalCredentials = False
add_unicodePwd = False
if require_supplementalCredentials:
a = "supplementalCredentials"
if a.lower() not in lower_attrs:
search_attrs += [a]
add_supplementalCredentials = True
a = "unicodePwd"
if a.lower() not in lower_attrs:
search_attrs += [a]
add_unicodePwd = True
add_sAMAcountName = False
a = "sAMAccountName"
if a.lower() not in lower_attrs:
search_attrs += [a]
add_sAMAcountName = True
raw_attrs = attrs[:]
has_wildcard_attr = "*" in raw_attrs
has_virtual_attrs = False
requested_attrs = []
implicit_attrs = []
add_userPrincipalName = False
upn = "userPrincipalName"
if upn.lower() not in lower_attrs:
search_attrs += [upn]
add_userPrincipalName = True
for raw_attr in raw_attrs:
a = parse_raw_attr(raw_attr)
requested_attrs.append(a)
search_attrs = []
has_virtual_attrs = False
for a in requested_attrs:
if a["vattr"] is not None:
has_virtual_attrs = True
continue
if a["raw_attr"] in search_attrs:
continue
search_attrs.append(a["raw_attr"])
if not has_wildcard_attr:
required_attrs = [
"sAMAccountName",
"userPrincipalName"
]
for required_attr in required_attrs:
a = parse_raw_attr(required_attr)
implicit_attrs.append(a)
if has_virtual_attrs:
required_attrs = [
"supplementalCredentials",
"unicodePwd",
]
for required_attr in required_attrs:
a = parse_raw_attr(required_attr, is_hidden=True)
implicit_attrs.append(a)
for a in implicit_attrs:
if a["attr"] in search_attrs:
continue
search_attrs.append(a["attr"])
if scope == ldb.SCOPE_BASE:
search_controls = ["show_deleted:1", "show_recycled:1"]
@ -1220,22 +1233,14 @@ class GetPasswordCommand(Command):
if "supplementalCredentials" in obj:
sc_blob = obj["supplementalCredentials"][0]
sc = ndr_unpack(drsblobs.supplementalCredentialsBlob, sc_blob)
if add_supplementalCredentials:
del obj["supplementalCredentials"]
if "unicodePwd" in obj:
unicodePwd = obj["unicodePwd"][0]
if add_unicodePwd:
del obj["unicodePwd"]
account_name = str(obj["sAMAccountName"][0])
if add_sAMAcountName:
del obj["sAMAccountName"]
if "userPrincipalName" in obj:
account_upn = str(obj["userPrincipalName"][0])
else:
realm = samdb.domain_dns_name()
account_upn = "%s@%s" % (account_name, realm.lower())
if add_userPrincipalName:
del obj["userPrincipalName"]
calculated = {}
@ -1479,10 +1484,32 @@ class GetPasswordCommand(Command):
primary_krb5)
return (krb5_blob.version, krb5_blob.ctr)
# Extract the rounds value from the options of a virtualCrypt attribute
# i.e. options = "rounds=20;other=ignored;" will return 20
# if the rounds option is not found or the value is not a number, 0 is returned
# which indicates that the default number of rounds should be used.
def get_rounds(opts):
val = get_option(opts, "rounds")
if val is None:
return 0
try:
return int(val)
except ValueError:
return 0
# We use sort here in order to have a predictable processing order
for a in sorted(virtual_attributes.keys()):
if not a.lower() in lower_attrs:
vattr = None
for ra in requested_attrs:
if ra["vattr"] is None:
continue
if ra["attr"].lower() != a.lower():
continue
vattr = ra
break
if vattr is None:
continue
attr_opts = vattr["opts"]
if a == "virtualClearTextUTF8":
b = get_package("Primary:CLEARTEXT")
@ -1510,13 +1537,13 @@ class GetPasswordCommand(Command):
bv = h.digest() + salt
v = "{SSHA}" + base64.b64encode(bv).decode('utf8')
elif a == "virtualCryptSHA256":
rounds = get_rounds(attr_opts[a])
rounds = get_rounds(attr_opts)
x = get_virtual_crypt_value(a, 5, rounds, username, account_name)
if x is None:
continue
v = x
elif a == "virtualCryptSHA512":
rounds = get_rounds(attr_opts[a])
rounds = get_rounds(attr_opts)
x = get_virtual_crypt_value(a, 6, rounds, username, account_name)
if x is None:
continue
@ -1552,6 +1579,30 @@ class GetPasswordCommand(Command):
else:
continue
obj[a] = ldb.MessageElement(v, ldb.FLAG_MOD_REPLACE, a)
# Now filter out implicit attributes
for delname in obj.keys():
keep = False
for ra in requested_attrs:
if delname.lower() != ra["raw_attr"].lower():
continue
keep = True
break
if keep:
continue
dattr = None
for ia in implicit_attrs:
if delname.lower() != ia["attr"].lower():
continue
dattr = ia
break
if dattr is None:
continue
if has_wildcard_attr and not dattr["is_hidden"]:
continue
del obj[delname]
return obj
def parse_attributes(self, attributes):