Initial Implementation of the DS objects access checks.
[ira/wip.git] / source4 / scripting / python / samba / provision.py
index 065677fa68c2138a467e73ee9865c767f6020578..64491c2b18dcff3a81f557dcc3127dfa8ad77681 100644 (file)
@@ -44,23 +44,20 @@ from credentials import Credentials, DONT_USE_KERBEROS
 from auth import system_session, admin_session
 from samba import version, Ldb, substitute_var, valid_netbios_name
 from samba import check_all_substituted
-from samba import DS_DOMAIN_FUNCTION_2000, DS_DC_FUNCTION_2008_R2
+from samba import DS_DOMAIN_FUNCTION_2000, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008, DS_DC_FUNCTION_2008_R2
 from samba.samdb import SamDB
 from samba.idmap import IDmapDB
 from samba.dcerpc import security
+from samba.ndr import ndr_pack
 import urllib
 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
 from ms_schema import read_ms_schema
 from ms_display_specifiers import read_ms_ldif
 from signal import SIGTERM
+from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
 
 __docformat__ = "restructuredText"
 
-
-class ProvisioningError(ValueError):
-  pass
-
-
 def find_setup_dir():
     """Find the setup directory used by provision."""
     dirname = os.path.dirname(__file__)
@@ -76,9 +73,47 @@ def find_setup_dir():
         return ret
     raise Exception("Unable to find setup directory.")
 
+def get_schema_descriptor(domain_sid):
+    sddl = "O:SAG:SAD:(A;CI;RPLCLORC;;;AU)(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \
+           "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+           "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+           "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
+           "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+           "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
+           "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \
+           "(AU;CISA;WP;;;WD)(AU;SA;CR;;;BA)" \
+           "(AU;SA;CR;;;DU)(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \
+           "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
+    sec = security.descriptor.from_sddl(sddl, domain_sid)
+    return b64encode(ndr_pack(sec))
+
+def get_config_descriptor(domain_sid):
+    sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+           "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+           "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+           "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+           "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+           "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+           "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+           "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
+           "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+           "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
+           "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+           "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
+           "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;S-1-5-21-3191434175-1265308384-3577286990-498)" \
+           "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
+           "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
+    sec = security.descriptor.from_sddl(sddl, domain_sid)
+    return b64encode(ndr_pack(sec))
+
 
 DEFAULTSITE = "Default-First-Site-Name"
 
+# Exception classes
+
+class ProvisioningError(Exception):
+    """A generic provision error."""
+
 class InvalidNetbiosName(Exception):
     """A specified name was not a valid NetBIOS name."""
     def __init__(self, name):
@@ -142,7 +177,7 @@ class ProvisionResult(object):
         self.samdb = None
         
 class Schema(object):
-    def __init__(self, setup_path, schemadn=None, 
+    def __init__(self, setup_path, domain_sid, schemadn=None,
                  serverdn=None, sambadn=None, ldap_backend_type=None):
         """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
         
@@ -165,8 +200,11 @@ class Schema(object):
                                                   {"SCHEMADN": schemadn,
                                                    "SERVERDN": serverdn,
                                                    })
+
+        descr = get_schema_descriptor(domain_sid)
         self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
-                                               {"SCHEMADN": schemadn
+                                               {"SCHEMADN": schemadn,
+                                                "DESCRIPTOR": descr
                                                 })
 
         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
@@ -318,7 +356,6 @@ def provision_paths_from_lp(lp, dnsdomain):
     """
     paths = ProvisionPaths()
     paths.private_dir = lp.get("private dir")
-    paths.keytab = "secrets.keytab"
     paths.dns_keytab = "dns.keytab"
 
     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
@@ -575,7 +612,9 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
     #   module when expanding the objectclass list)
     # - partition must be last
     # - each partition has its own module list then
-    modules_list = ["rootdse",
+    modules_list = ["resolve_oids",
+                    "rootdse",
+                    "acl",
                     "paged_results",
                     "ranged_results",
                     "anr",
@@ -657,26 +696,83 @@ def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
 
     samdb.transaction_commit()
     
+def secretsdb_self_join(secretsdb, domain, 
+                        netbiosname, domainsid, machinepass, 
+                        realm=None, dnsdomain=None,
+                        keytab_path=None, 
+                        key_version_number=1,
+                        secure_channel_type=SEC_CHAN_WKSTA):
+    """Add domain join-specific bits to a secrets database.
+    
+    :param secretsdb: Ldb Handle to the secrets database
+    :param machinepass: Machine password
+    """
+    attrs=["whenChanged",
+           "secret",
+           "priorSecret",
+           "priorChanged",
+           "krb5Keytab",
+           "privateKeytab"]
+    
+
+    msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
+    msg["secureChannelType"] = str(secure_channel_type)
+    msg["flatname"] = [domain]
+    msg["objectClass"] = ["top", "primaryDomain"]
+    if realm is not None:
+      if dnsdomain is None:
+        dnsdomain = realm.lower()
+      msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
+      msg["realm"] = realm
+      msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
+      msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
+      msg["privateKeytab"] = ["secrets.keytab"];
+
+
+    msg["secret"] = [machinepass]
+    msg["samAccountName"] = ["%s$" % netbiosname]
+    msg["secureChannelType"] = [str(secure_channel_type)]
+    msg["objectSid"] = [ndr_pack(domainsid)]
+    
+    res = secretsdb.search(base="cn=Primary Domains", 
+                           attrs=attrs, 
+                           expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
+                           scope=SCOPE_ONELEVEL)
+    
+    for del_msg in res:
+      if del_msg.dn is not msg.dn:
+        secretsdb.delete(del_msg.dn)
+
+    res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
+
+    if len(res) == 1:
+      msg["priorSecret"] = res[0]["secret"]
+      msg["priorWhenChanged"] = res[0]["whenChanged"]
 
+      if res["privateKeytab"] is not None:
+        msg["privateKeytab"] = res[0]["privateKeytab"]
+
+      if res["krb5Keytab"] is not None:
+        msg["krb5Keytab"] = res[0]["krb5Keytab"]
+
+      for el in msg:
+        el.set_flags(ldb.FLAG_MOD_REPLACE)
+        secretsdb.modify(msg)
+    else:
+      secretsdb.add(msg)
 
-def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, 
-                        netbiosname, domainsid, keytab_path, samdb_url
-                        dns_keytab_path, dnspass, machinepass):
-    """Add DC-specific bits to a secrets database.
+
+def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain
+                        dns_keytab_path, dnspass):
+    """Add DNS specific bits to a secrets database.
     
     :param secretsdb: Ldb Handle to the secrets database
     :param setup_path: Setup path function
     :param machinepass: Machine password
     """
-    setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { 
-            "MACHINEPASS_B64": b64encode(machinepass),
-            "DOMAIN": domain,
+    setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
             "REALM": realm,
             "DNSDOMAIN": dnsdomain,
-            "DOMAINSID": str(domainsid),
-            "SECRETS_KEYTAB": keytab_path,
-            "NETBIOSNAME": netbiosname,
-            "SAM_LDB": samdb_url,
             "DNS_KEYTAB": dns_keytab_path,
             "DNSPASS_B64": b64encode(dnspass),
             })
@@ -700,6 +796,7 @@ def setup_secretsdb(path, setup_path, session_info, credentials, lp):
     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
                       lp=lp)
+    secrets_ldb.transaction_start()
     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
 
     if credentials is not None and credentials.authentication_requested():
@@ -835,9 +932,10 @@ def setup_samdb(path, setup_path, session_info, credentials, lp,
     :note: This will wipe the main SAM database file!
     """
 
-    domainFunctionality = DS_DOMAIN_FUNCTION_2000
-    forestFunctionality = DS_DOMAIN_FUNCTION_2000
-    domainControllerFunctionality = DS_DC_FUNCTION_2008_R2
+    # Do NOT change these default values without discussion with the team and reslease manager.  
+    domainFunctionality = DS_DOMAIN_FUNCTION_2008
+    forestFunctionality = DS_DOMAIN_FUNCTION_2008
+    domainControllerFunctionality = DS_DC_FUNCTION_2008
 
     # Also wipes the database
     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
@@ -846,7 +944,7 @@ def setup_samdb(path, setup_path, session_info, credentials, lp,
                            ldap_backend=ldap_backend, serverrole=serverrole)
 
     if (schema == None):
-        schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
+        schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
             sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
 
     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
@@ -927,8 +1025,10 @@ def setup_samdb(path, setup_path, session_info, credentials, lp,
             })
 
         message("Adding configuration container")
+        descr = get_config_descriptor(domainsid);
         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
             "CONFIGDN": names.configdn, 
+            "DESCRIPTOR": descr,
             })
         message("Modifying configuration container")
         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
