2022-03-15 09:24:38 +03:00
# 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 <http://www.gnu.org/licenses/>.
#
import os
import string
import sys
import time
2023-04-05 06:20:57 +03:00
from hashlib import blake2b
2022-03-15 09:24:38 +03:00
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 '
2023-04-05 07:05:59 +03:00
late_ERR_CONSTRAINT_VIOLATION = b " a hack to allow Windows to sometimes fail late "
2023-04-05 08:20:46 +03:00
class SidStringBase ( TestCase ) :
2022-03-15 09:24:38 +03:00
@classmethod
def setUpDynamicTestCases ( cls ) :
2023-04-13 02:47:19 +03:00
if not hasattr ( cls , ' skip_local ' ) :
cls . skip_local = env_get_var_value ( ' SAMBA_SID_STRINGS_SKIP_LOCAL ' ,
allow_missing = True )
2022-03-15 09:24:38 +03:00
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 )
2023-04-13 02:47:19 +03:00
if not cls . skip_local :
cls . generate_dynamic_test ( ' test_sid_string_internal ' , name ,
code , expected_sid )
2022-03-15 09:24:38 +03:00
@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 ( )
2023-04-05 06:16:21 +03:00
cls . timestamp = str ( int ( time . time ( ) ) )
2023-04-13 02:30:26 +03:00
cls . domain_sid = cls . ldb . get_domain_sid ( )
2022-03-15 09:24:38 +03:00
def _test_sid_string_with_args ( self , code , expected_sid ) :
2023-04-05 06:20:57 +03:00
suffix = int ( blake2b ( code . encode ( ) , digest_size = 3 ) . hexdigest ( ) , 16 )
2022-03-15 09:24:38 +03:00
2023-04-05 06:20:57 +03:00
class_name = f ' my-Sid-String-Class- { self . timestamp } - { suffix } '
2022-03-15 09:24:38 +03:00
class_ldap_display_name = class_name . replace ( ' - ' , ' ' )
class_dn = f ' CN= { class_name } , { self . schema_dn } '
2023-04-05 06:20:57 +03:00
governs_id = f ' 1.3.6.1.4.1.7165.4.6.2.9. { self . timestamp [ - 8 : ] } . { suffix } '
2023-04-05 07:05:59 +03:00
# expected_sid can be a SID string, an error code, None, or a
# special value indicating a deferred error, as follows:
#
# * a number represents the expected error code at the *first*
# hurdle, creating the classSchema object.
#
# * late_ERR_CONSTRAINT_VIOLATION means an error when
# creating an object based on the class schema.
#
# * None means a somewhat unspecified error or failure to set
# the object owner sid.
#
# * a string is the expected owner sid. The rid is borrowed
# * and tacked onto the governs-id.
if expected_sid is None :
expected_err = ldb . ERR_UNWILLING_TO_PERFORM
elif isinstance ( expected_sid , int ) :
expected_err = expected_sid
elif expected_sid is late_ERR_CONSTRAINT_VIOLATION :
expected_err = None
else :
expected_err = None
2022-08-25 11:21:01 +03:00
# Append the RID to our OID to ensure more uniqueness.
rid = expected_sid . rsplit ( ' - ' , 1 ) [ 1 ]
governs_id + = f ' . { rid } '
2022-03-15 09:24:38 +03:00
ldif = f '''
dn : { class_dn }
objectClass : classSchema
cn : { class_name }
2022-08-25 11:21:01 +03:00
governsId : { governs_id }
2022-03-15 09:24:38 +03:00
subClassOf : top
possSuperiors : domainDNS
defaultSecurityDescriptor : O : { code }
'''
try :
self . ldb . add_ldif ( ldif )
except ldb . LdbError as err :
num , _ = err . args
2023-04-05 07:05:59 +03:00
self . assertEqual ( num , expected_err )
2022-03-15 09:24:38 +03:00
return
2023-04-05 07:05:59 +03:00
else :
if isinstance ( expected_sid , int ) :
self . fail ( " should have failed " )
2022-03-15 09:24:38 +03:00
# 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 )
2023-04-05 06:20:57 +03:00
object_name = f ' sddl_ { self . timestamp } _ { suffix } '
2022-03-15 09:24:38 +03:00
object_dn = f ' CN= { object_name } , { self . base_dn } '
ldif = f '''
dn : { object_dn }
objectClass : { class_ldap_display_name }
cn : { object_name }
'''
2023-04-05 07:05:59 +03:00
if expected_sid is late_ERR_CONSTRAINT_VIOLATION :
expected_err = ldb . ERR_CONSTRAINT_VIOLATION
try :
self . ldb . add_ldif ( ldif )
except ldb . LdbError as err :
num , _ = err . args
self . assertEqual ( num , expected_err )
return
if expected_sid is not None :
self . assertIsNone ( expected_err )
2022-03-15 09:24:38 +03:00
# 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 )
2023-04-13 02:30:26 +03:00
expected_sid = self . format_expected_sid ( expected_sid )
owner_sid = str ( descriptor . owner_sid )
self . assertEqual ( expected_sid , owner_sid )
2022-03-15 09:24:38 +03:00
2023-04-13 02:30:26 +03:00
def format_expected_sid ( self , expected_sid ) :
2022-03-15 09:24:38 +03:00
if expected_sid is None :
2023-04-13 02:30:26 +03:00
return f ' { self . domain_sid } - { security . DOMAIN_RID_ADMINS } '
2022-03-15 09:24:38 +03:00
2023-04-13 02:30:26 +03:00
if not isinstance ( expected_sid , str ) :
# never going to match, should have failed already
return None
2022-03-15 09:24:38 +03:00
2023-04-13 02:30:26 +03:00
return expected_sid . format ( domain_sid = self . domain_sid )
2022-03-15 09:24:38 +03:00
2023-04-13 02:47:19 +03:00
def _test_sid_string_internal_with_args ( self , code , expected_sid ) :
""" We just want to test the SIDs, which Samba can ' t really do because
it doesn ' t parse them until creating an object using the
schema class , at which time it doesn ' t distinguish between a
missing value and a nonsense value .
So let ' s also run the test using libcli/security/sddl.c and
see what we * would * have done .
"""
sddl = f " O: { code } "
domsid = security . dom_sid ( self . domain_sid )
try :
sd = security . descriptor . from_sddl ( sddl , domsid )
except ValueError :
# we don't have detail as to what went wrong
self . assertNotIsInstance ( expected_sid , str )
else :
expected_sid = self . format_expected_sid ( expected_sid )
self . assertEqual ( expected_sid , str ( sd . owner_sid ) )
2023-04-05 08:20:46 +03:00
@DynamicTestCase
class SidStringTests ( SidStringBase ) :
2023-04-05 06:39:24 +03:00
""" Testing two letter aliases. """
2022-03-15 09:24:38 +03:00
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 ,
}
2023-04-05 06:39:24 +03:00
@DynamicTestCase
class SidStringsThatStartWithS ( SidStringBase ) :
""" Testing normal or normal-adjacent SIDs """
cases = {
# testing explicit string to string round trips.
' S-1-5-32-579 ' : ' S-1-5-32-579 ' ,
' S-1-5-0x20-579 ' : ' S-1-5-32-579 ' , # hex
' S-1-0x05-32-579 ' : ' S-1-5-32-579 ' ,
' S-1-5-040-579 ' : ' S-1-5-40-579 ' , # no octal
' S-1-0x50000000-32-579 ' : ' S-1-1342177280-32-579 ' ,
' S-1-0x500000000-32-579 ' : ' S-1-0x500000000-32-579 ' ,
' S-1-21474836480-32-579 ' : ' S-1-0x500000000-32-579 ' , # >32 bit is hex
f ' S-1-5- { ( 1 << 32 ) - 1 } -579 ' : ' S-1-5-4294967295-579 ' ,
f ' S-1- { ( 1 << 48 ) - 1 } -579 ' : ' S-1-0xffffffffffff-579 ' ,
f ' S-1- { ( 1 << 48 ) } -579 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-99999999999999999999999999999999999999-32-11111111111 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-5-0-579 ' : ' S-1-5-0-579 ' ,
' S-1-0-0-579 ' : ' S-1-0-0-579 ' ,
' S-1-0x5-0x20-0x243 ' : ' S-1-5-32-579 ' ,
' S-1-5-32--579 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-5-32- 579 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-5-32 -579 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-5-3 2-579 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-1-1-1-1-1-1 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
# go to lower case in hex.
' S-1-0xABcDef123-0xABCDef-579 ' : ' S-1-0xabcdef123-11259375-579 ' ,
' S-1-1-1-1-1-1-1 ' : ' S-1-1-1-1-1-1-1 ' ,
' s-1-5-32-579 ' : ' S-1-5-32-579 ' ,
' S-01-5-32-579 ' : ' S-1-5-32-579 ' ,
' S-000000001-5-32-579 ' : ' S-1-5-32-579 ' ,
# some strings from https://bugzilla.samba.org/show_bug.cgi?id=14213
' S-1-0 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-22 ' : ldb . ERR_UNWILLING_TO_PERFORM ,
' S-1-22-1 ' : ' S-1-22-1 ' ,
' S-1-22-1-0 ' : ' S-1-22-1-0 ' ,
' S-1-3-0 ' : ' S-1-3-0 ' ,
' S-1-3-99 ' : ' S-1-3-99 ' ,
' S-01-05-020-0243 ' : ' S-1-5-20-243 ' ,
' S-000000000001-5-20-243 ' : ' S-1-5-20-243 ' ,
' S-1-000000000000000005-20-243 ' : ' S-1-5-20-243 ' ,
' S-1-5-20-00000000000243 ' : ' S-1-5-20-243 ' ,
}
2022-03-15 09:24:38 +03:00
if __name__ == ' __main__ ' :
global_asn1_print = False
global_hexdump = False
import unittest
unittest . main ( )