1
0
mirror of https://github.com/samba-team/samba.git synced 2025-12-18 08:23:51 +03:00
Files
samba-mirror/python/samba/tests/conditional_ace_assembler.py
Douglas Bagnall 15fe49a2f9 pytest: assembler for conditional ACEs
This is a helper module to construct conditional ACEs that can't be
created from SDDL.

There is a semi-infinite number of valid conditional ACEs that don't
have SDDL representations, and an even larger number of invalid (or
borderline invalid) ACEs.

This allows us to create those ACEs without having to deal with too
many array of numbers.

The next commit provides an example of its use.

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2023-09-26 23:45:35 +00:00

197 lines
5.2 KiB
Python

# Unix SMB/CIFS implementation.
# Copyright © Catalyst IT 2023
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""Fine-grained control over conditional ACE contents.
This deliberately allows you to do broken things that SDDL doesn't.
- token sequences that make no real sense
- sequences that make sense which SDDL can't encode
- strings that aren't proper utf-16
- etc.
"""
import struct
from samba.dcerpc import security, conditional_ace as ca
from samba.ndr import ndr_pack
class Composite:
token = ca.CONDITIONAL_ACE_TOKEN_COMPOSITE
def __init__(self, *tokens):
self.members = []
for t in tokens:
self.members.append(dwim_one_token(t))
def __bytes__(self):
v = []
for x in self.members:
v.extend(bytes(x))
return (bytes([self.token]) +
struct.pack("<I", len(v)) +
bytes(v))
class Int:
def __init__(self, value,
bits=ca.CONDITIONAL_ACE_TOKEN_INT64,
base=ca.CONDITIONAL_ACE_INT_BASE_10,
sign=ca.CONDITIONAL_ACE_INT_SIGN_NONE):
self.value = value
self.bits = int(bits)
self.base = int(base)
self.sign = int(sign)
def __bytes__(self):
n = struct.pack('<q', self.value)
return bytes([self.bits]) + n + bytes([self.sign, self.base])
class String:
"""A string is decoded as UTF-16.
Other iterables allows the insertion of arbitrary raw bytes."""
token = ca.CONDITIONAL_ACE_TOKEN_UNICODE
def __init__(self, value):
if isinstance(value, str):
value = value.encode('utf-16-le')
self.value = list(value)
def __bytes__(self):
header = struct.pack('<BI', self.token, len(self.value))
return header + bytes(self.value)
class LocalAttr(String):
token = ca.CONDITIONAL_ACE_LOCAL_ATTRIBUTE
class UserAttr(String):
token = ca.CONDITIONAL_ACE_USER_ATTRIBUTE
class DeviceAttr(String):
token = ca.CONDITIONAL_ACE_DEVICE_ATTRIBUTE
class ResourceAttr(String):
token = ca.CONDITIONAL_ACE_RESOURCE_ATTRIBUTE
class ByteString:
"""takes an iterable of 8-bit numbers, or a string."""
token = ca.CONDITIONAL_ACE_TOKEN_OCTET_STRING
def __init__(self, value):
if isinstance(value, str):
value = value.encode()
self.value = bytes(value)
if max(self.value) > 255 or min(self.value) < 0:
raise ValueError("bytes do need to be bytes (0-255)")
def __bytes__(self):
header = struct.pack('<BI', self.token, len(self.value))
return header + self.value
class SID:
token = ca.CONDITIONAL_ACE_TOKEN_SID
def __init__(self, sidstring):
self.sid = security.domsid(sidstring)
def __bytes__(self):
value = ndr_pack(self.sid)
header = struct.pack('B<I', self.token, len(value))
return header + value
class Token:
"""To add a raw byte, like
Token(ca.CONDITIONAL_ACE_TOKEN_COMPOSITE)
"""
def __init__(self, v):
self.token = v
def __bytes__(self):
return bytes([self.token])
def _add_tokens():
for tok in dir(ca):
if not tok[:22] == 'CONDITIONAL_ACE_TOKEN_':
continue
k = tok[22:]
globals()[k] = Token(getattr(ca, tok))
_add_tokens()
def dwim_one_token(t):
if isinstance(t, int):
return Int(t)
if isinstance(t, str):
return String(t)
if isinstance(t, tuple):
return Composite(*t)
if isinstance(t, bytes):
return ByteString(t)
return t
def assemble(*tokens):
program = b'artx'
if len(tokens) == 1 and isinstance(tokens, (list, tuple, set)):
print("WARNING: single argument container will become a composite. "
"you might have meant 'assemble(*args)', not 'assemble(args)'")
for t in tokens:
t = dwim_one_token(t)
program += bytes(t)
program += b'\x00\x00\x00'
program = program[:-(len(program) & 3)]
return program
def assemble_ace(tokens=[],
type=security.SEC_ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
trustee=None,
flags=None,
object=None,
access_mask=None):
type_strings = {
'XA': security.SEC_ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
'XD': security.SEC_ACE_TYPE_ACCESS_DENIED_CALLBACK,
'ZA': security.SEC_ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
}
a = security.ace()
a.type = type_strings.get(type, type)
if trustee is not None:
a.trustee = trustee
if flags is not None:
a.flags = flags
if object is not None:
a.object = object
a.coda = assemble(*tokens)
return a