summaryrefslogtreecommitdiffstats
path: root/python/samba/kcc/ldif_import_export.py
blob: 41f0fd75778c3e6300a92bcc5b93226183a7c4eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# LDIF helper functions for the samba_kcc tool
#
# Copyright (C) Dave Craft 2011
# Copyright (C) Andrew Bartlett 2015
#
# Andrew Bartlett's alleged work performed by his underlings Douglas
# Bagnall and Garming Sam.
#
# 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

from samba import Ldb, ldb, read_and_sub_file
from samba.auth import system_session
from samba.samdb import SamDB, dsdb_Dn


class LdifError(Exception):
    pass


def write_search_result(samdb, f, res):
    for msg in res:
        lstr = samdb.write_ldif(msg, ldb.CHANGETYPE_NONE)
        f.write("%s" % lstr)


def ldif_to_samdb(dburl, lp, ldif_file, forced_local_dsa=None):
    """Routine to import all objects and attributes that are relevant
    to the KCC algorithms from a previously exported LDIF file.

    The point of this function is to allow a programmer/debugger to
    import an LDIF file with non-security relevant information that
    was previously extracted from a DC database.  The LDIF file is used
    to create a temporary abbreviated database.  The KCC algorithm can
    then run against this abbreviated database for debug or test
    verification that the topology generated is computationally the
    same between different OSes and algorithms.

    :param dburl: path to the temporary abbreviated db to create
    :param ldif_file: path to the ldif file to import
    """
    if os.path.exists(dburl):
        raise LdifError("Specify a database (%s) that doesn't already exist." %
                        dburl)

    # Use ["modules:"] as we are attempting to build a sam
    # database as opposed to start it here.
    tmpdb = Ldb(url=dburl, session_info=system_session(),
                lp=lp, options=["modules:"])

    tmpdb.transaction_start()
    try:
        data = read_and_sub_file(ldif_file, None)
        tmpdb.add_ldif(data, None)
        if forced_local_dsa:
            tmpdb.modify_ldif("""dn: @ROOTDSE
changetype: modify
replace: dsServiceName
dsServiceName: CN=NTDS Settings,%s
            """ % forced_local_dsa)

        tmpdb.add_ldif("""dn: @MODULES
@LIST: rootdse,extended_dn_in,extended_dn_out_ldb,objectguid
-
""")

    except Exception as estr:
        tmpdb.transaction_cancel()
        raise LdifError("Failed to import %s: %s" % (ldif_file, estr))

    tmpdb.transaction_commit()

    # We have an abbreviated list of options here because we have built
    # an abbreviated database.  We use the rootdse and extended-dn
    # modules only during this re-open
    samdb = SamDB(url=dburl, session_info=system_session(), lp=lp)
    return samdb


