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