samba-tool domain demote: Use dn.add_base/dn.add_child
[nivanova/samba-autobuild/.git] / python / samba / remove_dc.py
1 # Unix SMB/CIFS implementation.
2 # Copyright Matthieu Patou <mat@matws.net> 2011
3 # Copyright Andrew Bartlett <abartlet@samba.org> 2008-2015
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import ldb
20 from ldb import LdbError
21 from samba.ndr import ndr_unpack
22 from samba.dcerpc import misc
23
24 class DemoteException(Exception):
25     """Base element for demote errors"""
26
27     def __init__(self, value):
28         self.value = value
29
30     def __str__(self):
31         return "DemoteException: " + self.value
32
33
34 def remove_sysvol_references(samdb, dc_name):
35     # DNs under the Configuration DN:
36     realm = samdb.domain_dns_name()
37     for s in ("CN=Enterprise,CN=Microsoft System Volumes,CN=System",
38               "CN=%s,CN=Microsoft System Volumes,CN=System" % realm):
39         dn = ldb.Dn(samdb, s)
40
41         # This is verbose, but it is the safe, escape-proof way
42         # to add a base and add an arbitrary RDN.
43         if dn.add_base(samdb.get_config_basedn()) == False:
44             raise DemoteException("Failed constructing DN %s by adding base %s" \
45                                   % (dn, samdb.get_config_basedn()))
46         if dn.add_child("CN=X") == False:
47             raise DemoteException("Failed constructing DN %s by adding child CN=X"\
48                                       % (dn))
49         dn.set_component(0, "CN", dc_name)
50         try:
51             samdb.delete(dn)
52         except ldb.LdbError as (enum, estr):
53             if enum == ldb.ERR_NO_SUCH_OBJECT:
54                 pass
55             else:
56                 raise
57
58     # DNs under the Domain DN:
59     for s in ("CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System",
60               "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System"):
61         # This is verbose, but it is the safe, escape-proof way
62         # to add a base and add an arbitrary RDN.
63         dn = ldb.Dn(samdb, s)
64         if dn.add_base(samdb.get_default_basedn()) == False:
65             raise DemoteException("Failed constructing DN %s by adding base" % \
66                                   (dn, samdb.get_default_basedn()))
67         if dn.add_child("CN=X") == False:
68             raise DemoteException("Failed constructing DN %s by adding child %s"\
69                                   % (dn, rdn))
70         dn.set_component(0, "CN", dc_name)
71         try:
72             samdb.delete(dn)
73         except ldb.LdbError as (enum, estr):
74             if enum == ldb.ERR_NO_SUCH_OBJECT:
75                 pass
76             else:
77                 raise
78
79
80 def remove_dns_references(samdb, dnsHostName):
81
82     # Check we are using in-database DNS
83     zones = samdb.search(base="", scope=ldb.SCOPE_SUBTREE,
84                          expression="(&(objectClass=dnsZone)(!(dc=RootDNSServers)))",
85                          attrs=[],
86                          controls=["search_options:0:2"])
87     if len(zones) == 0:
88         return
89
90     try:
91         rec = samdb.dns_lookup(dnsHostName)
92     except RuntimeError as (enum, estr):
93         if enum == 0x000025F2: #WERR_DNS_ERROR_NAME_DOES_NOT_EXIST
94               return
95         raise DemoteException("lookup of %s failed: %s" % (dnsHostName, estr))
96     samdb.dns_replace(dnsHostName, [])
97
98 def offline_remove_server(samdb, server_dn,
99                           remove_computer_obj=False,
100                           remove_server_obj=False,
101                           remove_sysvol_obj=False,
102                           remove_dns_names=False):
103     res = samdb.search("",
104                        scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
105     assert len(res) == 1
106     my_serviceName = res[0]["dsServiceName"][0]
107
108     # Confirm this is really a server object
109     msgs = samdb.search(base=server_dn,
110                         attrs=["serverReference", "cn",
111                                "dnsHostName"],
112                         scope=ldb.SCOPE_BASE,
113                         expression="(objectClass=server)")
114     msg = msgs[0]
115     dc_name = str(msgs[0]["cn"][0])
116
117     try:
118         computer_dn = ldb.Dn(samdb, msgs[0]["serverReference"][0])
119     except KeyError:
120         computer_dn = None
121
122     try:
123         dnsHostName = msgs[0]["dnsHostName"][0]
124     except KeyError:
125         dnsHostName = None
126
127     if remove_server_obj:
128         # Remove the server DN
129         samdb.delete(server_dn)
130
131     if computer_dn is not None:
132         computer_msgs = samdb.search(base=computer_dn,
133                                      expression="objectclass=computer",
134                                      attrs=["msDS-KrbTgtLink",
135                                             "rIDSetReferences"],
136                                      scope=ldb.SCOPE_BASE)
137         if "rIDSetReferences" in computer_msgs[0]:
138             samdb.delete(computer_msgs[0]["rIDSetReferences"][0])
139         if "msDS-KrbTgtLink" in computer_msgs[0]:
140             samdb.delete(computer_msgs[0]["msDS-KrbTgtLink"][0])
141
142         if remove_computer_obj:
143             # Delete the computer tree
144             samdb.delete(computer_dn, ["tree_delete:0"])
145
146         if "dnsHostName" in msgs[0]:
147             dnsHostName = msgs[0]["dnsHostName"][0]
148
149     if dnsHostName is not None and remove_dns_names:
150         remove_dns_references(samdb, dnsHostName)
151
152     if remove_sysvol_obj:
153         remove_sysvol_references(samdb, dc_name)
154
155 def offline_remove_ntds_dc(samdb, ntds_dn,
156                            remove_computer_obj=False,
157                            remove_server_obj=False,
158                            remove_connection_obj=False,
159                            seize_stale_fsmo=False,
160                            remove_sysvol_obj=False,
161                            remove_dns_names=False):
162     res = samdb.search("",
163                        scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
164     assert len(res) == 1
165     my_serviceName = ldb.Dn(samdb, res[0]["dsServiceName"][0])
166     server_dn = ntds_dn.parent()
167
168     if my_serviceName == ntds_dn:
169         raise DemoteException("Refusing to demote our own DSA: %s " % my_serviceName)
170
171     try:
172         msgs = samdb.search(base=ntds_dn, expression="objectClass=ntdsDSA",
173                         attrs=["objectGUID"], scope=ldb.SCOPE_BASE)
174     except LdbError as (enum, estr):
175         if enum == ldb.ERR_NO_SUCH_OBJECT:
176               raise DemoteException("Given DN %s doesn't exist" % ntds_dn)
177         else:
178             raise
179     if (len(msgs) == 0):
180         raise DemoteException("%s is not an ntdsda in %s"
181                               % (ntds_dn, samdb.domain_dns_name()))
182
183     msg = msgs[0]
184     if (msg.dn.get_rdn_name() != "CN" or
185         msg.dn.get_rdn_value() != "NTDS Settings"):
186         raise DemoteException("Given DN (%s) wasn't the NTDS Settings DN" %
187                               ntds_dn)
188
189     ntds_guid = ndr_unpack(misc.GUID, msg["objectGUID"][0])
190
191     if remove_connection_obj:
192         # Find any nTDSConnection objects with that DC as the fromServer.
193         # We use the GUID to avoid issues with any () chars in a server
194         # name.
195         stale_connections = samdb.search(base=samdb.get_config_basedn(),
196                                          expression="(&(objectclass=nTDSConnection)"
197                                          "(fromServer=<GUID=%s>))" % ntds_guid)
198         for conn in stale_connections:
199             samdb.delete(conn.dn)
200
201     if seize_stale_fsmo:
202         stale_fsmo_roles = samdb.search(base="", scope=ldb.SCOPE_SUBTREE,
203                                         expression="(fsmoRoleOwner=<GUID=%s>))"
204                                         % ntds_guid,
205                                         controls=["search_options:0:2"])
206         # Find any FSMO roles they have, give them to this server
207
208         for role in stale_fsmo_roles:
209             val = str(my_serviceName)
210             m = ldb.Message()
211             m.dn = role.dn
212             m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_REPLACE,
213                                             'fsmoRoleOwner')
214             samdb.modify(m)
215
216     # Remove the NTDS setting tree
217     try:
218         samdb.delete(ntds_dn, ["tree_delete:0"])
219     except LdbError as (enum, estr):
220         raise DemoteException("Failed to remove the DCs NTDS DSA object: %s"
221                               % estr)
222
223     offline_remove_server(samdb, server_dn,
224                           remove_computer_obj=remove_computer_obj,
225                           remove_server_obj=remove_server_obj,
226                           remove_sysvol_obj=remove_sysvol_obj,
227                           remove_dns_names=remove_dns_names)
228
229
230 def remove_dc(samdb, dc_name):
231
232     # TODO: Check if this is the last server (covered mostly by
233     # refusing to remove our own name)
234
235     samdb.transaction_start()
236
237     msgs = samdb.search(base=samdb.get_config_basedn(),
238                         attrs=["serverReference"],
239                         expression="(&(objectClass=server)(cn=%s))"
240                     % ldb.binary_encode(dc_name))
241     if (len(msgs) == 0):
242         raise DemoteException("%s is not an AD DC in %s"
243                               % (dc_name, samdb.domain_dns_name()))
244     server_dn = msgs[0].dn
245
246     ntds_dn = ldb.Dn(samdb, "CN=NTDS Settings")
247     ntds_dn.add_base(msgs[0].dn)
248
249     # Confirm this is really an ntdsDSA object
250     try:
251         msgs = samdb.search(base=ntds_dn, attrs=[], scope=ldb.SCOPE_BASE,
252                             expression="(objectClass=ntdsdsa)")
253     except LdbError as (enum, estr):
254         if enum == ldb.ERR_NO_SUCH_OBJECT:
255             offline_remove_server(samdb, msgs[0].dn,
256                                   remove_computer_obj=True,
257                                   remove_server_obj=True,
258                                   remove_sysvol_obj=True,
259                                   remove_dns_names=True)
260
261             samdb.transaction_commit()
262             return
263         else:
264             pass
265
266     offline_remove_ntds_dc(samdb, msgs[0].dn,
267                            remove_computer_obj=True,
268                            remove_server_obj=True,
269                            remove_connection_obj=True,
270                            seize_stale_fsmo=True,
271                            remove_sysvol_obj=True,
272                            remove_dns_names=True)
273
274     samdb.transaction_commit()
275
276
277
278 def offline_remove_dc_RemoveDsServer(samdb, ntds_dn):
279
280     samdb.start_transaction()
281
282     offline_remove_ntds_dc(samdb, ntds_dn)
283
284     samdb.commit_transaction()