# Unix SMB/CIFS implementation. # Copyright (C) Catalyst.NET Ltd 2022 # # 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 . # import os import random import string import sys import time import ldb from samba import param from samba.auth import system_session from samba.credentials import Credentials from samba.dcerpc import security from samba.ndr import ndr_unpack from samba.samdb import SamDB from samba.tests import ( DynamicTestCase, TestCase, delete_force, env_get_var_value, ) sys.path.insert(0, 'bin/python') os.environ['PYTHONUNBUFFERED'] = '1' @DynamicTestCase class SidStringTests(TestCase): @classmethod def setUpDynamicTestCases(cls): if env_get_var_value('CHECK_ALL_COMBINATIONS', allow_missing=True): for x in string.ascii_uppercase: for y in string.ascii_uppercase: code = x + y if code not in cls.cases: cls.cases[code] = None for code, expected_sid in cls.cases.items(): name = code cls.generate_dynamic_test('test_sid_string', name, code, expected_sid) @classmethod def setUpClass(cls): super().setUpClass() server = os.environ['DC_SERVER'] host = f'ldap://{server}' lp = param.LoadParm() lp.load(os.environ['SMB_CONF_PATH']) creds = Credentials() creds.guess(lp) creds.set_username(env_get_var_value('DC_USERNAME')) creds.set_password(env_get_var_value('DC_PASSWORD')) cls.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) cls.base_dn = cls.ldb.domain_dn() cls.schema_dn = cls.ldb.get_schema_basedn().get_linearized() def _test_sid_string_with_args(self, code, expected_sid): random_suffix = random.randint(0, 100000) timestamp = time.strftime('%s', time.gmtime()) class_name = f'my-Sid-String-Class{timestamp}{random_suffix}' class_ldap_display_name = class_name.replace('-', '') class_dn = f'CN={class_name},{self.schema_dn}' ldif = f''' dn: {class_dn} objectClass: classSchema cn: {class_name} governsId: 1.3.6.1.4.1.7165.4.6.2.6.3.{random_suffix} subClassOf: top possSuperiors: domainDNS defaultSecurityDescriptor: O:{code} ''' try: self.ldb.add_ldif(ldif) except ldb.LdbError as err: num, _ = err.args self.assertEqual(num, ldb.ERR_UNWILLING_TO_PERFORM) self.assertIsNone(expected_sid) return # Search for created objectclass res = self.ldb.search(class_dn, scope=ldb.SCOPE_BASE, attrs=['defaultSecurityDescriptor']) self.assertEqual(1, len(res)) self.assertEqual(res[0].get('defaultSecurityDescriptor', idx=0), f'O:{code}'.encode('utf-8')) ldif = ''' dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 ''' self.ldb.modify_ldif(ldif) object_name = f'sddl_{timestamp}_{random_suffix}' object_dn = f'CN={object_name},{self.base_dn}' ldif = f''' dn: {object_dn} objectClass: {class_ldap_display_name} cn: {object_name} ''' self.ldb.add_ldif(ldif) # Search for created object res = self.ldb.search(object_dn, scope=ldb.SCOPE_BASE, attrs=['nTSecurityDescriptor']) self.assertEqual(1, len(res)) # Delete the object delete_force(self.ldb, object_dn) data = res[0].get('nTSecurityDescriptor', idx=0) descriptor = ndr_unpack(security.descriptor, data) domain_sid = self.ldb.get_domain_sid() if expected_sid is None: expected_sid = f'{domain_sid}-{security.DOMAIN_RID_ADMINS}' else: expected_sid = expected_sid.format(domain_sid=domain_sid) owner_sid = str(descriptor.owner_sid) self.assertEqual(expected_sid, owner_sid) cases = { 'AA': 'S-1-5-32-579', 'AC': 'S-1-15-2-1', 'AN': 'S-1-5-7', 'AO': 'S-1-5-32-548', 'AP': '{domain_sid}-525', 'AS': 'S-1-18-1', 'AU': 'S-1-5-11', 'BA': 'S-1-5-32-544', 'BG': 'S-1-5-32-546', 'BO': 'S-1-5-32-551', 'BU': 'S-1-5-32-545', 'CA': '{domain_sid}-517', 'CD': 'S-1-5-32-574', 'CG': 'S-1-3-1', 'CN': '{domain_sid}-522', 'CO': 'S-1-3-0', 'CY': 'S-1-5-32-569', 'DC': '{domain_sid}-515', 'DD': '{domain_sid}-516', 'DG': '{domain_sid}-514', 'DU': '{domain_sid}-513', 'EA': '{domain_sid}-519', 'ED': 'S-1-5-9', 'EK': '{domain_sid}-527', 'ER': 'S-1-5-32-573', 'ES': 'S-1-5-32-576', 'HA': 'S-1-5-32-578', 'HI': 'S-1-16-12288', 'IS': 'S-1-5-32-568', 'IU': 'S-1-5-4', 'KA': '{domain_sid}-526', 'LA': '{domain_sid}-500', 'LG': '{domain_sid}-501', 'LS': 'S-1-5-19', 'LU': 'S-1-5-32-559', 'LW': 'S-1-16-4096', 'ME': 'S-1-16-8192', 'MP': 'S-1-16-8448', 'MS': 'S-1-5-32-577', 'MU': 'S-1-5-32-558', 'NO': 'S-1-5-32-556', 'NS': 'S-1-5-20', 'NU': 'S-1-5-2', 'OW': 'S-1-3-4', 'PA': '{domain_sid}-520', 'PO': 'S-1-5-32-550', 'PS': 'S-1-5-10', 'PU': 'S-1-5-32-547', 'RA': 'S-1-5-32-575', 'RC': 'S-1-5-12', 'RD': 'S-1-5-32-555', 'RE': 'S-1-5-32-552', 'RM': 'S-1-5-32-580', 'RO': '{domain_sid}-498', 'RS': '{domain_sid}-553', 'RU': 'S-1-5-32-554', 'SA': '{domain_sid}-518', 'SI': 'S-1-16-16384', 'SO': 'S-1-5-32-549', 'SS': 'S-1-18-2', 'SU': 'S-1-5-6', 'SY': 'S-1-5-18', # Not tested, as it always gives us an OPERATIONS_ERROR with Windows. # 'UD': 'S-1-5-84-0-0-0-0-0', 'WD': 'S-1-1-0', 'WR': 'S-1-5-33', 'aa': 'S-1-5-32-579', 'Aa': 'S-1-5-32-579', 'aA': 'S-1-5-32-579', 'BR': None, 'IF': None, 'LK': None, } if __name__ == '__main__': global_asn1_print = False global_hexdump = False import unittest unittest.main()