1# LDIF helper functions for the samba_kcc tool 2# 3# Copyright (C) Dave Craft 2011 4# Copyright (C) Andrew Bartlett 2015 5# 6# Andrew Bartlett's alleged work performed by his underlings Douglas 7# Bagnall and Garming Sam. 8# 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. 13# 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. 18# 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/>. 21 22import os 23 24from samba import Ldb, ldb, read_and_sub_file 25from samba.auth import system_session 26from samba.samdb import SamDB 27from samba.common import dsdb_Dn 28 29 30class LdifError(Exception): 31 pass 32 33 34def write_search_result(samdb, f, res): 35 for msg in res: 36 lstr = samdb.write_ldif(msg, ldb.CHANGETYPE_NONE) 37 f.write("%s" % lstr) 38 39 40def 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. 43 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. 51 52 :param dburl: path to the temporary abbreviated db to create 53 :param ldif_file: path to the ldif file to import 54 """ 55 if os.path.exists(dburl): 56 raise LdifError("Specify a database (%s) that doesn't already exist." % 57 dburl) 58 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:"]) 63 64 tmpdb.transaction_start() 65 try: 66 data = read_and_sub_file(ldif_file, None) 67 tmpdb.add_ldif(data, None) 68 if forced_local_dsa: 69 tmpdb.modify_ldif("""dn: @ROOTDSE 70changetype: modify 71replace: dsServiceName 72dsServiceName: CN=NTDS Settings,%s 73 """ % forced_local_dsa) 74 75 tmpdb.add_ldif("""dn: @MODULES 76@LIST: rootdse,extended_dn_in,extended_dn_out_ldb,objectguid 77- 78""") 79 80 except Exception as estr: 81 tmpdb.transaction_cancel() 82 raise LdifError("Failed to import %s: %s" % (ldif_file, estr)) 83 84 tmpdb.transaction_commit() 85 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) 90 return samdb 91 92 93def 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. 96 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. 104 105 :param dburl: LDAP database URL to extract info from 106 :param ldif_file: output LDIF file name to create 107 """ 108 try: 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" % 115 (dburl, estr)) 116 117 if os.path.exists(ldif_file): 118 raise LdifError("Specify a file (%s) that doesn't already exist." % 119 ldif_file) 120 121 try: 122 f = open(ldif_file, "w") 123 except IOError as ioerr: 124 raise LdifError("Unable to open (%s) : %s" % (ldif_file, str(ioerr))) 125 126 try: 127 # Query Partitions 128 attrs = ["objectClass", 129 "objectGUID", 130 "cn", 131 "whenChanged", 132 "objectSid", 133 "Enabled", 134 "systemFlags", 135 "dnsRoot", 136 "nCName", 137 "msDS-NC-Replica-Locations", 138 "msDS-NC-RO-Replica-Locations"] 139 140 sstr = "CN=Partitions,%s" % samdb.get_config_basedn() 141 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, 142 attrs=attrs, 143 expression="(objectClass=crossRef)") 144 145 # Write partitions output 146 write_search_result(samdb, f, res) 147 148 # Query cross reference container 149 attrs = ["objectClass", 150 "objectGUID", 151 "cn", 152 "whenChanged", 153 "fSMORoleOwner", 154 "systemFlags", 155 "msDS-Behavior-Version", 156 "msDS-EnabledFeature"] 157 158 sstr = "CN=Partitions,%s" % samdb.get_config_basedn() 159 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, 160 attrs=attrs, 161 expression="(objectClass=crossRefContainer)") 162 163 # Write cross reference container output 164 write_search_result(samdb, f, res) 165 166 # Query Sites 167 attrs = ["objectClass", 168 "objectGUID", 169 "cn", 170 "whenChanged", 171 "systemFlags"] 172 173 sstr = "CN=Sites,%s" % samdb.get_config_basedn() 174 sites = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, 175 attrs=attrs, 176 expression="(objectClass=site)") 177 178 # Write sites output 179 write_search_result(samdb, f, sites) 180 181 # Query NTDS Site Settings 182 for msg in sites: 183 sitestr = str(msg.dn) 184 185 attrs = ["objectClass", 186 "objectGUID", 187 "cn", 188 "whenChanged", 189 "interSiteTopologyGenerator", 190 "interSiteTopologyFailover", 191 "schedule", 192 "options"] 193 194 sstr = "CN=NTDS Site Settings,%s" % sitestr 195 res = samdb.search(base=sstr, scope=ldb.SCOPE_BASE, 196 attrs=attrs) 197 198 # Write Site Settings output 199 write_search_result(samdb, f, res) 200 201 # Naming context list 202 nclist = [] 203 204 # Query Directory Service Agents 205 for msg in sites: 206 sstr = str(msg.dn) 207 208 ncattrs = ["hasMasterNCs", 209 "msDS-hasMasterNCs", 210 "hasPartialReplicaNCs", 211 "msDS-HasDomainNCs", 212 "msDS-hasFullReplicaNCs", 213 "msDS-HasInstantiatedNCs"] 214 attrs = ["objectClass", 215 "objectGUID", 216 "cn", 217 "whenChanged", 218 "invocationID", 219 "options", 220 "msDS-isRODC", 221 "msDS-Behavior-Version"] 222 223 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, 224 attrs=attrs + ncattrs, 225 expression="(objectClass=nTDSDSA)") 226 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 230 for msg in res: 231 for k in msg.keys(): 232 if k in ncattrs: 233 for value in msg[k]: 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')) 237 dnstr = str(dsdn.dn) 238 if dnstr not in nclist: 239 nclist.append(dnstr) 240 241 # Write DSA output 242 write_search_result(samdb, f, res) 243 244 # Query NTDS Connections 245 for msg in sites: 246 sstr = str(msg.dn) 247 248 attrs = ["objectClass", 249 "objectGUID", 250 "cn", 251 "whenChanged", 252 "options", 253 "whenCreated", 254 "enabledConnection", 255 "schedule", 256 "transportType", 257 "fromServer", 258 "systemFlags"] 259 260 res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, 261 attrs=attrs, 262 expression="(objectClass=nTDSConnection)") 263 # Write NTDS Connection output 264 write_search_result(samdb, f, res) 265 266 # Query Intersite transports 267 attrs = ["objectClass", 268 "objectGUID", 269 "cn", 270 "whenChanged", 271 "options", 272 "name", 273 "bridgeheadServerListBL", 274 "transportAddressAttribute"] 275 276 sstr = "CN=Inter-Site Transports,CN=Sites,%s" % \ 277 samdb.get_config_basedn() 278 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, 279 attrs=attrs, 280 expression="(objectClass=interSiteTransport)") 281 282 # Write inter-site transport output 283 write_search_result(samdb, f, res) 284 285 # Query siteLink 286 attrs = ["objectClass", 287 "objectGUID", 288 "cn", 289 "whenChanged", 290 "systemFlags", 291 "options", 292 "schedule", 293 "replInterval", 294 "siteList", 295 "cost"] 296 297 sstr = "CN=Sites,%s" % \ 298 samdb.get_config_basedn() 299 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, 300 attrs=attrs, 301 expression="(objectClass=siteLink)", 302 controls=['extended_dn:0']) 303 304 # Write siteLink output 305 write_search_result(samdb, f, res) 306 307 # Query siteLinkBridge 308 attrs = ["objectClass", 309 "objectGUID", 310 "cn", 311 "whenChanged", 312 "siteLinkList"] 313 314 sstr = "CN=Sites,%s" % samdb.get_config_basedn() 315 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, 316 attrs=attrs, 317 expression="(objectClass=siteLinkBridge)") 318 319 # Write siteLinkBridge output 320 write_search_result(samdb, f, res) 321 322 # Query servers containers 323 # Needed for samdb.server_site_name() 324 attrs = ["objectClass", 325 "objectGUID", 326 "cn", 327 "whenChanged", 328 "systemFlags"] 329 330 sstr = "CN=Sites,%s" % samdb.get_config_basedn() 331 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, 332 attrs=attrs, 333 expression="(objectClass=serversContainer)") 334 335 # Write servers container output 336 write_search_result(samdb, f, res) 337 338 # Query servers 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", 343 "objectGUID", 344 "cn", 345 "whenChanged", 346 "systemFlags", 347 "dNSHostName", 348 "mailAddress"] 349 350 sstr = "CN=Sites,%s" % samdb.get_config_basedn() 351 res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, 352 attrs=attrs, 353 expression="(objectClass=server)") 354 355 # Write server output 356 write_search_result(samdb, f, res) 357 358 # Query Naming Context replicas 359 attrs = ["objectClass", 360 "objectGUID", 361 "cn", 362 "whenChanged", 363 "objectSid", 364 "fSMORoleOwner", 365 "msDS-Behavior-Version", 366 "repsFrom", 367 "repsTo"] 368 369 for sstr in nclist: 370 res = samdb.search(sstr, scope=ldb.SCOPE_BASE, 371 attrs=attrs) 372 373 # Write naming context output 374 write_search_result(samdb, f, res) 375 376 # Query rootDSE replicas 377 attrs = ["objectClass", 378 "objectGUID", 379 "cn", 380 "whenChanged", 381 "rootDomainNamingContext", 382 "configurationNamingContext", 383 "schemaNamingContext", 384 "defaultNamingContext", 385 "dsServiceName"] 386 387 sstr = "" 388 res = samdb.search(sstr, scope=ldb.SCOPE_BASE, 389 attrs=attrs) 390 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") 396 397 # Write rootdse output 398 write_search_result(samdb, f, res) 399 400 except ldb.LdbError as e1: 401 (enum, estr) = e1.args 402 raise LdifError("Error processing (%s) : %s" % (sstr, estr)) 403 404 f.close() 405