1 # LDIF helper functions for the samba_kcc tool
3 # Copyright (C) Dave Craft 2011
4 # Copyright (C) Andrew Bartlett 2015
6 # Andrew Bartlett's alleged work performed by his underlings Douglas
7 # Bagnall and Garming Sam.
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 from samba import Ldb, ldb, read_and_sub_file
25 from samba.auth import system_session
26 from samba.samdb import SamDB
27 from samba.common import dsdb_Dn
30 class LdifError(Exception):
34 def write_search_result(samdb, f, res):
36 lstr = samdb.write_ldif(msg, ldb.CHANGETYPE_NONE)
40 def ldif_to_samdb(dburl, lp, ldif_file, forced_local_dsa=None):
41 """Routine to import all objects and attributes that are relevent
42 to the KCC algorithms from a previously exported LDIF file.
44 The point of this function is to allow a programmer/debugger to
45 import an LDIF file with non-security relevent information that
46 was previously extracted from a DC database. The LDIF file is used
47 to create a temporary abbreviated database. The KCC algorithm can
48 then run against this abbreviated database for debug or test
49 verification that the topology generated is computationally the
50 same between different OSes and algorithms.
52 :param dburl: path to the temporary abbreviated db to create
53 :param ldif_file: path to the ldif file to import
55 if os.path.exists(dburl):
56 raise LdifError("Specify a database (%s) that doesn't already exist." %
59 # Use ["modules:"] as we are attempting to build a sam
60 # database as opposed to start it here.
61 tmpdb = Ldb(url=dburl, session_info=system_session(),
62 lp=lp, options=["modules:"])
64 tmpdb.transaction_start()
66 data = read_and_sub_file(ldif_file, None)
67 tmpdb.add_ldif(data, None)
69 tmpdb.modify_ldif("""dn: @ROOTDSE
71 replace: dsServiceName
72 dsServiceName: CN=NTDS Settings,%s
73 """ % forced_local_dsa)
75 tmpdb.add_ldif("""dn: @MODULES
76 @LIST: rootdse,extended_dn_in,extended_dn_out_ldb,objectguid
80 except Exception as estr:
81 tmpdb.transaction_cancel()
82 raise LdifError("Failed to import %s: %s" % (ldif_file, estr))
84 tmpdb.transaction_commit()
86 # We have an abbreviated list of options here because we have built
87 # an abbreviated database. We use the rootdse and extended-dn
88 # modules only during this re-open
89 samdb = SamDB(url=dburl, session_info=system_session(), lp=lp)
93 def samdb_to_ldif_file(samdb, dburl, lp, creds, ldif_file):
94 """Routine to extract all objects and attributes that are relevent
95 to the KCC algorithms from a DC database.
97 The point of this function is to allow a programmer/debugger to
98 extract an LDIF file with non-security relevent information from
99 a DC database. The LDIF file can then be used to "import" via
100 the import_ldif() function this file into a temporary abbreviated
101 database. The KCC algorithm can then run against this abbreviated
102 database for debug or test verification that the topology generated
103 is computationally the same between different OSes and algorithms.
105 :param dburl: LDAP database URL to extract info from
106 :param ldif_file: output LDIF file name to create
109 samdb = SamDB(url=dburl,
110 session_info=system_session(),
111 credentials=creds, lp=lp)
112 except ldb.LdbError as e:
113 (enum, estr) = e.args
114 raise LdifError("Unable to open sam database (%s) : %s" %
117 if os.path.exists(ldif_file):
118 raise LdifError("Specify a file (%s) that doesn't already exist." %
122 f = open(ldif_file, "w")
123 except IOError as ioerr:
124 raise LdifError("Unable to open (%s) : %s" % (ldif_file, str(ioerr)))
128 attrs = ["objectClass",
137 "msDS-NC-Replica-Locations",
138 "msDS-NC-RO-Replica-Locations"]
140 sstr = "CN=Partitions,%s" % samdb.get_config_basedn()
141 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
143 expression="(objectClass=crossRef)")
145 # Write partitions output
146 write_search_result(samdb, f, res)
148 # Query cross reference container
149 attrs = ["objectClass",
155 "msDS-Behavior-Version",
156 "msDS-EnabledFeature"]
158 sstr = "CN=Partitions,%s" % samdb.get_config_basedn()
159 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
161 expression="(objectClass=crossRefContainer)")
163 # Write cross reference container output
164 write_search_result(samdb, f, res)
167 attrs = ["objectClass",
173 sstr = "CN=Sites,%s" % samdb.get_config_basedn()
174 sites = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
176 expression="(objectClass=site)")
179 write_search_result(samdb, f, sites)
181 # Query NTDS Site Settings
183 sitestr = str(msg.dn)
185 attrs = ["objectClass",
189 "interSiteTopologyGenerator",
190 "interSiteTopologyFailover",
194 sstr = "CN=NTDS Site Settings,%s" % sitestr
195 res = samdb.search(base=sstr, scope=ldb.SCOPE_BASE,
198 # Write Site Settings output
199 write_search_result(samdb, f, res)
201 # Naming context list
204 # Query Directory Service Agents
208 ncattrs = ["hasMasterNCs",
210 "hasPartialReplicaNCs",
212 "msDS-hasFullReplicaNCs",
213 "msDS-HasInstantiatedNCs"]
214 attrs = ["objectClass",
221 "msDS-Behavior-Version"]
223 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
224 attrs=attrs + ncattrs,
225 expression="(objectClass=nTDSDSA)")
227 # Spin thru all the DSAs looking for NC replicas
228 # and build a list of all possible Naming Contexts
229 # for subsequent retrieval below
234 # Some of these have binary DNs so
235 # use dsdb_Dn to split out relevent parts
236 dsdn = dsdb_Dn(samdb, value.decode('utf8'))
238 if dnstr not in nclist:
242 write_search_result(samdb, f, res)
244 # Query NTDS Connections
248 attrs = ["objectClass",
260 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
262 expression="(objectClass=nTDSConnection)")
263 # Write NTDS Connection output
264 write_search_result(samdb, f, res)
266 # Query Intersite transports
267 attrs = ["objectClass",
273 "bridgeheadServerListBL",
274 "transportAddressAttribute"]
276 sstr = "CN=Inter-Site Transports,CN=Sites,%s" % \
277 samdb.get_config_basedn()
278 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
280 expression="(objectClass=interSiteTransport)")
282 # Write inter-site transport output
283 write_search_result(samdb, f, res)
286 attrs = ["objectClass",
297 sstr = "CN=Sites,%s" % \
298 samdb.get_config_basedn()
299 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
301 expression="(objectClass=siteLink)",
302 controls=['extended_dn:0'])
304 # Write siteLink output
305 write_search_result(samdb, f, res)
307 # Query siteLinkBridge
308 attrs = ["objectClass",
314 sstr = "CN=Sites,%s" % samdb.get_config_basedn()
315 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
317 expression="(objectClass=siteLinkBridge)")
319 # Write siteLinkBridge output
320 write_search_result(samdb, f, res)
322 # Query servers containers
323 # Needed for samdb.server_site_name()
324 attrs = ["objectClass",
330 sstr = "CN=Sites,%s" % samdb.get_config_basedn()
331 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
333 expression="(objectClass=serversContainer)")
335 # Write servers container output
336 write_search_result(samdb, f, res)
339 # Needed because some transport interfaces refer back to
340 # attributes found in the server object. Also needed
341 # so extended-dn will be happy with dsServiceName in rootDSE
342 attrs = ["objectClass",
350 sstr = "CN=Sites,%s" % samdb.get_config_basedn()
351 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
353 expression="(objectClass=server)")
355 # Write server output
356 write_search_result(samdb, f, res)
358 # Query Naming Context replicas
359 attrs = ["objectClass",
365 "msDS-Behavior-Version",
370 res = samdb.search(sstr, scope=ldb.SCOPE_BASE,
373 # Write naming context output
374 write_search_result(samdb, f, res)
376 # Query rootDSE replicas
377 attrs = ["objectClass",
381 "rootDomainNamingContext",
382 "configurationNamingContext",
383 "schemaNamingContext",
384 "defaultNamingContext",
388 res = samdb.search(sstr, scope=ldb.SCOPE_BASE,
391 # Record the rootDSE object as a dn as it
392 # would appear in the base ldb file. We have
393 # to save it this way because we are going to
394 # be importing as an abbreviated database.
395 res[0].dn = ldb.Dn(samdb, "@ROOTDSE")
397 # Write rootdse output
398 write_search_result(samdb, f, res)
400 except ldb.LdbError as e1:
401 (enum, estr) = e1.args
402 raise LdifError("Error processing (%s) : %s" % (sstr, estr))