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/>.
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]"
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
)
80 samdb
= SamDB(url
=H
, session_info
=system_session(), credentials
=creds
, lp
=lp
)
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
)
87 netbios_name
= lp
.get("netbios name")
88 samdb
= SamDB(url
=H
, session_info
=system_session(), credentials
=creds
, lp
=lp
)
90 res
= samdb
.search(expression
='(&(objectClass=computer)(serverReferenceBL=*))', attrs
=["dnsHostName", "name"])
92 raise CommandError("Unable to search for servers")
95 raise CommandError("You are the last server in the domain")
99 if str(e
["name"]).lower() != netbios_name
.lower():
100 server
= e
["dnsHostName"]
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
,
107 if len(msg
) == 0 or "options" not in msg
[0]:
108 raise CommandError("Failed to find options on %s" % ntds_guid
)
111 dsa_options
= int(str(msg
[0]['options']))
113 res
= samdb
.search(expression
="(fSMORoleOwner=%s)" % str(ntds_dn
),
114 controls
=["search_options:1:2"])
117 raise CommandError("Current DC is still the owner of %d role(s), "
118 "use the role command to transfer roles to "
122 self
.errf
.write("Using %s as partner server for the demotion\n" %
124 (drsuapiBind
, drsuapi_handle
, supportedExtensions
) = drsuapi_connect(server
, lp
, creds
)
126 self
.errf
.write("Deactivating inbound replication\n")
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")
136 self
.errf
.write("Asking partner server %s to synchronize from us\n"
138 for part
in (samdb
.get_schema_basedn(),
139 samdb
.get_config_basedn(),
140 samdb
.get_root_basedn()):
141 nc
= drsuapi
.DsReplicaObjectIdentifier()
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
)
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
:
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")
162 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part
), string
)
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"])
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():
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")
183 raise CommandError("Error while changing account control", e
)
186 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
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")
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
203 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
204 ldb
.FLAG_MOD_REPLACE
,
205 "userAccountControl")
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():
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")
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
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
)
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):
235 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
236 scope
=ldb
.SCOPE_ONELEVEL
)
239 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
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")
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
)
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():
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")
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()
286 req1
= drsuapi
.DsRemoveDSServerRequest1()
287 req1
.server_dn
= str(server_dsa_dn
)
288 req1
.domain_dn
= str(domain
)
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():
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")
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" %
314 raise CommandError("Error while sending a removeDsServer "
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"):
326 remote_samdb
.delete(ldb
.Dn(remote_samdb
,
327 "%s,%s" % (s
, str(newdn
))))
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(),
335 self
.errf
.write("Demote successful\n")