python/samba: bulk conversion of caller to dsdb_Dn 2nd param.
[samba.git] / python / samba / kcc / ldif_import_export.py
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
22 import os
23
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
28
29
30 class LdifError(Exception):
31     pass
32
33
34 def 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
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.
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
70 changetype: modify
71 replace: dsServiceName
72 dsServiceName: 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
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.
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()