2009-01-05 00:49:53 +03:00
# create schema.ldif (as a string) from WSPP documentation
#
# based on minschema.py and minschema_wspp
#
2011-09-13 03:10:37 +04:00
# 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/>.
2009-01-05 00:49:53 +03:00
2010-11-28 16:09:30 +03:00
""" Generate LDIF from WSPP documentation. """
2009-01-05 00:49:53 +03:00
import re
import base64
2009-11-16 10:45:21 +03:00
import uuid
2009-01-05 00:49:53 +03:00
bitFields = { }
# ADTS: 2.2.9
# bit positions as labeled in the docs
bitFields [ " searchflags " ] = {
' fATTINDEX ' : 31 , # IX
' fPDNTATTINDEX ' : 30 , # PI
2012-09-27 20:30:47 +04:00
' fANR ' : 29 , # AR
2009-01-05 00:49:53 +03:00
' fPRESERVEONDELETE ' : 28 , # PR
' fCOPY ' : 27 , # CP
' fTUPLEINDEX ' : 26 , # TP
' fSUBTREEATTINDEX ' : 25 , # ST
' fCONFIDENTIAL ' : 24 , # CF
2018-07-30 09:19:33 +03:00
' fCONFIDENTAIL ' : 24 , # typo
2009-01-05 00:49:53 +03:00
' fNEVERVALUEAUDIT ' : 23 , # NV
' fRODCAttribute ' : 22 , # RO
# missing in ADTS but required by LDIF
2018-04-28 08:22:29 +03:00
' fRODCFilteredAttribute ' : 22 , # RO
2018-07-30 09:19:33 +03:00
' fRODCFILTEREDATTRIBUTE ' : 22 , # case
2018-04-28 08:22:29 +03:00
' fEXTENDEDLINKTRACKING ' : 21 , # XL
' fBASEONLY ' : 20 , # BO
' fPARTITIONSECRET ' : 19 , # SE
2018-07-30 09:14:37 +03:00
}
2009-01-05 00:49:53 +03:00
# ADTS: 2.2.10
bitFields [ " systemflags " ] = {
2010-03-29 17:43:43 +04:00
' FLAG_ATTR_NOT_REPLICATED ' : 31 , ' FLAG_CR_NTDS_NC ' : 31 , # NR
' FLAG_ATTR_REQ_PARTIAL_SET_MEMBER ' : 30 , ' FLAG_CR_NTDS_DOMAIN ' : 30 , # PS
' FLAG_ATTR_IS_CONSTRUCTED ' : 29 , ' FLAG_CR_NTDS_NOT_GC_REPLICATED ' : 29 , # CS
' FLAG_ATTR_IS_OPERATIONAL ' : 28 , # OP
' FLAG_SCHEMA_BASE_OBJECT ' : 27 , # BS
' FLAG_ATTR_IS_RDN ' : 26 , # RD
' FLAG_DISALLOW_MOVE_ON_DELETE ' : 6 , # DE
' FLAG_DOMAIN_DISALLOW_MOVE ' : 5 , # DM
' FLAG_DOMAIN_DISALLOW_RENAME ' : 4 , # DR
' FLAG_CONFIG_ALLOW_LIMITED_MOVE ' : 3 , # AL
' FLAG_CONFIG_ALLOW_MOVE ' : 2 , # AM
' FLAG_CONFIG_ALLOW_RENAME ' : 1 , # AR
' FLAG_DISALLOW_DELETE ' : 0 # DD
2018-07-30 09:14:37 +03:00
}
2009-01-05 00:49:53 +03:00
# ADTS: 2.2.11
bitFields [ " schemaflagsex " ] = {
' FLAG_ATTR_IS_CRITICAL ' : 31
2018-07-30 09:14:37 +03:00
}
2009-01-05 00:49:53 +03:00
# ADTS: 3.1.1.2.2.2
oMObjectClassBER = {
2018-07-30 09:17:14 +03:00
' 1.3.12.2.1011.28.0.702 ' : base64 . b64encode ( b ' \x2B \x0C \x02 \x87 \x73 \x1C \x00 \x85 \x3E ' ) . decode ( ' utf8 ' ) ,
2018-03-05 19:08:01 +03:00
' 1.2.840.113556.1.1.1.12 ' : base64 . b64encode ( b ' \x2A \x86 \x48 \x86 \xF7 \x14 \x01 \x01 \x01 \x0C ' ) . decode ( ' utf8 ' ) ,
2018-07-30 09:17:14 +03:00
' 2.6.6.1.2.5.11.29 ' : base64 . b64encode ( b ' \x56 \x06 \x01 \x02 \x05 \x0B \x1D ' ) . decode ( ' utf8 ' ) ,
2018-03-05 19:08:01 +03:00
' 1.2.840.113556.1.1.1.11 ' : base64 . b64encode ( b ' \x2A \x86 \x48 \x86 \xF7 \x14 \x01 \x01 \x01 \x0B ' ) . decode ( ' utf8 ' ) ,
2018-07-30 09:17:14 +03:00
' 1.3.12.2.1011.28.0.714 ' : base64 . b64encode ( b ' \x2B \x0C \x02 \x87 \x73 \x1C \x00 \x85 \x4A ' ) . decode ( ' utf8 ' ) ,
' 1.3.12.2.1011.28.0.732 ' : base64 . b64encode ( b ' \x2B \x0C \x02 \x87 \x73 \x1C \x00 \x85 \x5C ' ) . decode ( ' utf8 ' ) ,
' 1.2.840.113556.1.1.1.6 ' : base64 . b64encode ( b ' \x2A \x86 \x48 \x86 \xF7 \x14 \x01 \x01 \x01 \x06 ' ) . decode ( ' utf8 ' )
2009-01-05 00:49:53 +03:00
}
# separated by commas in docs, and must be broken up
2018-07-30 09:19:05 +03:00
multivalued_attrs = set ( [ " auxiliaryclass " , " maycontain " , " mustcontain " , " posssuperiors " ,
" systemauxiliaryclass " , " systemmaycontain " , " systemmustcontain " ,
2009-01-05 00:49:53 +03:00
" systemposssuperiors " ] )
2018-07-30 09:20:39 +03:00
2009-01-05 00:49:53 +03:00
def __read_folded_line ( f , buffer ) :
""" reads a line from an LDIF file, unfolding it """
line = buffer
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
while True :
l = f . readline ( )
if l [ : 1 ] == " " :
# continued line
# cannot fold an empty line
assert ( line != " " and line != " \n " )
# preserves '\n '
line = line + l
else :
2012-09-27 20:30:47 +04:00
# non-continued line
2009-01-05 00:49:53 +03:00
if line == " " :
line = l
if l == " " :
# eof, definitely won't be folded
break
else :
# marks end of a folded line
# line contains the now unfolded line
# buffer contains the start of the next possibly folded line
buffer = l
break
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
return ( line , buffer )
def __read_raw_entries ( f ) :
""" reads an LDIF entry, only unfolding lines """
2017-01-19 18:24:34 +03:00
import sys
2009-01-05 00:49:53 +03:00
# will not match options after the attribute type
attr_type_re = re . compile ( " ^([A-Za-z]+[A-Za-z0-9-]*): " )
buffer = " "
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
while True :
entry = [ ]
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
while True :
( l , buffer ) = __read_folded_line ( f , buffer )
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
if l [ : 1 ] == " # " :
continue
if l == " \n " or l == " " :
break
m = attr_type_re . match ( l )
if m :
if l [ - 1 : ] == " \n " :
l = l [ : - 1 ]
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
entry . append ( l )
else :
2018-03-09 16:53:45 +03:00
print ( " Invalid line: %s " % l , end = ' ' , file = sys . stderr )
2009-01-05 00:49:53 +03:00
sys . exit ( 1 )
if len ( entry ) :
yield entry
if l == " " :
break
def fix_dn ( dn ) :
""" fix a string DN to use $ {SCHEMADN} """
# folding?
if dn . find ( " <RootDomainDN> " ) != - 1 :
dn = dn . replace ( " \n " , " " )
dn = dn . replace ( " " , " " )
return dn . replace ( " CN=Schema,CN=Configuration,<RootDomainDN> " , " $ {SCHEMADN} " )
2016-09-19 04:52:54 +03:00
elif dn . endswith ( " DC=X " ) :
return dn . replace ( " CN=Schema,CN=Configuration,DC=X " , " $ {SCHEMADN} " )
elif dn . endswith ( " CN=X " ) :
return dn . replace ( " CN=Schema,CN=Configuration,CN=X " , " $ {SCHEMADN} " )
2009-01-05 00:49:53 +03:00
else :
return dn
2018-07-30 09:20:39 +03:00
2009-01-05 00:49:53 +03:00
def __convert_bitfield ( key , value ) :
""" Evaluate the OR expression in ' value ' """
2020-07-04 04:47:44 +03:00
assert ( isinstance ( value , str ) )
2009-01-05 00:49:53 +03:00
value = value . replace ( " \n " , " " )
value = value . replace ( " " , " " )
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
try :
# some attributes already have numeric values
o = int ( value )
except ValueError :
o = 0
flags = value . split ( " | " )
for f in flags :
bitpos = bitFields [ key ] [ f ]
o = o | ( 1 << ( 31 - bitpos ) )
return str ( o )
2018-07-30 09:20:39 +03:00
2009-01-05 00:49:53 +03:00
def __write_ldif_one ( entry ) :
""" Write out entry as LDIF """
out = [ ]
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
for l in entry :
2020-07-04 04:47:44 +03:00
if isinstance ( l [ 1 ] , str ) :
2009-01-05 00:49:53 +03:00
vl = [ l [ 1 ] ]
else :
vl = l [ 1 ]
2017-08-18 04:46:57 +03:00
if l [ 2 ] :
2009-01-05 00:49:53 +03:00
out . append ( " %s :: %s " % ( l [ 0 ] , l [ 1 ] ) )
continue
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
for v in vl :
out . append ( " %s : %s " % ( l [ 0 ] , v ) )
return " \n " . join ( out )
2012-09-27 20:30:47 +04:00
2018-07-30 09:20:39 +03:00
2009-01-05 00:49:53 +03:00
def __transform_entry ( entry , objectClass ) :
""" Perform transformations required to convert the LDIF-like schema
file entries to LDIF , including Samba - specific stuff . """
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
entry = [ l . split ( " : " , 1 ) for l in entry ]
cn = " "
2017-08-18 04:46:57 +03:00
skip_dn = skip_objectclass = skip_admin_description = skip_admin_display_name = False
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
for l in entry :
2017-08-18 04:46:57 +03:00
if l [ 1 ] . startswith ( ' : ' ) :
l . append ( True )
l [ 1 ] = l [ 1 ] [ 2 : ]
else :
l . append ( False )
2009-01-05 00:49:53 +03:00
key = l [ 0 ] . lower ( )
l [ 1 ] = l [ 1 ] . lstrip ( )
2009-01-09 10:17:55 +03:00
l [ 1 ] = l [ 1 ] . rstrip ( )
2009-01-05 00:49:53 +03:00
if not cn and key == " cn " :
cn = l [ 1 ]
2011-09-13 03:10:37 +04:00
2009-01-05 00:49:53 +03:00
if key in multivalued_attrs :
# unlike LDIF, these are comma-separated
l [ 1 ] = l [ 1 ] . replace ( " \n " , " " )
l [ 1 ] = l [ 1 ] . replace ( " " , " " )
l [ 1 ] = l [ 1 ] . split ( " , " )
2011-09-13 03:10:37 +04:00
2009-01-05 00:49:53 +03:00
if key in bitFields :
l [ 1 ] = __convert_bitfield ( key , l [ 1 ] )
if key == " omobjectclass " :
2017-08-18 04:46:57 +03:00
if not l [ 2 ] :
l [ 1 ] = oMObjectClassBER [ l [ 1 ] . strip ( ) ]
l [ 2 ] = True
2009-01-05 00:49:53 +03:00
2020-07-04 04:47:44 +03:00
if isinstance ( l [ 1 ] , str ) :
2009-01-05 00:49:53 +03:00
l [ 1 ] = fix_dn ( l [ 1 ] )
2017-08-18 04:46:57 +03:00
if key == ' dn ' :
skip_dn = True
dn = l [ 1 ]
if key == ' objectclass ' :
skip_objectclass = True
elif key == ' admindisplayname ' :
skip_admin_display_name = True
elif key == ' admindescription ' :
skip_admin_description = True
2009-01-05 00:49:53 +03:00
assert ( cn )
2012-09-27 20:30:47 +04:00
2017-08-18 04:46:57 +03:00
header = [ ]
if not skip_dn :
header . append ( [ " dn " , " CN= %s ,$ {SCHEMADN} " % cn , False ] )
else :
header . append ( [ " dn " , dn , False ] )
if not skip_objectclass :
header . append ( [ " objectClass " , [ " top " , objectClass ] , False ] )
if not skip_admin_description :
header . append ( [ " adminDescription " , cn , False ] )
if not skip_admin_display_name :
header . append ( [ " adminDisplayName " , cn , False ] )
header . append ( [ " objectGUID " , str ( uuid . uuid4 ( ) ) , False ] )
2009-03-20 09:32:19 +03:00
2018-03-15 20:32:31 +03:00
entry = header + [ x for x in entry if x [ 0 ] . lower ( ) not in set ( [ ' dn ' , ' changetype ' , ' objectcategory ' ] ) ]
2009-03-20 09:32:19 +03:00
2009-01-05 00:49:53 +03:00
return entry
2018-07-30 09:20:39 +03:00
2009-01-05 00:49:53 +03:00
def __parse_schema_file ( filename , objectClass ) :
""" Load and transform a schema file. """
out = [ ]
2012-09-27 20:30:47 +04:00
2018-08-08 18:52:35 +03:00
from io import open
2021-09-01 06:42:28 +03:00
with open ( filename , " r " , encoding = ' latin-1 ' ) as f :
for entry in __read_raw_entries ( f ) :
out . append ( __write_ldif_one ( __transform_entry ( entry , objectClass ) ) )
2009-01-05 00:49:53 +03:00
return " \n \n " . join ( out )
2018-07-30 09:19:21 +03:00
def read_ms_schema ( attr_file , classes_file , dump_attributes = True , dump_classes = True , debug = False ) :
2009-01-05 00:49:53 +03:00
""" Read WSPP documentation-derived schema files. """
attr_ldif = " "
classes_ldif = " "
2012-09-27 20:30:47 +04:00
2009-01-05 00:49:53 +03:00
if dump_attributes :
2018-07-30 09:17:44 +03:00
attr_ldif = __parse_schema_file ( attr_file , " attributeSchema " )
2009-01-05 00:49:53 +03:00
if dump_classes :
classes_ldif = __parse_schema_file ( classes_file , " classSchema " )
return attr_ldif + " \n \n " + classes_ldif + " \n \n "
2009-03-05 09:03:13 +03:00
2018-07-30 09:21:29 +03:00
2009-03-05 09:03:13 +03:00
if __name__ == ' __main__ ' :
import sys
try :
attr_file = sys . argv [ 1 ]
classes_file = sys . argv [ 2 ]
except IndexError :
2018-03-09 16:53:45 +03:00
print ( " Usage: %s attr-file.txt classes-file.txt " % ( sys . argv [ 0 ] ) , file = sys . stderr )
2009-03-05 09:03:13 +03:00
sys . exit ( 1 )
2011-09-13 03:10:37 +04:00
2018-03-09 16:53:45 +03:00
print ( read_ms_schema ( attr_file , classes_file ) )