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
49 check_all_substituted,
55 from samba.dcerpc import security, misc
56 from samba.dcerpc.misc import (
60 from samba.dsdb import (
61 DS_DOMAIN_FUNCTION_2003,
62 DS_DOMAIN_FUNCTION_2008_R2,
65 from samba.idmap import IDmapDB
66 from samba.ms_display_specifiers import read_ms_ldif
67 from samba.ntacls import setntacl, dsacl2fsacl
68 from samba.ndr import ndr_pack, ndr_unpack
69 from samba.provision.backend import (
75 from samba.provision.descriptor import (
76 get_config_descriptor,
79 from samba.provision.common import (
84 from samba.provision.sambadns import (
86 create_dns_update_list
91 from samba.schema import Schema
92 from samba.samdb import SamDB
93 from samba.dbchecker import dbcheck
96 VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~"
97 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
98 DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
99 DEFAULTSITE = "Default-First-Site-Name"
100 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
103 class ProvisionPaths(object):
106 self.shareconf = None
117 self.dns_keytab = None
120 self.private_dir = None
121 self.phpldapadminconfig = None
124 class ProvisionNames(object):
131 self.ldapmanagerdn = None
132 self.dnsdomain = None
134 self.netbiosname = None
140 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
141 """Get key provision parameters (realm, domain, ...) from a given provision
143 :param samdb: An LDB object connected to the sam.ldb file
144 :param secretsdb: An LDB object connected to the secrets.ldb file
145 :param idmapdb: An LDB object connected to the idmap.ldb file
146 :param paths: A list of path to provision object
147 :param smbconf: Path to the smb.conf file
148 :param lp: A LoadParm object
149 :return: A list of key provision parameters
151 names = ProvisionNames()
152 names.adminpass = None
154 # NT domain, kerberos realm, root dn, domain dn, domain dns name
155 names.domain = string.upper(lp.get("workgroup"))
156 names.realm = lp.get("realm")
157 names.dnsdomain = names.realm.lower()
158 basedn = samba.dn_from_dns_name(names.dnsdomain)
159 names.realm = string.upper(names.realm)
161 # Get the netbiosname first (could be obtained from smb.conf in theory)
162 res = secretsdb.search(expression="(flatname=%s)" %
163 names.domain,base="CN=Primary Domains",
164 scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
165 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
167 names.smbconf = smbconf
169 # That's a bit simplistic but it's ok as long as we have only 3
171 current = samdb.search(expression="(objectClass=*)",
172 base="", scope=ldb.SCOPE_BASE,
173 attrs=["defaultNamingContext", "schemaNamingContext",
174 "configurationNamingContext","rootDomainNamingContext"])
176 names.configdn = current[0]["configurationNamingContext"]
177 configdn = str(names.configdn)
178 names.schemadn = current[0]["schemaNamingContext"]
179 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
180 current[0]["defaultNamingContext"][0]))):
181 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
182 "is not the same ..." % (paths.samdb,
183 str(current[0]["defaultNamingContext"][0]),
184 paths.smbconf, basedn)))
186 names.domaindn=current[0]["defaultNamingContext"]
187 names.rootdn=current[0]["rootDomainNamingContext"]
189 res3 = samdb.search(expression="(objectClass=site)",
190 base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
191 names.sitename = str(res3[0]["cn"])
193 # dns hostname and server dn
194 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
195 base="OU=Domain Controllers,%s" % basedn,
196 scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
197 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
199 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
200 attrs=[], base=configdn)
201 names.serverdn = server_res[0].dn
203 # invocation id/objectguid
204 res5 = samdb.search(expression="(objectClass=*)",
205 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=ldb.SCOPE_BASE,
206 attrs=["invocationID", "objectGUID"])
207 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
208 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
211 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
212 scope=ldb.SCOPE_BASE, attrs=["objectGUID",
213 "objectSid","msDS-Behavior-Version" ])
214 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
215 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
216 if res6[0].get("msDS-Behavior-Version") is None or \
217 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
218 names.domainlevel = DS_DOMAIN_FUNCTION_2000
220 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
223 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
224 base="CN=Policies,CN=System," + basedn,
225 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
226 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
228 res8 = samdb.search(expression="(displayName=Default Domain Controllers"
230 base="CN=Policies,CN=System," + basedn,
231 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
233 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
235 names.policyid_dc = None
236 res9 = idmapdb.search(expression="(cn=%s)" %
237 (security.SID_BUILTIN_ADMINISTRATORS),
240 names.wheel_gid = res9[0]["xidNumber"]
242 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
246 def update_provision_usn(samdb, low, high, id, replace=False):
247 """Update the field provisionUSN in sam.ldb
249 This field is used to track range of USN modified by provision and
251 This value is used afterward by next provision to figure out if
252 the field have been modified since last provision.
254 :param samdb: An LDB object connect to sam.ldb
255 :param low: The lowest USN modified by this upgrade
256 :param high: The highest USN modified by this upgrade
257 :param id: The invocation id of the samba's dc
258 :param replace: A boolean indicating if the range should replace any
259 existing one or appended (default)
264 entry = samdb.search(base="@PROVISION",
265 scope=ldb.SCOPE_BASE,
266 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
267 for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
268 if not re.search(';', e):
269 e = "%s;%s" % (e, id)
272 tab.append("%s-%s;%s" % (low, high, id))
273 delta = ldb.Message()
274 delta.dn = ldb.Dn(samdb, "@PROVISION")
275 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
276 ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
277 entry = samdb.search(expression='provisionnerID=*',
278 base="@PROVISION", scope=ldb.SCOPE_BASE,
279 attrs=["provisionnerID"])
280 if len(entry) == 0 or len(entry[0]) == 0:
281 delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
285 def set_provision_usn(samdb, low, high, id):
286 """Set the field provisionUSN in sam.ldb
287 This field is used to track range of USN modified by provision and
289 This value is used afterward by next provision to figure out if
290 the field have been modified since last provision.
292 :param samdb: An LDB object connect to sam.ldb
293 :param low: The lowest USN modified by this upgrade
294 :param high: The highest USN modified by this upgrade
295 :param id: The invocationId of the provision"""
298 tab.append("%s-%s;%s" % (low, high, id))
300 delta = ldb.Message()
301 delta.dn = ldb.Dn(samdb, "@PROVISION")
302 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
303 ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
307 def get_max_usn(samdb,basedn):
308 """ This function return the biggest USN present in the provision
310 :param samdb: A LDB object pointing to the sam.ldb
311 :param basedn: A string containing the base DN of the provision
313 :return: The biggest USN in the provision"""
315 res = samdb.search(expression="objectClass=*",base=basedn,
316 scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
317 controls=["search_options:1:2",
318 "server_sort:1:1:uSNChanged",
319 "paged_results:1:1"])
320 return res[0]["uSNChanged"]
323 def get_last_provision_usn(sam):
324 """Get USNs ranges modified by a provision or an upgradeprovision
326 :param sam: An LDB object pointing to the sam.ldb
327 :return: a dictionnary which keys are invocation id and values are an array
328 of integer representing the different ranges
331 entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
332 base="@PROVISION", scope=ldb.SCOPE_BASE,
333 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
334 except ldb.LdbError, (ecode, emsg):
335 if ecode == ldb.ERR_NO_SUCH_OBJECT:
342 if entry[0].get("provisionnerID"):
343 for e in entry[0]["provisionnerID"]:
345 for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
346 tab1 = str(r).split(';')
351 if (len(myids) > 0 and id not in myids):
353 tab2 = p.split(tab1[0])
354 if range.get(id) == None:
356 range[id].append(tab2[0])
357 range[id].append(tab2[1])
363 class ProvisionResult(object):
364 """Result of a provision.
366 :ivar server_role: The server role
367 :ivar paths: ProvisionPaths instance
368 :ivar domaindn: The domain dn, as string
372 self.server_role = None
379 self.domainsid = None
380 self.adminpass_generated = None
381 self.adminpass = None
382 self.backend_result = None
384 def report_logger(self, logger):
385 """Report this provision result to a logger."""
387 "Once the above files are installed, your Samba4 server will "
389 if self.adminpass_generated:
390 logger.info("Admin password: %s", self.adminpass)
391 logger.info("Server Role: %s", self.server_role)
392 logger.info("Hostname: %s", self.names.hostname)
393 logger.info("NetBIOS Domain: %s", self.names.domain)
394 logger.info("DNS Domain: %s", self.names.dnsdomain)
395 logger.info("DOMAIN SID: %s", self.domainsid)
397 if self.paths.phpldapadminconfig is not None:
399 "A phpLDAPadmin configuration file suitable for administering "
400 "the Samba 4 LDAP server has been created in %s.",
401 self.paths.phpldapadminconfig)
403 if self.backend_result:
404 self.backend_result.report_logger(logger)
407 def check_install(lp, session_info, credentials):
408 """Check whether the current install seems ok.
410 :param lp: Loadparm context
411 :param session_info: Session information
412 :param credentials: Credentials
414 if lp.get("realm") == "":
415 raise Exception("Realm empty")
416 samdb = Ldb(lp.samdb_url(), session_info=session_info,
417 credentials=credentials, lp=lp)
418 if len(samdb.search("(cn=Administrator)")) != 1:
419 raise ProvisioningError("No administrator account found")
422 def findnss(nssfn, names):
423 """Find a user or group from a list of possibilities.
425 :param nssfn: NSS Function to try (should raise KeyError if not found)
426 :param names: Names to check.
427 :return: Value return by first names list.
434 raise KeyError("Unable to find user/group in %r" % names)
437 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
438 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
441 def provision_paths_from_lp(lp, dnsdomain):
442 """Set the default paths for provisioning.
444 :param lp: Loadparm context.
445 :param dnsdomain: DNS Domain name
447 paths = ProvisionPaths()
448 paths.private_dir = lp.get("private dir")
450 # This is stored without path prefix for the "privateKeytab" attribute in
451 # "secrets_dns.ldif".
452 paths.dns_keytab = "dns.keytab"
453 paths.keytab = "secrets.keytab"
455 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
456 paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
457 paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
458 paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
459 paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
460 paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
461 paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
462 paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
463 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
464 paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
465 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
466 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
467 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
468 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
469 paths.phpldapadminconfig = os.path.join(paths.private_dir,
470 "phpldapadmin-config.php")
471 paths.hklm = "hklm.ldb"
472 paths.hkcr = "hkcr.ldb"
473 paths.hkcu = "hkcu.ldb"
474 paths.hku = "hku.ldb"
475 paths.hkpd = "hkpd.ldb"
476 paths.hkpt = "hkpt.ldb"
477 paths.sysvol = lp.get("path", "sysvol")
478 paths.netlogon = lp.get("path", "netlogon")
479 paths.smbconf = lp.configfile
483 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
484 serverrole=None, rootdn=None, domaindn=None, configdn=None,
485 schemadn=None, serverdn=None, sitename=None):
486 """Guess configuration settings to use."""
489 hostname = socket.gethostname().split(".")[0]
491 netbiosname = lp.get("netbios name")
492 if netbiosname is None:
493 netbiosname = hostname
494 # remove forbidden chars
496 for x in netbiosname:
497 if x.isalnum() or x in VALID_NETBIOS_CHARS:
498 newnbname = "%s%c" % (newnbname, x)
499 # force the length to be <16
500 netbiosname = newnbname[0:15]
501 assert netbiosname is not None
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"), serverrole, lp.configfile))
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
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, serverrole,
590 targetdir, sid_generator="internal", 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
596 hostname = socket.gethostname().split(".")[0]
597 netbiosname = hostname.upper()
598 # remove forbidden chars
600 for x in netbiosname:
601 if x.isalnum() or x in VALID_NETBIOS_CHARS:
602 newnbname = "%s%c" % (newnbname, x)
603 #force the length to be <16
604 netbiosname = newnbname[0:15]
606 netbiosname = hostname.upper()
608 if serverrole is None:
609 serverrole = "standalone"
611 assert serverrole in ("domain controller", "member server", "standalone")
612 if serverrole == "domain controller":
614 elif serverrole == "member server":
615 smbconfsuffix = "member"
616 elif serverrole == "standalone":
617 smbconfsuffix = "standalone"
619 if sid_generator is None:
620 sid_generator = "internal"
622 assert domain is not None
623 domain = domain.upper()
625 assert realm is not None
626 realm = realm.upper()
629 lp = samba.param.LoadParm()
630 #Load non-existant file
631 if os.path.exists(smbconf):
633 if eadb and not lp.get("posix:eadb"):
634 if targetdir is not None:
635 privdir = os.path.join(targetdir, "private")
637 privdir = lp.get("private dir")
638 lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
640 if server_services is not None:
641 server_services_line = "server services = " + " ".join(server_services)
643 server_services_line = ""
645 if targetdir is not None:
646 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
647 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
648 statedir_line = "state directory = " + os.path.abspath(targetdir)
649 cachedir_line = "cache directory = " + os.path.abspath(targetdir)
651 lp.set("lock dir", os.path.abspath(targetdir))
652 lp.set("state directory", os.path.abspath(targetdir))
653 lp.set("cache directory", os.path.abspath(targetdir))
660 sysvol = os.path.join(lp.get("state directory"), "sysvol")
661 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
663 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
665 "NETBIOS_NAME": netbiosname,
668 "SERVERROLE": serverrole,
669 "NETLOGONPATH": netlogon,
670 "SYSVOLPATH": sysvol,
671 "PRIVATEDIR_LINE": privatedir_line,
672 "LOCKDIR_LINE": lockdir_line,
673 "STATEDIR_LINE": statedir_line,
674 "CACHEDIR_LINE": cachedir_line,
675 "SERVER_SERVICES_LINE": server_services_line
678 # reload the smb.conf
681 # and dump it without any values that are the default
682 # this ensures that any smb.conf parameters that were set
683 # on the provision/join command line are set in the resulting smb.conf
684 f = open(smbconf, mode='w')
689 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
690 users_gid, wheel_gid):
691 """setup reasonable name mappings for sam names to unix names.
693 :param samdb: SamDB object.
694 :param idmap: IDmap db object.
695 :param sid: The domain sid.
696 :param domaindn: The domain DN.
697 :param root_uid: uid of the UNIX root user.
698 :param nobody_uid: uid of the UNIX nobody user.
699 :param users_gid: gid of the UNIX users group.
700 :param wheel_gid: gid of the UNIX wheel group.
702 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
703 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
705 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
706 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
709 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
710 provision_backend, names, schema, serverrole,
712 """Setup the partitions for the SAM database.
714 Alternatively, provision() may call this, and then populate the database.
716 :note: This will wipe the Sam Database!
718 :note: This function always removes the local SAM LDB file. The erase
719 parameter controls whether to erase the existing data, which
720 may not be stored locally but in LDAP.
723 assert session_info is not None
725 # We use options=["modules:"] to stop the modules loading - we
726 # just want to wipe and re-initialise the database, not start it up
729 os.unlink(samdb_path)
733 samdb = Ldb(url=samdb_path, session_info=session_info,
734 lp=lp, options=["modules:"])
736 ldap_backend_line = "# No LDAP backend"
737 if provision_backend.type != "ldb":
738 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
740 samdb.transaction_start()
742 logger.info("Setting up sam.ldb partitions and settings")
743 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
744 "LDAP_BACKEND_LINE": ldap_backend_line
748 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
749 "BACKEND_TYPE": provision_backend.type,
750 "SERVER_ROLE": serverrole
753 logger.info("Setting up sam.ldb rootDSE")
754 setup_samdb_rootdse(samdb, names)
756 samdb.transaction_cancel()
759 samdb.transaction_commit()
762 def secretsdb_self_join(secretsdb, domain,
763 netbiosname, machinepass, domainsid=None,
764 realm=None, dnsdomain=None,
766 key_version_number=1,
767 secure_channel_type=SEC_CHAN_WKSTA):
768 """Add domain join-specific bits to a secrets database.
770 :param secretsdb: Ldb Handle to the secrets database
771 :param machinepass: Machine password
773 attrs = ["whenChanged",
780 if realm is not None:
781 if dnsdomain is None:
782 dnsdomain = realm.lower()
783 dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
786 shortname = netbiosname.lower()
788 # We don't need to set msg["flatname"] here, because rdn_name will handle
789 # it, and it causes problems for modifies anyway
790 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
791 msg["secureChannelType"] = [str(secure_channel_type)]
792 msg["objectClass"] = ["top", "primaryDomain"]
793 if dnsname is not None:
794 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
795 msg["realm"] = [realm]
796 msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
797 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
798 msg["privateKeytab"] = ["secrets.keytab"]
800 msg["secret"] = [machinepass]
801 msg["samAccountName"] = ["%s$" % netbiosname]
802 msg["secureChannelType"] = [str(secure_channel_type)]
803 if domainsid is not None:
804 msg["objectSid"] = [ndr_pack(domainsid)]
806 # This complex expression tries to ensure that we don't have more
807 # than one record for this SID, realm or netbios domain at a time,
808 # but we don't delete the old record that we are about to modify,
809 # because that would delete the keytab and previous password.
810 res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
811 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
812 scope=ldb.SCOPE_ONELEVEL)
815 secretsdb.delete(del_msg.dn)
817 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
820 msg["priorSecret"] = [res[0]["secret"][0]]
821 msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
824 msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
829 msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
835 msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
836 secretsdb.modify(msg)
837 secretsdb.rename(res[0].dn, msg.dn)
839 spn = [ 'HOST/%s' % shortname ]
840 if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
841 # we are a domain controller then we add servicePrincipalName
842 # entries for the keytab code to update.
843 spn.extend([ 'HOST/%s' % dnsname ])
844 msg["servicePrincipalName"] = spn
849 def setup_secretsdb(paths, session_info, backend_credentials, lp):
850 """Setup the secrets database.
852 :note: This function does not handle exceptions and transaction on purpose,
853 it's up to the caller to do this job.
855 :param path: Path to the secrets database.
856 :param session_info: Session info.
857 :param credentials: Credentials
858 :param lp: Loadparm context
859 :return: LDB handle for the created secrets database
861 if os.path.exists(paths.secrets):
862 os.unlink(paths.secrets)
864 keytab_path = os.path.join(paths.private_dir, paths.keytab)
865 if os.path.exists(keytab_path):
866 os.unlink(keytab_path)
868 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
869 if os.path.exists(dns_keytab_path):
870 os.unlink(dns_keytab_path)
874 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
876 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
877 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
878 secrets_ldb.transaction_start()
880 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
882 if (backend_credentials is not None and
883 backend_credentials.authentication_requested()):
884 if backend_credentials.get_bind_dn() is not None:
885 setup_add_ldif(secrets_ldb,
886 setup_path("secrets_simple_ldap.ldif"), {
887 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
888 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
891 setup_add_ldif(secrets_ldb,
892 setup_path("secrets_sasl_ldap.ldif"), {
893 "LDAPADMINUSER": backend_credentials.get_username(),
894 "LDAPADMINREALM": backend_credentials.get_realm(),
895 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
898 secrets_ldb.transaction_cancel()
903 def setup_privileges(path, session_info, lp):
904 """Setup the privileges database.
906 :param path: Path to the privileges database.
907 :param session_info: Session info.
908 :param credentials: Credentials
909 :param lp: Loadparm context
910 :return: LDB handle for the created secrets database
912 if os.path.exists(path):
914 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
915 privilege_ldb.erase()
916 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
919 def setup_registry(path, session_info, lp):
920 """Setup the registry.
922 :param path: Path to the registry database
923 :param session_info: Session information
924 :param credentials: Credentials
925 :param lp: Loadparm context
927 reg = samba.registry.Registry()
928 hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
929 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
930 provision_reg = setup_path("provision.reg")
931 assert os.path.exists(provision_reg)
932 reg.diff_apply(provision_reg)
935 def setup_idmapdb(path, session_info, lp):
936 """Setup the idmap database.
938 :param path: path to the idmap database
939 :param session_info: Session information
940 :param credentials: Credentials
941 :param lp: Loadparm context
943 if os.path.exists(path):
946 idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
948 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
952 def setup_samdb_rootdse(samdb, names):
953 """Setup the SamDB rootdse.
955 :param samdb: Sam Database handle
957 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
958 "SCHEMADN": names.schemadn,
959 "DOMAINDN": names.domaindn,
960 "ROOTDN" : names.rootdn,
961 "CONFIGDN": names.configdn,
962 "SERVERDN": names.serverdn,
966 def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
967 dnspass, domainsid, next_rid, invocationid, policyguid, policyguid_dc,
968 domainControllerFunctionality, ntdsguid=None, dc_rid=None):
969 """Join a host to its own domain."""
970 assert isinstance(invocationid, str)
971 if ntdsguid is not None:
972 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
979 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
980 "CONFIGDN": names.configdn,
981 "SCHEMADN": names.schemadn,
982 "DOMAINDN": names.domaindn,
983 "SERVERDN": names.serverdn,
984 "INVOCATIONID": invocationid,
985 "NETBIOSNAME": names.netbiosname,
986 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
987 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
988 "DOMAINSID": str(domainsid),
989 "DCRID": str(dc_rid),
990 "SAMBA_VERSION_STRING": version,
991 "NTDSGUID": ntdsguid_line,
992 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
993 domainControllerFunctionality),
994 "RIDALLOCATIONSTART": str(next_rid + 100),
995 "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
997 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
998 "POLICYGUID": policyguid,
999 "POLICYGUID_DC": policyguid_dc,
1000 "DNSDOMAIN": names.dnsdomain,
1001 "DOMAINDN": names.domaindn})
1003 # If we are setting up a subdomain, then this has been replicated in, so we
1004 # don't need to add it
1005 if fill == FILL_FULL:
1006 setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), {
1007 "CONFIGDN": names.configdn,
1008 "SCHEMADN": names.schemadn,
1009 "DOMAINDN": names.domaindn,
1010 "SERVERDN": names.serverdn,
1011 "INVOCATIONID": invocationid,
1012 "NETBIOSNAME": names.netbiosname,
1013 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
1014 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
1015 "DOMAINSID": str(domainsid),
1016 "DCRID": str(dc_rid),
1017 "SAMBA_VERSION_STRING": version,
1018 "NTDSGUID": ntdsguid_line,
1019 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
1020 domainControllerFunctionality)})
1022 # Setup fSMORoleOwner entries to point at the newly created DC entry
1023 setup_modify_ldif(samdb,
1024 setup_path("provision_self_join_modify_config.ldif"), {
1025 "CONFIGDN": names.configdn,
1026 "SCHEMADN": names.schemadn,
1027 "DEFAULTSITE": names.sitename,
1028 "NETBIOSNAME": names.netbiosname,
1029 "SERVERDN": names.serverdn,
1032 system_session_info = system_session()
1033 samdb.set_session_info(system_session_info)
1034 # Setup fSMORoleOwner entries to point at the newly created DC entry to
1035 # modify a serverReference under cn=config when we are a subdomain, we must
1036 # be system due to ACLs
1037 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1038 "DOMAINDN": names.domaindn,
1039 "SERVERDN": names.serverdn,
1040 "NETBIOSNAME": names.netbiosname,
1043 samdb.set_session_info(admin_session_info)
1045 # This is Samba4 specific and should be replaced by the correct
1046 # DNS AD-style setup
1047 setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
1048 "DNSDOMAIN": names.dnsdomain,
1049 "DOMAINDN": names.domaindn,
1050 "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
1051 "HOSTNAME" : names.hostname,
1052 "DNSNAME" : '%s.%s' % (
1053 names.netbiosname.lower(), names.dnsdomain.lower())
1057 def getpolicypath(sysvolpath, dnsdomain, guid):
1058 """Return the physical path of policy given its guid.
1060 :param sysvolpath: Path to the sysvol folder
1061 :param dnsdomain: DNS name of the AD domain
1062 :param guid: The GUID of the policy
1063 :return: A string with the complete path to the policy folder
1066 guid = "{%s}" % guid
1067 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
1071 def create_gpo_struct(policy_path):
1072 if not os.path.exists(policy_path):
1073 os.makedirs(policy_path, 0775)
1074 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1075 "[General]\r\nVersion=0")
1076 p = os.path.join(policy_path, "MACHINE")
1077 if not os.path.exists(p):
1078 os.makedirs(p, 0775)
1079 p = os.path.join(policy_path, "USER")
1080 if not os.path.exists(p):
1081 os.makedirs(p, 0775)
1084 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
1085 """Create the default GPO for a domain
1087 :param sysvolpath: Physical path for the sysvol folder
1088 :param dnsdomain: DNS domain name of the AD domain
1089 :param policyguid: GUID of the default domain policy
1090 :param policyguid_dc: GUID of the default domain controler policy
1092 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
1093 create_gpo_struct(policy_path)
1095 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
1096 create_gpo_struct(policy_path)
1099 def setup_samdb(path, session_info, provision_backend, lp, names,
1100 logger, fill, serverrole, schema, am_rodc=False):
1101 """Setup a complete SAM Database.
1103 :note: This will wipe the main SAM database file!
1106 # Also wipes the database
1107 setup_samdb_partitions(path, logger=logger, lp=lp,
1108 provision_backend=provision_backend, session_info=session_info,
1109 names=names, serverrole=serverrole, schema=schema)
1111 # Load the database, but don's load the global schema and don't connect
1113 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
1114 credentials=provision_backend.credentials, lp=lp,
1115 global_schema=False, am_rodc=am_rodc)
1117 logger.info("Pre-loading the Samba 4 and AD schema")
1119 # Load the schema from the one we computed earlier
1120 samdb.set_schema(schema)
1122 # Set the NTDS settings DN manually - in order to have it already around
1123 # before the provisioned tree exists and we connect
1124 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1126 # And now we can connect to the DB - the schema won't be loaded from the
1133 def fill_samdb(samdb, lp, names,
1134 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
1135 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
1136 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
1137 next_rid=None, dc_rid=None):
1139 if next_rid is None:
1142 # Provision does not make much sense values larger than 1000000000
1143 # as the upper range of the rIDAvailablePool is 1073741823 and
1144 # we don't want to create a domain that cannot allocate rids.
1145 if next_rid < 1000 or next_rid > 1000000000:
1146 error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
1147 error += "the valid range is %u-%u. The default is %u." % (
1148 1000, 1000000000, 1000)
1149 raise ProvisioningError(error)
1151 # ATTENTION: Do NOT change these default values without discussion with the
1152 # team and/or release manager. They have a big impact on the whole program!
1153 domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
1155 if dom_for_fun_level is None:
1156 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1158 if dom_for_fun_level > domainControllerFunctionality:
1159 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!")
1161 domainFunctionality = dom_for_fun_level
1162 forestFunctionality = dom_for_fun_level
1164 # Set the NTDS settings DN manually - in order to have it already around
1165 # before the provisioned tree exists and we connect
1166 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1168 samdb.transaction_start()
1170 # Set the domain functionality levels onto the database.
1171 # Various module (the password_hash module in particular) need
1172 # to know what level of AD we are emulating.
1174 # These will be fixed into the database via the database
1175 # modifictions below, but we need them set from the start.
1176 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1177 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1178 samdb.set_opaque_integer("domainControllerFunctionality",
1179 domainControllerFunctionality)
1181 samdb.set_domain_sid(str(domainsid))
1182 samdb.set_invocation_id(invocationid)
1184 logger.info("Adding DomainDN: %s" % names.domaindn)
1186 # impersonate domain admin
1187 admin_session_info = admin_session(lp, str(domainsid))
1188 samdb.set_session_info(admin_session_info)
1189 if domainguid is not None:
1190 domainguid_line = "objectGUID: %s\n-" % domainguid
1192 domainguid_line = ""
1194 descr = b64encode(get_domain_descriptor(domainsid))
1195 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1196 "DOMAINDN": names.domaindn,
1197 "DOMAINSID": str(domainsid),
1198 "DESCRIPTOR": descr,
1199 "DOMAINGUID": domainguid_line
1202 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1203 "DOMAINDN": names.domaindn,
1204 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1205 "NEXTRID": str(next_rid),
1206 "DEFAULTSITE": names.sitename,
1207 "CONFIGDN": names.configdn,
1208 "POLICYGUID": policyguid,
1209 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1210 "SAMBA_VERSION_STRING": version
1213 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1214 if fill == FILL_FULL:
1215 logger.info("Adding configuration container")
1216 descr = b64encode(get_config_descriptor(domainsid))
1217 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1218 "CONFIGDN": names.configdn,
1219 "DESCRIPTOR": descr,
1222 # The LDIF here was created when the Schema object was constructed
1223 logger.info("Setting up sam.ldb schema")
1224 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1225 samdb.modify_ldif(schema.schema_dn_modify)
1226 samdb.write_prefixes_from_schema()
1227 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1228 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1229 {"SCHEMADN": names.schemadn})
1231 # Now register this container in the root of the forest
1232 msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
1233 msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
1237 samdb.transaction_cancel()
1240 samdb.transaction_commit()
1242 samdb.transaction_start()
1244 samdb.invocation_id = invocationid
1246 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1247 if fill == FILL_FULL:
1248 logger.info("Setting up sam.ldb configuration data")
1249 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1250 "CONFIGDN": names.configdn,
1251 "NETBIOSNAME": names.netbiosname,
1252 "DEFAULTSITE": names.sitename,
1253 "DNSDOMAIN": names.dnsdomain,
1254 "DOMAIN": names.domain,
1255 "SCHEMADN": names.schemadn,
1256 "DOMAINDN": names.domaindn,
1257 "SERVERDN": names.serverdn,
1258 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1259 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1262 logger.info("Setting up display specifiers")
1263 display_specifiers_ldif = read_ms_ldif(
1264 setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1265 display_specifiers_ldif = substitute_var(display_specifiers_ldif,
1266 {"CONFIGDN": names.configdn})
1267 check_all_substituted(display_specifiers_ldif)
1268 samdb.add_ldif(display_specifiers_ldif)
1270 logger.info("Adding users container")
1271 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1272 "DOMAINDN": names.domaindn})
1273 logger.info("Modifying users container")
1274 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1275 "DOMAINDN": names.domaindn})
1276 logger.info("Adding computers container")
1277 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1278 "DOMAINDN": names.domaindn})
1279 logger.info("Modifying computers container")
1280 setup_modify_ldif(samdb,
1281 setup_path("provision_computers_modify.ldif"), {
1282 "DOMAINDN": names.domaindn})
1283 logger.info("Setting up sam.ldb data")
1284 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1285 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1286 "DOMAINDN": names.domaindn,
1287 "NETBIOSNAME": names.netbiosname,
1288 "DEFAULTSITE": names.sitename,
1289 "CONFIGDN": names.configdn,
1290 "SERVERDN": names.serverdn,
1291 "RIDAVAILABLESTART": str(next_rid + 600),
1292 "POLICYGUID_DC": policyguid_dc
1295 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1296 if fill == FILL_FULL:
1297 setup_modify_ldif(samdb,
1298 setup_path("provision_configuration_references.ldif"), {
1299 "CONFIGDN": names.configdn,
1300 "SCHEMADN": names.schemadn})
1302 logger.info("Setting up well known security principals")
1303 setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
1304 "CONFIGDN": names.configdn,
1307 if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
1308 setup_modify_ldif(samdb,
1309 setup_path("provision_basedn_references.ldif"),
1310 {"DOMAINDN": names.domaindn})
1312 logger.info("Setting up sam.ldb users and groups")
1313 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1314 "DOMAINDN": names.domaindn,
1315 "DOMAINSID": str(domainsid),
1316 "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
1317 "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
1320 logger.info("Setting up self join")
1321 setup_self_join(samdb, admin_session_info, names=names, fill=fill,
1322 invocationid=invocationid,
1324 machinepass=machinepass,
1325 domainsid=domainsid,
1328 policyguid=policyguid,
1329 policyguid_dc=policyguid_dc,
1330 domainControllerFunctionality=domainControllerFunctionality,
1333 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1334 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1335 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1336 assert isinstance(names.ntdsguid, str)
1338 samdb.transaction_cancel()
1341 samdb.transaction_commit()
1346 FILL_SUBDOMAIN = "SUBDOMAIN"
1347 FILL_NT4SYNC = "NT4SYNC"
1349 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1350 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)"
1353 def set_dir_acl(path, acl, lp, domsid):
1354 setntacl(lp, path, acl, domsid)
1355 for root, dirs, files in os.walk(path, topdown=False):
1357 setntacl(lp, os.path.join(root, name), acl, domsid)
1359 setntacl(lp, os.path.join(root, name), acl, domsid)
1362 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1363 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1366 :param sysvol: Physical path for the sysvol folder
1367 :param dnsdomain: The DNS name of the domain
1368 :param domainsid: The SID of the domain
1369 :param domaindn: The DN of the domain (ie. DC=...)
1370 :param samdb: An LDB object on the SAM db
1371 :param lp: an LP object
1374 # Set ACL for GPO root folder
1375 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1376 setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
1378 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1379 attrs=["cn", "nTSecurityDescriptor"],
1380 expression="", scope=ldb.SCOPE_ONELEVEL)
1383 acl = ndr_unpack(security.descriptor,
1384 str(policy["nTSecurityDescriptor"])).as_sddl()
1385 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1386 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1390 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1392 """Set the ACL for the sysvol share and the subfolders
1394 :param samdb: An LDB object on the SAM db
1395 :param netlogon: Physical path for the netlogon folder
1396 :param sysvol: Physical path for the sysvol folder
1397 :param gid: The GID of the "Domain adminstrators" group
1398 :param domainsid: The SID of the domain
1399 :param dnsdomain: The DNS name of the domain
1400 :param domaindn: The DN of the domain (ie. DC=...)
1404 os.chown(sysvol, -1, gid)
1410 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1411 setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
1412 for root, dirs, files in os.walk(sysvol, topdown=False):
1415 os.chown(os.path.join(root, name), -1, gid)
1416 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1419 os.chown(os.path.join(root, name), -1, gid)
1420 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1422 # Set acls on Policy folder and policies folders
1423 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
1426 def interface_ips_v4(lp):
1427 '''return only IPv4 IPs'''
1428 ips = samba.interface_ips(lp, False)
1431 if i.find(':') == -1:
1435 def interface_ips_v6(lp, linklocal=False):
1436 '''return only IPv6 IPs'''
1437 ips = samba.interface_ips(lp, False)
1440 if i.find(':') != -1 and (linklocal or i.find('%') == -1):
1445 def provision_fill(samdb, secrets_ldb, logger, names, paths,
1446 domainsid, schema=None,
1447 targetdir=None, samdb_fill=FILL_FULL,
1448 hostip=None, hostip6=None,
1449 next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
1450 domainguid=None, policyguid=None, policyguid_dc=None,
1451 invocationid=None, machinepass=None, ntdsguid=None,
1452 dns_backend=None, dnspass=None,
1453 serverrole=None, dom_for_fun_level=None,
1454 am_rodc=False, lp=None):
1455 # create/adapt the group policy GUIDs
1456 # Default GUID for default policy are described at
1457 # "How Core Group Policy Works"
1458 # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
1459 if policyguid is None:
1460 policyguid = DEFAULT_POLICY_GUID
1461 policyguid = policyguid.upper()
1462 if policyguid_dc is None:
1463 policyguid_dc = DEFAULT_DC_POLICY_GUID
1464 policyguid_dc = policyguid_dc.upper()
1466 if invocationid is None:
1467 invocationid = str(uuid.uuid4())
1469 if krbtgtpass is None:
1470 krbtgtpass = samba.generate_random_password(128, 255)
1471 if machinepass is None:
1472 machinepass = samba.generate_random_password(128, 255)
1474 dnspass = samba.generate_random_password(128, 255)
1476 samdb = fill_samdb(samdb, lp, names, logger=logger,
1477 domainsid=domainsid, schema=schema, domainguid=domainguid,
1478 policyguid=policyguid, policyguid_dc=policyguid_dc,
1479 fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
1480 invocationid=invocationid, machinepass=machinepass,
1481 dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
1482 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1483 next_rid=next_rid, dc_rid=dc_rid)
1485 if serverrole == "domain controller":
1486 # Set up group policies (domain policy and domain controller
1488 create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
1490 setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
1491 domainsid, names.dnsdomain, names.domaindn, lp)
1493 secretsdb_self_join(secrets_ldb, domain=names.domain,
1494 realm=names.realm, dnsdomain=names.dnsdomain,
1495 netbiosname=names.netbiosname, domainsid=domainsid,
1496 machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
1498 # Now set up the right msDS-SupportedEncryptionTypes into the DB
1499 # In future, this might be determined from some configuration
1500 kerberos_enctypes = str(ENC_ALL_TYPES)
1503 msg = ldb.Message(ldb.Dn(samdb,
1504 samdb.searchone("distinguishedName",
1505 expression="samAccountName=%s$" % names.netbiosname,
1506 scope=ldb.SCOPE_SUBTREE)))
1507 msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
1508 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
1509 name="msDS-SupportedEncryptionTypes")
1511 except ldb.LdbError, (enum, estr):
1512 if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
1513 # It might be that this attribute does not exist in this schema
1516 setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
1517 hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
1518 dnspass=dnspass, os_level=dom_for_fun_level,
1519 targetdir=targetdir, site=DEFAULTSITE)
1521 domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
1522 attribute="objectGUID")
1523 assert isinstance(domainguid, str)
1525 lastProvisionUSNs = get_last_provision_usn(samdb)
1526 maxUSN = get_max_usn(samdb, str(names.rootdn))
1527 if lastProvisionUSNs is not None:
1528 update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
1530 set_provision_usn(samdb, 0, maxUSN, invocationid)
1532 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1533 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
1534 { 'NTDSGUID' : names.ntdsguid })
1536 # fix any dangling GUIDs from the provision
1537 logger.info("Fixing provision GUIDs")
1538 chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True,
1540 samdb.transaction_start()
1542 # a small number of GUIDs are missing because of ordering issues in the
1544 for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
1545 chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
1546 scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
1547 chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
1548 scope=ldb.SCOPE_ONELEVEL,
1549 attrs=['ipsecOwnersReference',
1550 'ipsecFilterReference',
1551 'ipsecISAKMPReference',
1552 'ipsecNegotiationPolicyReference',
1553 'ipsecNFAReference'])
1555 samdb.transaction_cancel()
1558 samdb.transaction_commit()
1562 "ROLE_STANDALONE": "standalone",
1563 "ROLE_DOMAIN_MEMBER": "member server",
1564 "ROLE_DOMAIN_BDC": "domain controller",
1565 "ROLE_DOMAIN_PDC": "domain controller",
1566 "dc": "domain controller",
1567 "member": "member server",
1568 "domain controller": "domain controller",
1569 "member server": "member server",
1570 "standalone": "standalone",
1574 def sanitize_server_role(role):
1575 """Sanitize a server role name.
1577 :param role: Server role
1578 :raise ValueError: If the role can not be interpreted
1579 :return: Sanitized server role (one of "member server",
1580 "domain controller", "standalone")
1583 return _ROLES_MAP[role]
1585 raise ValueError(role)
1588 def provision(logger, session_info, credentials, smbconf=None,
1589 targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
1590 domaindn=None, schemadn=None, configdn=None, serverdn=None,
1591 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
1592 next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None,
1593 domainguid=None, policyguid=None, policyguid_dc=None,
1594 dns_backend=None, dnspass=None,
1595 invocationid=None, machinepass=None, ntdsguid=None,
1596 root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
1597 serverrole=None, dom_for_fun_level=None,
1598 backend_type=None, sitename=None,
1599 ol_mmr_urls=None, ol_olc=None, slapd_path=None,
1600 useeadb=False, am_rodc=False,
1604 :note: caution, this wipes all existing data!
1608 serverrole = sanitize_server_role(serverrole)
1610 raise ProvisioningError('server role (%s) should be one of "domain controller", "member server", "standalone"' % serverrole)
1612 if ldapadminpass is None:
1613 # Make a new, random password between Samba and it's LDAP server
1614 ldapadminpass = samba.generate_random_password(128, 255)
1616 if backend_type is None:
1617 backend_type = "ldb"
1619 if domainsid is None:
1620 domainsid = security.random_sid()
1622 domainsid = security.dom_sid(domainsid)
1624 sid_generator = "internal"
1625 if backend_type == "fedora-ds":
1626 sid_generator = "backend"
1628 root_uid = findnss_uid([root or "root"])
1629 nobody_uid = findnss_uid([nobody or "nobody"])
1630 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1632 wheel_gid = findnss_gid(["wheel", "adm"])
1634 wheel_gid = findnss_gid([wheel])
1636 bind_gid = findnss_gid(["bind", "named"])
1640 if targetdir is not None:
1641 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1642 elif smbconf is None:
1643 smbconf = samba.param.default_path()
1644 if not os.path.exists(os.path.dirname(smbconf)):
1645 os.makedirs(os.path.dirname(smbconf))
1647 server_services = None
1648 if dns_backend == "SAMBA_INTERNAL":
1649 server_services = [ "+dns" ]
1651 # only install a new smb.conf if there isn't one there already
1652 if os.path.exists(smbconf):
1653 # if Samba Team members can't figure out the weird errors
1654 # loading an empty smb.conf gives, then we need to be smarter.
1655 # Pretend it just didn't exist --abartlet
1656 data = open(smbconf, 'r').read()
1657 data = data.lstrip()
1658 if data is None or data == "":
1659 make_smbconf(smbconf, hostname, domain, realm,
1660 serverrole, targetdir, sid_generator, useeadb,
1661 lp=lp, server_services=server_services)
1663 make_smbconf(smbconf, hostname, domain, realm, serverrole,
1664 targetdir, sid_generator, useeadb, lp=lp,
1665 server_services=server_services)
1668 lp = samba.param.LoadParm()
1670 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1671 dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
1672 configdn=configdn, schemadn=schemadn, serverdn=serverdn,
1673 sitename=sitename, rootdn=rootdn)
1674 paths = provision_paths_from_lp(lp, names.dnsdomain)
1676 paths.bind_gid = bind_gid
1677 paths.wheel_gid = wheel_gid
1680 logger.info("Looking up IPv4 addresses")
1681 hostips = interface_ips_v4(lp)
1682 if len(hostips) > 0:
1684 if len(hostips) > 1:
1685 logger.warning("More than one IPv4 address found. Using %s",
1687 if hostip == "127.0.0.1":
1690 logger.warning("No IPv4 address will be assigned")
1693 logger.info("Looking up IPv6 addresses")
1694 hostips = interface_ips_v6(lp, linklocal=False)
1696 hostip6 = hostips[0]
1697 if len(hostips) > 1:
1698 logger.warning("More than one IPv6 address found. Using %s", hostip6)
1700 logger.warning("No IPv6 address will be assigned")
1702 names.hostip = hostip
1703 names.hostip6 = hostip6
1705 if serverrole is None:
1706 serverrole = lp.get("server role")
1708 if not os.path.exists(paths.private_dir):
1709 os.mkdir(paths.private_dir)
1710 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1711 os.mkdir(os.path.join(paths.private_dir, "tls"))
1713 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1715 schema = Schema(domainsid, invocationid=invocationid,
1716 schemadn=names.schemadn)
1718 if backend_type == "ldb":
1719 provision_backend = LDBBackend(backend_type, paths=paths,
1720 lp=lp, credentials=credentials,
1721 names=names, logger=logger)
1722 elif backend_type == "existing":
1723 # If support for this is ever added back, then the URI will need to be specified again
1724 provision_backend = ExistingBackend(backend_type, paths=paths,
1725 lp=lp, credentials=credentials,
1726 names=names, logger=logger,
1727 ldap_backend_forced_uri=None)
1728 elif backend_type == "fedora-ds":
1729 provision_backend = FDSBackend(backend_type, paths=paths,
1730 lp=lp, credentials=credentials,
1731 names=names, logger=logger, domainsid=domainsid,
1732 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1733 slapd_path=slapd_path,
1735 elif backend_type == "openldap":
1736 provision_backend = OpenLDAPBackend(backend_type, paths=paths,
1737 lp=lp, credentials=credentials,
1738 names=names, logger=logger, domainsid=domainsid,
1739 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1740 slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls)
1742 raise ValueError("Unknown LDAP backend type selected")
1744 provision_backend.init()
1745 provision_backend.start()
1747 # only install a new shares config db if there is none
1748 if not os.path.exists(paths.shareconf):
1749 logger.info("Setting up share.ldb")
1750 share_ldb = Ldb(paths.shareconf, session_info=session_info, lp=lp)
1751 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1753 logger.info("Setting up secrets.ldb")
1754 secrets_ldb = setup_secretsdb(paths,
1755 session_info=session_info,
1756 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1759 logger.info("Setting up the registry")
1760 setup_registry(paths.hklm, session_info, lp=lp)
1762 logger.info("Setting up the privileges database")
1763 setup_privileges(paths.privilege, session_info, lp=lp)
1765 logger.info("Setting up idmap db")
1766 idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp)
1768 setup_name_mappings(idmap, sid=str(domainsid),
1769 root_uid=root_uid, nobody_uid=nobody_uid,
1770 users_gid=users_gid, wheel_gid=wheel_gid)
1772 logger.info("Setting up SAM db")
1773 samdb = setup_samdb(paths.samdb, session_info,
1774 provision_backend, lp, names, logger=logger,
1775 serverrole=serverrole,
1776 schema=schema, fill=samdb_fill, am_rodc=am_rodc)
1778 if serverrole == "domain controller":
1779 if paths.netlogon is None:
1780 logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1781 logger.info("Please either remove %s or see the template at %s" %
1782 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1783 assert paths.netlogon is not None
1785 if paths.sysvol is None:
1786 logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1787 " are configuring a DC.")
1788 logger.info("Please either remove %s or see the template at %s" %
1789 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1790 assert paths.sysvol is not None
1792 if not os.path.isdir(paths.netlogon):
1793 os.makedirs(paths.netlogon, 0755)
1795 if adminpass is None:
1796 adminpass = samba.generate_random_password(12, 32)
1797 adminpass_generated = True
1799 adminpass_generated = False
1801 if samdb_fill == FILL_FULL:
1802 provision_fill(samdb, secrets_ldb, logger, names, paths,
1803 schema=schema, targetdir=targetdir, samdb_fill=samdb_fill,
1804 hostip=hostip, hostip6=hostip6, domainsid=domainsid,
1805 next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
1806 krbtgtpass=krbtgtpass, domainguid=domainguid,
1807 policyguid=policyguid, policyguid_dc=policyguid_dc,
1808 invocationid=invocationid, machinepass=machinepass,
1809 ntdsguid=ntdsguid, dns_backend=dns_backend,
1810 dnspass=dnspass, serverrole=serverrole,
1811 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1814 create_krb5_conf(paths.krb5conf,
1815 dnsdomain=names.dnsdomain, hostname=names.hostname,
1817 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1818 "generated at %s", paths.krb5conf)
1820 if serverrole == "domain controller":
1821 create_dns_update_list(lp, logger, paths)
1823 backend_result = provision_backend.post_setup()
1824 provision_backend.shutdown()
1826 create_phpldapadmin_config(paths.phpldapadminconfig,
1829 secrets_ldb.transaction_cancel()
1832 # Now commit the secrets.ldb to disk
1833 secrets_ldb.transaction_commit()
1835 # the commit creates the dns.keytab, now chown it
1836 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1837 if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
1839 os.chmod(dns_keytab_path, 0640)
1840 os.chown(dns_keytab_path, -1, paths.bind_gid)
1842 if not os.environ.has_key('SAMBA_SELFTEST'):
1843 logger.info("Failed to chown %s to bind gid %u",
1844 dns_keytab_path, paths.bind_gid)
1846 result = ProvisionResult()
1847 result.server_role = serverrole
1848 result.domaindn = domaindn
1849 result.paths = paths
1850 result.names = names
1852 result.samdb = samdb
1853 result.idmap = idmap
1854 result.domainsid = str(domainsid)
1856 if samdb_fill == FILL_FULL:
1857 result.adminpass_generated = adminpass_generated
1858 result.adminpass = adminpass
1860 result.adminpass_generated = False
1861 result.adminpass = None
1863 result.backend_result = backend_result
1868 def provision_become_dc(smbconf=None, targetdir=None,
1869 realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
1870 serverdn=None, domain=None, hostname=None, domainsid=None,
1871 adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
1872 policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
1873 dns_backend=None, root=None, nobody=None, users=None, wheel=None,
1874 backup=None, serverrole=None, ldap_backend=None,
1875 ldap_backend_type=None, sitename=None, debuglevel=1):
1877 logger = logging.getLogger("provision")
1878 samba.set_debug_level(debuglevel)
1880 res = provision(logger, system_session(), None,
1881 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1882 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1883 configdn=configdn, serverdn=serverdn, domain=domain,
1884 hostname=hostname, hostip=None, domainsid=domainsid,
1885 machinepass=machinepass, serverrole="domain controller",
1886 sitename=sitename, dns_backend=dns_backend, dnspass=dnspass)
1887 res.lp.set("debuglevel", str(debuglevel))
1891 def create_phpldapadmin_config(path, ldapi_uri):
1892 """Create a PHP LDAP admin configuration file.
1894 :param path: Path to write the configuration to.
1896 setup_file(setup_path("phpldapadmin-config.php"), path,
1897 {"S4_LDAPI_URI": ldapi_uri})
1900 def create_krb5_conf(path, dnsdomain, hostname, realm):
1901 """Write out a file containing zone statements suitable for inclusion in a
1902 named.conf file (including GSS-TSIG configuration).
1904 :param path: Path of the new named.conf file.
1905 :param dnsdomain: DNS Domain name
1906 :param hostname: Local hostname
1907 :param realm: Realm name
1909 setup_file(setup_path("krb5.conf"), path, {
1910 "DNSDOMAIN": dnsdomain,
1911 "HOSTNAME": hostname,
1916 class ProvisioningError(Exception):
1917 """A generic provision error."""
1919 def __init__(self, value):
1923 return "ProvisioningError: " + self.value
1926 class InvalidNetbiosName(Exception):
1927 """A specified name was not a valid NetBIOS name."""
1929 def __init__(self, name):
1930 super(InvalidNetbiosName, self).__init__(
1931 "The name '%r' is not a valid NetBIOS name" % name)