1 # Changes a FSMO role owner
3 # Copyright Nadezhda Ivanova 2009
4 # Copyright Jelmer Vernooij 2009
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 import samba
.getopt
as options
23 from ldb
import LdbError
24 from samba
.dcerpc
import drsuapi
, misc
25 from samba
.auth
import system_session
26 import samba
.drs_utils
27 from samba
.netcmd
import (
33 from samba
.samdb
import SamDB
36 def get_fsmo_roleowner(samdb
, roledn
, role
):
37 """Gets the owner of an FSMO role
39 :param roledn: The DN of the FSMO role
40 :param role: The FSMO role
43 res
= samdb
.search(roledn
,
44 scope
=ldb
.SCOPE_BASE
, attrs
=["fSMORoleOwner"])
45 except LdbError
as e7
:
47 if num
== ldb
.ERR_NO_SUCH_OBJECT
:
48 raise CommandError("The '%s' role is not present in this domain" % role
)
51 if 'fSMORoleOwner' in res
[0]:
52 master_owner
= (ldb
.Dn(samdb
, res
[0]["fSMORoleOwner"][0].decode('utf8')))
59 def transfer_dns_role(outf
, sambaopts
, credopts
, role
, samdb
):
60 """Transfer dns FSMO role. """
62 if role
== "domaindns":
63 domain_dn
= samdb
.domain_dn()
64 role_object
= "CN=Infrastructure,DC=DomainDnsZones," + domain_dn
65 elif role
== "forestdns":
66 forest_dn
= samba
.dn_from_dns_name(samdb
.forest_dns_name())
67 role_object
= "CN=Infrastructure,DC=ForestDnsZones," + forest_dn
69 new_host_dns_name
= samdb
.host_dns_name()
71 res
= samdb
.search(role_object
,
72 attrs
=["fSMORoleOwner"],
74 controls
=["extended_dn:1:1"])
76 if 'fSMORoleOwner' in res
[0]:
78 master_guid
= str(misc
.GUID(ldb
.Dn(samdb
,
79 res
[0]['fSMORoleOwner'][0].decode('utf8'))
80 .get_extended_component('GUID')))
81 master_owner
= str(ldb
.Dn(samdb
, res
[0]['fSMORoleOwner'][0].decode('utf8')))
82 except LdbError
as e3
:
84 raise CommandError("No GUID found in naming master DN %s : %s \n" %
85 (res
[0]['fSMORoleOwner'][0], msg
))
87 outf
.write("* The '%s' role does not have an FSMO roleowner\n" % role
)
90 if role
== "domaindns":
91 master_dns_name
= '%s._msdcs.%s' % (master_guid
,
92 samdb
.domain_dns_name())
93 new_dns_name
= '%s._msdcs.%s' % (samdb
.get_ntds_GUID(),
94 samdb
.domain_dns_name())
95 elif role
== "forestdns":
96 master_dns_name
= '%s._msdcs.%s' % (master_guid
,
97 samdb
.forest_dns_name())
98 new_dns_name
= '%s._msdcs.%s' % (samdb
.get_ntds_GUID(),
99 samdb
.forest_dns_name())
101 new_owner
= samdb
.get_dsServiceName()
103 if master_dns_name
!= new_dns_name
:
104 lp
= sambaopts
.get_loadparm()
105 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
106 samdb
= SamDB(url
="ldap://%s" % (master_dns_name
),
107 session_info
=system_session(),
108 credentials
=creds
, lp
=lp
)
111 m
.dn
= ldb
.Dn(samdb
, role_object
)
112 m
["fSMORoleOwner_Del"] = ldb
.MessageElement(master_owner
,
115 m
["fSMORoleOwner_Add"] = ldb
.MessageElement(new_owner
,
120 except LdbError
as e5
:
122 raise CommandError("Failed to add role '%s': %s" % (role
, msg
))
125 connection
= samba
.drs_utils
.drsuapi_connect(new_host_dns_name
,
127 except samba
.drs_utils
.drsException
as e
:
128 raise CommandError("Drsuapi Connect failed", e
)
131 drsuapi_connection
= connection
[0]
132 drsuapi_handle
= connection
[1]
133 req_options
= drsuapi
.DRSUAPI_DRS_WRIT_REP
134 NC
= role_object
[18:]
135 samba
.drs_utils
.sendDsReplicaSync(drsuapi_connection
,
139 except samba
.drs_utils
.drsException
as estr
:
140 raise CommandError("Replication failed", estr
)
142 outf
.write("FSMO transfer of '%s' role successful\n" % role
)
145 outf
.write("This DC already has the '%s' FSMO role\n" % role
)
149 def transfer_role(outf
, role
, samdb
):
150 """Transfer standard FSMO role. """
152 domain_dn
= samdb
.domain_dn()
153 rid_dn
= "CN=RID Manager$,CN=System," + domain_dn
154 naming_dn
= "CN=Partitions,%s" % samdb
.get_config_basedn()
155 infrastructure_dn
= "CN=Infrastructure," + domain_dn
156 schema_dn
= str(samdb
.get_schema_basedn())
157 new_owner
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
159 m
.dn
= ldb
.Dn(samdb
, "")
161 master_owner
= get_fsmo_roleowner(samdb
, rid_dn
, role
)
162 m
["becomeRidMaster"] = ldb
.MessageElement(
163 "1", ldb
.FLAG_MOD_REPLACE
,
166 master_owner
= get_fsmo_roleowner(samdb
, domain_dn
, role
)
168 res
= samdb
.search(domain_dn
,
169 scope
=ldb
.SCOPE_BASE
, attrs
=["objectSid"])
171 sid
= res
[0]["objectSid"][0]
172 m
["becomePdc"] = ldb
.MessageElement(
173 sid
, ldb
.FLAG_MOD_REPLACE
,
175 elif role
== "naming":
176 master_owner
= get_fsmo_roleowner(samdb
, naming_dn
, role
)
177 m
["becomeDomainMaster"] = ldb
.MessageElement(
178 "1", ldb
.FLAG_MOD_REPLACE
,
179 "becomeDomainMaster")
180 elif role
== "infrastructure":
181 master_owner
= get_fsmo_roleowner(samdb
, infrastructure_dn
, role
)
182 m
["becomeInfrastructureMaster"] = ldb
.MessageElement(
183 "1", ldb
.FLAG_MOD_REPLACE
,
184 "becomeInfrastructureMaster")
185 elif role
== "schema":
186 master_owner
= get_fsmo_roleowner(samdb
, schema_dn
, role
)
187 m
["becomeSchemaMaster"] = ldb
.MessageElement(
188 "1", ldb
.FLAG_MOD_REPLACE
,
189 "becomeSchemaMaster")
191 raise CommandError("Invalid FSMO role.")
193 if master_owner
is None:
194 outf
.write("Cannot transfer, no DC assigned to the %s role. Try 'seize' instead\n" % role
)
197 if master_owner
!= new_owner
:
200 except LdbError
as e6
:
202 raise CommandError("Transfer of '%s' role failed: %s" %
205 outf
.write("FSMO transfer of '%s' role successful\n" % role
)
208 outf
.write("This DC already has the '%s' FSMO role\n" % role
)
212 class cmd_fsmo_seize(Command
):
213 """Seize the role."""
215 synopsis
= "%prog [options]"
217 takes_optiongroups
= {
218 "sambaopts": options
.SambaOptions
,
219 "credopts": options
.CredentialsOptions
,
220 "versionopts": options
.VersionOptions
,
224 Option("-H", "--URL", help="LDB URL for database or target server",
225 type=str, metavar
="URL", dest
="H"),
227 help="Force seizing of role without attempting to transfer.",
228 action
="store_true"),
229 Option("--role", type="choice", choices
=["rid", "pdc", "infrastructure",
230 "schema", "naming", "domaindns", "forestdns", "all"],
231 help="""The FSMO role to seize or transfer.\n
232 rid=RidAllocationMasterRole\n
233 schema=SchemaMasterRole\n
234 pdc=PdcEmulationMasterRole\n
235 naming=DomainNamingMasterRole\n
236 infrastructure=InfrastructureMasterRole\n
237 domaindns=DomainDnsZonesMasterRole\n
238 forestdns=ForestDnsZonesMasterRole\n
239 all=all of the above\n
240 You must provide an Admin user and password."""),
245 def seize_role(self
, role
, samdb
, force
):
246 """Seize standard fsmo role. """
248 serviceName
= samdb
.get_dsServiceName()
249 domain_dn
= samdb
.domain_dn()
250 self
.infrastructure_dn
= "CN=Infrastructure," + domain_dn
251 self
.naming_dn
= "CN=Partitions,%s" % samdb
.get_config_basedn()
252 self
.schema_dn
= str(samdb
.get_schema_basedn())
253 self
.rid_dn
= "CN=RID Manager$,CN=System," + domain_dn
257 m
.dn
= ldb
.Dn(samdb
, self
.rid_dn
)
259 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
260 elif role
== "naming":
261 m
.dn
= ldb
.Dn(samdb
, self
.naming_dn
)
262 elif role
== "infrastructure":
263 m
.dn
= ldb
.Dn(samdb
, self
.infrastructure_dn
)
264 elif role
== "schema":
265 m
.dn
= ldb
.Dn(samdb
, self
.schema_dn
)
267 raise CommandError("Invalid FSMO role.")
268 # first try to transfer to avoid problem if the owner is still active
270 master_owner
= get_fsmo_roleowner(samdb
, m
.dn
, role
)
271 # if there is a different owner
272 if master_owner
is not None:
273 # if there is a different owner
274 if master_owner
!= serviceName
:
275 # if --force isn't given, attempt transfer
277 self
.message("Attempting transfer...")
279 transfer_role(self
.outf
, role
, samdb
)
281 # transfer failed, use the big axe...
283 self
.message("Transfer unsuccessful, seizing...")
285 self
.message("Transfer successful, not seizing role")
288 self
.outf
.write("This DC already has the '%s' FSMO role\n" %
294 if force
is not None or seize
:
295 self
.message("Seizing %s FSMO role..." % role
)
296 m
["fSMORoleOwner"] = ldb
.MessageElement(
297 serviceName
, ldb
.FLAG_MOD_REPLACE
,
300 samdb
.transaction_start()
304 # We may need to allocate the initial RID Set
305 samdb
.create_own_rid_set()
307 except LdbError
as e1
:
309 if role
== "rid" and num
== ldb
.ERR_ENTRY_ALREADY_EXISTS
:
311 # Try again without the RID Set allocation
312 # (normal). We have to manage the transaction as
313 # we do not have nested transactions and creating
314 # a RID set touches multiple objects. :-(
315 samdb
.transaction_cancel()
316 samdb
.transaction_start()
319 except LdbError
as e
:
321 samdb
.transaction_cancel()
322 raise CommandError("Failed to seize '%s' role: %s" %
326 samdb
.transaction_cancel()
327 raise CommandError("Failed to seize '%s' role: %s" %
329 samdb
.transaction_commit()
330 self
.outf
.write("FSMO seize of '%s' role successful\n" % role
)
334 def seize_dns_role(self
, role
, samdb
, credopts
, sambaopts
,
336 """Seize DNS FSMO role. """
338 serviceName
= samdb
.get_dsServiceName()
339 domain_dn
= samdb
.domain_dn()
340 forest_dn
= samba
.dn_from_dns_name(samdb
.forest_dns_name())
341 self
.domaindns_dn
= "CN=Infrastructure,DC=DomainDnsZones," + domain_dn
342 self
.forestdns_dn
= "CN=Infrastructure,DC=ForestDnsZones," + forest_dn
345 if role
== "domaindns":
346 m
.dn
= ldb
.Dn(samdb
, self
.domaindns_dn
)
347 elif role
== "forestdns":
348 m
.dn
= ldb
.Dn(samdb
, self
.forestdns_dn
)
350 raise CommandError("Invalid FSMO role.")
351 # first try to transfer to avoid problem if the owner is still active
353 master_owner
= get_fsmo_roleowner(samdb
, m
.dn
, role
)
354 if master_owner
is not None:
355 # if there is a different owner
356 if master_owner
!= serviceName
:
357 # if --force isn't given, attempt transfer
359 self
.message("Attempting transfer...")
361 transfer_dns_role(self
.outf
, sambaopts
, credopts
, role
,
364 # transfer failed, use the big axe...
366 self
.message("Transfer unsuccessful, seizing...")
368 self
.message("Transfer successful, not seizing role\n")
371 self
.outf
.write("This DC already has the '%s' FSMO role\n" %
377 if force
is not None or seize
:
378 self
.message("Seizing %s FSMO role..." % role
)
379 m
["fSMORoleOwner"] = ldb
.MessageElement(
380 serviceName
, ldb
.FLAG_MOD_REPLACE
,
384 except LdbError
as e2
:
386 raise CommandError("Failed to seize '%s' role: %s" %
388 self
.outf
.write("FSMO seize of '%s' role successful\n" % role
)
391 def run(self
, force
=None, H
=None, role
=None,
392 credopts
=None, sambaopts
=None, versionopts
=None):
394 lp
= sambaopts
.get_loadparm()
395 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
397 samdb
= SamDB(url
=H
, session_info
=system_session(),
398 credentials
=creds
, lp
=lp
)
401 self
.seize_role("rid", samdb
, force
)
402 self
.seize_role("pdc", samdb
, force
)
403 self
.seize_role("naming", samdb
, force
)
404 self
.seize_role("infrastructure", samdb
, force
)
405 self
.seize_role("schema", samdb
, force
)
406 self
.seize_dns_role("domaindns", samdb
, credopts
, sambaopts
,
408 self
.seize_dns_role("forestdns", samdb
, credopts
, sambaopts
,
411 if role
== "domaindns" or role
== "forestdns":
412 self
.seize_dns_role(role
, samdb
, credopts
, sambaopts
,
415 self
.seize_role(role
, samdb
, force
)
418 class cmd_fsmo_show(Command
):
419 """Show the roles."""
421 synopsis
= "%prog [options]"
423 takes_optiongroups
= {
424 "sambaopts": options
.SambaOptions
,
425 "credopts": options
.CredentialsOptions
,
426 "versionopts": options
.VersionOptions
,
430 Option("-H", "--URL", help="LDB URL for database or target server",
431 type=str, metavar
="URL", dest
="H"),
436 def run(self
, H
=None, credopts
=None, sambaopts
=None, versionopts
=None):
437 lp
= sambaopts
.get_loadparm()
438 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
440 samdb
= SamDB(url
=H
, session_info
=system_session(),
441 credentials
=creds
, lp
=lp
)
443 domain_dn
= samdb
.domain_dn()
444 forest_dn
= samba
.dn_from_dns_name(samdb
.forest_dns_name())
445 infrastructure_dn
= "CN=Infrastructure," + domain_dn
446 naming_dn
= "CN=Partitions,%s" % samdb
.get_config_basedn()
447 schema_dn
= samdb
.get_schema_basedn()
448 rid_dn
= "CN=RID Manager$,CN=System," + domain_dn
449 domaindns_dn
= "CN=Infrastructure,DC=DomainDnsZones," + domain_dn
450 forestdns_dn
= "CN=Infrastructure,DC=ForestDnsZones," + forest_dn
452 masters
= [(schema_dn
, "schema", "SchemaMasterRole"),
453 (infrastructure_dn
, "infrastructure", "InfrastructureMasterRole"),
454 (rid_dn
, "rid", "RidAllocationMasterRole"),
455 (domain_dn
, "pdc", "PdcEmulationMasterRole"),
456 (naming_dn
, "naming", "DomainNamingMasterRole"),
457 (domaindns_dn
, "domaindns", "DomainDnsZonesMasterRole"),
458 (forestdns_dn
, "forestdns", "ForestDnsZonesMasterRole"),
461 for master
in masters
:
462 (dn
, short_name
, long_name
) = master
464 master
= get_fsmo_roleowner(samdb
, dn
, short_name
)
465 if master
is not None:
466 self
.message("%s owner: %s" % (long_name
, str(master
)))
468 self
.message("%s has no current owner" % (long_name
))
469 except CommandError
as e
:
470 self
.message("%s: * %s" % (long_name
, e
.message
))
473 class cmd_fsmo_transfer(Command
):
474 """Transfer the role."""
476 synopsis
= "%prog [options]"
478 takes_optiongroups
= {
479 "sambaopts": options
.SambaOptions
,
480 "credopts": options
.CredentialsOptions
,
481 "versionopts": options
.VersionOptions
,
485 Option("-H", "--URL", help="LDB URL for database or target server",
486 type=str, metavar
="URL", dest
="H"),
487 Option("--role", type="choice", choices
=["rid", "pdc", "infrastructure",
488 "schema", "naming", "domaindns", "forestdns", "all"],
489 help="""The FSMO role to seize or transfer.\n
490 rid=RidAllocationMasterRole\n
491 schema=SchemaMasterRole\n
492 pdc=PdcEmulationMasterRole\n
493 naming=DomainNamingMasterRole\n
494 infrastructure=InfrastructureMasterRole\n
495 domaindns=DomainDnsZonesMasterRole\n
496 forestdns=ForestDnsZonesMasterRole\n
497 all=all of the above\n
498 You must provide an Admin user and password."""),
503 def run(self
, force
=None, H
=None, role
=None,
504 credopts
=None, sambaopts
=None, versionopts
=None):
506 lp
= sambaopts
.get_loadparm()
507 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
509 samdb
= SamDB(url
=H
, session_info
=system_session(),
510 credentials
=creds
, lp
=lp
)
513 transfer_role(self
.outf
, "rid", samdb
)
514 transfer_role(self
.outf
, "pdc", samdb
)
515 transfer_role(self
.outf
, "naming", samdb
)
516 transfer_role(self
.outf
, "infrastructure", samdb
)
517 transfer_role(self
.outf
, "schema", samdb
)
518 transfer_dns_role(self
.outf
, sambaopts
, credopts
,
520 transfer_dns_role(self
.outf
, sambaopts
, credopts
, "forestdns",
523 if role
== "domaindns" or role
== "forestdns":
524 transfer_dns_role(self
.outf
, sambaopts
, credopts
, role
, samdb
)
526 transfer_role(self
.outf
, role
, samdb
)
529 class cmd_fsmo(SuperCommand
):
530 """Flexible Single Master Operations (FSMO) roles management."""
533 subcommands
["seize"] = cmd_fsmo_seize()
534 subcommands
["show"] = cmd_fsmo_show()
535 subcommands
["transfer"] = cmd_fsmo_transfer()