s4-provision: freeze the DNS zone before creating the zone file
[abartlet/samba.git/.git] / source4 / scripting / python / samba / provision.py
index 58c172fc0fdd62a33ae5e7d2df7dbe129310362a..1e1bf480f53259e9273a4c8cc1d8a51cbfd4c9cd 100644 (file)
@@ -35,13 +35,14 @@ import socket
 import param
 import registry
 import urllib
+import shutil
 
 import ldb
 
 from samba.auth import system_session, admin_session
 from samba import glue, version, Ldb, substitute_var, valid_netbios_name
 from samba import check_all_substituted, read_and_sub_file, setup_file
-from samba import DS_DOMAIN_FUNCTION_2003, DS_DC_FUNCTION_2008
+from samba import DS_DOMAIN_FUNCTION_2003, DS_DC_FUNCTION_2008, DS_DC_FUNCTION_2008_R2
 from samba.dcerpc import security
 from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
 from samba.idmap import IDmapDB
@@ -294,8 +295,9 @@ def provision_paths_from_lp(lp, dnsdomain):
     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
     paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
-    paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
+    paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
+    paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
@@ -463,7 +465,11 @@ def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
     if os.path.exists(smbconf):
         default_lp.load(smbconf)
     if eadb:
-        posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(os.path.join(targetdir, "private"),"eadb.tdb"))
+        if targetdir is not None:
+            privdir = os.path.join(targetdir, "private")
+        else:
+            privdir = default_lp.get("private dir")
+        posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(privdir,"eadb.tdb"))
     else:
         posixeadb_line = ""
 
@@ -646,7 +652,8 @@ def secretsdb_self_join(secretsdb, domain,
       secretsdb.add(msg)
 
 
-def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain, 
+def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
+                        realm, dnsdomain,
                         dns_keytab_path, dnspass):
     """Add DNS specific bits to a secrets database.
     
@@ -654,6 +661,11 @@ def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain,
     :param setup_path: Setup path function
     :param machinepass: Machine password
     """
+    try:
+        os.unlink(os.path.join(private_dir, dns_keytab_path))
+    except OSError:
+        pass
+
     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
             "REALM": realm,
             "DNSDOMAIN": dnsdomain,
@@ -1053,8 +1065,8 @@ def setup_samdb(path, setup_path, session_info, provision_backend, lp,
 FILL_FULL = "FULL"
 FILL_NT4SYNC = "NT4SYNC"
 FILL_DRS = "DRS"
-SYSVOL_ACL = "O:${DOMAINSID}-500G:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;S-1-5-32-549)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
-POLICIES_ACL = "O:${DOMAINSID}-500G:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;S-1-5-32-549)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)(A;OICI;0x001301bf;;;${DOMAINSID}-520)"
+SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
+POLICIES_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)(A;OICI;0x001301bf;;;PA)"
 
 def set_gpo_acl(path,acl,lp,domsid):
        setntacl(lp,path,acl,domsid)
@@ -1066,27 +1078,25 @@ def set_gpo_acl(path,acl,lp,domsid):
 
 def setsysvolacl(samdb,names,netlogon,sysvol,gid,domainsid,lp):
        canchown = 1
-       acl = SYSVOL_ACL.replace("${DOMAINSID}",str(domainsid))
        try:
                os.chown(sysvol,-1,gid)
        except:
                canchown = 0
 
-       setntacl(lp,sysvol,acl,str(domainsid))
+       setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
        for root, dirs, files in os.walk(sysvol, topdown=False):
                for name in files:
                        if canchown:
                                os.chown(os.path.join(root, name),-1,gid)
-                       setntacl(lp,os.path.join(root, name),acl,str(domainsid))
+                       setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
                for name in dirs:
                        if canchown:
                                os.chown(os.path.join(root, name),-1,gid)
-                       setntacl(lp,os.path.join(root, name),acl,str(domainsid))
+                       setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
 
        # Set ACL for GPO
        policy_path = os.path.join(sysvol, names.dnsdomain, "Policies")
-       acl = POLICIES_ACL.replace("${DOMAINSID}",str(domainsid))
-       set_gpo_acl(policy_path,dsacl2fsacl(acl,str(domainsid)),lp,str(domainsid))
+       set_gpo_acl(policy_path,dsacl2fsacl(POLICIES_ACL,str(domainsid)),lp,str(domainsid))
        res = samdb.search(base="CN=Policies,CN=System,%s"%(names.domaindn),
                                                attrs=["cn","nTSecurityDescriptor"],
                                                expression="", scope=ldb.SCOPE_ONELEVEL)
@@ -1163,6 +1173,10 @@ def provision(setup_dir, message, session_info,
         wheel_gid = findnss_gid(["wheel", "adm"])
     else:
         wheel_gid = findnss_gid([wheel])
+    try:
+        bind_gid = findnss_gid(["bind", "named"])
+    except KeyError:
+        bind_gid = None
 
     if targetdir is not None:
         if (not os.path.exists(os.path.join(targetdir, "etc"))):
@@ -1172,9 +1186,18 @@ def provision(setup_dir, message, session_info,
         smbconf = param.default_path()
 
     # only install a new smb.conf if there isn't one there already
-    if not os.path.exists(smbconf):
+    if os.path.exists(smbconf):
+        # if Samba Team members can't figure out the weird errors
+        # loading an empty smb.conf gives, then we need to be smarter.
+        # Pretend it just didn't exist --abartlet
+        data = open(smbconf, 'r').read()
+        data = data.lstrip()
+        if data is None or data == "":
+            make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
+                         targetdir, sid_generator, useeadb)
+    else: 
         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
-                     targetdir, sid_generator,useeadb)
+                     targetdir, sid_generator, useeadb)
 
     lp = param.LoadParm()
     lp.load(smbconf)
