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