ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / tests / sid_strings.py
blobfbeb0a940775355599fa3d65585896615f989a13
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Catalyst.NET Ltd 2022
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import os
19 import string
20 import sys
21 import time
22 from hashlib import blake2b
24 import ldb
26 from samba import param
28 from samba.auth import system_session
29 from samba.credentials import Credentials
30 from samba.dcerpc import security
31 from samba.ndr import ndr_unpack
32 from samba.samdb import SamDB
33 from samba.tests import (
34 DynamicTestCase,
35 TestCase,
36 delete_force,
37 env_get_var_value,
40 sys.path.insert(0, 'bin/python')
41 os.environ['PYTHONUNBUFFERED'] = '1'
44 late_ERR_CONSTRAINT_VIOLATION = b"a hack to allow Windows to sometimes fail late"
47 class SidStringBase(TestCase):
48 @classmethod
49 def setUpDynamicTestCases(cls):
50 if not hasattr(cls, 'skip_local'):
51 cls.skip_local = env_get_var_value('SAMBA_SID_STRINGS_SKIP_LOCAL',
52 allow_missing=True)
54 if env_get_var_value('CHECK_ALL_COMBINATIONS',
55 allow_missing=True):
56 for x in string.ascii_uppercase:
57 for y in string.ascii_uppercase:
58 code = x + y
59 if code not in cls.cases:
60 cls.cases[code] = None
62 for code, expected_sid in cls.cases.items():
63 name = code
65 cls.generate_dynamic_test('test_sid_string', name,
66 code, expected_sid)
67 if not cls.skip_local:
68 cls.generate_dynamic_test('test_sid_string_internal', name,
69 code, expected_sid)
71 @classmethod
72 def setUpClass(cls):
73 super().setUpClass()
75 server = os.environ['DC_SERVER']
76 host = f'ldap://{server}'
78 lp = param.LoadParm()
79 lp.load(os.environ['SMB_CONF_PATH'])
81 creds = Credentials()
82 creds.guess(lp)
83 creds.set_username(env_get_var_value('DC_USERNAME'))
84 creds.set_password(env_get_var_value('DC_PASSWORD'))
86 cls.ldb = SamDB(host, credentials=creds,
87 session_info=system_session(lp), lp=lp)
88 cls.base_dn = cls.ldb.domain_dn()
89 cls.schema_dn = cls.ldb.get_schema_basedn().get_linearized()
90 cls.timestamp = str(int(time.time()))
91 cls.domain_sid = cls.ldb.get_domain_sid()
93 def _test_sid_string_with_args(self, code, expected_sid):
94 suffix = int(blake2b(code.encode(), digest_size=3).hexdigest(), 16)
96 class_name = f'my-Sid-String-Class-{self.timestamp}-{suffix}'
97 class_ldap_display_name = class_name.replace('-', '')
99 class_dn = f'CN={class_name},{self.schema_dn}'
101 governs_id = f'1.3.6.1.4.1.7165.4.6.2.9.1{self.timestamp[-7:]}.{suffix}'
103 # expected_sid can be a SID string, an error code, None, or a
104 # special value indicating a deferred error, as follows:
106 # * a number represents the expected error code at the *first*
107 # hurdle, creating the classSchema object.
109 # * late_ERR_CONSTRAINT_VIOLATION means an error when
110 # creating an object based on the class schema.
112 # * None means a somewhat unspecified error or failure to set
113 # the object owner sid.
115 # * a string is the expected owner sid. The rid is borrowed
116 # * and tacked onto the governs-id.
118 if expected_sid is None:
119 expected_err = ldb.ERR_UNWILLING_TO_PERFORM
120 elif isinstance(expected_sid, int):
121 expected_err = expected_sid
122 elif expected_sid is late_ERR_CONSTRAINT_VIOLATION:
123 expected_err = None
124 else:
125 expected_err = None
126 # Append the RID to our OID to ensure more uniqueness.
127 rid = expected_sid.rsplit('-', 1)[1]
128 governs_id += f'.{rid}'
130 ldif = f'''
131 dn: {class_dn}
132 objectClass: classSchema
133 cn: {class_name}
134 governsId: {governs_id}
135 subClassOf: top
136 possSuperiors: domainDNS
137 defaultSecurityDescriptor: O:{code}
139 try:
140 self.ldb.add_ldif(ldif)
141 except ldb.LdbError as err:
142 num, _ = err.args
143 self.assertEqual(num, expected_err)
144 return
145 else:
146 if isinstance(expected_sid, int):
147 self.fail("should have failed")
149 # Search for created objectclass
150 res = self.ldb.search(class_dn, scope=ldb.SCOPE_BASE,
151 attrs=['defaultSecurityDescriptor'])
152 self.assertEqual(1, len(res))
153 self.assertEqual(res[0].get('defaultSecurityDescriptor', idx=0),
154 f'O:{code}'.encode('utf-8'))
156 ldif = '''
158 changetype: modify
159 add: schemaUpdateNow
160 schemaUpdateNow: 1
162 self.ldb.modify_ldif(ldif)
164 object_name = f'sddl_{self.timestamp}_{suffix}'
165 object_dn = f'CN={object_name},{self.base_dn}'
167 ldif = f'''
168 dn: {object_dn}
169 objectClass: {class_ldap_display_name}
170 cn: {object_name}
172 if expected_sid is late_ERR_CONSTRAINT_VIOLATION:
173 expected_err = ldb.ERR_CONSTRAINT_VIOLATION
175 try:
176 self.ldb.add_ldif(ldif)
177 except ldb.LdbError as err:
178 num, _ = err.args
179 self.assertEqual(num, expected_err)
180 return
182 if expected_sid is not None:
183 self.assertIsNone(expected_err)
185 # Search for created object
186 res = self.ldb.search(object_dn, scope=ldb.SCOPE_BASE,
187 attrs=['nTSecurityDescriptor'])
188 self.assertEqual(1, len(res))
190 # Delete the object
191 delete_force(self.ldb, object_dn)
193 data = res[0].get('nTSecurityDescriptor', idx=0)
194 descriptor = ndr_unpack(security.descriptor, data)
196 expected_sid = self.format_expected_sid(expected_sid)
197 owner_sid = str(descriptor.owner_sid)
198 self.assertEqual(expected_sid, owner_sid)
200 def format_expected_sid(self, expected_sid):
201 if expected_sid is None:
202 return f'{self.domain_sid}-{security.DOMAIN_RID_ADMINS}'
204 if not isinstance(expected_sid, str):
205 # never going to match, should have failed already
206 return None
208 return expected_sid.format(domain_sid=self.domain_sid)
210 def _test_sid_string_internal_with_args(self, code, expected_sid):
211 """We just want to test the SIDs, which Samba can't really do because
212 it doesn't parse them until creating an object using the
213 schema class, at which time it doesn't distinguish between a
214 missing value and a nonsense value.
216 So let's also run the test using libcli/security/sddl.c and
217 see what we *would* have done.
219 sddl = f"O:{code}"
220 domsid = security.dom_sid(self.domain_sid)
222 try:
223 sd = security.descriptor.from_sddl(sddl, domsid)
224 except (ValueError, security.SDDLValueError):
225 # we don't have detail as to what went wrong
226 self.assertNotIsInstance(expected_sid, str)
227 else:
228 expected_sid = self.format_expected_sid(expected_sid)
229 self.assertEqual(expected_sid, str(sd.owner_sid))
232 @DynamicTestCase
233 class SidStringTests(SidStringBase):
234 """Testing two letter aliases."""
235 cases = {
236 'AA': 'S-1-5-32-579',
237 'AC': 'S-1-15-2-1',
238 'AN': 'S-1-5-7',
239 'AO': 'S-1-5-32-548',
240 'AP': '{domain_sid}-525',
241 'AS': 'S-1-18-1',
242 'AU': 'S-1-5-11',
243 'BA': 'S-1-5-32-544',
244 'BG': 'S-1-5-32-546',
245 'BO': 'S-1-5-32-551',
246 'BU': 'S-1-5-32-545',
247 'CA': '{domain_sid}-517',
248 'CD': 'S-1-5-32-574',
249 'CG': 'S-1-3-1',
250 'CN': '{domain_sid}-522',
251 'CO': 'S-1-3-0',
252 'CY': 'S-1-5-32-569',
253 'DC': '{domain_sid}-515',
254 'DD': '{domain_sid}-516',
255 'DG': '{domain_sid}-514',
256 'DU': '{domain_sid}-513',
257 'EA': '{domain_sid}-519',
258 'ED': 'S-1-5-9',
259 'EK': '{domain_sid}-527',
260 'ER': 'S-1-5-32-573',
261 'ES': 'S-1-5-32-576',
262 'HA': 'S-1-5-32-578',
263 'HI': 'S-1-16-12288',
264 'IS': 'S-1-5-32-568',
265 'IU': 'S-1-5-4',
266 'KA': '{domain_sid}-526',
267 'LA': '{domain_sid}-500',
268 'LG': '{domain_sid}-501',
269 'LS': 'S-1-5-19',
270 'LU': 'S-1-5-32-559',
271 'LW': 'S-1-16-4096',
272 'ME': 'S-1-16-8192',
273 'MP': 'S-1-16-8448',
274 'MS': 'S-1-5-32-577',
275 'MU': 'S-1-5-32-558',
276 'NO': 'S-1-5-32-556',
277 'NS': 'S-1-5-20',
278 'NU': 'S-1-5-2',
279 'OW': 'S-1-3-4',
280 'PA': '{domain_sid}-520',
281 'PO': 'S-1-5-32-550',
282 'PS': 'S-1-5-10',
283 'PU': 'S-1-5-32-547',
284 'RA': 'S-1-5-32-575',
285 'RC': 'S-1-5-12',
286 'RD': 'S-1-5-32-555',
287 'RE': 'S-1-5-32-552',
288 'RM': 'S-1-5-32-580',
289 'RO': '{domain_sid}-498',
290 'RS': '{domain_sid}-553',
291 'RU': 'S-1-5-32-554',
292 'SA': '{domain_sid}-518',
293 'SI': 'S-1-16-16384',
294 'SO': 'S-1-5-32-549',
295 'SS': 'S-1-18-2',
296 'SU': 'S-1-5-6',
297 'SY': 'S-1-5-18',
298 # Not tested, as it always gives us an OPERATIONS_ERROR with Windows.
299 # 'UD': 'S-1-5-84-0-0-0-0-0',
300 'WD': 'S-1-1-0',
301 'WR': 'S-1-5-33',
302 'aa': 'S-1-5-32-579',
303 'Aa': 'S-1-5-32-579',
304 'aA': 'S-1-5-32-579',
305 'BR': None,
306 'IF': None,
307 'LK': None,
311 @DynamicTestCase
312 class SidStringsThatStartWithS(SidStringBase):
313 """Testing normal or normal-adjacent SIDs"""
314 cases = {
315 # testing explicit string to string round trips.
316 'S-1-5-32-579': 'S-1-5-32-579',
317 'S-1-5-0x20-579': 'S-1-5-32-579', # hex
318 'S-1-0x05-32-579': 'S-1-5-32-579',
319 'S-1-5-040-579': 'S-1-5-40-579', # no octal
320 'S-1-0x50000000-32-579': 'S-1-1342177280-32-579',
321 'S-1-0x500000000-32-579': 'S-1-0x500000000-32-579',
322 'S-1-21474836480-32-579': 'S-1-0x500000000-32-579', # >32 bit is hex
323 f'S-1-5-{(1 << 32) - 1}-579': 'S-1-5-4294967295-579',
324 f'S-1-{(1 << 48) - 1}-579': 'S-1-0xffffffffffff-579',
325 f'S-1-{(1 << 48)}-579': ldb.ERR_UNWILLING_TO_PERFORM,
326 'S-1-99999999999999999999999999999999999999-32-11111111111': ldb.ERR_UNWILLING_TO_PERFORM,
327 'S-1-5-0-579': 'S-1-5-0-579',
328 'S-1-0-0-579': 'S-1-0-0-579',
329 'S-1-0x5-0x20-0x243': 'S-1-5-32-579',
330 'S-1-5-32--579': ldb.ERR_UNWILLING_TO_PERFORM,
331 'S-1-5-32- 579': ldb.ERR_UNWILLING_TO_PERFORM,
332 'S-1-5-32 -579': ldb.ERR_UNWILLING_TO_PERFORM,
333 'S-1-5-3 2-579': ldb.ERR_UNWILLING_TO_PERFORM,
334 ' S-1-1-1-1-1-1-1': ldb.ERR_UNWILLING_TO_PERFORM,
335 # go to lower case in hex.
336 'S-1-0xABcDef123-0xABCDef-579': 'S-1-0xabcdef123-11259375-579',
337 'S-1-1-1-1-1-1-1': 'S-1-1-1-1-1-1-1',
338 's-1-5-32-579': 'S-1-5-32-579',
339 'S-01-5-32-579': 'S-1-5-32-579',
340 'S-000000001-5-32-579': 'S-1-5-32-579',
341 # some strings from https://bugzilla.samba.org/show_bug.cgi?id=14213
342 'S-1-0': ldb.ERR_UNWILLING_TO_PERFORM,
343 'S-1-22': ldb.ERR_UNWILLING_TO_PERFORM,
344 'S-1-22-1': 'S-1-22-1',
345 'S-1-22-1-0': 'S-1-22-1-0',
346 'S-1-3-0': 'S-1-3-0',
347 'S-1-3-99': 'S-1-3-99',
348 'S-01-05-020-0243': 'S-1-5-20-243',
349 'S-000000000001-5-20-243': 'S-1-5-20-243',
350 'S-1-000000000000000005-20-243': 'S-1-5-20-243',
351 'S-1-5-20-00000000000243': 'S-1-5-20-243',
355 @DynamicTestCase
356 class SidStringBehavioursThatWindowsAllows(SidStringBase):
357 """Windows interpretations that we probably don't want to follow"""
358 cases = {
359 # saturating sub-auth values at 32 bits
360 'S-1-5-9999999999-579': 'S-1-5-4294967295-579',
361 'S-1-0x500000000-0x500000000-579': 'S-1-0x500000000-4294967295-579',
362 'S-1-5-11111111111111111111111111111111111-579': 'S-1-5-4294967295-579',
363 f'S-1-5-{(1 << 64) - 1}-579': 'S-1-5-4294967295-579',
364 f'S-1-5-{1 << 64}-579': 'S-1-5-4294967295-579',
365 # S-0x1- ?! on Windows this makes everything else a hex number.
366 'S-0x1-5-40-579': 'S-1-5-64-1401',
367 'S-0x1-0-0-579': 'S-1-0-0-1401',
368 'S-0x1-500000000-20-243': 'S-1-0x500000000-32-579',
369 'S-0x1-5-20-243': 'S-1-5-32-579',
370 'S-0x1-0x5-020-0243': 'S-1-5-32-579',
371 'S-1-0xABcDef123-0xABCDef123-579': 'S-1-0xabcdef123-4294967295-579',
373 'S-0-5-32-579': late_ERR_CONSTRAINT_VIOLATION,
374 'S-2-5-32-579': late_ERR_CONSTRAINT_VIOLATION,
375 'S-10-5-32-579': late_ERR_CONSTRAINT_VIOLATION,
379 @DynamicTestCase
380 class SidStringBehavioursThatSambaPrefers(SidStringBase):
381 """Aspirational alternative answers to the
382 SidStringBehavioursThatWindowsAllows cases."""
383 cases = {
384 'S-1-5-9999999999-579': ldb.ERR_UNWILLING_TO_PERFORM,
385 'S-1-0x500000000-0x500000000-579': ldb.ERR_UNWILLING_TO_PERFORM,
386 'S-1-5-11111111111111111111111111111111111-579': ldb.ERR_UNWILLING_TO_PERFORM,
387 f'S-1-5-{(1 << 64) - 1}-579': ldb.ERR_UNWILLING_TO_PERFORM,
388 f'S-1-5-{1 << 64}-579': ldb.ERR_UNWILLING_TO_PERFORM,
389 'S-0x1-5-40-579': ldb.ERR_UNWILLING_TO_PERFORM,
390 'S-0x1-0-0-579': ldb.ERR_UNWILLING_TO_PERFORM,
391 'S-0x1-500000000-20-243': ldb.ERR_UNWILLING_TO_PERFORM,
392 'S-0x1-5-20-243': ldb.ERR_UNWILLING_TO_PERFORM,
393 'S-0x1-0x5-020-0243': ldb.ERR_UNWILLING_TO_PERFORM,
394 'S-1-0xABcDef123-0xABCDef123-579': ldb.ERR_UNWILLING_TO_PERFORM,
396 'S-0-5-32-579': ldb.ERR_UNWILLING_TO_PERFORM,
397 'S-2-5-32-579': ldb.ERR_UNWILLING_TO_PERFORM,
398 'S-10-5-32-579': ldb.ERR_UNWILLING_TO_PERFORM,
402 @DynamicTestCase
403 class SidStringsAsDnInSearchBase(SidStringBase):
404 """How does a bad <SID=x> dn work as a search base, if at all?
406 This suggests that Windows does the SID parsing
407 (INVALID_DN_SYNTAX) before starting the search (NO_SUCH_OBJECT).
409 Currently Samba does not.
411 skip_local = True
412 cases = {' S-1-1-1-1-1-1-1': ldb.ERR_INVALID_DN_SYNTAX,
413 'S-0-5-32-545': ldb.ERR_INVALID_DN_SYNTAX,
414 'S-000000000001-5-20-243': ldb.ERR_INVALID_DN_SYNTAX,
415 'S-000000001-5-32-545': ldb.ERR_INVALID_DN_SYNTAX,
416 'S-01-05-020-0243': ldb.ERR_NO_SUCH_OBJECT,
417 'S-01-5-32-11579': ldb.ERR_NO_SUCH_OBJECT,
418 'S-0x1-0-0-579': ldb.ERR_INVALID_DN_SYNTAX,
419 'S-0x1-0x5-020-0243': ldb.ERR_INVALID_DN_SYNTAX,
420 'S-0x1-5-20-243': ldb.ERR_INVALID_DN_SYNTAX,
421 'S-0x1-5-40-579': ldb.ERR_INVALID_DN_SYNTAX,
422 'S-0x1-500000000-20-243': ldb.ERR_INVALID_DN_SYNTAX,
423 'S-1-0': ldb.ERR_NO_SUCH_OBJECT,
424 'S-1-0-0-579': ldb.ERR_NO_SUCH_OBJECT,
425 'S-1-0x05-32-11579': ldb.ERR_NO_SUCH_OBJECT,
426 'S-1-0x5-0x20-0x221': None,
427 'S-1-0x50000000-32-579': ldb.ERR_NO_SUCH_OBJECT,
428 'S-1-0x500000000-0x500000000-579': ldb.ERR_INVALID_DN_SYNTAX,
429 'S-1-0x500000000-32-579': ldb.ERR_NO_SUCH_OBJECT,
430 'S-1-0xABcDef123-0xABCDef123-579': ldb.ERR_INVALID_DN_SYNTAX,
431 'S-1-1-1-1-1-1-1': ldb.ERR_NO_SUCH_OBJECT,
432 'S-1-21474836480-32-579': ldb.ERR_NO_SUCH_OBJECT,
433 'S-1-22': ldb.ERR_NO_SUCH_OBJECT,
434 'S-1-22-1': ldb.ERR_NO_SUCH_OBJECT,
435 'S-1-22-1-0': ldb.ERR_NO_SUCH_OBJECT,
436 'S-1-281474976710655-579': ldb.ERR_NO_SUCH_OBJECT,
437 'S-1-281474976710656-579': ldb.ERR_INVALID_DN_SYNTAX,
438 'S-1-3-0': ldb.ERR_NO_SUCH_OBJECT,
439 'S-1-3-99': ldb.ERR_NO_SUCH_OBJECT,
440 'S-1-5-0-579': ldb.ERR_NO_SUCH_OBJECT,
441 'S-1-5-040-579': ldb.ERR_NO_SUCH_OBJECT,
442 'S-1-5-0x20-545': None,
443 'S-1-5-11111111111111111111111111111111111-579': ldb.ERR_INVALID_DN_SYNTAX,
444 'S-1-5-18446744073709551615-579': ldb.ERR_INVALID_DN_SYNTAX,
445 'S-1-5-18446744073709551616-579': ldb.ERR_INVALID_DN_SYNTAX,
446 'S-1-5-3 2-579': ldb.ERR_NO_SUCH_OBJECT,
447 'S-1-5-32 -11111579': None,
448 'S-1-5-32- 579': ldb.ERR_INVALID_DN_SYNTAX,
449 'S-1-5-32--579': ldb.ERR_INVALID_DN_SYNTAX,
450 'S-1-5-32-11579': ldb.ERR_NO_SUCH_OBJECT,
451 'S-1-5-4294967295-579': ldb.ERR_NO_SUCH_OBJECT,
452 'S-1-5-9999999999-579': ldb.ERR_INVALID_DN_SYNTAX,
453 'S-1-99999999999999999999999999999999999999-32-11111111111': ldb.ERR_INVALID_DN_SYNTAX,
454 'S-10-5-32-579': ldb.ERR_INVALID_DN_SYNTAX,
455 'S-2-5-32-579': ldb.ERR_INVALID_DN_SYNTAX,
456 's-1-5-32-5791': ldb.ERR_NO_SUCH_OBJECT,
457 's-1-5-32-545': None,
458 'AA': ldb.ERR_INVALID_DN_SYNTAX,
461 def _test_sid_string_with_args(self, code, expected):
462 try:
463 self.ldb.search(base=f"<SID={code}>",
464 scope=ldb.SCOPE_BASE,
465 attrs=[])
466 except ldb.LdbError as e:
467 self.assertEqual(e.args[0], expected)
468 else:
469 self.assertIsNone(expected)
472 @DynamicTestCase
473 class SidStringsAsDnSearchWithDnObject(SidStringBase):
474 """How does a bad <SID=x> dn work as a search base, if at all?
476 This time we parse the DN in ldb first.
478 skip_local = True
479 cases = {' S-1-1-1-1-1-1-1': ('parse error', None),
480 'S-0-5-32-579': (None, ldb.ERR_INVALID_DN_SYNTAX),
481 'S-000000000001-5-20-243': ('parse error', None),
482 'S-000000001-5-32-579': ('parse error', None),
483 'S-01-05-020-0243': (None, ldb.ERR_NO_SUCH_OBJECT),
484 'S-0x1-0-0-579': ('parse error', None),
485 'S-0x1-0x5-020-0243': ('parse error', None),
486 'S-0x1-5-20-243': ('parse error', None),
487 'S-0x1-5-40-579': ('parse error', None),
488 'S-0x1-500000000-20-243': ('parse error', None),
489 'S-1-0': (None, ldb.ERR_NO_SUCH_OBJECT),
490 'S-1-0-0-579': (None, ldb.ERR_NO_SUCH_OBJECT),
491 'S-1-0x05-32-579': (None, None),
492 'S-1-0x5-0x20-0x221': (None, None),
493 'S-1-0x50000000-32-579': (None, ldb.ERR_NO_SUCH_OBJECT),
494 'S-1-0x500000000-0x500000000-579': ('parse error', None),
495 'S-1-0x500000000-32-579': (None, ldb.ERR_NO_SUCH_OBJECT),
496 'S-1-0xABcDef123-0xABCDef123-579': ('parse error', None),
497 'S-1-1-1-1-1-1-1': (None, ldb.ERR_NO_SUCH_OBJECT),
498 'S-1-21474836480-32-579': (None, ldb.ERR_NO_SUCH_OBJECT),
499 'S-1-22': (None, ldb.ERR_NO_SUCH_OBJECT),
500 'S-1-22-1': (None, ldb.ERR_NO_SUCH_OBJECT),
501 'S-1-22-1-0': (None, ldb.ERR_NO_SUCH_OBJECT),
502 'S-1-281474976710655-579': (None, ldb.ERR_NO_SUCH_OBJECT),
503 'S-1-281474976710656-579': ('parse error', None),
504 'S-1-3-0': (None, ldb.ERR_NO_SUCH_OBJECT),
505 'S-1-3-99': (None, ldb.ERR_NO_SUCH_OBJECT),
506 'S-1-5-0-579': (None, ldb.ERR_NO_SUCH_OBJECT),
507 'S-1-5-040-579': (None, ldb.ERR_NO_SUCH_OBJECT),
508 'S-1-5-0x20-545': (None, None),
509 'S-1-5-11111111111111111111111111111111111-579': ('parse error', None),
510 'S-1-5-18446744073709551615-579': ('parse error', None),
511 'S-1-5-18446744073709551616-579': ('parse error', None),
512 'S-1-5-3 2-579': (None, ldb.ERR_NO_SUCH_OBJECT),
513 'S-1-5-32- 579': ('parse error', None),
514 'S-1-5-32--579': ('parse error', None),
515 'S-1-5-4294967295-579': (None, ldb.ERR_NO_SUCH_OBJECT),
516 'S-1-5-9999999999-579': ('parse error', None),
517 'S-1-99999999999999999999999999999999999999-32-11111111111': ('parse error',
518 None),
519 'S-10-5-32-579': (None, ldb.ERR_INVALID_DN_SYNTAX),
520 'S-2-5-32-579': (None, ldb.ERR_INVALID_DN_SYNTAX),
521 's-1-5-32-545': (None, None),
522 's-1-5-1234-5678': (None, ldb.ERR_NO_SUCH_OBJECT),
525 def _test_sid_string_with_args(self, code, expected):
526 dn_err, search_err = expected
527 dn_str = f"<SID={code}>"
528 try:
529 dn = ldb.Dn(self.ldb, dn_str)
530 except ValueError:
531 self.assertEqual(dn_err, 'parse error')
532 return
533 except ldb.LdbError as e:
534 self.assertEqual(dn_err, e.args[0])
535 return
537 self.assertIsNone(dn_err)
539 try:
540 self.ldb.search(dn, scope=ldb.SCOPE_BASE, attrs=['*'])
541 except ldb.LdbError as e:
542 self.assertEqual(search_err, e.args[0])
543 return
545 self.assertIsNone(search_err)
548 @DynamicTestCase
549 class SidStringsAsDnInSearchFilter(SidStringBase):
550 """How does a bad <SID=x> dn work in a search filter?
552 Answer: on Windows it always works.
554 skip_local = True
555 cases = {}
556 cases.update(SidStringTests.cases)
557 cases.update(SidStringsThatStartWithS.cases)
558 cases.update(SidStringBehavioursThatSambaPrefers.cases)
560 def _test_sid_string_with_args(self, code, _dummy):
561 basedn = self.ldb.get_default_basedn()
562 try:
563 self.ldb.search(base=basedn,
564 scope=ldb.SCOPE_ONELEVEL,
565 expression=f"(distinguishedName=<SID={code}>)")
566 except ldb.LdbError as e:
567 self.fail(f"expected no failure, got {e}")
570 @DynamicTestCase
571 class SidStringsForSimpleBind(SidStringBase):
572 """Check whether dodgy SID strings work differently for simple-bind.
574 One of the many fallbacks for ldap simple bind is SID strings. We
575 just want to ensure that SIDs that might fail strangely in SID
576 parsing don't leak that strangeness (they don't).
578 skip_local = True
579 # here we are testing only the SID-like SIDs ("S-1-...", not "AA")
580 cases = {}
581 cases.update(SidStringsThatStartWithS.cases)
582 cases.update(SidStringBehavioursThatSambaPrefers.cases)
584 @classmethod
585 def setUpClass(cls):
586 super().setUpClass()
587 server = os.environ['DC_SERVER']
588 cls.lp = param.LoadParm()
589 cls.host = f'ldap://{server}'
591 def _test_sid_string_with_args(self, code, _dummy):
592 bind_creds = Credentials()
593 bind_creds.set_username(code)
594 bind_creds.set_password("please")
596 try:
597 SamDB(url=self.host,
598 lp=self.lp,
599 credentials=bind_creds)
600 self.fail(f"{code} seems to have connected properly")
601 except ldb.LdbError as e:
602 num, msg = e.args
603 self.assertIn("NT_STATUS_INVALID_PARAMETER", msg)
606 if __name__ == '__main__':
607 global_asn1_print = False
608 global_hexdump = False
609 import unittest
610 unittest.main()