@@ -1186,6 +1209,8 @@ def provision(setup_dir, message, session_info,
 
     paths = provision_paths_from_lp(lp, names.dnsdomain)
 
+    paths.bind_gid = bind_gid
+
     if hostip is None:
         try:
             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
@@ -1339,7 +1364,8 @@ def provision(setup_dir, message, session_info,
                             secure_channel_type=SEC_CHAN_BDC)
 
         if serverrole == "domain controller":
-            secretsdb_setup_dns(secrets_ldb, setup_path, 
+            secretsdb_setup_dns(secrets_ldb, setup_path,
+                                paths.private_dir,
                                 realm=names.realm, dnsdomain=names.dnsdomain,
                                 dns_keytab_path=paths.dns_keytab,
                                 dnspass=dnspass)
@@ -1349,13 +1375,13 @@ def provision(setup_dir, message, session_info,
 
             # Only make a zone file on the first DC, it should be replicated
             # with DNS replication
-            create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
+            create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain=names.dnsdomain,
                              hostip=hostip,
                              hostip6=hostip6, hostname=names.hostname,
                              realm=names.realm,
                              domainguid=domainguid, ntdsguid=names.ntdsguid)
 
-            create_named_conf(paths.namedconf, setup_path, realm=names.realm,
+            create_named_conf(paths, setup_path, realm=names.realm,
                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
 
             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
@@ -1378,6 +1404,16 @@ def provision(setup_dir, message, session_info,
     #Now commit the secrets.ldb to disk
     secrets_ldb.transaction_commit()
 
+    # the commit creates the dns.keytab, now chown it
+    dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
+    if (os.path.isfile(dns_keytab_path) and paths.bind_gid is not None):
+        try:
+            os.chmod(dns_keytab_path, 0640)
+            os.chown(dns_keytab_path, -1, paths.bind_gid)
+        except OSError:
+            message("Failed to chown %s to bind gid %u" % (dns_keytab_path, paths.bind_gid))
+
+
     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
 
     message("Once the above files are installed, your Samba4 server will be ready to use")
@@ -1450,12 +1486,12 @@ def create_phpldapadmin_config(path, setup_path, ldapi_uri):
             {"S4_LDAPI_URI": ldapi_uri})
 
 
-def create_zone_file(path, setup_path, dnsdomain, 
+def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
                      hostip, hostip6, hostname, realm, domainguid,
                      ntdsguid):
     """Write out a DNS zone file, from the info in the current database.
 
-    :param path: Path of the new zone file.
+    :param paths: paths object
     :param setup_path: Setup path function.
     :param dnsdomain: DNS Domain name
     :param domaindn: DN of the Domain
@@ -1482,7 +1518,21 @@ def create_zone_file(path, setup_path, dnsdomain,
         hostip_base_line = ""
         hostip_host_line = ""
 
-    setup_file(setup_path("provision.zone"), path, {
+    dns_dir = os.path.dirname(paths.dns)
+
+    try:
+        shutil.rmtree(dns_dir, True)
+    except OSError:
+        pass
+
+    os.mkdir(dns_dir, 0775)
+
+    # we need to freeze the zone while we update the contents
+    if targetdir is None:
+        rndc = lp.get("rndc command")
+        os.system(rndc + " freeze " + lp.get("realm"))
+
+    setup_file(setup_path("provision.zone"), paths.dns, {
             "HOSTNAME": hostname,
             "DNSDOMAIN": dnsdomain,
             "REALM": realm,
@@ -1496,13 +1546,26 @@ def create_zone_file(path, setup_path, dnsdomain,
             "HOSTIP6_HOST_LINE": hostip6_host_line,
         })
 
+    if paths.bind_gid is not None:
+        try:
+            os.chown(dns_dir, -1, paths.bind_gid)
+            os.chown(paths.dns, -1, paths.bind_gid)
+            # chmod needed to cope with umask
+            os.chmod(dns_dir, 0775)
+            os.chmod(paths.dns, 0664)
+        except OSError:
+            message("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
+
+    if targetdir is None:
+        os.system(rndc + " unfreeze " + lp.get("realm"))
+
 
-def create_named_conf(path, setup_path, realm, dnsdomain,
+def create_named_conf(paths, setup_path, realm, dnsdomain,
                       private_dir):
     """Write out a file containing zone statements suitable for inclusion in a
     named.conf file (including GSS-TSIG configuration).
     
-    :param path: Path of the new named.conf file.
+    :param paths: all paths
     :param setup_path: Setup path function.
     :param realm: Realm name
     :param dnsdomain: DNS Domain name
@@ -1510,13 +1573,17 @@ def create_named_conf(path, setup_path, realm, dnsdomain,
     :param keytab_name: File name of DNS keytab file
     """
 
-    setup_file(setup_path("named.conf"), path, {
+    setup_file(setup_path("named.conf"), paths.namedconf, {
             "DNSDOMAIN": dnsdomain,
             "REALM": realm,
+            "ZONE_FILE": paths.dns,
             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
-            "PRIVATE_DIR": private_dir
+            "NAMED_CONF": paths.namedconf,
+            "NAMED_CONF_UPDATE": paths.namedconf_update
             })
 
+    setup_file(setup_path("named.conf.update"), paths.namedconf_update)
+
 def create_named_txt(path, setup_path, realm, dnsdomain,
                       private_dir, keytab_name):
     """Write out a file containing zone statements suitable for inclusion in a