2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 """Functions for setting up a Samba configuration."""
28 __docformat__ = "restructuredText"
30 from base64 import b64encode
44 from samba.auth import system_session, admin_session
46 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
50 check_all_substituted,
51 is_valid_netbios_char,
57 from samba.dcerpc import security, misc
58 from samba.dcerpc.misc import (
62 from samba.dsdb import (
63 DS_DOMAIN_FUNCTION_2003,
64 DS_DOMAIN_FUNCTION_2008_R2,
67 from samba.idmap import IDmapDB
68 from samba.ms_display_specifiers import read_ms_ldif
69 from samba.ntacls import setntacl, dsacl2fsacl
70 from samba.ndr import ndr_pack, ndr_unpack
71 from samba.provision.backend import (
77 from samba.provision.descriptor import (
78 get_config_descriptor,
81 from samba.provision.common import (
86 from samba.provision.sambadns import (
88 create_dns_update_list
93 from samba.schema import Schema
94 from samba.samdb import SamDB
95 from samba.dbchecker import dbcheck
98 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
99 DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
100 DEFAULTSITE = "Default-First-Site-Name"
101 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
104 class ProvisionPaths(object):
107 self.shareconf = None
118 self.dns_keytab = None
121 self.private_dir = None
122 self.phpldapadminconfig = None
125 class ProvisionNames(object):
132 self.ldapmanagerdn = None
133 self.dnsdomain = None
135 self.netbiosname = None
141 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
142 """Get key provision parameters (realm, domain, ...) from a given provision
144 :param samdb: An LDB object connected to the sam.ldb file
145 :param secretsdb: An LDB object connected to the secrets.ldb file
146 :param idmapdb: An LDB object connected to the idmap.ldb file
147 :param paths: A list of path to provision object
148 :param smbconf: Path to the smb.conf file
149 :param lp: A LoadParm object
150 :return: A list of key provision parameters
152 names = ProvisionNames()
153 names.adminpass = None
155 # NT domain, kerberos realm, root dn, domain dn, domain dns name
156 names.domain = string.upper(lp.get("workgroup"))
157 names.realm = lp.get("realm")
158 names.dnsdomain = names.realm.lower()
159 basedn = samba.dn_from_dns_name(names.dnsdomain)
160 names.realm = string.upper(names.realm)
162 # Get the netbiosname first (could be obtained from smb.conf in theory)
163 res = secretsdb.search(expression="(flatname=%s)" %
164 names.domain,base="CN=Primary Domains",
165 scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
166 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
168 names.smbconf = smbconf
170 # That's a bit simplistic but it's ok as long as we have only 3
172 current = samdb.search(expression="(objectClass=*)",
173 base="", scope=ldb.SCOPE_BASE,
174 attrs=["defaultNamingContext", "schemaNamingContext",
175 "configurationNamingContext","rootDomainNamingContext"])
177 names.configdn = current[0]["configurationNamingContext"]
178 configdn = str(names.configdn)
179 names.schemadn = current[0]["schemaNamingContext"]
180 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
181 current[0]["defaultNamingContext"][0]))):
182 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
183 "is not the same ..." % (paths.samdb,
184 str(current[0]["defaultNamingContext"][0]),
185 paths.smbconf, basedn)))
187 names.domaindn=current[0]["defaultNamingContext"]
188 names.rootdn=current[0]["rootDomainNamingContext"]
190 res3 = samdb.search(expression="(objectClass=site)",
191 base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
192 names.sitename = str(res3[0]["cn"])
194 # dns hostname and server dn
195 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
196 base="OU=Domain Controllers,%s" % basedn,
197 scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
198 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
200 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
201 attrs=[], base=configdn)
202 names.serverdn = server_res[0].dn
204 # invocation id/objectguid
205 res5 = samdb.search(expression="(objectClass=*)",
206 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=ldb.SCOPE_BASE,
207 attrs=["invocationID", "objectGUID"])
208 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
209 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
212 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
213 scope=ldb.SCOPE_BASE, attrs=["objectGUID",
214 "objectSid","msDS-Behavior-Version" ])
215 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
216 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
217 if res6[0].get("msDS-Behavior-Version") is None or \
218 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
219 names.domainlevel = DS_DOMAIN_FUNCTION_2000
221 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
224 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
225 base="CN=Policies,CN=System," + basedn,
226 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
227 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
229 res8 = samdb.search(expression="(displayName=Default Domain Controllers"
231 base="CN=Policies,CN=System," + basedn,
232 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
234 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
236 names.policyid_dc = None
237 res9 = idmapdb.search(expression="(cn=%s)" %
238 (security.SID_BUILTIN_ADMINISTRATORS),
241 names.wheel_gid = res9[0]["xidNumber"]
243 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
247 def update_provision_usn(samdb, low, high, id, replace=False):
248 """Update the field provisionUSN in sam.ldb
250 This field is used to track range of USN modified by provision and
252 This value is used afterward by next provision to figure out if
253 the field have been modified since last provision.
255 :param samdb: An LDB object connect to sam.ldb
256 :param low: The lowest USN modified by this upgrade
257 :param high: The highest USN modified by this upgrade
258 :param id: The invocation id of the samba's dc
259 :param replace: A boolean indicating if the range should replace any
260 existing one or appended (default)
265 entry = samdb.search(base="@PROVISION",
266 scope=ldb.SCOPE_BASE,
267 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
268 for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
269 if not re.search(';', e):
270 e = "%s;%s" % (e, id)
273 tab.append("%s-%s;%s" % (low, high, id))
274 delta = ldb.Message()
275 delta.dn = ldb.Dn(samdb, "@PROVISION")
276 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
277 ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
278 entry = samdb.search(expression='provisionnerID=*',
279 base="@PROVISION", scope=ldb.SCOPE_BASE,
280 attrs=["provisionnerID"])
281 if len(entry) == 0 or len(entry[0]) == 0:
282 delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
286 def set_provision_usn(samdb, low, high, id):
287 """Set the field provisionUSN in sam.ldb
288 This field is used to track range of USN modified by provision and
290 This value is used afterward by next provision to figure out if
291 the field have been modified since last provision.
293 :param samdb: An LDB object connect to sam.ldb
294 :param low: The lowest USN modified by this upgrade
295 :param high: The highest USN modified by this upgrade
296 :param id: The invocationId of the provision"""
299 tab.append("%s-%s;%s" % (low, high, id))
301 delta = ldb.Message()
302 delta.dn = ldb.Dn(samdb, "@PROVISION")
303 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
304 ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
308 def get_max_usn(samdb,basedn):
309 """ This function return the biggest USN present in the provision
311 :param samdb: A LDB object pointing to the sam.ldb
312 :param basedn: A string containing the base DN of the provision
314 :return: The biggest USN in the provision"""
316 res = samdb.search(expression="objectClass=*",base=basedn,
317 scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
318 controls=["search_options:1:2",
319 "server_sort:1:1:uSNChanged",
320 "paged_results:1:1"])
321 return res[0]["uSNChanged"]
324 def get_last_provision_usn(sam):
325 """Get USNs ranges modified by a provision or an upgradeprovision
327 :param sam: An LDB object pointing to the sam.ldb
328 :return: a dictionnary which keys are invocation id and values are an array
329 of integer representing the different ranges
332 entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
333 base="@PROVISION", scope=ldb.SCOPE_BASE,
334 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
335 except ldb.LdbError, (ecode, emsg):
336 if ecode == ldb.ERR_NO_SUCH_OBJECT:
343 if entry[0].get("provisionnerID"):
344 for e in entry[0]["provisionnerID"]:
346 for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
347 tab1 = str(r).split(';')
352 if (len(myids) > 0 and id not in myids):
354 tab2 = p.split(tab1[0])
355 if range.get(id) == None:
357 range[id].append(tab2[0])
358 range[id].append(tab2[1])
364 class ProvisionResult(object):
365 """Result of a provision.
367 :ivar server_role: The server role
368 :ivar paths: ProvisionPaths instance
369 :ivar domaindn: The domain dn, as string
373 self.server_role = None
380 self.domainsid = None
381 self.adminpass_generated = None
382 self.adminpass = None
383 self.backend_result = None
385 def report_logger(self, logger):
386 """Report this provision result to a logger."""
388 "Once the above files are installed, your Samba4 server will "
390 if self.adminpass_generated:
391 logger.info("Admin password: %s", self.adminpass)
392 logger.info("Server Role: %s", self.server_role)
393 logger.info("Hostname: %s", self.names.hostname)
394 logger.info("NetBIOS Domain: %s", self.names.domain)
395 logger.info("DNS Domain: %s", self.names.dnsdomain)
396 logger.info("DOMAIN SID: %s", self.domainsid)
398 if self.paths.phpldapadminconfig is not None:
400 "A phpLDAPadmin configuration file suitable for administering "
401 "the Samba 4 LDAP server has been created in %s.",
402 self.paths.phpldapadminconfig)
404 if self.backend_result:
405 self.backend_result.report_logger(logger)
408 def check_install(lp, session_info, credentials):
409 """Check whether the current install seems ok.
411 :param lp: Loadparm context
412 :param session_info: Session information
413 :param credentials: Credentials
415 if lp.get("realm") == "":
416 raise Exception("Realm empty")
417 samdb = Ldb(lp.samdb_url(), session_info=session_info,
418 credentials=credentials, lp=lp)
419 if len(samdb.search("(cn=Administrator)")) != 1:
420 raise ProvisioningError("No administrator account found")
423 def findnss(nssfn, names):
424 """Find a user or group from a list of possibilities.
426 :param nssfn: NSS Function to try (should raise KeyError if not found)
427 :param names: Names to check.
428 :return: Value return by first names list.
435 raise KeyError("Unable to find user/group in %r" % names)
438 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
439 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
442 def provision_paths_from_lp(lp, dnsdomain):
443 """Set the default paths for provisioning.
445 :param lp: Loadparm context.
446 :param dnsdomain: DNS Domain name
448 paths = ProvisionPaths()
449 paths.private_dir = lp.get("private dir")
451 # This is stored without path prefix for the "privateKeytab" attribute in
452 # "secrets_dns.ldif".
453 paths.dns_keytab = "dns.keytab"
454 paths.keytab = "secrets.keytab"
456 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
457 paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
458 paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
459 paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
460 paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
461 paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
462 paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
463 paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
464 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
465 paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
466 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
467 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
468 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
469 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
470 paths.phpldapadminconfig = os.path.join(paths.private_dir,
471 "phpldapadmin-config.php")
472 paths.hklm = "hklm.ldb"
473 paths.hkcr = "hkcr.ldb"
474 paths.hkcu = "hkcu.ldb"
475 paths.hku = "hku.ldb"
476 paths.hkpd = "hkpd.ldb"
477 paths.hkpt = "hkpt.ldb"
478 paths.sysvol = lp.get("path", "sysvol")
479 paths.netlogon = lp.get("path", "netlogon")
480 paths.smbconf = lp.configfile
484 def determine_netbios_name(hostname):
485 """Determine a netbios name from a hostname."""
486 # remove forbidden chars and force the length to be <16
487 netbiosname = "".join([x for x in hostname if is_valid_netbios_char(x)])
488 return netbiosname[:MAX_NETBIOS_NAME_LEN].upper()
491 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
492 serverrole=None, rootdn=None, domaindn=None, configdn=None,
493 schemadn=None, serverdn=None, sitename=None):
494 """Guess configuration settings to use."""
497 hostname = socket.gethostname().split(".")[0]
499 netbiosname = lp.get("netbios name")
500 if netbiosname is None:
501 netbiosname = determine_netbios_name(hostname)
502 netbiosname = netbiosname.upper()
503 if not valid_netbios_name(netbiosname):
504 raise InvalidNetbiosName(netbiosname)
506 if dnsdomain is None:
507 dnsdomain = lp.get("realm")
508 if dnsdomain is None or dnsdomain == "":
509 raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
511 dnsdomain = dnsdomain.lower()
513 if serverrole is None:
514 serverrole = lp.get("server role")
515 if serverrole is None:
516 raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
518 serverrole = serverrole.lower()
520 realm = dnsdomain.upper()
522 if lp.get("realm") == "":
523 raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile)
525 if lp.get("realm").upper() != realm:
526 raise ProvisioningError("guess_names: 'realm=%s' in %s must match chosen realm '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("realm").upper(), realm, lp.configfile))
528 if lp.get("server role").lower() != serverrole:
529 raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role"), lp.configfile, serverrole))
531 if serverrole == "domain controller":
533 # This will, for better or worse, default to 'WORKGROUP'
534 domain = lp.get("workgroup")
535 domain = domain.upper()
537 if lp.get("workgroup").upper() != domain:
538 raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
541 domaindn = samba.dn_from_dns_name(dnsdomain)
543 if domain == netbiosname:
544 raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
548 domaindn = "DC=" + netbiosname
550 if not valid_netbios_name(domain):
551 raise InvalidNetbiosName(domain)
553 if hostname.upper() == realm:
554 raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
555 if netbiosname.upper() == realm:
556 raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
558 raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
564 configdn = "CN=Configuration," + rootdn
566 schemadn = "CN=Schema," + configdn
569 sitename = DEFAULTSITE
571 names = ProvisionNames()
572 names.rootdn = rootdn
573 names.domaindn = domaindn
574 names.configdn = configdn
575 names.schemadn = schemadn
576 names.ldapmanagerdn = "CN=Manager," + rootdn
577 names.dnsdomain = dnsdomain
578 names.domain = domain
580 names.netbiosname = netbiosname
581 names.hostname = hostname
582 names.sitename = sitename
583 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
584 netbiosname, sitename, configdn)
589 def make_smbconf(smbconf, hostname, domain, realm, targetdir,
590 serverrole=None, sid_generator=None, eadb=False, lp=None,
591 server_services=None):
592 """Create a new smb.conf file based on a couple of basic settings.
594 assert smbconf is not None
597 hostname = socket.gethostname().split(".")[0]
599 netbiosname = determine_netbios_name(hostname)
601 if serverrole is None:
602 serverrole = "standalone"
604 if sid_generator is None:
605 sid_generator = "internal"
607 assert domain is not None
608 domain = domain.upper()
610 assert realm is not None
611 realm = realm.upper()
614 "passdb backend": "samba4",
615 "netbios name": netbiosname,
618 "server role": serverrole,
622 lp = samba.param.LoadParm()
623 #Load non-existant file
624 if os.path.exists(smbconf):
626 if eadb and not lp.get("posix:eadb"):
627 if targetdir is not None:
628 privdir = os.path.join(targetdir, "private")
630 privdir = lp.get("private dir")
631 lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
633 if server_services is not None:
634 global_settings["server services"] = " ".join(server_services)
636 if targetdir is not None:
637 global_settings["private dir"] = os.path.abspath(os.path.join(targetdir, "private"))
638 global_settings["lock dir"] = os.path.abspath(targetdir)
639 global_settings["state directory"] = os.path.abspath(targetdir)
640 global_settings["cache directory"] = os.path.abspath(targetdir)
642 lp.set("lock dir", os.path.abspath(targetdir))
643 lp.set("state directory", os.path.abspath(targetdir))
644 lp.set("cache directory", os.path.abspath(targetdir))
647 if serverrole == "domain controller":
648 shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol")
649 shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(),
652 f = open(smbconf, 'w')
654 f.write("[globals]\n")
655 for key, val in global_settings.iteritems():
656 f.write("\t%s = %s\n" % (key, val))
659 for name, path in shares.iteritems():
660 f.write("[%s]\n" % name)
661 f.write("\tpath = %s\n" % path)
662 f.write("\tread only = no\n")
666 # reload the smb.conf
669 # and dump it without any values that are the default
670 # this ensures that any smb.conf parameters that were set
671 # on the provision/join command line are set in the resulting smb.conf
672 f = open(smbconf, mode='w')
679 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
680 users_gid, wheel_gid):
681 """setup reasonable name mappings for sam names to unix names.
683 :param samdb: SamDB object.
684 :param idmap: IDmap db object.
685 :param sid: The domain sid.
686 :param domaindn: The domain DN.
687 :param root_uid: uid of the UNIX root user.
688 :param nobody_uid: uid of the UNIX nobody user.
689 :param users_gid: gid of the UNIX users group.
690 :param wheel_gid: gid of the UNIX wheel group.
692 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
693 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
695 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
696 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
699 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
700 provision_backend, names, schema, serverrole,
702 """Setup the partitions for the SAM database.
704 Alternatively, provision() may call this, and then populate the database.
706 :note: This will wipe the Sam Database!
708 :note: This function always removes the local SAM LDB file. The erase
709 parameter controls whether to erase the existing data, which
710 may not be stored locally but in LDAP.
713 assert session_info is not None
715 # We use options=["modules:"] to stop the modules loading - we
716 # just want to wipe and re-initialise the database, not start it up
719 os.unlink(samdb_path)
723 samdb = Ldb(url=samdb_path, session_info=session_info,
724 lp=lp, options=["modules:"])
726 ldap_backend_line = "# No LDAP backend"
727 if provision_backend.type != "ldb":
728 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
730 samdb.transaction_start()
732 logger.info("Setting up sam.ldb partitions and settings")
733 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
734 "LDAP_BACKEND_LINE": ldap_backend_line
738 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
739 "BACKEND_TYPE": provision_backend.type,
740 "SERVER_ROLE": serverrole
743 logger.info("Setting up sam.ldb rootDSE")
744 setup_samdb_rootdse(samdb, names)
746 samdb.transaction_cancel()
749 samdb.transaction_commit()
752 def secretsdb_self_join(secretsdb, domain,
753 netbiosname, machinepass, domainsid=None,
754 realm=None, dnsdomain=None,
756 key_version_number=1,
757 secure_channel_type=SEC_CHAN_WKSTA):
758 """Add domain join-specific bits to a secrets database.
760 :param secretsdb: Ldb Handle to the secrets database
761 :param machinepass: Machine password
763 attrs = ["whenChanged",
770 if realm is not None:
771 if dnsdomain is None:
772 dnsdomain = realm.lower()
773 dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
776 shortname = netbiosname.lower()
778 # We don't need to set msg["flatname"] here, because rdn_name will handle
779 # it, and it causes problems for modifies anyway
780 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
781 msg["secureChannelType"] = [str(secure_channel_type)]
782 msg["objectClass"] = ["top", "primaryDomain"]
783 if dnsname is not None:
784 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
785 msg["realm"] = [realm]
786 msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
787 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
788 msg["privateKeytab"] = ["secrets.keytab"]
790 msg["secret"] = [machinepass]
791 msg["samAccountName"] = ["%s$" % netbiosname]
792 msg["secureChannelType"] = [str(secure_channel_type)]
793 if domainsid is not None:
794 msg["objectSid"] = [ndr_pack(domainsid)]
796 # This complex expression tries to ensure that we don't have more
797 # than one record for this SID, realm or netbios domain at a time,
798 # but we don't delete the old record that we are about to modify,
799 # because that would delete the keytab and previous password.
800 res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
801 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
802 scope=ldb.SCOPE_ONELEVEL)
805 secretsdb.delete(del_msg.dn)
807 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
810 msg["priorSecret"] = [res[0]["secret"][0]]
811 msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
814 msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
819 msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
825 msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
826 secretsdb.modify(msg)
827 secretsdb.rename(res[0].dn, msg.dn)
829 spn = [ 'HOST/%s' % shortname ]
830 if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
831 # we are a domain controller then we add servicePrincipalName
832 # entries for the keytab code to update.
833 spn.extend([ 'HOST/%s' % dnsname ])
834 msg["servicePrincipalName"] = spn
839 def setup_secretsdb(paths, session_info, backend_credentials, lp):
840 """Setup the secrets database.
842 :note: This function does not handle exceptions and transaction on purpose,
843 it's up to the caller to do this job.
845 :param path: Path to the secrets database.
846 :param session_info: Session info.
847 :param credentials: Credentials
848 :param lp: Loadparm context
849 :return: LDB handle for the created secrets database
851 if os.path.exists(paths.secrets):
852 os.unlink(paths.secrets)
854 keytab_path = os.path.join(paths.private_dir, paths.keytab)
855 if os.path.exists(keytab_path):
856 os.unlink(keytab_path)
858 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
859 if os.path.exists(dns_keytab_path):
860 os.unlink(dns_keytab_path)
864 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
866 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
867 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
868 secrets_ldb.transaction_start()
870 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
872 if (backend_credentials is not None and
873 backend_credentials.authentication_requested()):
874 if backend_credentials.get_bind_dn() is not None:
875 setup_add_ldif(secrets_ldb,
876 setup_path("secrets_simple_ldap.ldif"), {
877 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
878 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
881 setup_add_ldif(secrets_ldb,
882 setup_path("secrets_sasl_ldap.ldif"), {
883 "LDAPADMINUSER": backend_credentials.get_username(),
884 "LDAPADMINREALM": backend_credentials.get_realm(),
885 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
888 secrets_ldb.transaction_cancel()
893 def setup_privileges(path, session_info, lp):
894 """Setup the privileges database.
896 :param path: Path to the privileges database.
897 :param session_info: Session info.
898 :param credentials: Credentials
899 :param lp: Loadparm context
900 :return: LDB handle for the created secrets database
902 if os.path.exists(path):
904 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
905 privilege_ldb.erase()
906 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
909 def setup_registry(path, session_info, lp):
910 """Setup the registry.
912 :param path: Path to the registry database
913 :param session_info: Session information
914 :param credentials: Credentials
915 :param lp: Loadparm context
917 reg = samba.registry.Registry()
918 hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
919 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
920 provision_reg = setup_path("provision.reg")
921 assert os.path.exists(provision_reg)
922 reg.diff_apply(provision_reg)
925 def setup_idmapdb(path, session_info, lp):
926 """Setup the idmap database.
928 :param path: path to the idmap database
929 :param session_info: Session information
930 :param credentials: Credentials
931 :param lp: Loadparm context
933 if os.path.exists(path):
936 idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
938 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
942 def setup_samdb_rootdse(samdb, names):
943 """Setup the SamDB rootdse.
945 :param samdb: Sam Database handle
947 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
948 "SCHEMADN": names.schemadn,
949 "DOMAINDN": names.domaindn,
950 "ROOTDN" : names.rootdn,
951 "CONFIGDN": names.configdn,
952 "SERVERDN": names.serverdn,
956 def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
957 dnspass, domainsid, next_rid, invocationid, policyguid, policyguid_dc,
958 domainControllerFunctionality, ntdsguid=None, dc_rid=None):
959 """Join a host to its own domain."""
960 assert isinstance(invocationid, str)
961 if ntdsguid is not None:
962 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
969 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
970 "CONFIGDN": names.configdn,
971 "SCHEMADN": names.schemadn,
972 "DOMAINDN": names.domaindn,
973 "SERVERDN": names.serverdn,
974 "INVOCATIONID": invocationid,
975 "NETBIOSNAME": names.netbiosname,
976 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
977 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
978 "DOMAINSID": str(domainsid),
979 "DCRID": str(dc_rid),
980 "SAMBA_VERSION_STRING": version,
981 "NTDSGUID": ntdsguid_line,
982 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
983 domainControllerFunctionality),
984 "RIDALLOCATIONSTART": str(next_rid + 100),
985 "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
987 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
988 "POLICYGUID": policyguid,
989 "POLICYGUID_DC": policyguid_dc,
990 "DNSDOMAIN": names.dnsdomain,
991 "DOMAINDN": names.domaindn})
993 # If we are setting up a subdomain, then this has been replicated in, so we
994 # don't need to add it
995 if fill == FILL_FULL:
996 setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), {
997 "CONFIGDN": names.configdn,
998 "SCHEMADN": names.schemadn,
999 "DOMAINDN": names.domaindn,
1000 "SERVERDN": names.serverdn,
1001 "INVOCATIONID": invocationid,
1002 "NETBIOSNAME": names.netbiosname,
1003 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
1004 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
1005 "DOMAINSID": str(domainsid),
1006 "DCRID": str(dc_rid),
1007 "SAMBA_VERSION_STRING": version,
1008 "NTDSGUID": ntdsguid_line,
1009 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
1010 domainControllerFunctionality)})
1012 # Setup fSMORoleOwner entries to point at the newly created DC entry
1013 setup_modify_ldif(samdb,
1014 setup_path("provision_self_join_modify_config.ldif"), {
1015 "CONFIGDN": names.configdn,
1016 "SCHEMADN": names.schemadn,
1017 "DEFAULTSITE": names.sitename,
1018 "NETBIOSNAME": names.netbiosname,
1019 "SERVERDN": names.serverdn,
1022 system_session_info = system_session()
1023 samdb.set_session_info(system_session_info)
1024 # Setup fSMORoleOwner entries to point at the newly created DC entry to
1025 # modify a serverReference under cn=config when we are a subdomain, we must
1026 # be system due to ACLs
1027 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1028 "DOMAINDN": names.domaindn,
1029 "SERVERDN": names.serverdn,
1030 "NETBIOSNAME": names.netbiosname,
1033 samdb.set_session_info(admin_session_info)
1035 # This is Samba4 specific and should be replaced by the correct
1036 # DNS AD-style setup
1037 setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
1038 "DNSDOMAIN": names.dnsdomain,
1039 "DOMAINDN": names.domaindn,
1040 "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
1041 "HOSTNAME" : names.hostname,
1042 "DNSNAME" : '%s.%s' % (
1043 names.netbiosname.lower(), names.dnsdomain.lower())
1047 def getpolicypath(sysvolpath, dnsdomain, guid):
1048 """Return the physical path of policy given its guid.
1050 :param sysvolpath: Path to the sysvol folder
1051 :param dnsdomain: DNS name of the AD domain
1052 :param guid: The GUID of the policy
1053 :return: A string with the complete path to the policy folder
1056 guid = "{%s}" % guid
1057 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
1061 def create_gpo_struct(policy_path):
1062 if not os.path.exists(policy_path):
1063 os.makedirs(policy_path, 0775)
1064 f = open(os.path.join(policy_path, "GPT.INI"), 'w')
1066 f.write("[General]\r\nVersion=0")
1069 p = os.path.join(policy_path, "MACHINE")
1070 if not os.path.exists(p):
1071 os.makedirs(p, 0775)
1072 p = os.path.join(policy_path, "USER")
1073 if not os.path.exists(p):
1074 os.makedirs(p, 0775)
1077 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
1078 """Create the default GPO for a domain
1080 :param sysvolpath: Physical path for the sysvol folder
1081 :param dnsdomain: DNS domain name of the AD domain
1082 :param policyguid: GUID of the default domain policy
1083 :param policyguid_dc: GUID of the default domain controler policy
1085 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
1086 create_gpo_struct(policy_path)
1088 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
1089 create_gpo_struct(policy_path)
1092 def setup_samdb(path, session_info, provision_backend, lp, names,
1093 logger, fill, serverrole, schema, am_rodc=False):
1094 """Setup a complete SAM Database.
1096 :note: This will wipe the main SAM database file!
1099 # Also wipes the database
1100 setup_samdb_partitions(path, logger=logger, lp=lp,
1101 provision_backend=provision_backend, session_info=session_info,
1102 names=names, serverrole=serverrole, schema=schema)
1104 # Load the database, but don's load the global schema and don't connect
1106 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
1107 credentials=provision_backend.credentials, lp=lp,
1108 global_schema=False, am_rodc=am_rodc)
1110 logger.info("Pre-loading the Samba 4 and AD schema")
1112 # Load the schema from the one we computed earlier
1113 samdb.set_schema(schema)
1115 # Set the NTDS settings DN manually - in order to have it already around
1116 # before the provisioned tree exists and we connect
1117 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1119 # And now we can connect to the DB - the schema won't be loaded from the
1126 def fill_samdb(samdb, lp, names,
1127 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
1128 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
1129 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
1130 next_rid=None, dc_rid=None):
1132 if next_rid is None:
1135 # Provision does not make much sense values larger than 1000000000
1136 # as the upper range of the rIDAvailablePool is 1073741823 and
1137 # we don't want to create a domain that cannot allocate rids.
1138 if next_rid < 1000 or next_rid > 1000000000:
1139 error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
1140 error += "the valid range is %u-%u. The default is %u." % (
1141 1000, 1000000000, 1000)
1142 raise ProvisioningError(error)
1144 # ATTENTION: Do NOT change these default values without discussion with the
1145 # team and/or release manager. They have a big impact on the whole program!
1146 domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
1148 if dom_for_fun_level is None:
1149 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1151 if dom_for_fun_level > domainControllerFunctionality:
1152 raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008_R2). This won't work!")
1154 domainFunctionality = dom_for_fun_level
1155 forestFunctionality = dom_for_fun_level
1157 # Set the NTDS settings DN manually - in order to have it already around
1158 # before the provisioned tree exists and we connect
1159 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1161 samdb.transaction_start()
1163 # Set the domain functionality levels onto the database.
1164 # Various module (the password_hash module in particular) need
1165 # to know what level of AD we are emulating.
1167 # These will be fixed into the database via the database
1168 # modifictions below, but we need them set from the start.
1169 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1170 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1171 samdb.set_opaque_integer("domainControllerFunctionality",
1172 domainControllerFunctionality)
1174 samdb.set_domain_sid(str(domainsid))
1175 samdb.set_invocation_id(invocationid)
1177 logger.info("Adding DomainDN: %s" % names.domaindn)
1179 # impersonate domain admin
1180 admin_session_info = admin_session(lp, str(domainsid))
1181 samdb.set_session_info(admin_session_info)
1182 if domainguid is not None:
1183 domainguid_line = "objectGUID: %s\n-" % domainguid
1185 domainguid_line = ""
1187 descr = b64encode(get_domain_descriptor(domainsid))
1188 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1189 "DOMAINDN": names.domaindn,
1190 "DOMAINSID": str(domainsid),
1191 "DESCRIPTOR": descr,
1192 "DOMAINGUID": domainguid_line
1195 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1196 "DOMAINDN": names.domaindn,
1197 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1198 "NEXTRID": str(next_rid),
1199 "DEFAULTSITE": names.sitename,
1200 "CONFIGDN": names.configdn,
1201 "POLICYGUID": policyguid,
1202 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1203 "SAMBA_VERSION_STRING": version
1206 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1207 if fill == FILL_FULL:
1208 logger.info("Adding configuration container")
1209 descr = b64encode(get_config_descriptor(domainsid))
1210 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1211 "CONFIGDN": names.configdn,
1212 "DESCRIPTOR": descr,
1215 # The LDIF here was created when the Schema object was constructed
1216 logger.info("Setting up sam.ldb schema")
1217 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1218 samdb.modify_ldif(schema.schema_dn_modify)
1219 samdb.write_prefixes_from_schema()
1220 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1221 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1222 {"SCHEMADN": names.schemadn})
1224 # Now register this container in the root of the forest
1225 msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
1226 msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
1230 samdb.transaction_cancel()
1233 samdb.transaction_commit()
1235 samdb.transaction_start()
1237 samdb.invocation_id = invocationid
1239 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1240 if fill == FILL_FULL:
1241 logger.info("Setting up sam.ldb configuration data")
1242 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1243 "CONFIGDN": names.configdn,
1244 "NETBIOSNAME": names.netbiosname,
1245 "DEFAULTSITE": names.sitename,
1246 "DNSDOMAIN": names.dnsdomain,
1247 "DOMAIN": names.domain,
1248 "SCHEMADN": names.schemadn,
1249 "DOMAINDN": names.domaindn,
1250 "SERVERDN": names.serverdn,
1251 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1252 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1255 logger.info("Setting up display specifiers")
1256 display_specifiers_ldif = read_ms_ldif(
1257 setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1258 display_specifiers_ldif = substitute_var(display_specifiers_ldif,
1259 {"CONFIGDN": names.configdn})
1260 check_all_substituted(display_specifiers_ldif)
1261 samdb.add_ldif(display_specifiers_ldif)
1263 logger.info("Adding users container")
1264 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1265 "DOMAINDN": names.domaindn})
1266 logger.info("Modifying users container")
1267 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1268 "DOMAINDN": names.domaindn})
1269 logger.info("Adding computers container")
1270 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1271 "DOMAINDN": names.domaindn})
1272 logger.info("Modifying computers container")
1273 setup_modify_ldif(samdb,
1274 setup_path("provision_computers_modify.ldif"), {
1275 "DOMAINDN": names.domaindn})
1276 logger.info("Setting up sam.ldb data")
1277 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1278 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1279 "DOMAINDN": names.domaindn,
1280 "NETBIOSNAME": names.netbiosname,
1281 "DEFAULTSITE": names.sitename,
1282 "CONFIGDN": names.configdn,
1283 "SERVERDN": names.serverdn,
1284 "RIDAVAILABLESTART": str(next_rid + 600),
1285 "POLICYGUID_DC": policyguid_dc
1288 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1289 if fill == FILL_FULL:
1290 setup_modify_ldif(samdb,
1291 setup_path("provision_configuration_references.ldif"), {
1292 "CONFIGDN": names.configdn,
1293 "SCHEMADN": names.schemadn})
1295 logger.info("Setting up well known security principals")
1296 setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
1297 "CONFIGDN": names.configdn,
1300 if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
1301 setup_modify_ldif(samdb,
1302 setup_path("provision_basedn_references.ldif"),
1303 {"DOMAINDN": names.domaindn})
1305 logger.info("Setting up sam.ldb users and groups")
1306 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1307 "DOMAINDN": names.domaindn,
1308 "DOMAINSID": str(domainsid),
1309 "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
1310 "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
1313 logger.info("Setting up self join")
1314 setup_self_join(samdb, admin_session_info, names=names, fill=fill,
1315 invocationid=invocationid,
1317 machinepass=machinepass,
1318 domainsid=domainsid,
1321 policyguid=policyguid,
1322 policyguid_dc=policyguid_dc,
1323 domainControllerFunctionality=domainControllerFunctionality,
1326 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1327 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1328 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1329 assert isinstance(names.ntdsguid, str)
1331 samdb.transaction_cancel()
1334 samdb.transaction_commit()
1339 FILL_SUBDOMAIN = "SUBDOMAIN"
1340 FILL_NT4SYNC = "NT4SYNC"
1342 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1343 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)"
1346 def set_dir_acl(path, acl, lp, domsid):
1347 setntacl(lp, path, acl, domsid)
1348 for root, dirs, files in os.walk(path, topdown=False):
1350 setntacl(lp, os.path.join(root, name), acl, domsid)
1352 setntacl(lp, os.path.join(root, name), acl, domsid)
1355 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1356 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1359 :param sysvol: Physical path for the sysvol folder
1360 :param dnsdomain: The DNS name of the domain
1361 :param domainsid: The SID of the domain
1362 :param domaindn: The DN of the domain (ie. DC=...)
1363 :param samdb: An LDB object on the SAM db
1364 :param lp: an LP object
1367 # Set ACL for GPO root folder
1368 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1369 setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
1371 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1372 attrs=["cn", "nTSecurityDescriptor"],
1373 expression="", scope=ldb.SCOPE_ONELEVEL)
1376 acl = ndr_unpack(security.descriptor,
1377 str(policy["nTSecurityDescriptor"])).as_sddl()
1378 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1379 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1383 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1385 """Set the ACL for the sysvol share and the subfolders
1387 :param samdb: An LDB object on the SAM db
1388 :param netlogon: Physical path for the netlogon folder
1389 :param sysvol: Physical path for the sysvol folder
1390 :param gid: The GID of the "Domain adminstrators" group
1391 :param domainsid: The SID of the domain
1392 :param dnsdomain: The DNS name of the domain
1393 :param domaindn: The DN of the domain (ie. DC=...)
1397 os.chown(sysvol, -1, gid)
1403 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1404 setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
1405 for root, dirs, files in os.walk(sysvol, topdown=False):
1408 os.chown(os.path.join(root, name), -1, gid)
1409 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1412 os.chown(os.path.join(root, name), -1, gid)
1413 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1415 # Set acls on Policy folder and policies folders
1416 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
1419 def interface_ips_v4(lp):
1420 '''return only IPv4 IPs'''
1421 ips = samba.interface_ips(lp, False)
1424 if i.find(':') == -1:
1428 def interface_ips_v6(lp, linklocal=False):
1429 '''return only IPv6 IPs'''
1430 ips = samba.interface_ips(lp, False)
1433 if i.find(':') != -1 and (linklocal or i.find('%') == -1):
1438 def provision_fill(samdb, secrets_ldb, logger, names, paths,
1439 domainsid, schema=None,
1440 targetdir=None, samdb_fill=FILL_FULL,
1441 hostip=None, hostip6=None,
1442 next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
1443 domainguid=None, policyguid=None, policyguid_dc=None,
1444 invocationid=None, machinepass=None, ntdsguid=None,
1445 dns_backend=None, dnspass=None,
1446 serverrole=None, dom_for_fun_level=None,
1447 am_rodc=False, lp=None):
1448 # create/adapt the group policy GUIDs
1449 # Default GUID for default policy are described at
1450 # "How Core Group Policy Works"
1451 # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
1452 if policyguid is None:
1453 policyguid = DEFAULT_POLICY_GUID
1454 policyguid = policyguid.upper()
1455 if policyguid_dc is None:
1456 policyguid_dc = DEFAULT_DC_POLICY_GUID
1457 policyguid_dc = policyguid_dc.upper()
1459 if invocationid is None:
1460 invocationid = str(uuid.uuid4())
1462 if krbtgtpass is None:
1463 krbtgtpass = samba.generate_random_password(128, 255)
1464 if machinepass is None:
1465 machinepass = samba.generate_random_password(128, 255)
1467 dnspass = samba.generate_random_password(128, 255)
1469 samdb = fill_samdb(samdb, lp, names, logger=logger,
1470 domainsid=domainsid, schema=schema, domainguid=domainguid,
1471 policyguid=policyguid, policyguid_dc=policyguid_dc,
1472 fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
1473 invocationid=invocationid, machinepass=machinepass,
1474 dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
1475 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1476 next_rid=next_rid, dc_rid=dc_rid)
1478 if serverrole == "domain controller":
1479 # Set up group policies (domain policy and domain controller
1481 create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
1483 setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
1484 domainsid, names.dnsdomain, names.domaindn, lp)
1486 secretsdb_self_join(secrets_ldb, domain=names.domain,
1487 realm=names.realm, dnsdomain=names.dnsdomain,
1488 netbiosname=names.netbiosname, domainsid=domainsid,
1489 machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
1491 # Now set up the right msDS-SupportedEncryptionTypes into the DB
1492 # In future, this might be determined from some configuration
1493 kerberos_enctypes = str(ENC_ALL_TYPES)
1496 msg = ldb.Message(ldb.Dn(samdb,
1497 samdb.searchone("distinguishedName",
1498 expression="samAccountName=%s$" % names.netbiosname,
1499 scope=ldb.SCOPE_SUBTREE)))
1500 msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
1501 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
1502 name="msDS-SupportedEncryptionTypes")
1504 except ldb.LdbError, (enum, estr):
1505 if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
1506 # It might be that this attribute does not exist in this schema
1509 setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
1510 hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
1511 dnspass=dnspass, os_level=dom_for_fun_level,
1512 targetdir=targetdir, site=DEFAULTSITE)
1514 domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
1515 attribute="objectGUID")
1516 assert isinstance(domainguid, str)
1518 lastProvisionUSNs = get_last_provision_usn(samdb)
1519 maxUSN = get_max_usn(samdb, str(names.rootdn))
1520 if lastProvisionUSNs is not None:
1521 update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
1523 set_provision_usn(samdb, 0, maxUSN, invocationid)
1525 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1526 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
1527 { 'NTDSGUID' : names.ntdsguid })
1529 # fix any dangling GUIDs from the provision
1530 logger.info("Fixing provision GUIDs")
1531 chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True,
1533 samdb.transaction_start()
1535 # a small number of GUIDs are missing because of ordering issues in the
1537 for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
1538 chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
1539 scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
1540 chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
1541 scope=ldb.SCOPE_ONELEVEL,
1542 attrs=['ipsecOwnersReference',
1543 'ipsecFilterReference',
1544 'ipsecISAKMPReference',
1545 'ipsecNegotiationPolicyReference',
1546 'ipsecNFAReference'])
1548 samdb.transaction_cancel()
1551 samdb.transaction_commit()
1555 "ROLE_STANDALONE": "standalone",
1556 "ROLE_DOMAIN_MEMBER": "member server",
1557 "ROLE_DOMAIN_BDC": "domain controller",
1558 "ROLE_DOMAIN_PDC": "domain controller",
1559 "dc": "domain controller",
1560 "member": "member server",
1561 "domain controller": "domain controller",
1562 "member server": "member server",
1563 "standalone": "standalone",
1567 def sanitize_server_role(role):
1568 """Sanitize a server role name.
1570 :param role: Server role
1571 :raise ValueError: If the role can not be interpreted
1572 :return: Sanitized server role (one of "member server",
1573 "domain controller", "standalone")
1576 return _ROLES_MAP[role]
1578 raise ValueError(role)
1581 def provision(logger, session_info, credentials, smbconf=None,
1582 targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
1583 domaindn=None, schemadn=None, configdn=None, serverdn=None,
1584 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
1585 next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None,
1586 domainguid=None, policyguid=None, policyguid_dc=None,
1587 dns_backend=None, dnspass=None,
1588 invocationid=None, machinepass=None, ntdsguid=None,
1589 root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
1590 serverrole=None, dom_for_fun_level=None,
1591 backend_type=None, sitename=None,
1592 ol_mmr_urls=None, ol_olc=None, slapd_path=None,
1593 useeadb=False, am_rodc=False,
1597 :note: caution, this wipes all existing data!
1601 serverrole = sanitize_server_role(serverrole)
1603 raise ProvisioningError('server role (%s) should be one of "domain controller", "member server", "standalone"' % serverrole)
1605 if ldapadminpass is None:
1606 # Make a new, random password between Samba and it's LDAP server
1607 ldapadminpass = samba.generate_random_password(128, 255)
1609 if backend_type is None:
1610 backend_type = "ldb"
1612 if domainsid is None:
1613 domainsid = security.random_sid()
1615 domainsid = security.dom_sid(domainsid)
1617 sid_generator = "internal"
1618 if backend_type == "fedora-ds":
1619 sid_generator = "backend"
1621 root_uid = findnss_uid([root or "root"])
1622 nobody_uid = findnss_uid([nobody or "nobody"])
1623 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1625 wheel_gid = findnss_gid(["wheel", "adm"])
1627 wheel_gid = findnss_gid([wheel])
1629 bind_gid = findnss_gid(["bind", "named"])
1633 if targetdir is not None:
1634 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1635 elif smbconf is None:
1636 smbconf = samba.param.default_path()
1637 if not os.path.exists(os.path.dirname(smbconf)):
1638 os.makedirs(os.path.dirname(smbconf))
1640 server_services = None
1641 if dns_backend == "SAMBA_INTERNAL":
1642 server_services = ["+dns"]
1644 # only install a new smb.conf if there isn't one there already
1645 if os.path.exists(smbconf):
1646 # if Samba Team members can't figure out the weird errors
1647 # loading an empty smb.conf gives, then we need to be smarter.
1648 # Pretend it just didn't exist --abartlet
1649 f = open(smbconf, 'r')
1651 data = f.read().lstrip()
1654 if data is None or data == "":
1655 make_smbconf(smbconf, hostname, domain, realm,
1656 targetdir, serverrole=serverrole,
1657 sid_generator=sid_generator, eadb=useeadb,
1658 lp=lp, server_services=server_services)
1660 make_smbconf(smbconf, hostname, domain, realm, targetdir,
1661 serverrole=serverrole, sid_generator=sid_generator,
1662 eadb=useeadb, lp=lp, server_services=server_services)
1665 lp = samba.param.LoadParm()
1667 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1668 dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
1669 configdn=configdn, schemadn=schemadn, serverdn=serverdn,
1670 sitename=sitename, rootdn=rootdn)
1671 paths = provision_paths_from_lp(lp, names.dnsdomain)
1673 paths.bind_gid = bind_gid
1674 paths.wheel_gid = wheel_gid
1677 logger.info("Looking up IPv4 addresses")
1678 hostips = interface_ips_v4(lp)
1679 if len(hostips) > 0:
1681 if len(hostips) > 1:
1682 logger.warning("More than one IPv4 address found. Using %s",
1684 if hostip == "127.0.0.1":
1687 logger.warning("No IPv4 address will be assigned")
1690 logger.info("Looking up IPv6 addresses")
1691 hostips = interface_ips_v6(lp, linklocal=False)
1693 hostip6 = hostips[0]
1694 if len(hostips) > 1:
1695 logger.warning("More than one IPv6 address found. Using %s", hostip6)
1697 logger.warning("No IPv6 address will be assigned")
1699 names.hostip = hostip
1700 names.hostip6 = hostip6
1702 if serverrole is None:
1703 serverrole = lp.get("server role")
1705 if not os.path.exists(paths.private_dir):
1706 os.mkdir(paths.private_dir)
1707 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1708 os.mkdir(os.path.join(paths.private_dir, "tls"))
1710 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1712 schema = Schema(domainsid, invocationid=invocationid,
1713 schemadn=names.schemadn)
1715 if backend_type == "ldb":
1716 provision_backend = LDBBackend(backend_type, paths=paths,
1717 lp=lp, credentials=credentials,
1718 names=names, logger=logger)
1719 elif backend_type == "existing":
1720 # If support for this is ever added back, then the URI will need to be specified again
1721 provision_backend = ExistingBackend(backend_type, paths=paths,
1722 lp=lp, credentials=credentials,
1723 names=names, logger=logger,
1724 ldap_backend_forced_uri=None)
1725 elif backend_type == "fedora-ds":
1726 provision_backend = FDSBackend(backend_type, paths=paths,
1727 lp=lp, credentials=credentials,
1728 names=names, logger=logger, domainsid=domainsid,
1729 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1730 slapd_path=slapd_path,
1732 elif backend_type == "openldap":
1733 provision_backend = OpenLDAPBackend(backend_type, paths=paths,
1734 lp=lp, credentials=credentials,
1735 names=names, logger=logger, domainsid=domainsid,
1736 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1737 slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls)
1739 raise ValueError("Unknown LDAP backend type selected")
1741 provision_backend.init()
1742 provision_backend.start()
1744 # only install a new shares config db if there is none
1745 if not os.path.exists(paths.shareconf):
1746 logger.info("Setting up share.ldb")
1747 share_ldb = Ldb(paths.shareconf, session_info=session_info, lp=lp)
1748 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1750 logger.info("Setting up secrets.ldb")
1751 secrets_ldb = setup_secretsdb(paths,
1752 session_info=session_info,
1753 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1756 logger.info("Setting up the registry")
1757 setup_registry(paths.hklm, session_info, lp=lp)
1759 logger.info("Setting up the privileges database")
1760 setup_privileges(paths.privilege, session_info, lp=lp)
1762 logger.info("Setting up idmap db")
1763 idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp)
1765 setup_name_mappings(idmap, sid=str(domainsid),
1766 root_uid=root_uid, nobody_uid=nobody_uid,
1767 users_gid=users_gid, wheel_gid=wheel_gid)
1769 logger.info("Setting up SAM db")
1770 samdb = setup_samdb(paths.samdb, session_info,
1771 provision_backend, lp, names, logger=logger,
1772 serverrole=serverrole,
1773 schema=schema, fill=samdb_fill, am_rodc=am_rodc)
1775 if serverrole == "domain controller":
1776 if paths.netlogon is None:
1777 raise MissingShareError("netlogon", paths.smbconf,
1778 setup_path("provision.smb.conf.dc"))
1780 if paths.sysvol is None:
1781 raise MissingShareError("sysvol", paths.smbconf,
1782 setup_path("provision.smb.conf.dc"))
1784 if not os.path.isdir(paths.netlogon):
1785 os.makedirs(paths.netlogon, 0755)
1787 if adminpass is None:
1788 adminpass = samba.generate_random_password(12, 32)
1789 adminpass_generated = True
1791 adminpass_generated = False
1793 if samdb_fill == FILL_FULL:
1794 provision_fill(samdb, secrets_ldb, logger, names, paths,
1795 schema=schema, targetdir=targetdir, samdb_fill=samdb_fill,
1796 hostip=hostip, hostip6=hostip6, domainsid=domainsid,
1797 next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
1798 krbtgtpass=krbtgtpass, domainguid=domainguid,
1799 policyguid=policyguid, policyguid_dc=policyguid_dc,
1800 invocationid=invocationid, machinepass=machinepass,
1801 ntdsguid=ntdsguid, dns_backend=dns_backend,
1802 dnspass=dnspass, serverrole=serverrole,
1803 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1806 create_krb5_conf(paths.krb5conf,
1807 dnsdomain=names.dnsdomain, hostname=names.hostname,
1809 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1810 "generated at %s", paths.krb5conf)
1812 if serverrole == "domain controller":
1813 create_dns_update_list(lp, logger, paths)
1815 backend_result = provision_backend.post_setup()
1816 provision_backend.shutdown()
1818 create_phpldapadmin_config(paths.phpldapadminconfig,
1821 secrets_ldb.transaction_cancel()
1824 # Now commit the secrets.ldb to disk
1825 secrets_ldb.transaction_commit()
1827 # the commit creates the dns.keytab, now chown it
1828 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1829 if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
1831 os.chmod(dns_keytab_path, 0640)
1832 os.chown(dns_keytab_path, -1, paths.bind_gid)
1834 if not os.environ.has_key('SAMBA_SELFTEST'):
1835 logger.info("Failed to chown %s to bind gid %u",
1836 dns_keytab_path, paths.bind_gid)
1838 result = ProvisionResult()
1839 result.server_role = serverrole
1840 result.domaindn = domaindn
1841 result.paths = paths
1842 result.names = names
1844 result.samdb = samdb
1845 result.idmap = idmap
1846 result.domainsid = str(domainsid)
1848 if samdb_fill == FILL_FULL:
1849 result.adminpass_generated = adminpass_generated
1850 result.adminpass = adminpass
1852 result.adminpass_generated = False
1853 result.adminpass = None
1855 result.backend_result = backend_result
1860 def provision_become_dc(smbconf=None, targetdir=None,
1861 realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
1862 serverdn=None, domain=None, hostname=None, domainsid=None,
1863 adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
1864 policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
1865 dns_backend=None, root=None, nobody=None, users=None, wheel=None,
1866 backup=None, serverrole=None, ldap_backend=None,
1867 ldap_backend_type=None, sitename=None, debuglevel=1):
1869 logger = logging.getLogger("provision")
1870 samba.set_debug_level(debuglevel)
1872 res = provision(logger, system_session(), None,
1873 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1874 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1875 configdn=configdn, serverdn=serverdn, domain=domain,
1876 hostname=hostname, hostip=None, domainsid=domainsid,
1877 machinepass=machinepass, serverrole="domain controller",
1878 sitename=sitename, dns_backend=dns_backend, dnspass=dnspass)
1879 res.lp.set("debuglevel", str(debuglevel))
1883 def create_phpldapadmin_config(path, ldapi_uri):
1884 """Create a PHP LDAP admin configuration file.
1886 :param path: Path to write the configuration to.
1888 setup_file(setup_path("phpldapadmin-config.php"), path,
1889 {"S4_LDAPI_URI": ldapi_uri})
1892 def create_krb5_conf(path, dnsdomain, hostname, realm):
1893 """Write out a file containing zone statements suitable for inclusion in a
1894 named.conf file (including GSS-TSIG configuration).
1896 :param path: Path of the new named.conf file.
1897 :param dnsdomain: DNS Domain name
1898 :param hostname: Local hostname
1899 :param realm: Realm name
1901 setup_file(setup_path("krb5.conf"), path, {
1902 "DNSDOMAIN": dnsdomain,
1903 "HOSTNAME": hostname,
1908 class ProvisioningError(Exception):
1909 """A generic provision error."""
1911 def __init__(self, value):
1915 return "ProvisioningError: " + self.value
1918 class InvalidNetbiosName(Exception):
1919 """A specified name was not a valid NetBIOS name."""
1921 def __init__(self, name):
1922 super(InvalidNetbiosName, self).__init__(
1923 "The name '%r' is not a valid NetBIOS name" % name)
1926 class MissingShareError(ProvisioningError):
1928 def __init__(self, name, smbconf, smbconf_template):
1929 super(MissingShareError, self).__init__(
1930 "Existing smb.conf does not have a [%s] share, but you are "
1931 "configuring a DC. Please either remove %s or see the template "
1932 "at %s" % (name, smbconf, smbconf_template))