3 # Copyright Andrew Bartlett <abartlet@samba.org>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import samba.getopt as options
27 from samba.samdb import SamDB
30 from samba.ntacls import backup_online, backup_restore
31 from samba.auth import system_session
32 from samba.join import DCJoinContext, join_clone, DCCloneAndRenameContext
33 from samba.dcerpc.security import dom_sid
34 from samba.netcmd import Option, CommandError
35 from samba.dcerpc import misc
37 from fsmo import cmd_fsmo_seize
38 from samba.provision import make_smbconf
39 from samba.upgradehelpers import update_krbtgt_account_password
40 from samba.remove_dc import remove_dc
41 from samba.provision import secretsdb_self_join
42 from samba.dbchecker import dbcheck
44 from samba.provision import guess_names, determine_host_ip, determine_host_ip6
45 from samba.provision.sambadns import (fill_dns_data_partitions,
50 # work out a SID (based on a free RID) to use when the domain gets restored.
51 # This ensures that the restored DC's SID won't clash with any other RIDs
52 # already in use in the domain
53 def get_sid_for_restore(samdb):
54 # Find the DN of the RID set of the server
55 res = samdb.search(base=ldb.Dn(samdb, samdb.get_serverName()),
56 scope=ldb.SCOPE_BASE, attrs=["serverReference"])
57 server_ref_dn = ldb.Dn(samdb, res[0]['serverReference'][0])
58 res = samdb.search(base=server_ref_dn,
60 attrs=['rIDSetReferences'])
61 rid_set_dn = ldb.Dn(samdb, res[0]['rIDSetReferences'][0])
63 # Get the alloc pools and next RID of the RID set
64 res = samdb.search(base=rid_set_dn,
65 scope=ldb.SCOPE_SUBTREE,
66 expression="(rIDNextRID=*)",
67 attrs=['rIDAllocationPool',
68 'rIDPreviousAllocationPool',
71 # Decode the bounds of the RID allocation pools
72 rid = int(res[0].get('rIDNextRID')[0])
75 high = (0xFFFFFFFF00000000 & int(num)) >> 32
76 low = 0x00000000FFFFFFFF & int(num)
78 pool_l, pool_h = split_val(res[0].get('rIDPreviousAllocationPool')[0])
79 npool_l, npool_h = split_val(res[0].get('rIDAllocationPool')[0])
81 # Calculate next RID based on pool bounds
83 raise CommandError('Out of RIDs, finished AllocPool')
86 raise CommandError('Out of RIDs, finished PrevAllocPool.')
92 sid = dom_sid(samdb.get_domain_sid())
93 return str(sid) + '-' + str(rid)
97 return datetime.datetime.now().isoformat().replace(':', '-')
100 def backup_filepath(targetdir, name, time_str):
101 filename = 'samba-backup-{}-{}.tar.bz2'.format(name, time_str)
102 return os.path.join(targetdir, filename)
105 def create_backup_tar(logger, tmpdir, backup_filepath):
106 # Adds everything in the tmpdir into a new tar file
107 logger.info("Creating backup file %s..." % backup_filepath)
108 tf = tarfile.open(backup_filepath, 'w:bz2')
109 tf.add(tmpdir, arcname='./')
113 # Add a backup-specific marker to the DB with info that we'll use during
114 # the restore process
115 def add_backup_marker(samdb, marker, value):
117 m.dn = ldb.Dn(samdb, "@SAMBA_DSDB")
118 m[marker] = ldb.MessageElement(value, ldb.FLAG_MOD_ADD, marker)
122 def check_online_backup_args(logger, credopts, server, targetdir):
123 # Make sure we have all the required args.
124 u_p = {'user': credopts.creds.get_username(),
125 'pass': credopts.creds.get_password()}
126 if None in u_p.values():
127 raise CommandError("Creds required.")
129 raise CommandError('Server required')
130 if targetdir is None:
131 raise CommandError('Target directory required')
133 if not os.path.exists(targetdir):
134 logger.info('Creating targetdir %s...' % targetdir)
135 os.makedirs(targetdir)
138 class cmd_domain_backup_online(samba.netcmd.Command):
139 '''Copy a running DC's current DB into a backup tar file.
141 Takes a backup copy of the current domain from a running DC. If the domain
142 were to undergo a catastrophic failure, then the backup file can be used to
143 recover the domain. The backup created is similar to the DB that a new DC
144 would receive when it joins the domain.
147 - it's recommended to run 'samba-tool dbcheck' before taking a backup-file
148 and fix any errors it reports.
149 - all the domain's secrets are included in the backup file.
150 - although the DB contents can be untarred and examined manually, you need
151 to run 'samba-tool domain backup restore' before you can start a Samba DC
152 from the backup file.'''
154 synopsis = "%prog --server=<DC-to-backup> --targetdir=<output-dir>"
155 takes_optiongroups = {
156 "sambaopts": options.SambaOptions,
157 "credopts": options.CredentialsOptions,
161 Option("--server", help="The DC to backup", type=str),
162 Option("--targetdir", type=str,
163 help="Directory to write the backup file to"),
166 def run(self, sambaopts=None, credopts=None, server=None, targetdir=None):
167 logger = self.get_logger()
168 logger.setLevel(logging.DEBUG)
170 # Make sure we have all the required args.
171 check_online_backup_args(logger, credopts, server, targetdir)
173 lp = sambaopts.get_loadparm()
174 creds = credopts.get_credentials(lp)
176 if not os.path.exists(targetdir):
177 logger.info('Creating targetdir %s...' % targetdir)
178 os.makedirs(targetdir)
180 tmpdir = tempfile.mkdtemp(dir=targetdir)
182 # Run a clone join on the remote
183 ctx = join_clone(logger=logger, creds=creds, lp=lp,
184 include_secrets=True, dns_backend='SAMBA_INTERNAL',
185 server=server, targetdir=tmpdir)
187 # get the paths used for the clone, then drop the old samdb connection
191 # Get a free RID to use as the new DC's SID (when it gets restored)
192 remote_sam = SamDB(url='ldap://' + server, credentials=creds,
193 session_info=system_session(), lp=lp)
194 new_sid = get_sid_for_restore(remote_sam)
195 realm = remote_sam.domain_dns_name()
197 # Grab the remote DC's sysvol files and bundle them into a tar file
198 sysvol_tar = os.path.join(tmpdir, 'sysvol.tar.gz')
199 smb_conn = smb.SMB(server, "sysvol", lp=lp, creds=creds)
200 backup_online(smb_conn, sysvol_tar, remote_sam.get_domain_sid())
202 # remove the default sysvol files created by the clone (we want to
203 # make sure we restore the sysvol.tar.gz files instead)
204 shutil.rmtree(paths.sysvol)
206 # Edit the downloaded sam.ldb to mark it as a backup
207 samdb = SamDB(url=paths.samdb, session_info=system_session(), lp=lp)
208 time_str = get_timestamp()
209 add_backup_marker(samdb, "backupDate", time_str)
210 add_backup_marker(samdb, "sidForRestore", new_sid)
212 # Add everything in the tmpdir to the backup tar file
213 backup_file = backup_filepath(targetdir, realm, time_str)
214 create_backup_tar(logger, tmpdir, backup_file)
216 shutil.rmtree(tmpdir)
219 class cmd_domain_backup_restore(cmd_fsmo_seize):
220 '''Restore the domain's DB from a backup-file.
222 This restores a previously backed up copy of the domain's DB on a new DC.
224 Note that the restored DB will not contain the original DC that the backup
225 was taken from (or any other DCs in the original domain). Only the new DC
226 (specified by --newservername) will be present in the restored DB.
228 Samba can then be started against the restored DB. Any existing DCs for the
229 domain should be shutdown before the new DC is started. Other DCs can then
230 be joined to the new DC to recover the network.
232 Note that this command should be run as the root user - it will fail
235 synopsis = ("%prog --backup-file=<tar-file> --targetdir=<output-dir> "
236 "--newservername=<DC-name>")
238 Option("--backup-file", help="Path to backup file", type=str),
239 Option("--targetdir", help="Path to write to", type=str),
240 Option("--newservername", help="Name for new server", type=str),
241 Option("--host-ip", type="string", metavar="IPADDRESS",
242 help="set IPv4 ipaddress"),
243 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
244 help="set IPv6 ipaddress"),
247 takes_optiongroups = {
248 "sambaopts": options.SambaOptions,
249 "credopts": options.CredentialsOptions,
252 def register_dns_zone(self, logger, samdb, lp, ntdsguid, host_ip,
255 Registers the new realm's DNS objects when a renamed domain backup
258 names = guess_names(lp)
259 domaindn = names.domaindn
260 forestdn = samdb.get_root_basedn().get_linearized()
261 dnsdomain = names.dnsdomain.lower()
262 dnsforest = dnsdomain
263 hostname = names.netbiosname.lower()
264 domainsid = dom_sid(samdb.get_domain_sid())
265 dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
266 domainguid = get_domainguid(samdb, domaindn)
268 # work out the IP address to use for the new DC's DNS records
269 host_ip = determine_host_ip(logger, lp, host_ip)
270 host_ip6 = determine_host_ip6(logger, lp, host_ip6)
272 if host_ip is None and host_ip6 is None:
273 raise CommandError('Please specify a host-ip for the new server')
275 logger.info("DNS realm was renamed to %s" % dnsdomain)
276 logger.info("Populating DNS partitions for new realm...")
278 # Add the DNS objects for the new realm (note: the backup clone already
279 # has the root server objects, so don't add them again)
280 fill_dns_data_partitions(samdb, domainsid, names.sitename, domaindn,
281 forestdn, dnsdomain, dnsforest, hostname,
282 host_ip, host_ip6, domainguid, ntdsguid,
283 dnsadmins_sid, add_root=False)
285 def fix_old_dc_references(self, samdb):
286 '''Fixes attributes that reference the old/removed DCs'''
288 # we just want to fix up DB problems here that were introduced by us
289 # removing the old DCs. We restrict what we fix up so that the restored
290 # DB matches the backed-up DB as close as possible. (There may be other
291 # DB issues inherited from the backed-up DC, but it's not our place to
292 # silently try to fix them here).
293 samdb.transaction_start()
294 chk = dbcheck(samdb, quiet=True, fix=True, yes=False,
297 # fix up stale references to the old DC
298 setattr(chk, 'fix_all_old_dn_string_component_mismatch', 'ALL')
299 attrs = ['lastKnownParent', 'interSiteTopologyGenerator']
301 # fix-up stale one-way links that point to the old DC
302 setattr(chk, 'remove_plausible_deleted_DN_links', 'ALL')
303 attrs += ['msDS-NC-Replica-Locations']
305 cross_ncs_ctrl = 'search_options:1:2'
306 controls = ['show_deleted:1', cross_ncs_ctrl]
307 chk.check_database(controls=controls, attrs=attrs)
308 samdb.transaction_commit()
310 def run(self, sambaopts=None, credopts=None, backup_file=None,
311 targetdir=None, newservername=None, host_ip=None, host_ip6=None):
312 if not (backup_file and os.path.exists(backup_file)):
313 raise CommandError('Backup file not found.')
314 if targetdir is None:
315 raise CommandError('Please specify a target directory')
316 # allow restoredc to install into a directory prepopulated by selftest
317 if (os.path.exists(targetdir) and os.listdir(targetdir) and
318 os.environ.get('SAMBA_SELFTEST') != '1'):
319 raise CommandError('Target directory is not empty')
320 if not newservername:
321 raise CommandError('Server name required')
323 logger = logging.getLogger()
324 logger.setLevel(logging.DEBUG)
325 logger.addHandler(logging.StreamHandler(sys.stdout))
327 # ldapcmp prefers the server's netBIOS name in upper-case
328 newservername = newservername.upper()
330 # extract the backup .tar to a temp directory
331 targetdir = os.path.abspath(targetdir)
332 tf = tarfile.open(backup_file)
333 tf.extractall(targetdir)
336 # use the smb.conf that got backed up, by default (save what was
337 # actually backed up, before we mess with it)
338 smbconf = os.path.join(targetdir, 'etc', 'smb.conf')
339 shutil.copyfile(smbconf, smbconf + ".orig")
341 # if a smb.conf was specified on the cmd line, then use that instead
342 cli_smbconf = sambaopts.get_loadparm_path()
344 logger.info("Using %s as restored domain's smb.conf" % cli_smbconf)
345 shutil.copyfile(cli_smbconf, smbconf)
347 lp = samba.param.LoadParm()
350 # open a DB connection to the restored DB
351 private_dir = os.path.join(targetdir, 'private')
352 samdb_path = os.path.join(private_dir, 'sam.ldb')
353 samdb = SamDB(url=samdb_path, session_info=system_session(), lp=lp)
355 # Create account using the join_add_objects function in the join object
356 # We need namingContexts, account control flags, and the sid saved by
357 # the backup process.
358 res = samdb.search(base="", scope=ldb.SCOPE_BASE,
359 attrs=['namingContexts'])
360 ncs = [str(r) for r in res[0].get('namingContexts')]
362 creds = credopts.get_credentials(lp)
363 ctx = DCJoinContext(logger, creds=creds, lp=lp,
364 forced_local_samdb=samdb,
365 netbios_name=newservername)
367 ctx.full_nc_list = ncs
368 ctx.userAccountControl = (samba.dsdb.UF_SERVER_TRUST_ACCOUNT |
369 samba.dsdb.UF_TRUSTED_FOR_DELEGATION)
371 # rewrite the smb.conf to make sure it uses the new targetdir settings.
372 # (This doesn't update all filepaths in a customized config, but it
373 # corrects the same paths that get set by a new provision)
374 logger.info('Updating basic smb.conf settings...')
375 make_smbconf(smbconf, newservername, ctx.domain_name,
376 ctx.realm, targetdir, lp=lp,
377 serverrole="active directory domain controller")
379 # Get the SID saved by the backup process and create account
380 res = samdb.search(base=ldb.Dn(samdb, "@SAMBA_DSDB"),
381 scope=ldb.SCOPE_BASE,
382 attrs=['sidForRestore', 'backupRename'])
383 is_rename = True if 'backupRename' in res[0] else False
384 sid = res[0].get('sidForRestore')[0]
385 logger.info('Creating account with SID: ' + str(sid))
386 ctx.join_add_objects(specified_sid=dom_sid(sid))
389 m.dn = ldb.Dn(samdb, '@ROOTDSE')
390 ntds_guid = str(ctx.ntds_guid)
391 m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % ntds_guid,
392 ldb.FLAG_MOD_REPLACE,
396 # if we renamed the backed-up domain, then we need to add the DNS
397 # objects for the new realm (we do this in the restore, now that we
398 # know the new DC's IP address)
400 self.register_dns_zone(logger, samdb, lp, ctx.ntds_guid,
403 secrets_path = os.path.join(private_dir, 'secrets.ldb')
404 secrets_ldb = Ldb(secrets_path, session_info=system_session(), lp=lp)
405 secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
406 realm=ctx.realm, dnsdomain=ctx.dnsdomain,
407 netbiosname=ctx.myname, domainsid=ctx.domsid,
408 machinepass=ctx.acct_pass,
409 key_version_number=ctx.key_version_number,
410 secure_channel_type=misc.SEC_CHAN_BDC)
413 domain_dn = samdb.domain_dn()
414 forest_dn = samba.dn_from_dns_name(samdb.forest_dns_name())
415 domaindns_dn = ("CN=Infrastructure,DC=DomainDnsZones,", domain_dn)
416 forestdns_dn = ("CN=Infrastructure,DC=ForestDnsZones,", forest_dn)
417 for dn_prefix, dns_dn in [forestdns_dn, domaindns_dn]:
418 if dns_dn not in ncs:
420 full_dn = dn_prefix + dns_dn
422 m.dn = ldb.Dn(samdb, full_dn)
423 m["fSMORoleOwner"] = ldb.MessageElement(samdb.get_dsServiceName(),
424 ldb.FLAG_MOD_REPLACE,
429 for role in ['rid', 'pdc', 'naming', 'infrastructure', 'schema']:
430 self.seize_role(role, samdb, force=True)
432 # Get all DCs and remove them (this ensures these DCs cannot
433 # replicate because they will not have a password)
434 search_expr = "(&(objectClass=Server)(serverReference=*))"
435 res = samdb.search(samdb.get_config_basedn(), scope=ldb.SCOPE_SUBTREE,
436 expression=search_expr)
439 if cn != newservername:
440 remove_dc(samdb, logger, cn)
442 # Remove the repsFrom and repsTo from each NC to ensure we do
443 # not try (and fail) to talk to the old DCs
446 msg.dn = ldb.Dn(samdb, nc)
448 msg["repsFrom"] = ldb.MessageElement([],
449 ldb.FLAG_MOD_REPLACE,
451 msg["repsTo"] = ldb.MessageElement([],
452 ldb.FLAG_MOD_REPLACE,
456 # Update the krbtgt passwords twice, ensuring no tickets from
457 # the old domain are valid
458 update_krbtgt_account_password(samdb)
459 update_krbtgt_account_password(samdb)
461 # restore the sysvol directory from the backup tar file, including the
462 # original NTACLs. Note that the backup_restore() will fail if not root
463 sysvol_tar = os.path.join(targetdir, 'sysvol.tar.gz')
464 dest_sysvol_dir = lp.get('path', 'sysvol')
465 if not os.path.exists(dest_sysvol_dir):
466 os.makedirs(dest_sysvol_dir)
467 backup_restore(sysvol_tar, dest_sysvol_dir, samdb, smbconf)
468 os.remove(sysvol_tar)
470 # fix up any stale links to the old DCs we just removed
471 logger.info("Fixing up any remaining references to the old DCs...")
472 self.fix_old_dc_references(samdb)
474 # Remove DB markers added by the backup process
476 m.dn = ldb.Dn(samdb, "@SAMBA_DSDB")
477 m["backupDate"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
479 m["sidForRestore"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
482 m["backupRename"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
486 logger.info("Backup file successfully restored to %s" % targetdir)
487 logger.info("Please check the smb.conf settings are correct before "
491 class cmd_domain_backup_rename(samba.netcmd.Command):
492 '''Copy a running DC's DB to backup file, renaming the domain in the process.
494 Where <new-domain> is the new domain's NetBIOS name, and <new-dnsrealm> is
495 the new domain's realm in DNS form.
497 This is similar to 'samba-tool backup online' in that it clones the DB of a
498 running DC. However, this option also renames all the domain entries in the
499 DB. Renaming the domain makes it possible to restore and start a new Samba
500 DC without it interfering with the existing Samba domain. In other words,
501 you could use this option to clone your production samba domain and restore
502 it to a separate pre-production environment that won't overlap or interfere
503 with the existing production Samba domain.
506 - it's recommended to run 'samba-tool dbcheck' before taking a backup-file
507 and fix any errors it reports.
508 - all the domain's secrets are included in the backup file.
509 - although the DB contents can be untarred and examined manually, you need
510 to run 'samba-tool domain backup restore' before you can start a Samba DC
511 from the backup file.
512 - GPO and sysvol information will still refer to the old realm and will
513 need to be updated manually.
514 - if you specify 'keep-dns-realm', then the DNS records will need updating
515 in order to work (they will still refer to the old DC's IP instead of the
517 - we recommend that you only use this option if you know what you're doing.
520 synopsis = ("%prog <new-domain> <new-dnsrealm> --server=<DC-to-backup> "
521 "--targetdir=<output-dir>")
522 takes_optiongroups = {
523 "sambaopts": options.SambaOptions,
524 "credopts": options.CredentialsOptions,
528 Option("--server", help="The DC to backup", type=str),
529 Option("--targetdir", help="Directory to write the backup file",
531 Option("--keep-dns-realm", action="store_true", default=False,
532 help="Retain the DNS entries for the old realm in the backup"),
535 takes_args = ["new_domain_name", "new_dns_realm"]
537 def update_dns_root(self, logger, samdb, old_realm, delete_old_dns):
538 '''Updates dnsRoot for the partition objects to reflect the rename'''
540 # lookup the crossRef objects that hold the old realm's dnsRoot
541 partitions_dn = samdb.get_partitions_dn()
542 res = samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL,
544 expression='(&(objectClass=crossRef)(dnsRoot=*))')
545 new_realm = samdb.domain_dns_name()
547 # go through and add the new realm
549 # dnsRoot can be multi-valued, so only look for the old realm
550 for dns_root in res_msg["dnsRoot"]:
552 if old_realm in dns_root:
553 new_dns_root = re.sub('%s$' % old_realm, new_realm,
555 logger.info("Adding %s dnsRoot to %s" % (new_dns_root, dn))
559 m["dnsRoot"] = ldb.MessageElement(new_dns_root,
564 # optionally remove the dnsRoot for the old realm
566 logger.info("Removing %s dnsRoot from %s" % (dns_root,
568 m["dnsRoot"] = ldb.MessageElement(dns_root,
573 # Updates the CN=<domain>,CN=Partitions,CN=Configuration,... object to
574 # reflect the domain rename
575 def rename_domain_partition(self, logger, samdb, new_netbios_name):
576 '''Renames the domain parition object and updates its nETBIOSName'''
578 # lookup the crossRef object that holds the nETBIOSName (nCName has
579 # already been updated by this point, but the netBIOS hasn't)
580 base_dn = samdb.get_default_basedn()
581 nc_name = ldb.binary_encode(str(base_dn))
582 partitions_dn = samdb.get_partitions_dn()
583 res = samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL,
584 attrs=["nETBIOSName"],
585 expression='ncName=%s' % nc_name)
587 logger.info("Changing backup domain's NetBIOS name to %s" %
591 m["nETBIOSName"] = ldb.MessageElement(new_netbios_name,
592 ldb.FLAG_MOD_REPLACE,
596 # renames the object itself to reflect the change in domain
597 new_dn = "CN=%s,%s" % (new_netbios_name, partitions_dn)
598 logger.info("Renaming %s --> %s" % (res[0].dn, new_dn))
599 samdb.rename(res[0].dn, new_dn, controls=['relax:0'])
601 def delete_old_dns_zones(self, logger, samdb, old_realm):
602 # remove the top-level DNS entries for the old realm
603 basedn = samdb.get_default_basedn()
604 dn = "DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones,%s" % (old_realm, basedn)
605 logger.info("Deleting old DNS zone %s" % dn)
606 samdb.delete(dn, ["tree_delete:1"])
608 forestdn = samdb.get_root_basedn().get_linearized()
609 dn = "DC=_msdcs.%s,CN=MicrosoftDNS,DC=ForestDnsZones,%s" % (old_realm,
611 logger.info("Deleting old DNS zone %s" % dn)
612 samdb.delete(dn, ["tree_delete:1"])
614 def fix_old_dn_attributes(self, samdb):
615 '''Fixes attributes (i.e. objectCategory) that still use the old DN'''
617 samdb.transaction_start()
618 # Just fix any mismatches in DN detected (leave any other errors)
619 chk = dbcheck(samdb, quiet=True, fix=True, yes=False,
621 # fix up incorrect objectCategory/etc attributes
622 setattr(chk, 'fix_all_old_dn_string_component_mismatch', 'ALL')
623 cross_ncs_ctrl = 'search_options:1:2'
624 controls = ['show_deleted:1', cross_ncs_ctrl]
625 chk.check_database(controls=controls)
626 samdb.transaction_commit()
628 def run(self, new_domain_name, new_dns_realm, sambaopts=None,
629 credopts=None, server=None, targetdir=None, keep_dns_realm=False):
630 logger = self.get_logger()
631 logger.setLevel(logging.INFO)
633 # Make sure we have all the required args.
634 check_online_backup_args(logger, credopts, server, targetdir)
635 delete_old_dns = not keep_dns_realm
637 new_dns_realm = new_dns_realm.lower()
638 new_domain_name = new_domain_name.upper()
640 new_base_dn = samba.dn_from_dns_name(new_dns_realm)
641 logger.info("New realm for backed up domain: %s" % new_dns_realm)
642 logger.info("New base DN for backed up domain: %s" % new_base_dn)
643 logger.info("New domain NetBIOS name: %s" % new_domain_name)
645 tmpdir = tempfile.mkdtemp(dir=targetdir)
647 # Clone and rename the remote server
648 lp = sambaopts.get_loadparm()
649 creds = credopts.get_credentials(lp)
650 ctx = DCCloneAndRenameContext(new_base_dn, new_domain_name,
651 new_dns_realm, logger=logger,
652 creds=creds, lp=lp, include_secrets=True,
653 dns_backend='SAMBA_INTERNAL',
654 server=server, targetdir=tmpdir)
657 # get the paths used for the clone, then drop the old samdb connection
661 # get a free RID to use as the new DC's SID (when it gets restored)
662 remote_sam = SamDB(url='ldap://' + server, credentials=creds,
663 session_info=system_session(), lp=lp)
664 new_sid = get_sid_for_restore(remote_sam)
665 old_realm = remote_sam.domain_dns_name()
667 # Grab the remote DC's sysvol files and bundle them into a tar file.
668 # Note we end up with 2 sysvol dirs - the original domain's files (that
669 # use the old realm) backed here, as well as default files generated
670 # for the new realm as part of the clone/join.
671 sysvol_tar = os.path.join(tmpdir, 'sysvol.tar.gz')
672 smb_conn = smb.SMB(server, "sysvol", lp=lp, creds=creds)
673 backup_online(smb_conn, sysvol_tar, remote_sam.get_domain_sid())
675 # connect to the local DB (making sure we use the new/renamed config)
676 lp.load(paths.smbconf)
677 samdb = SamDB(url=paths.samdb, session_info=system_session(), lp=lp)
679 # Edit the cloned sam.ldb to mark it as a backup
680 time_str = get_timestamp()
681 add_backup_marker(samdb, "backupDate", time_str)
682 add_backup_marker(samdb, "sidForRestore", new_sid)
683 add_backup_marker(samdb, "backupRename", old_realm)
685 # fix up the DNS objects that are using the old dnsRoot value
686 self.update_dns_root(logger, samdb, old_realm, delete_old_dns)
688 # update the netBIOS name and the Partition object for the domain
689 self.rename_domain_partition(logger, samdb, new_domain_name)
692 self.delete_old_dns_zones(logger, samdb, old_realm)
694 logger.info("Fixing DN attributes after rename...")
695 self.fix_old_dn_attributes(samdb)
697 # Add everything in the tmpdir to the backup tar file
698 backup_file = backup_filepath(targetdir, new_dns_realm, time_str)
699 create_backup_tar(logger, tmpdir, backup_file)
701 shutil.rmtree(tmpdir)
704 class cmd_domain_backup(samba.netcmd.SuperCommand):
705 '''Create or restore a backup of the domain.'''
706 subcommands = {'online': cmd_domain_backup_online(),
707 'rename': cmd_domain_backup_rename(),
708 'restore': cmd_domain_backup_restore()}