2009-10-30 06:31:25 +03:00
#
# Unix SMB/CIFS implementation.
# backend code for provisioning a Samba4 server
#
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
# Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
#
# 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.
2010-11-28 05:34:47 +03:00
#
2009-10-30 06:31:25 +03:00
# 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.
2010-11-28 05:34:47 +03:00
#
2009-10-30 06:31:25 +03:00
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
""" Functions for setting up a Samba Schema. """
from base64 import b64encode
from samba import read_and_sub_file , substitute_var , check_all_substituted
2010-06-11 01:12:53 +04:00
from samba . dcerpc import security
from samba . ms_schema import read_ms_schema
2009-10-30 06:31:25 +03:00
from samba . ndr import ndr_pack
2010-06-11 01:12:53 +04:00
from samba . samdb import SamDB
2020-09-11 23:29:46 +03:00
from samba . common import get_string
2010-06-20 03:43:51 +04:00
from samba import dsdb
2010-03-01 06:46:40 +03:00
from ldb import SCOPE_SUBTREE , SCOPE_ONELEVEL
2009-10-30 06:31:25 +03:00
2018-07-30 09:20:39 +03:00
2013-01-23 18:53:00 +04:00
def get_schema_descriptor ( domain_sid , name_map = { } ) :
2010-10-23 22:27:50 +04:00
sddl = " O:SAG:SAD:AI(OA;;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;SA) " \
" (OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED) " \
" (OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED) " \
" (OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED) " \
" (OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA) " \
" (OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA) " \
" (OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA) " \
" (A;CI;RPLCLORC;;;AU) " \
" (A;CI;RPWPCRCCLCLORCWOWDSW;;;SA) " \
" (A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY) " \
" (OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED) " \
" (OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED) " \
" (OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA) " \
" (OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA) " \
2022-03-15 00:20:59 +03:00
" (OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;RO) " \
" (OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;RO) " \
" (OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;RO) " \
2010-10-23 22:27:50 +04:00
" S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD) " \
" (AU;CISA;WP;;;WD) " \
" (AU;SA;CR;;;BA) " \
" (AU;SA;CR;;;DU) " \
" (OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD) " \
" (OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD) "
2009-10-30 06:31:25 +03:00
sec = security . descriptor . from_sddl ( sddl , domain_sid )
2009-11-27 17:37:14 +03:00
return ndr_pack ( sec )
2009-10-30 06:31:25 +03:00
2010-11-28 05:34:47 +03:00
2009-10-30 06:31:25 +03:00
class Schema ( object ) :
2010-04-09 00:14:50 +04:00
2017-10-04 02:30:59 +03:00
# the schema files (and corresponding object version) that we know about
base_schemas = {
2018-07-30 09:17:14 +03:00
" 2008_R2_old " : ( " MS-AD_Schema_2K8_R2_Attributes.txt " ,
2018-09-03 16:05:48 +03:00
" MS-AD_Schema_2K8_R2_Classes.txt " ,
47 ) ,
2018-07-30 09:17:14 +03:00
" 2008_R2 " : ( " Attributes_for_AD_DS__Windows_Server_2008_R2.ldf " ,
2018-09-03 16:05:48 +03:00
" Classes_for_AD_DS__Windows_Server_2008_R2.ldf " ,
47 ) ,
2018-07-30 09:17:14 +03:00
" 2012 " : ( " AD_DS_Attributes__Windows_Server_2012.ldf " ,
2018-09-03 16:05:48 +03:00
" AD_DS_Classes__Windows_Server_2012.ldf " ,
56 ) ,
2018-07-30 09:17:14 +03:00
" 2012_R2 " : ( " AD_DS_Attributes__Windows_Server_2012_R2.ldf " ,
2018-09-03 16:05:48 +03:00
" AD_DS_Classes__Windows_Server_2012_R2.ldf " ,
69 ) ,
2017-10-04 02:30:59 +03:00
}
2011-02-05 02:34:51 +03:00
def __init__ ( self , domain_sid , invocationid = None , schemadn = None ,
2017-10-04 02:30:59 +03:00
files = None , override_prefixmap = None , additional_prefixmap = None ,
base_schema = None ) :
2011-02-05 02:34:51 +03:00
from samba . provision import setup_path
2010-11-28 05:34:47 +03:00
""" Load schema for the SamDB from the AD schema files and
samba4_schema . ldif
2009-10-30 06:31:25 +03:00
: param samdb : Load a schema into a SamDB .
: param schemadn : DN of the schema
2010-11-28 05:34:47 +03:00
Returns the schema data loaded , to avoid double - parsing when then
needing to add it to the db
2009-10-30 06:31:25 +03:00
"""
2017-10-04 02:30:59 +03:00
if base_schema is None :
base_schema = Schema . default_base_schema ( )
self . base_schema = base_schema
2009-10-30 06:31:25 +03:00
self . schemadn = schemadn
2010-11-28 16:09:30 +03:00
# We need to have the am_rodc=False just to keep some warnings quiet -
# this isn't a real SAM, so it's meaningless.
2010-06-21 17:18:53 +04:00
self . ldb = SamDB ( global_schema = False , am_rodc = False )
2010-04-20 05:48:51 +04:00
if invocationid is not None :
self . ldb . set_invocation_id ( invocationid )
2010-04-09 00:14:50 +04:00
self . schema_data = read_ms_schema (
2017-10-04 02:30:59 +03:00
setup_path ( ' ad-schema/ %s ' % Schema . base_schemas [ base_schema ] [ 0 ] ) ,
setup_path ( ' ad-schema/ %s ' % Schema . base_schemas [ base_schema ] [ 1 ] ) )
2009-11-13 18:58:20 +03:00
2021-09-01 06:42:28 +03:00
def read_file ( file ) :
with open ( file , ' rb ' ) as data_file :
return data_file . read ( )
2009-11-13 18:58:20 +03:00
if files is not None :
2021-09-01 06:42:28 +03:00
self . schema_data = " " . join ( get_string ( read_file ( file ) )
for file in files )
2009-11-13 18:58:20 +03:00
2010-04-09 00:14:50 +04:00
self . schema_data = substitute_var ( self . schema_data ,
2018-07-30 09:16:12 +03:00
{ " SCHEMADN " : schemadn } )
2009-10-30 06:31:25 +03:00
check_all_substituted ( self . schema_data )
2017-10-04 02:30:59 +03:00
schema_version = str ( Schema . get_version ( base_schema ) )
2010-04-09 00:14:50 +04:00
self . schema_dn_modify = read_and_sub_file (
setup_path ( " provision_schema_basedn_modify.ldif " ) ,
2018-07-30 09:17:14 +03:00
{ " SCHEMADN " : schemadn , " OBJVERSION " : schema_version } )
2009-10-30 06:31:25 +03:00
2018-05-04 17:29:59 +03:00
descr = b64encode ( get_schema_descriptor ( domain_sid ) ) . decode ( ' utf8 ' )
2010-04-09 00:14:50 +04:00
self . schema_dn_add = read_and_sub_file (
setup_path ( " provision_schema_basedn.ldif " ) ,
{ " SCHEMADN " : schemadn , " DESCRIPTOR " : descr } )
2009-10-30 06:31:25 +03:00
2010-06-10 02:58:44 +04:00
if override_prefixmap is not None :
self . prefixmap_data = override_prefixmap
else :
2021-09-01 06:42:28 +03:00
self . prefixmap_data = read_file ( setup_path ( " prefixMap.txt " ) )
2009-11-13 18:58:20 +03:00
2010-06-10 02:58:44 +04:00
if additional_prefixmap is not None :
2019-08-26 00:06:19 +03:00
self . prefixmap_data + = " " . join ( " %s \n " % map for map in additional_prefixmap )
2009-11-13 18:58:20 +03:00
2018-05-04 17:29:59 +03:00
self . prefixmap_data = b64encode ( self . prefixmap_data ) . decode ( ' utf8 ' )
2009-10-30 06:31:25 +03:00
# We don't actually add this ldif, just parse it
2011-11-11 19:32:05 +04:00
prefixmap_ldif = " dn: %s \n prefixMap:: %s \n \n " % ( self . schemadn , self . prefixmap_data )
self . set_from_ldif ( prefixmap_ldif , self . schema_data , self . schemadn )
2010-06-20 04:32:23 +04:00
2017-10-04 02:30:59 +03:00
@staticmethod
def default_base_schema ( ) :
""" Returns the default base schema to use """
2019-01-15 01:19:54 +03:00
return " 2012_R2 "
2017-10-04 02:30:59 +03:00
@staticmethod
def get_version ( base_schema ) :
""" Returns the base schema ' s object version, e.g. 47 for 2008_R2 """
return Schema . base_schemas [ base_schema ] [ 2 ]
2011-11-11 19:32:05 +04:00
def set_from_ldif ( self , pf , df , dn ) :
2011-11-11 19:34:48 +04:00
dsdb . _dsdb_set_schema_from_ldif ( self . ldb , pf , df , dn )
2009-10-30 06:31:25 +03:00
def write_to_tmp_ldb ( self , schemadb_path ) :
2010-04-20 05:48:51 +04:00
self . ldb . connect ( url = schemadb_path )
2009-10-30 06:31:25 +03:00
self . ldb . transaction_start ( )
2010-04-09 00:14:50 +04:00
try :
2017-09-02 07:21:29 +03:00
# These are actually ignored, as the schema has been forced
# when the ldb object was created, and that overrides this
2010-04-09 00:14:50 +04:00
self . ldb . add_ldif ( """ dn: @ATTRIBUTES
2009-10-30 06:31:25 +03:00
linkID : INTEGER
dn : @INDEXLIST
@IDXATTR : linkID
@IDXATTR : attributeSyntax
2017-09-02 07:21:29 +03:00
@IDXGUID : objectGUID
2009-10-30 06:31:25 +03:00
""" )
2017-09-02 07:31:21 +03:00
schema_dn_add = self . schema_dn_add \
2018-09-03 16:05:26 +03:00
+ " objectGUID: 24e2ca70-b093-4ae8-84c0-2d7ac652a1b8 \n "
2017-09-02 07:31:21 +03:00
2010-04-09 00:14:50 +04:00
# These bits of LDIF are supplied when the Schema object is created
2017-09-02 07:31:21 +03:00
self . ldb . add_ldif ( schema_dn_add )
2010-04-09 00:14:50 +04:00
self . ldb . modify_ldif ( self . schema_dn_modify )
self . ldb . add_ldif ( self . schema_data )
2012-02-25 18:56:25 +04:00
except :
2010-04-09 00:14:50 +04:00
self . ldb . transaction_cancel ( )
raise
else :
self . ldb . transaction_commit ( )
2009-10-30 06:31:25 +03:00
2010-11-28 05:34:47 +03:00
# Return a hash with the forward attribute as a key and the back as the
# value
2009-10-30 06:31:25 +03:00
def linked_attributes ( self ) :
return get_linked_attributes ( self . schemadn , self . ldb )
def dnsyntax_attributes ( self ) :
return get_dnsyntax_attributes ( self . schemadn , self . ldb )
2010-06-20 03:43:51 +04:00
def convert_to_openldap ( self , target , mapping ) :
2010-06-20 17:22:49 +04:00
return dsdb . _dsdb_convert_schema_to_openldap ( self . ldb , target , mapping )
2010-06-20 03:43:51 +04:00
2012-09-27 20:30:47 +04:00
# Return a hash with the forward attribute as a key and the back as the value
2014-06-02 04:53:01 +04:00
def get_linked_attributes ( schemadn , schemaldb ) :
2009-10-30 06:31:25 +03:00
attrs = [ " linkID " , " lDAPDisplayName " ]
2014-06-02 04:53:01 +04:00
res = schemaldb . search (
expression = " (&(linkID=*) "
" (!(linkID:1.2.840.113556.1.4.803:=1)) "
" (objectclass=attributeSchema) "
" (attributeSyntax=2.5.5.1)) " ,
base = schemadn , scope = SCOPE_ONELEVEL , attrs = attrs )
2009-10-30 06:31:25 +03:00
attributes = { }
2014-06-02 03:33:43 +04:00
for i in range ( 0 , len ( res ) ) :
2014-06-02 04:53:01 +04:00
expression = ( " (&(objectclass=attributeSchema)(linkID= %d ) "
" (attributeSyntax=2.5.5.1)) " %
2018-07-30 09:18:25 +03:00
( int ( res [ i ] [ " linkID " ] [ 0 ] ) + 1 ) )
2012-09-27 20:30:47 +04:00
target = schemaldb . searchone ( basedn = schemadn ,
expression = expression ,
attribute = " lDAPDisplayName " ,
2009-10-30 06:31:25 +03:00
scope = SCOPE_SUBTREE )
if target is not None :
2018-07-30 09:18:03 +03:00
attributes [ str ( res [ i ] [ " lDAPDisplayName " ] ) ] = str ( target )
2010-11-28 05:34:47 +03:00
2009-10-30 06:31:25 +03:00
return attributes
2010-04-09 00:14:50 +04:00
2018-07-30 09:19:05 +03:00
def get_dnsyntax_attributes ( schemadn , schemaldb ) :
2010-04-09 00:14:50 +04:00
res = schemaldb . search (
expression = " (&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1)) " ,
base = schemadn , scope = SCOPE_ONELEVEL ,
attrs = [ " linkID " , " lDAPDisplayName " ] )
2009-10-30 06:31:25 +03:00
attributes = [ ]
2014-06-02 03:33:43 +04:00
for i in range ( 0 , len ( res ) ) :
2009-10-30 06:31:25 +03:00
attributes . append ( str ( res [ i ] [ " lDAPDisplayName " ] ) )
return attributes
2010-04-09 00:14:50 +04:00
2011-02-05 02:34:51 +03:00
def ldb_with_schema ( schemadn = " cn=schema,cn=configuration,dc=example,dc=com " ,
domainsid = None ,
override_prefixmap = None ) :
2009-11-10 07:18:52 +03:00
""" Load schema for the SamDB from the AD schema files and samba4_schema.ldif
2010-11-28 05:34:47 +03:00
2009-11-10 07:18:52 +03:00
: param schemadn : DN of the schema
: param serverdn : DN of the server
2010-11-28 05:34:47 +03:00
2009-11-10 07:18:52 +03:00
Returns the schema data loaded as an object , with . ldb being a
new ldb with the schema loaded . This allows certain tests to
operate without a remote or local schema .
"""
2010-11-28 05:34:47 +03:00
2009-11-10 07:18:52 +03:00
if domainsid is None :
domainsid = security . random_sid ( )
else :
domainsid = security . dom_sid ( domainsid )
2011-02-05 02:34:51 +03:00
return Schema ( domainsid , schemadn = schemadn ,
2018-07-30 09:16:12 +03:00
override_prefixmap = override_prefixmap )