ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / netcmd / domain / demote.py
blobae4d11dee43c708c61a62d3d677d27ef98e0944c
1 # domain management - domain demote
3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import ldb
26 import samba.getopt as options
27 from samba import dsdb, remove_dc, werror
28 from samba.auth import system_session
29 from samba.dcerpc import drsuapi, misc
30 from samba.drs_utils import drsuapi_connect
31 from samba.dsdb import (
32 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
33 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
34 UF_PARTIAL_SECRETS_ACCOUNT,
35 UF_SERVER_TRUST_ACCOUNT,
36 UF_TRUSTED_FOR_DELEGATION,
37 UF_WORKSTATION_TRUST_ACCOUNT
39 from samba.net import Net
40 from samba.netcmd import Command, CommandError, Option
41 from samba.samdb import SamDB
44 class cmd_domain_demote(Command):
45 """Demote ourselves from the role of Domain Controller."""
47 synopsis = "%prog [options]"
49 takes_options = [
50 Option("--server", help="writable DC to write demotion changes on", type=str),
51 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
52 metavar="URL", dest="H"),
53 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
54 "to remove ALL references to (rather than this DC)", type=str),
55 Option("-q", "--quiet", help="Be quiet", action="store_true"),
56 Option("-v", "--verbose", help="Be verbose", action="store_true"),
59 takes_optiongroups = {
60 "sambaopts": options.SambaOptions,
61 "credopts": options.CredentialsOptions,
62 "versionopts": options.VersionOptions,
65 def run(self, sambaopts=None, credopts=None,
66 versionopts=None, server=None,
67 remove_other_dead_server=None, H=None,
68 verbose=False, quiet=False):
69 lp = sambaopts.get_loadparm()
70 creds = credopts.get_credentials(lp)
72 logger = self.get_logger(verbose=verbose, quiet=quiet)
74 if remove_other_dead_server is not None:
75 if server is not None:
76 samdb = SamDB(url="ldap://%s" % server,
77 session_info=system_session(),
78 credentials=creds, lp=lp)
79 else:
80 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
81 try:
82 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
83 except remove_dc.DemoteException as err:
84 raise CommandError("Demote failed: %s" % err)
85 return
87 netbios_name = lp.get("netbios name")
88 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
89 if not server:
90 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
91 if (len(res) == 0):
92 raise CommandError("Unable to search for servers")
94 if (len(res) == 1):
95 raise CommandError("You are the last server in the domain")
97 server = None
98 for e in res:
99 if str(e["name"]).lower() != netbios_name.lower():
100 server = e["dnsHostName"]
101 break
103 ntds_guid = samdb.get_ntds_GUID()
104 msg = samdb.search(base=str(samdb.get_config_basedn()),
105 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
106 attrs=['options'])
107 if len(msg) == 0 or "options" not in msg[0]:
108 raise CommandError("Failed to find options on %s" % ntds_guid)
110 ntds_dn = msg[0].dn
111 dsa_options = int(str(msg[0]['options']))
113 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
114 controls=["search_options:1:2"])
116 if len(res) != 0:
117 raise CommandError("Current DC is still the owner of %d role(s), "
118 "use the role command to transfer roles to "
119 "another DC" %
120 len(res))
122 self.errf.write("Using %s as partner server for the demotion\n" %
123 server)
124 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
126 self.errf.write("Deactivating inbound replication\n")
128 nmsg = ldb.Message()
129 nmsg.dn = msg[0].dn
131 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
132 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
133 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
134 samdb.modify(nmsg)
136 self.errf.write("Asking partner server %s to synchronize from us\n"
137 % server)
138 for part in (samdb.get_schema_basedn(),
139 samdb.get_config_basedn(),
140 samdb.get_root_basedn()):
141 nc = drsuapi.DsReplicaObjectIdentifier()
142 nc.dn = str(part)
144 req1 = drsuapi.DsReplicaSyncRequest1()
145 req1.naming_context = nc
146 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
147 req1.source_dsa_guid = misc.GUID(ntds_guid)
149 try:
150 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
151 except RuntimeError as e1:
152 (werr, string) = e1.args
153 if werr == werror.WERR_DS_DRA_NO_REPLICA:
154 pass
155 else:
156 self.errf.write(
157 "Error while replicating out last local changes from '%s' for demotion, "
158 "re-enabling inbound replication\n" % part)
159 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
160 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
161 samdb.modify(nmsg)
162 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
163 try:
164 remote_samdb = SamDB(url="ldap://%s" % server,
165 session_info=system_session(),
166 credentials=creds, lp=lp)
168 self.errf.write("Changing userControl and container\n")
169 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
170 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
171 netbios_name.upper(),
172 attrs=["userAccountControl"])
173 dc_dn = res[0].dn
174 uac = int(str(res[0]["userAccountControl"]))
176 except Exception as e:
177 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
178 self.errf.write(
179 "Error while demoting, re-enabling inbound replication\n")
180 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
181 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
182 samdb.modify(nmsg)
183 raise CommandError("Error while changing account control", e)
185 if (len(res) != 1):
186 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
187 self.errf.write(
188 "Error while demoting, re-enabling inbound replication\n")
189 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
190 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
191 samdb.modify(nmsg)
192 raise CommandError("Unable to find object with samaccountName = %s$"
193 " in the remote dc" % netbios_name.upper())
195 uac &= ~(UF_SERVER_TRUST_ACCOUNT |
196 UF_TRUSTED_FOR_DELEGATION |
197 UF_PARTIAL_SECRETS_ACCOUNT)
198 uac |= UF_WORKSTATION_TRUST_ACCOUNT
200 msg = ldb.Message()
201 msg.dn = dc_dn
203 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
204 ldb.FLAG_MOD_REPLACE,
205 "userAccountControl")
206 try:
207 remote_samdb.modify(msg)
208 except Exception as e:
209 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
210 self.errf.write(
211 "Error while demoting, re-enabling inbound replication\n")
212 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
213 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
214 samdb.modify(nmsg)
216 raise CommandError("Error while changing account control", e)
218 dc_name = res[0].dn.get_rdn_value()
219 rdn = "CN=%s" % dc_name
221 # Let's move to the Computer container
222 i = 0
223 newrdn = str(rdn)
225 computer_dn = remote_samdb.get_wellknown_dn(
226 remote_samdb.get_default_basedn(),
227 dsdb.DS_GUID_COMPUTERS_CONTAINER)
228 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
230 if (len(res) != 0):
231 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
232 scope=ldb.SCOPE_ONELEVEL)
233 while(len(res) != 0 and i < 100):
234 i = i + 1
235 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
236 scope=ldb.SCOPE_ONELEVEL)
238 if i == 100:
239 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
240 self.errf.write(
241 "Error while demoting, re-enabling inbound replication\n")
242 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
243 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
244 samdb.modify(nmsg)
246 msg = ldb.Message()
247 msg.dn = dc_dn
249 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
250 ldb.FLAG_MOD_REPLACE,
251 "userAccountControl")
253 remote_samdb.modify(msg)
255 raise CommandError("Unable to find a slot for renaming %s,"
256 " all names from %s-1 to %s-%d seemed used" %
257 (str(dc_dn), rdn, rdn, i - 9))
259 newrdn = "%s-%d" % (rdn, i)
261 try:
262 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
263 remote_samdb.rename(dc_dn, newdn)
264 except Exception as e:
265 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
266 self.errf.write(
267 "Error while demoting, re-enabling inbound replication\n")
268 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
269 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
270 samdb.modify(nmsg)
272 msg = ldb.Message()
273 msg.dn = dc_dn
275 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
276 ldb.FLAG_MOD_REPLACE,
277 "userAccountControl")
279 remote_samdb.modify(msg)
280 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
282 server_dsa_dn = samdb.get_serverName()
283 domain = remote_samdb.get_root_basedn()
285 try:
286 req1 = drsuapi.DsRemoveDSServerRequest1()
287 req1.server_dn = str(server_dsa_dn)
288 req1.domain_dn = str(domain)
289 req1.commit = 1
291 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
292 except RuntimeError as e3:
293 (werr, string) = e3.args
294 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
295 self.errf.write(
296 "Error while demoting, re-enabling inbound replication\n")
297 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
298 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
299 samdb.modify(nmsg)
301 msg = ldb.Message()
302 msg.dn = newdn
304 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
305 ldb.FLAG_MOD_REPLACE,
306 "userAccountControl")
307 remote_samdb.modify(msg)
308 remote_samdb.rename(newdn, dc_dn)
309 if werr == werror.WERR_DS_DRA_NO_REPLICA:
310 raise CommandError("The DC %s is not present on (already "
311 "removed from) the remote server: %s" %
312 (server_dsa_dn, e3))
313 else:
314 raise CommandError("Error while sending a removeDsServer "
315 "of %s: %s" %
316 (server_dsa_dn, e3))
318 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
320 # These are objects under the computer account that should be deleted
321 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
322 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
323 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
324 "CN=NTFRS Subscriptions"):
325 try:
326 remote_samdb.delete(ldb.Dn(remote_samdb,
327 "%s,%s" % (s, str(newdn))))
328 except ldb.LdbError:
329 pass
331 # get dns host name for target server to demote, remove dns references
332 remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
333 ignore_no_name=True)
335 self.errf.write("Demote successful\n")