@@ -1048,10 +1148,13 @@ def provision(setup_dir, message, session_info,
     """
 
     def setup_path(file):
-        return os.path.join(setup_dir, file)
+      return os.path.join(setup_dir, file)
 
     if domainsid is None:
-        domainsid = security.random_sid()
+      domainsid = security.random_sid()
+    else:
+      domainsid = security.dom_sid(domainsid)
+
 
     # create/adapt the group policy GUIDs
     if policyguid is None:
@@ -1128,7 +1231,7 @@ def provision(setup_dir, message, session_info,
 
     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
     
-    schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
+    schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
     
     secrets_credentials = credentials
@@ -1232,16 +1335,18 @@ def provision(setup_dir, message, session_info,
 
         # Only make a zone file on the first DC, it should be replicated with DNS replication
         if serverrole == "domain controller":
-            secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
-                              credentials=credentials, lp=lp)
-            secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
+            secretsdb_self_join(secrets_ldb, domain=domain,
                                 realm=names.realm,
+                                dnsdomain=names.dnsdomain,
                                 netbiosname=names.netbiosname,
                                 domainsid=domainsid, 
-                                keytab_path=paths.keytab, samdb_url=paths.samdb,
+                                machinepass=machinepass,
+                                secure_channel_type=SEC_CHAN_BDC)
+
+            secretsdb_setup_dns(secrets_ldb, setup_path, 
+                                realm=names.realm, dnsdomain=names.dnsdomain,
                                 dns_keytab_path=paths.dns_keytab,
-                                dnspass=dnspass, machinepass=machinepass,
-                                dnsdomain=names.dnsdomain)
+                                dnspass=dnspass)
 
             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
             assert isinstance(domainguid, str)
@@ -1266,6 +1371,8 @@ def provision(setup_dir, message, session_info,
                              realm=names.realm)
             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
 
+    #Now commit the secrets.ldb to disk
+    secrets_ldb.transaction_commit()
 
     if provision_backend is not None: 
       if ldap_backend_type == "fedora-ds":