def samdb_to_ldif_file(samdb, dburl, lp, creds, ldif_file):
    """Routine to extract all objects and attributes that are relevant
    to the KCC algorithms from a DC database.

    The point of this function is to allow a programmer/debugger to
    extract an LDIF file with non-security relevant information from
    a DC database.  The LDIF file can then be used to "import" via
    the import_ldif() function this file into a temporary abbreviated
    database.  The KCC algorithm can then run against this abbreviated
    database for debug or test verification that the topology generated
    is computationally the same between different OSes and algorithms.

    :param dburl: LDAP database URL to extract info from
    :param ldif_file: output LDIF file name to create
    """
    try:
        samdb = SamDB(url=dburl,
                      session_info=system_session(),
                      credentials=creds, lp=lp)
    except ldb.LdbError as e:
        (enum, estr) = e.args
        raise LdifError("Unable to open sam database (%s) : %s" %
                        (dburl, estr))

    if os.path.exists(ldif_file):
        raise LdifError("Specify a file (%s) that doesn't already exist." %
                        ldif_file)

    try:
        f = open(ldif_file, "w")
    except IOError as ioerr:
        raise LdifError("Unable to open (%s) : %s" % (ldif_file, str(ioerr)))

    try:
        # Query Partitions
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "objectSid",
                 "Enabled",
                 "systemFlags",
                 "dnsRoot",
                 "nCName",
                 "msDS-NC-Replica-Locations",
                 "msDS-NC-RO-Replica-Locations"]

        sstr = "CN=Partitions,%s" % samdb.get_config_basedn()
        res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=crossRef)")

        # Write partitions output
        write_search_result(samdb, f, res)

        # Query cross reference container
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "fSMORoleOwner",
                 "systemFlags",
                 "msDS-Behavior-Version",
                 "msDS-EnabledFeature"]

        sstr = "CN=Partitions,%s" % samdb.get_config_basedn()
        res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=crossRefContainer)")

        # Write cross reference container output
        write_search_result(samdb, f, res)

        # Query Sites
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "systemFlags"]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        sites = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
                             attrs=attrs,
                             expression="(objectClass=site)")

        # Write sites output
        write_search_result(samdb, f, sites)

        # Query NTDS Site Settings
        for msg in sites:
            sitestr = str(msg.dn)

            attrs = ["objectClass",
                     "objectGUID",
                     "cn",
                     "whenChanged",
                     "interSiteTopologyGenerator",
                     "interSiteTopologyFailover",
                     "schedule",
                     "options"]

            sstr = "CN=NTDS Site Settings,%s" % sitestr
            res = samdb.search(base=sstr, scope=ldb.SCOPE_BASE,
                               attrs=attrs)

            # Write Site Settings output
            write_search_result(samdb, f, res)

        # Naming context list
        nclist = []

        # Query Directory Service Agents
        for msg in sites:
            sstr = str(msg.dn)

            ncattrs = ["hasMasterNCs",
                       "msDS-hasMasterNCs",
                       "hasPartialReplicaNCs",
                       "msDS-HasDomainNCs",
                       "msDS-hasFullReplicaNCs",
                       "msDS-HasInstantiatedNCs"]
            attrs = ["objectClass",
                     "objectGUID",
                     "cn",
                     "whenChanged",
                     "invocationID",
                     "options",
                     "msDS-isRODC",
                     "msDS-Behavior-Version"]

            res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
                               attrs=attrs + ncattrs,
                               expression="(objectClass=nTDSDSA)")

            # Spin thru all the DSAs looking for NC replicas
            # and build a list of all possible Naming Contexts
            # for subsequent retrieval below
            for res_msg in res:
                for k in res_msg.keys():
                    if k in ncattrs:
                        for value in res_msg[k]:
                            # Some of these have binary DNs so
                            # use dsdb_Dn to split out relevant parts
                            dsdn = dsdb_Dn(samdb, value.decode('utf8'))
                            dnstr = str(dsdn.dn)
                            if dnstr not in nclist:
                                nclist.append(dnstr)

            # Write DSA output
            write_search_result(samdb, f, res)

        # Query NTDS Connections
        for msg in sites:
            sstr = str(msg.dn)

            attrs = ["objectClass",
                     "objectGUID",
                     "cn",
                     "whenChanged",
                     "options",
                     "whenCreated",
                     "enabledConnection",
                     "schedule",
                     "transportType",
                     "fromServer",
                     "systemFlags"]

            res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE,
                               attrs=attrs,
                               expression="(objectClass=nTDSConnection)")
            # Write NTDS Connection output
            write_search_result(samdb, f, res)

        # Query Intersite transports
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "options",
                 "name",
                 "bridgeheadServerListBL",
                 "transportAddressAttribute"]

        sstr = "CN=Inter-Site Transports,CN=Sites,%s" % \
               samdb.get_config_basedn()
        res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=interSiteTransport)")

        # Write inter-site transport output
        write_search_result(samdb, f, res)

        # Query siteLink
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "systemFlags",
                 "options",
                 "schedule",
                 "replInterval",
                 "siteList",
                 "cost"]

        sstr = "CN=Sites,%s" % \
               samdb.get_config_basedn()
        res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=siteLink)",
                           controls=['extended_dn:0'])

        # Write siteLink output
        write_search_result(samdb, f, res)

        # Query siteLinkBridge
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "siteLinkList"]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=siteLinkBridge)")

        # Write siteLinkBridge output
        write_search_result(samdb, f, res)

        # Query servers containers
        # Needed for samdb.server_site_name()
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "systemFlags"]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=serversContainer)")

        # Write servers container output
        write_search_result(samdb, f, res)

        # Query servers
        # Needed because some transport interfaces refer back to
        # attributes found in the server object.   Also needed
        # so extended-dn will be happy with dsServiceName in rootDSE
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "systemFlags",
                 "dNSHostName",
                 "mailAddress"]

        sstr = "CN=Sites,%s" % samdb.get_config_basedn()
        res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE,
                           attrs=attrs,
                           expression="(objectClass=server)")

        # Write server output
        write_search_result(samdb, f, res)

        # Query Naming Context replicas
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "objectSid",
                 "fSMORoleOwner",
                 "msDS-Behavior-Version",
                 "repsFrom",
                 "repsTo"]

        for sstr in nclist:
            res = samdb.search(sstr, scope=ldb.SCOPE_BASE,
                               attrs=attrs)

            # Write naming context output
            write_search_result(samdb, f, res)

        # Query rootDSE replicas
        attrs = ["objectClass",
                 "objectGUID",
                 "cn",
                 "whenChanged",
                 "rootDomainNamingContext",
                 "configurationNamingContext",
                 "schemaNamingContext",
                 "defaultNamingContext",
                 "dsServiceName"]

        sstr = ""
        res = samdb.search(sstr, scope=ldb.SCOPE_BASE,
                           attrs=attrs)

        # Record the rootDSE object as a dn as it
        # would appear in the base ldb file.  We have
        # to save it this way because we are going to
        # be importing as an abbreviated database.
        res[0].dn = ldb.Dn(samdb, "@ROOTDSE")

        # Write rootdse output
        write_search_result(samdb, f, res)

    except ldb.LdbError as e1:
        (enum, estr) = e1.args
        raise LdifError("Error processing (%s) : %s" % (sstr, estr))

    f.close()