c31fc6735b5782b5f66272c3f25d453040d0e0f1
[bbaumbach/samba-autobuild/.git] / python / samba / netcmd / fsmo.py
1 # Changes a FSMO role owner
2 #
3 # Copyright Nadezhda Ivanova 2009
4 # Copyright Jelmer Vernooij 2009
5 #
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.
10 #
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.
15 #
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/>.
18 #
19
20 import samba
21 import samba.getopt as options
22 import ldb
23 from ldb import LdbError
24 from samba.dcerpc import drsuapi, misc
25 from samba.auth import system_session
26 from samba.netcmd import (
27     Command,
28     CommandError,
29     SuperCommand,
30     Option,
31 )
32 from samba.samdb import SamDB
33
34 def get_fsmo_roleowner(samdb, roledn, role):
35     """Gets the owner of an FSMO role
36
37     :param roledn: The DN of the FSMO role
38     :param role: The FSMO role
39     """
40     try:
41         res = samdb.search(roledn,
42                            scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
43     except LdbError as e7:
44         (num, msg) = e7.args
45         if num == ldb.ERR_NO_SUCH_OBJECT:
46             raise CommandError("The '%s' role is not present in this domain" % role)
47         raise
48
49     if 'fSMORoleOwner' in res[0]:
50         master_owner = (ldb.Dn(samdb, res[0]["fSMORoleOwner"][0].decode('utf8')))
51     else:
52         master_owner = None
53
54     return master_owner
55
56
57 def transfer_dns_role(outf, sambaopts, credopts, role, samdb):
58     """Transfer dns FSMO role. """
59
60     if role == "domaindns":
61         domain_dn = samdb.domain_dn()
62         role_object = "CN=Infrastructure,DC=DomainDnsZones," + domain_dn
63     elif role == "forestdns":
64         forest_dn = samba.dn_from_dns_name(samdb.forest_dns_name())
65         role_object = "CN=Infrastructure,DC=ForestDnsZones," + forest_dn
66
67     res = samdb.search(role_object,
68                        attrs=["fSMORoleOwner"],
69                        scope=ldb.SCOPE_BASE,
70                        controls=["extended_dn:1:1"])
71
72     if 'fSMORoleOwner' in res[0]:
73         try:
74             master_guid = str(misc.GUID(ldb.Dn(samdb,
75                               res[0]['fSMORoleOwner'][0].decode('utf8'))
76                               .get_extended_component('GUID')))
77             master_owner = str(ldb.Dn(samdb, res[0]['fSMORoleOwner'][0].decode('utf8')))
78         except LdbError as e3:
79             (num, msg) = e3.args
80             raise CommandError("No GUID found in naming master DN %s : %s \n" %
81                                (res[0]['fSMORoleOwner'][0], msg))
82     else:
83         outf.write("* The '%s' role does not have an FSMO roleowner\n" % role)
84         return False
85
86     if role == "domaindns":
87         master_dns_name = '%s._msdcs.%s' % (master_guid,
88                                             samdb.domain_dns_name())
89         new_dns_name = '%s._msdcs.%s' % (samdb.get_ntds_GUID(),
90                                          samdb.domain_dns_name())
91     elif role == "forestdns":
92         master_dns_name = '%s._msdcs.%s' % (master_guid,
93                                             samdb.forest_dns_name())
94         new_dns_name = '%s._msdcs.%s' % (samdb.get_ntds_GUID(),
95                                          samdb.forest_dns_name())
96
97     new_owner = samdb.get_dsServiceName()
98
99     if master_dns_name != new_dns_name:
100         lp = sambaopts.get_loadparm()
101         creds = credopts.get_credentials(lp, fallback_machine=True)
102         samdb = SamDB(url="ldap://%s" % (master_dns_name),
103                       session_info=system_session(),
104                       credentials=creds, lp=lp)
105
106         m = ldb.Message()
107         m.dn = ldb.Dn(samdb, role_object)
108         m["fSMORoleOwner"] = ldb.MessageElement(master_owner,
109                                                 ldb.FLAG_MOD_DELETE,
110                                                 "fSMORoleOwner")
111
112         try:
113             samdb.modify(m)
114         except LdbError as e4:
115             (num, msg) = e4.args
116             raise CommandError("Failed to delete role '%s': %s" %
117                                (role, msg))
118
119         m = ldb.Message()
120         m.dn = ldb.Dn(samdb, role_object)
121         m["fSMORoleOwner"]= ldb.MessageElement(new_owner,
122                                                ldb.FLAG_MOD_ADD,
123                                                "fSMORoleOwner")
124         try:
125             samdb.modify(m)
126         except LdbError as e5:
127             (num, msg) = e5.args
128             raise CommandError("Failed to add role '%s': %s" % (role, msg))
129
130         try:
131             connection = samba.drs_utils.drsuapi_connect(samdb.host_dns_name(),
132                                                          lp, creds)
133         except samba.drs_utils.drsException as e:
134             raise CommandError("Drsuapi Connect failed", e)
135
136         try:
137             drsuapi_connection = connection[0]
138             drsuapi_handle = connection[1]
139             req_options = drsuapi.DRSUAPI_DRS_WRIT_REP
140             NC = role_object[18:]
141             samba.drs_utils.sendDsReplicaSync(drsuapi_connection,
142                                               drsuapi_handle,
143                                               master_guid,
144                                               NC, req_options)
145         except samba.drs_utils.drsException as estr:
146             raise CommandError("Replication failed", estr)
147
148         outf.write("FSMO transfer of '%s' role successful\n" % role)
149         return True
150     else:
151         outf.write("This DC already has the '%s' FSMO role\n" % role)
152         return False
153
154 def transfer_role(outf, role, samdb):
155     """Transfer standard FSMO role. """
156
157     domain_dn = samdb.domain_dn()
158     rid_dn = "CN=RID Manager$,CN=System," + domain_dn
159     naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn()
160     infrastructure_dn = "CN=Infrastructure," + domain_dn
161     schema_dn = str(samdb.get_schema_basedn())
162     new_owner = ldb.Dn(samdb, samdb.get_dsServiceName())
163     m = ldb.Message()
164     m.dn = ldb.Dn(samdb, "")
165     if role == "rid":
166         master_owner = get_fsmo_roleowner(samdb, rid_dn, role)
167         m["becomeRidMaster"]= ldb.MessageElement(
168             "1", ldb.FLAG_MOD_REPLACE,
169             "becomeRidMaster")
170     elif role == "pdc":
171         master_owner = get_fsmo_roleowner(samdb, domain_dn, role)
172
173         res = samdb.search(domain_dn,
174                            scope=ldb.SCOPE_BASE, attrs=["objectSid"])
175         assert len(res) == 1
176         sid = res[0]["objectSid"][0]
177         m["becomePdc"]= ldb.MessageElement(
178             sid, ldb.FLAG_MOD_REPLACE,
179             "becomePdc")
180     elif role == "naming":
181         master_owner = get_fsmo_roleowner(samdb, naming_dn, role)
182         m["becomeDomainMaster"]= ldb.MessageElement(
183             "1", ldb.FLAG_MOD_REPLACE,
184             "becomeDomainMaster")
185     elif role == "infrastructure":
186         master_owner = get_fsmo_roleowner(samdb, infrastructure_dn, role)
187         m["becomeInfrastructureMaster"]= ldb.MessageElement(
188             "1", ldb.FLAG_MOD_REPLACE,
189             "becomeInfrastructureMaster")
190     elif role == "schema":
191         master_owner = get_fsmo_roleowner(samdb, schema_dn, role)
192         m["becomeSchemaMaster"]= ldb.MessageElement(
193             "1", ldb.FLAG_MOD_REPLACE,
194             "becomeSchemaMaster")
195     else:
196         raise CommandError("Invalid FSMO role.")
197
198     if master_owner is None:
199         outf.write("Cannot transfer, no DC assigned to the %s role.  Try 'seize' instead\n" % role)
200         return False
201
202     if master_owner != new_owner:
203         try:
204             samdb.modify(m)
205         except LdbError as e6:
206             (num, msg) = e6.args
207             raise CommandError("Transfer of '%s' role failed: %s" %
208                                (role, msg))
209
210         outf.write("FSMO transfer of '%s' role successful\n" % role)
211         return True
212     else:
213         outf.write("This DC already has the '%s' FSMO role\n" % role)
214         return False
215
216 class cmd_fsmo_seize(Command):
217     """Seize the role."""
218
219     synopsis = "%prog [options]"
220
221     takes_optiongroups = {
222         "sambaopts": options.SambaOptions,
223         "credopts": options.CredentialsOptions,
224         "versionopts": options.VersionOptions,
225     }
226
227     takes_options = [
228         Option("-H", "--URL", help="LDB URL for database or target server",
229                type=str, metavar="URL", dest="H"),
230         Option("--force",
231                help="Force seizing of role without attempting to transfer.",
232                action="store_true"),
233         Option("--role", type="choice", choices=["rid", "pdc", "infrastructure",
234                "schema", "naming", "domaindns", "forestdns", "all"],
235                help="""The FSMO role to seize or transfer.\n
236 rid=RidAllocationMasterRole\n
237 schema=SchemaMasterRole\n
238 pdc=PdcEmulationMasterRole\n
239 naming=DomainNamingMasterRole\n
240 infrastructure=InfrastructureMasterRole\n
241 domaindns=DomainDnsZonesMasterRole\n
242 forestdns=ForestDnsZonesMasterRole\n
243 all=all of the above\n
244 You must provide an Admin user and password."""),
245     ]
246
247     takes_args = []
248
249     def seize_role(self, role, samdb, force):
250         """Seize standard fsmo role. """
251
252         serviceName = samdb.get_dsServiceName()
253         domain_dn = samdb.domain_dn()
254         self.infrastructure_dn = "CN=Infrastructure," + domain_dn
255         self.naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn()
256         self.schema_dn = str(samdb.get_schema_basedn())
257         self.rid_dn = "CN=RID Manager$,CN=System," + domain_dn
258
259         m = ldb.Message()
260         if role == "rid":
261             m.dn = ldb.Dn(samdb, self.rid_dn)
262         elif role == "pdc":
263             m.dn = ldb.Dn(samdb, domain_dn)
264         elif role == "naming":
265             m.dn = ldb.Dn(samdb, self.naming_dn)
266         elif role == "infrastructure":
267             m.dn = ldb.Dn(samdb, self.infrastructure_dn)
268         elif role == "schema":
269             m.dn = ldb.Dn(samdb, self.schema_dn)
270         else:
271             raise CommandError("Invalid FSMO role.")
272         #first try to transfer to avoid problem if the owner is still active
273         seize = False
274         master_owner = get_fsmo_roleowner(samdb, m.dn, role)
275         # if there is a different owner
276         if master_owner is not None:
277             # if there is a different owner
278             if master_owner != serviceName:
279                 # if --force isn't given, attempt transfer
280                 if force is None:
281                     self.message("Attempting transfer...")
282                     try:
283                         transfer_role(self.outf, role, samdb)
284                     except:
285                         #transfer failed, use the big axe...
286                         seize = True
287                         self.message("Transfer unsuccessful, seizing...")
288                     else:
289                         self.message("Transfer successful, not seizing role")
290                         return True
291             else:
292                 self.outf.write("This DC already has the '%s' FSMO role\n" %
293                                 role)
294                 return False
295         else:
296             seize = True
297
298         if force is not None or seize == True:
299             self.message("Seizing %s FSMO role..." % role)
300             m["fSMORoleOwner"]= ldb.MessageElement(
301                 serviceName, ldb.FLAG_MOD_REPLACE,
302                 "fSMORoleOwner")
303
304             samdb.transaction_start()
305             try:
306                 samdb.modify(m)
307                 if role == "rid":
308                     # We may need to allocate the initial RID Set
309                     samdb.create_own_rid_set()
310
311             except LdbError as e1:
312                 (num, msg) = e1.args
313                 if role == "rid" and num == ldb.ERR_ENTRY_ALREADY_EXISTS:
314
315                     # Try again without the RID Set allocation
316                     # (normal).  We have to manage the transaction as
317                     # we do not have nested transactions and creating
318                     # a RID set touches multiple objects. :-(
319                     samdb.transaction_cancel()
320                     samdb.transaction_start()
321                     try:
322                         samdb.modify(m)
323                     except LdbError as e:
324                         (num, msg) = e.args
325                         samdb.transaction_cancel()
326                         raise CommandError("Failed to seize '%s' role: %s" %
327                                            (role, msg))
328
329                 else:
330                     samdb.transaction_cancel()
331                     raise CommandError("Failed to seize '%s' role: %s" %
332                                        (role, msg))
333             samdb.transaction_commit()
334             self.outf.write("FSMO seize of '%s' role successful\n" % role)
335
336             return True
337
338     def seize_dns_role(self, role, samdb, credopts, sambaopts,
339                        versionopts, force):
340         """Seize DNS FSMO role. """
341
342         serviceName = samdb.get_dsServiceName()
343         domain_dn = samdb.domain_dn()
344         forest_dn = samba.dn_from_dns_name(samdb.forest_dns_name())
345         self.domaindns_dn = "CN=Infrastructure,DC=DomainDnsZones," + domain_dn
346         self.forestdns_dn = "CN=Infrastructure,DC=ForestDnsZones," + forest_dn
347
348         m = ldb.Message()
349         if role == "domaindns":
350             m.dn = ldb.Dn(samdb, self.domaindns_dn)
351         elif role == "forestdns":
352             m.dn = ldb.Dn(samdb, self.forestdns_dn)
353         else:
354             raise CommandError("Invalid FSMO role.")
355         #first try to transfer to avoid problem if the owner is still active
356         seize = False
357         master_owner = get_fsmo_roleowner(samdb, m.dn, role)
358         if master_owner is not None:
359             # if there is a different owner
360             if master_owner != serviceName:
361                 # if --force isn't given, attempt transfer
362                 if force is None:
363                     self.message("Attempting transfer...")
364                     try:
365                         transfer_dns_role(self.outf, sambaopts, credopts, role,
366                                           samdb)
367                     except:
368                         #transfer failed, use the big axe...
369                         seize = True
370                         self.message("Transfer unsuccessful, seizing...")
371                     else:
372                         self.message("Transfer successful, not seizing role\n")
373                         return True
374             else:
375                 self.outf.write("This DC already has the '%s' FSMO role\n" %
376                                 role)
377                 return False
378         else:
379             seize = True
380
381         if force is not None or seize == True:
382             self.message("Seizing %s FSMO role..." % role)
383             m["fSMORoleOwner"]= ldb.MessageElement(
384                 serviceName, ldb.FLAG_MOD_REPLACE,
385                 "fSMORoleOwner")
386             try:
387                 samdb.modify(m)
388             except LdbError as e2:
389                 (num, msg) = e2.args
390                 raise CommandError("Failed to seize '%s' role: %s" %
391                                    (role, msg))
392             self.outf.write("FSMO seize of '%s' role successful\n" % role)
393             return True
394
395
396     def run(self, force=None, H=None, role=None,
397             credopts=None, sambaopts=None, versionopts=None):
398
399         lp = sambaopts.get_loadparm()
400         creds = credopts.get_credentials(lp, fallback_machine=True)
401
402         samdb = SamDB(url=H, session_info=system_session(),
403                       credentials=creds, lp=lp)
404
405         if role == "all":
406             self.seize_role("rid", samdb, force)
407             self.seize_role("pdc", samdb, force)
408             self.seize_role("naming", samdb, force)
409             self.seize_role("infrastructure", samdb, force)
410             self.seize_role("schema", samdb, force)
411             self.seize_dns_role("domaindns", samdb, credopts, sambaopts,
412                                 versionopts, force)
413             self.seize_dns_role("forestdns", samdb, credopts, sambaopts,
414                                 versionopts, force)
415         else:
416             if role == "domaindns" or role == "forestdns":
417                 self.seize_dns_role(role, samdb, credopts, sambaopts,
418                                     versionopts, force)
419             else:
420                 self.seize_role(role, samdb, force)
421
422
423 class cmd_fsmo_show(Command):
424     """Show the roles."""
425
426     synopsis = "%prog [options]"
427
428     takes_optiongroups = {
429         "sambaopts": options.SambaOptions,
430         "credopts": options.CredentialsOptions,
431         "versionopts": options.VersionOptions,
432     }
433
434     takes_options = [
435         Option("-H", "--URL", help="LDB URL for database or target server",
436                type=str, metavar="URL", dest="H"),
437     ]
438
439     takes_args = []
440
441     def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
442         lp = sambaopts.get_loadparm()
443         creds = credopts.get_credentials(lp, fallback_machine=True)
444
445         samdb = SamDB(url=H, session_info=system_session(),
446             credentials=creds, lp=lp)
447
448         domain_dn = samdb.domain_dn()
449         forest_dn = samba.dn_from_dns_name(samdb.forest_dns_name())
450         infrastructure_dn = "CN=Infrastructure," + domain_dn
451         naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn()
452         schema_dn = samdb.get_schema_basedn()
453         rid_dn = "CN=RID Manager$,CN=System," + domain_dn
454         domaindns_dn = "CN=Infrastructure,DC=DomainDnsZones," + domain_dn
455         forestdns_dn = "CN=Infrastructure,DC=ForestDnsZones," + forest_dn
456
457         masters = [(schema_dn, "schema", "SchemaMasterRole"),
458                    (infrastructure_dn, "infrastructure", "InfrastructureMasterRole"),
459                    (rid_dn, "rid", "RidAllocationMasterRole"),
460                    (domain_dn, "pdc", "PdcEmulationMasterRole"),
461                    (naming_dn, "naming", "DomainNamingMasterRole"),
462                    (domaindns_dn, "domaindns", "DomainDnsZonesMasterRole"),
463                    (forestdns_dn, "forestdns", "ForestDnsZonesMasterRole"),
464                    ]
465
466         for master in masters:
467             (dn, short_name, long_name) = master
468             try:
469                 master = get_fsmo_roleowner(samdb, dn, short_name)
470                 if master is not None:
471                     self.message("%s owner: %s" % (long_name, str(master)))
472                 else:
473                     self.message("%s has no current owner" % (long_name))
474             except CommandError as e:
475                 self.message("%s: * %s" % (long_name, e.message))
476
477 class cmd_fsmo_transfer(Command):
478     """Transfer the role."""
479
480     synopsis = "%prog [options]"
481
482     takes_optiongroups = {
483         "sambaopts": options.SambaOptions,
484         "credopts": options.CredentialsOptions,
485         "versionopts": options.VersionOptions,
486     }
487
488     takes_options = [
489         Option("-H", "--URL", help="LDB URL for database or target server",
490                type=str, metavar="URL", dest="H"),
491         Option("--role", type="choice", choices=["rid", "pdc", "infrastructure",
492                "schema", "naming", "domaindns", "forestdns", "all"],
493                help="""The FSMO role to seize or transfer.\n
494 rid=RidAllocationMasterRole\n
495 schema=SchemaMasterRole\n
496 pdc=PdcEmulationMasterRole\n
497 naming=DomainNamingMasterRole\n
498 infrastructure=InfrastructureMasterRole\n
499 domaindns=DomainDnsZonesMasterRole\n
500 forestdns=ForestDnsZonesMasterRole\n
501 all=all of the above\n
502 You must provide an Admin user and password."""),
503     ]
504
505     takes_args = []
506
507     def run(self, force=None, H=None, role=None,
508             credopts=None, sambaopts=None, versionopts=None):
509
510         lp = sambaopts.get_loadparm()
511         creds = credopts.get_credentials(lp, fallback_machine=True)
512
513         samdb = SamDB(url=H, session_info=system_session(),
514                       credentials=creds, lp=lp)
515
516         if role == "all":
517             transfer_role(self.outf, "rid", samdb)
518             transfer_role(self.outf, "pdc", samdb)
519             transfer_role(self.outf, "naming", samdb)
520             transfer_role(self.outf, "infrastructure", samdb)
521             transfer_role(self.outf, "schema", samdb)
522             transfer_dns_role(self.outf, sambaopts, credopts,
523                               "domaindns", samdb)
524             transfer_dns_role(self.outf, sambaopts, credopts, "forestdns",
525                               samdb)
526         else:
527             if role == "domaindns" or role == "forestdns":
528                 transfer_dns_role(self.outf, sambaopts, credopts, role, samdb)
529             else:
530                 transfer_role(self.outf, role, samdb)
531
532
533 class cmd_fsmo(SuperCommand):
534     """Flexible Single Master Operations (FSMO) roles management."""
535
536     subcommands = {}
537     subcommands["seize"] = cmd_fsmo_seize()
538     subcommands["show"] = cmd_fsmo_show()
539     subcommands["transfer"] = cmd_fsmo_transfer()