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