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, 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 = determine_netbios_name(hostname)
599 netbiosname = hostname.upper()
601 if serverrole is None:
602 serverrole = "standalone"
604 assert serverrole in ("domain controller", "member server", "standalone")
605 if serverrole == "domain controller":
607 elif serverrole == "member server":
608 smbconfsuffix = "member"
609 elif serverrole == "standalone":
610 smbconfsuffix = "standalone"
612 if sid_generator is None:
613 sid_generator = "internal"
615 assert domain is not None
616 domain = domain.upper()
618 assert realm is not None
619 realm = realm.upper()
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 server_services_line = "server services = " + " ".join(server_services)
636 server_services_line = ""
638 if targetdir is not None:
639 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
640 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
641 statedir_line = "state directory = " + os.path.abspath(targetdir)
642 cachedir_line = "cache directory = " + os.path.abspath(targetdir)
644 lp.set("lock dir", os.path.abspath(targetdir))
645 lp.set("state directory", os.path.abspath(targetdir))
646 lp.set("cache directory", os.path.abspath(targetdir))
653 sysvol = os.path.join(lp.get("state directory"), "sysvol")
654 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
656 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
658 "NETBIOS_NAME": netbiosname,
661 "SERVERROLE": serverrole,
662 "NETLOGONPATH": netlogon,
663 "SYSVOLPATH": sysvol,
664 "PRIVATEDIR_LINE": privatedir_line,
665 "LOCKDIR_LINE": lockdir_line,
666 "STATEDIR_LINE": statedir_line,
667 "CACHEDIR_LINE": cachedir_line,
668 "SERVER_SERVICES_LINE": server_services_line
671 # reload the smb.conf
674 # and dump it without any values that are the default
675 # this ensures that any smb.conf parameters that were set
676 # on the provision/join command line are set in the resulting smb.conf
677 f = open(smbconf, mode='w')
684 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
685 users_gid, wheel_gid):
686 """setup reasonable name mappings for sam names to unix names.
688 :param samdb: SamDB object.
689 :param idmap: IDmap db object.
690 :param sid: The domain sid.
691 :param domaindn: The domain DN.
692 :param root_uid: uid of the UNIX root user.
693 :param nobody_uid: uid of the UNIX nobody user.
694 :param users_gid: gid of the UNIX users group.
695 :param wheel_gid: gid of the UNIX wheel group.
697 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
698 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
700 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
701 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
704 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
705 provision_backend, names, schema, serverrole,
707 """Setup the partitions for the SAM database.
709 Alternatively, provision() may call this, and then populate the database.
711 :note: This will wipe the Sam Database!
713 :note: This function always removes the local SAM LDB file. The erase
714 parameter controls whether to erase the existing data, which
715 may not be stored locally but in LDAP.
718 assert session_info is not None
720 # We use options=["modules:"] to stop the modules loading - we
721 # just want to wipe and re-initialise the database, not start it up
724 os.unlink(samdb_path)
728 samdb = Ldb(url=samdb_path, session_info=session_info,
729 lp=lp, options=["modules:"])
731 ldap_backend_line = "# No LDAP backend"
732 if provision_backend.type != "ldb":
733 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
735 samdb.transaction_start()
737 logger.info("Setting up sam.ldb partitions and settings")
738 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
739 "LDAP_BACKEND_LINE": ldap_backend_line
743 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
744 "BACKEND_TYPE": provision_backend.type,
745 "SERVER_ROLE": serverrole
748 logger.info("Setting up sam.ldb rootDSE")
749 setup_samdb_rootdse(samdb, names)
751 samdb.transaction_cancel()
754 samdb.transaction_commit()
757 def secretsdb_self_join(secretsdb, domain,
758 netbiosname, machinepass, domainsid=None,
759 realm=None, dnsdomain=None,
761 key_version_number=1,
762 secure_channel_type=SEC_CHAN_WKSTA):
763 """Add domain join-specific bits to a secrets database.
765 :param secretsdb: Ldb Handle to the secrets database
766 :param machinepass: Machine password
768 attrs = ["whenChanged",
775 if realm is not None:
776 if dnsdomain is None:
777 dnsdomain = realm.lower()
778 dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
781 shortname = netbiosname.lower()
783 # We don't need to set msg["flatname"] here, because rdn_name will handle
784 # it, and it causes problems for modifies anyway
785 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
786 msg["secureChannelType"] = [str(secure_channel_type)]
787 msg["objectClass"] = ["top", "primaryDomain"]
788 if dnsname is not None:
789 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
790 msg["realm"] = [realm]
791 msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
792 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
793 msg["privateKeytab"] = ["secrets.keytab"]
795 msg["secret"] = [machinepass]
796 msg["samAccountName"] = ["%s$" % netbiosname]
797 msg["secureChannelType"] = [str(secure_channel_type)]
798 if domainsid is not None:
799 msg["objectSid"] = [ndr_pack(domainsid)]
801 # This complex expression tries to ensure that we don't have more
802 # than one record for this SID, realm or netbios domain at a time,
803 # but we don't delete the old record that we are about to modify,
804 # because that would delete the keytab and previous password.
805 res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
806 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
807 scope=ldb.SCOPE_ONELEVEL)
810 secretsdb.delete(del_msg.dn)
812 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
815 msg["priorSecret"] = [res[0]["secret"][0]]
816 msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
819 msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
824 msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
830 msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
831 secretsdb.modify(msg)
832 secretsdb.rename(res[0].dn, msg.dn)
834 spn = [ 'HOST/%s' % shortname ]
835 if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
836 # we are a domain controller then we add servicePrincipalName
837 # entries for the keytab code to update.
838 spn.extend([ 'HOST/%s' % dnsname ])
839 msg["servicePrincipalName"] = spn
844 def setup_secretsdb(paths, session_info, backend_credentials, lp):
845 """Setup the secrets database.
847 :note: This function does not handle exceptions and transaction on purpose,
848 it's up to the caller to do this job.
850 :param path: Path to the secrets database.
851 :param session_info: Session info.
852 :param credentials: Credentials
853 :param lp: Loadparm context
854 :return: LDB handle for the created secrets database
856 if os.path.exists(paths.secrets):
857 os.unlink(paths.secrets)
859 keytab_path = os.path.join(paths.private_dir, paths.keytab)
860 if os.path.exists(keytab_path):
861 os.unlink(keytab_path)
863 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
864 if os.path.exists(dns_keytab_path):
865 os.unlink(dns_keytab_path)
869 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
871 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
872 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
873 secrets_ldb.transaction_start()
875 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
877 if (backend_credentials is not None and
878 backend_credentials.authentication_requested()):
879 if backend_credentials.get_bind_dn() is not None:
880 setup_add_ldif(secrets_ldb,
881 setup_path("secrets_simple_ldap.ldif"), {
882 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
883 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
886 setup_add_ldif(secrets_ldb,
887 setup_path("secrets_sasl_ldap.ldif"), {
888 "LDAPADMINUSER": backend_credentials.get_username(),
889 "LDAPADMINREALM": backend_credentials.get_realm(),
890 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
893 secrets_ldb.transaction_cancel()
898 def setup_privileges(path, session_info, lp):
899 """Setup the privileges database.
901 :param path: Path to the privileges database.
902 :param session_info: Session info.
903 :param credentials: Credentials
904 :param lp: Loadparm context
905 :return: LDB handle for the created secrets database
907 if os.path.exists(path):
909 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
910 privilege_ldb.erase()
911 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
914 def setup_registry(path, session_info, lp):
915 """Setup the registry.
917 :param path: Path to the registry database
918 :param session_info: Session information
919 :param credentials: Credentials
920 :param lp: Loadparm context
922 reg = samba.registry.Registry()
923 hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
924 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
925 provision_reg = setup_path("provision.reg")
926 assert os.path.exists(provision_reg)
927 reg.diff_apply(provision_reg)
930 def setup_idmapdb(path, session_info, lp):
931 """Setup the idmap database.
933 :param path: path to the idmap database
934 :param session_info: Session information
935 :param credentials: Credentials
936 :param lp: Loadparm context
938 if os.path.exists(path):
941 idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
943 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
947 def setup_samdb_rootdse(samdb, names):
948 """Setup the SamDB rootdse.
950 :param samdb: Sam Database handle
952 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
953 "SCHEMADN": names.schemadn,
954 "DOMAINDN": names.domaindn,
955 "ROOTDN" : names.rootdn,
956 "CONFIGDN": names.configdn,
957 "SERVERDN": names.serverdn,
961 def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
962 dnspass, domainsid, next_rid, invocationid, policyguid, policyguid_dc,
963 domainControllerFunctionality, ntdsguid=None, dc_rid=None):
964 """Join a host to its own domain."""
965 assert isinstance(invocationid, str)
966 if ntdsguid is not None:
967 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
974 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
975 "CONFIGDN": names.configdn,
976 "SCHEMADN": names.schemadn,
977 "DOMAINDN": names.domaindn,
978 "SERVERDN": names.serverdn,
979 "INVOCATIONID": invocationid,
980 "NETBIOSNAME": names.netbiosname,
981 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
982 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
983 "DOMAINSID": str(domainsid),
984 "DCRID": str(dc_rid),
985 "SAMBA_VERSION_STRING": version,
986 "NTDSGUID": ntdsguid_line,
987 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
988 domainControllerFunctionality),
989 "RIDALLOCATIONSTART": str(next_rid + 100),
990 "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
992 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
993 "POLICYGUID": policyguid,
994 "POLICYGUID_DC": policyguid_dc,
995 "DNSDOMAIN": names.dnsdomain,
996 "DOMAINDN": names.domaindn})
998 # If we are setting up a subdomain, then this has been replicated in, so we
999 # don't need to add it
1000 if fill == FILL_FULL:
1001 setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), {
1002 "CONFIGDN": names.configdn,
1003 "SCHEMADN": names.schemadn,
1004 "DOMAINDN": names.domaindn,
1005 "SERVERDN": names.serverdn,
1006 "INVOCATIONID": invocationid,
1007 "NETBIOSNAME": names.netbiosname,
1008 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
1009 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
1010 "DOMAINSID": str(domainsid),
1011 "DCRID": str(dc_rid),
1012 "SAMBA_VERSION_STRING": version,
1013 "NTDSGUID": ntdsguid_line,
1014 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
1015 domainControllerFunctionality)})
1017 # Setup fSMORoleOwner entries to point at the newly created DC entry
1018 setup_modify_ldif(samdb,
1019 setup_path("provision_self_join_modify_config.ldif"), {
1020 "CONFIGDN": names.configdn,
1021 "SCHEMADN": names.schemadn,
1022 "DEFAULTSITE": names.sitename,
1023 "NETBIOSNAME": names.netbiosname,
1024 "SERVERDN": names.serverdn,
1027 system_session_info = system_session()
1028 samdb.set_session_info(system_session_info)
1029 # Setup fSMORoleOwner entries to point at the newly created DC entry to
1030 # modify a serverReference under cn=config when we are a subdomain, we must
1031 # be system due to ACLs
1032 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1033 "DOMAINDN": names.domaindn,
1034 "SERVERDN": names.serverdn,
1035 "NETBIOSNAME": names.netbiosname,
1038 samdb.set_session_info(admin_session_info)
1040 # This is Samba4 specific and should be replaced by the correct
1041 # DNS AD-style setup
1042 setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
1043 "DNSDOMAIN": names.dnsdomain,
1044 "DOMAINDN": names.domaindn,
1045 "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
1046 "HOSTNAME" : names.hostname,
1047 "DNSNAME" : '%s.%s' % (
1048 names.netbiosname.lower(), names.dnsdomain.lower())
1052 def getpolicypath(sysvolpath, dnsdomain, guid):
1053 """Return the physical path of policy given its guid.
1055 :param sysvolpath: Path to the sysvol folder
1056 :param dnsdomain: DNS name of the AD domain
1057 :param guid: The GUID of the policy
1058 :return: A string with the complete path to the policy folder
1061 guid = "{%s}" % guid
1062 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
1066 def create_gpo_struct(policy_path):
1067 if not os.path.exists(policy_path):
1068 os.makedirs(policy_path, 0775)
1069 f = open(os.path.join(policy_path, "GPT.INI"), 'w')
1071 f.write("[General]\r\nVersion=0")
1074 p = os.path.join(policy_path, "MACHINE")
1075 if not os.path.exists(p):
1076 os.makedirs(p, 0775)
1077 p = os.path.join(policy_path, "USER")
1078 if not os.path.exists(p):
1079 os.makedirs(p, 0775)
1082 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
1083 """Create the default GPO for a domain
1085 :param sysvolpath: Physical path for the sysvol folder
1086 :param dnsdomain: DNS domain name of the AD domain
1087 :param policyguid: GUID of the default domain policy
1088 :param policyguid_dc: GUID of the default domain controler policy
1090 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
1091 create_gpo_struct(policy_path)
1093 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
1094 create_gpo_struct(policy_path)
1097 def setup_samdb(path, session_info, provision_backend, lp, names,
1098 logger, fill, serverrole, schema, am_rodc=False):
1099 """Setup a complete SAM Database.
1101 :note: This will wipe the main SAM database file!
1104 # Also wipes the database
1105 setup_samdb_partitions(path, logger=logger, lp=lp,
1106 provision_backend=provision_backend, session_info=session_info,
1107 names=names, serverrole=serverrole, schema=schema)
1109 # Load the database, but don's load the global schema and don't connect
1111 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
1112 credentials=provision_backend.credentials, lp=lp,
1113 global_schema=False, am_rodc=am_rodc)
1115 logger.info("Pre-loading the Samba 4 and AD schema")
1117 # Load the schema from the one we computed earlier
1118 samdb.set_schema(schema)
1120 # Set the NTDS settings DN manually - in order to have it already around
1121 # before the provisioned tree exists and we connect
1122 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1124 # And now we can connect to the DB - the schema won't be loaded from the
1131 def fill_samdb(samdb, lp, names,
1132 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
1133 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
1134 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
1135 next_rid=None, dc_rid=None):
1137 if next_rid is None:
1140 # Provision does not make much sense values larger than 1000000000
1141 # as the upper range of the rIDAvailablePool is 1073741823 and
1142 # we don't want to create a domain that cannot allocate rids.
1143 if next_rid < 1000 or next_rid > 1000000000:
1144 error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
1145 error += "the valid range is %u-%u. The default is %u." % (
1146 1000, 1000000000, 1000)
1147 raise ProvisioningError(error)
1149 # ATTENTION: Do NOT change these default values without discussion with the
1150 # team and/or release manager. They have a big impact on the whole program!
1151 domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
1153 if dom_for_fun_level is None:
1154 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1156 if dom_for_fun_level > domainControllerFunctionality:
1157 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!")
1159 domainFunctionality = dom_for_fun_level
1160 forestFunctionality = dom_for_fun_level
1162 # Set the NTDS settings DN manually - in order to have it already around
1163 # before the provisioned tree exists and we connect
1164 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1166 samdb.transaction_start()
1168 # Set the domain functionality levels onto the database.
1169 # Various module (the password_hash module in particular) need
1170 # to know what level of AD we are emulating.
1172 # These will be fixed into the database via the database
1173 # modifictions below, but we need them set from the start.
1174 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1175 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1176 samdb.set_opaque_integer("domainControllerFunctionality",
1177 domainControllerFunctionality)
1179 samdb.set_domain_sid(str(domainsid))
1180 samdb.set_invocation_id(invocationid)
1182 logger.info("Adding DomainDN: %s" % names.domaindn)
1184 # impersonate domain admin
1185 admin_session_info = admin_session(lp, str(domainsid))
1186 samdb.set_session_info(admin_session_info)
1187 if domainguid is not None:
1188 domainguid_line = "objectGUID: %s\n-" % domainguid
1190 domainguid_line = ""
1192 descr = b64encode(get_domain_descriptor(domainsid))
1193 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1194 "DOMAINDN": names.domaindn,
1195 "DOMAINSID": str(domainsid),
1196 "DESCRIPTOR": descr,
1197 "DOMAINGUID": domainguid_line
1200 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1201 "DOMAINDN": names.domaindn,
1202 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1203 "NEXTRID": str(next_rid),
1204 "DEFAULTSITE": names.sitename,
1205 "CONFIGDN": names.configdn,
1206 "POLICYGUID": policyguid,
1207 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1208 "SAMBA_VERSION_STRING": version
1211 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1212 if fill == FILL_FULL:
1213 logger.info("Adding configuration container")
1214 descr = b64encode(get_config_descriptor(domainsid))
1215 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1216 "CONFIGDN": names.configdn,
1217 "DESCRIPTOR": descr,
1220 # The LDIF here was created when the Schema object was constructed
1221 logger.info("Setting up sam.ldb schema")
1222 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1223 samdb.modify_ldif(schema.schema_dn_modify)
1224 samdb.write_prefixes_from_schema()
1225 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1226 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1227 {"SCHEMADN": names.schemadn})
1229 # Now register this container in the root of the forest
1230 msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
1231 msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
1235 samdb.transaction_cancel()
1238 samdb.transaction_commit()
1240 samdb.transaction_start()
1242 samdb.invocation_id = invocationid
1244 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1245 if fill == FILL_FULL:
1246 logger.info("Setting up sam.ldb configuration data")
1247 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1248 "CONFIGDN": names.configdn,
1249 "NETBIOSNAME": names.netbiosname,
1250 "DEFAULTSITE": names.sitename,
1251 "DNSDOMAIN": names.dnsdomain,
1252 "DOMAIN": names.domain,
1253 "SCHEMADN": names.schemadn,
1254 "DOMAINDN": names.domaindn,
1255 "SERVERDN": names.serverdn,
1256 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1257 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1260 logger.info("Setting up display specifiers")
1261 display_specifiers_ldif = read_ms_ldif(
1262 setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1263 display_specifiers_ldif = substitute_var(display_specifiers_ldif,
1264 {"CONFIGDN": names.configdn})
1265 check_all_substituted(display_specifiers_ldif)
1266 samdb.add_ldif(display_specifiers_ldif)
1268 logger.info("Adding users container")
1269 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1270 "DOMAINDN": names.domaindn})
1271 logger.info("Modifying users container")
1272 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1273 "DOMAINDN": names.domaindn})
1274 logger.info("Adding computers container")
1275 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1276 "DOMAINDN": names.domaindn})
1277 logger.info("Modifying computers container")
1278 setup_modify_ldif(samdb,
1279 setup_path("provision_computers_modify.ldif"), {
1280 "DOMAINDN": names.domaindn})
1281 logger.info("Setting up sam.ldb data")
1282 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1283 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1284 "DOMAINDN": names.domaindn,
1285 "NETBIOSNAME": names.netbiosname,
1286 "DEFAULTSITE": names.sitename,
1287 "CONFIGDN": names.configdn,
1288 "SERVERDN": names.serverdn,
1289 "RIDAVAILABLESTART": str(next_rid + 600),
1290 "POLICYGUID_DC": policyguid_dc
1293 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1294 if fill == FILL_FULL:
1295 setup_modify_ldif(samdb,
1296 setup_path("provision_configuration_references.ldif"), {
1297 "CONFIGDN": names.configdn,
1298 "SCHEMADN": names.schemadn})
1300 logger.info("Setting up well known security principals")
1301 setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
1302 "CONFIGDN": names.configdn,
1305 if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
1306 setup_modify_ldif(samdb,
1307 setup_path("provision_basedn_references.ldif"),
1308 {"DOMAINDN": names.domaindn})
1310 logger.info("Setting up sam.ldb users and groups")
1311 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1312 "DOMAINDN": names.domaindn,
1313 "DOMAINSID": str(domainsid),
1314 "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
1315 "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
1318 logger.info("Setting up self join")
1319 setup_self_join(samdb, admin_session_info, names=names, fill=fill,
1320 invocationid=invocationid,
1322 machinepass=machinepass,
1323 domainsid=domainsid,
1326 policyguid=policyguid,
1327 policyguid_dc=policyguid_dc,
1328 domainControllerFunctionality=domainControllerFunctionality,
1331 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1332 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1333 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1334 assert isinstance(names.ntdsguid, str)
1336 samdb.transaction_cancel()
1339 samdb.transaction_commit()
1344 FILL_SUBDOMAIN = "SUBDOMAIN"
1345 FILL_NT4SYNC = "NT4SYNC"
1347 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1348 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)"
1351 def set_dir_acl(path, acl, lp, domsid):
1352 setntacl(lp, path, acl, domsid)
1353 for root, dirs, files in os.walk(path, topdown=False):
1355 setntacl(lp, os.path.join(root, name), acl, domsid)
1357 setntacl(lp, os.path.join(root, name), acl, domsid)
1360 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1361 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1364 :param sysvol: Physical path for the sysvol folder
1365 :param dnsdomain: The DNS name of the domain
1366 :param domainsid: The SID of the domain
1367 :param domaindn: The DN of the domain (ie. DC=...)
1368 :param samdb: An LDB object on the SAM db
1369 :param lp: an LP object
1372 # Set ACL for GPO root folder
1373 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1374 setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
1376 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1377 attrs=["cn", "nTSecurityDescriptor"],
1378 expression="", scope=ldb.SCOPE_ONELEVEL)
1381 acl = ndr_unpack(security.descriptor,
1382 str(policy["nTSecurityDescriptor"])).as_sddl()
1383 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1384 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1388 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1390 """Set the ACL for the sysvol share and the subfolders
1392 :param samdb: An LDB object on the SAM db
1393 :param netlogon: Physical path for the netlogon folder
1394 :param sysvol: Physical path for the sysvol folder
1395 :param gid: The GID of the "Domain adminstrators" group
1396 :param domainsid: The SID of the domain
1397 :param dnsdomain: The DNS name of the domain
1398 :param domaindn: The DN of the domain (ie. DC=...)
1402 os.chown(sysvol, -1, gid)
1408 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1409 setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
1410 for root, dirs, files in os.walk(sysvol, topdown=False):
1413 os.chown(os.path.join(root, name), -1, gid)
1414 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1417 os.chown(os.path.join(root, name), -1, gid)
1418 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1420 # Set acls on Policy folder and policies folders
1421 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
1424 def interface_ips_v4(lp):
1425 '''return only IPv4 IPs'''
1426 ips = samba.interface_ips(lp, False)
1429 if i.find(':') == -1:
1433 def interface_ips_v6(lp, linklocal=False):
1434 '''return only IPv6 IPs'''
1435 ips = samba.interface_ips(lp, False)
1438 if i.find(':') != -1 and (linklocal or i.find('%') == -1):
1443 def provision_fill(samdb, secrets_ldb, logger, names, paths,
1444 domainsid, schema=None,
1445 targetdir=None, samdb_fill=FILL_FULL,
1446 hostip=None, hostip6=None,
1447 next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
1448 domainguid=None, policyguid=None, policyguid_dc=None,
1449 invocationid=None, machinepass=None, ntdsguid=None,
1450 dns_backend=None, dnspass=None,
1451 serverrole=None, dom_for_fun_level=None,
1452 am_rodc=False, lp=None):
1453 # create/adapt the group policy GUIDs
1454 # Default GUID for default policy are described at
1455 # "How Core Group Policy Works"
1456 # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
1457 if policyguid is None:
1458 policyguid = DEFAULT_POLICY_GUID
1459 policyguid = policyguid.upper()
1460 if policyguid_dc is None:
1461 policyguid_dc = DEFAULT_DC_POLICY_GUID
1462 policyguid_dc = policyguid_dc.upper()
1464 if invocationid is None:
1465 invocationid = str(uuid.uuid4())
1467 if krbtgtpass is None:
1468 krbtgtpass = samba.generate_random_password(128, 255)
1469 if machinepass is None:
1470 machinepass = samba.generate_random_password(128, 255)
1472 dnspass = samba.generate_random_password(128, 255)
1474 samdb = fill_samdb(samdb, lp, names, logger=logger,
1475 domainsid=domainsid, schema=schema, domainguid=domainguid,
1476 policyguid=policyguid, policyguid_dc=policyguid_dc,
1477 fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
1478 invocationid=invocationid, machinepass=machinepass,
1479 dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
1480 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1481 next_rid=next_rid, dc_rid=dc_rid)
1483 if serverrole == "domain controller":
1484 # Set up group policies (domain policy and domain controller
1486 create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
1488 setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
1489 domainsid, names.dnsdomain, names.domaindn, lp)
1491 secretsdb_self_join(secrets_ldb, domain=names.domain,
1492 realm=names.realm, dnsdomain=names.dnsdomain,
1493 netbiosname=names.netbiosname, domainsid=domainsid,
1494 machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
1496 # Now set up the right msDS-SupportedEncryptionTypes into the DB
1497 # In future, this might be determined from some configuration
1498 kerberos_enctypes = str(ENC_ALL_TYPES)
1501 msg = ldb.Message(ldb.Dn(samdb,
1502 samdb.searchone("distinguishedName",
1503 expression="samAccountName=%s$" % names.netbiosname,
1504 scope=ldb.SCOPE_SUBTREE)))
1505 msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
1506 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
1507 name="msDS-SupportedEncryptionTypes")
1509 except ldb.LdbError, (enum, estr):
1510 if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
1511 # It might be that this attribute does not exist in this schema
1514 setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
1515 hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
1516 dnspass=dnspass, os_level=dom_for_fun_level,
1517 targetdir=targetdir, site=DEFAULTSITE)
1519 domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
1520 attribute="objectGUID")
1521 assert isinstance(domainguid, str)
1523 lastProvisionUSNs = get_last_provision_usn(samdb)
1524 maxUSN = get_max_usn(samdb, str(names.rootdn))
1525 if lastProvisionUSNs is not None:
1526 update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
1528 set_provision_usn(samdb, 0, maxUSN, invocationid)
1530 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1531 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
1532 { 'NTDSGUID' : names.ntdsguid })
1534 # fix any dangling GUIDs from the provision
1535 logger.info("Fixing provision GUIDs")
1536 chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True,
1538 samdb.transaction_start()
1540 # a small number of GUIDs are missing because of ordering issues in the
1542 for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
1543 chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
1544 scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
1545 chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
1546 scope=ldb.SCOPE_ONELEVEL,
1547 attrs=['ipsecOwnersReference',
1548 'ipsecFilterReference',
1549 'ipsecISAKMPReference',
1550 'ipsecNegotiationPolicyReference',
1551 'ipsecNFAReference'])
1553 samdb.transaction_cancel()
1556 samdb.transaction_commit()
1560 "ROLE_STANDALONE": "standalone",
1561 "ROLE_DOMAIN_MEMBER": "member server",
1562 "ROLE_DOMAIN_BDC": "domain controller",
1563 "ROLE_DOMAIN_PDC": "domain controller",
1564 "dc": "domain controller",
1565 "member": "member server",
1566 "domain controller": "domain controller",
1567 "member server": "member server",
1568 "standalone": "standalone",
1572 def sanitize_server_role(role):
1573 """Sanitize a server role name.
1575 :param role: Server role
1576 :raise ValueError: If the role can not be interpreted
1577 :return: Sanitized server role (one of "member server",
1578 "domain controller", "standalone")
1581 return _ROLES_MAP[role]
1583 raise ValueError(role)
1586 def provision(logger, session_info, credentials, smbconf=None,
1587 targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
1588 domaindn=None, schemadn=None, configdn=None, serverdn=None,
1589 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
1590 next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None,
1591 domainguid=None, policyguid=None, policyguid_dc=None,
1592 dns_backend=None, dnspass=None,
1593 invocationid=None, machinepass=None, ntdsguid=None,
1594 root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
1595 serverrole=None, dom_for_fun_level=None,
1596 backend_type=None, sitename=None,
1597 ol_mmr_urls=None, ol_olc=None, slapd_path=None,
1598 useeadb=False, am_rodc=False,
1602 :note: caution, this wipes all existing data!
1606 serverrole = sanitize_server_role(serverrole)
1608 raise ProvisioningError('server role (%s) should be one of "domain controller", "member server", "standalone"' % serverrole)
1610 if ldapadminpass is None:
1611 # Make a new, random password between Samba and it's LDAP server
1612 ldapadminpass = samba.generate_random_password(128, 255)
1614 if backend_type is None:
1615 backend_type = "ldb"
1617 if domainsid is None:
1618 domainsid = security.random_sid()
1620 domainsid = security.dom_sid(domainsid)
1622 sid_generator = "internal"
1623 if backend_type == "fedora-ds":
1624 sid_generator = "backend"
1626 root_uid = findnss_uid([root or "root"])
1627 nobody_uid = findnss_uid([nobody or "nobody"])
1628 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1630 wheel_gid = findnss_gid(["wheel", "adm"])
1632 wheel_gid = findnss_gid([wheel])
1634 bind_gid = findnss_gid(["bind", "named"])
1638 if targetdir is not None:
1639 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1640 elif smbconf is None:
1641 smbconf = samba.param.default_path()
1642 if not os.path.exists(os.path.dirname(smbconf)):
1643 os.makedirs(os.path.dirname(smbconf))
1645 server_services = None
1646 if dns_backend == "SAMBA_INTERNAL":
1647 server_services = ["+dns"]
1649 # only install a new smb.conf if there isn't one there already
1650 if os.path.exists(smbconf):
1651 # if Samba Team members can't figure out the weird errors
1652 # loading an empty smb.conf gives, then we need to be smarter.
1653 # Pretend it just didn't exist --abartlet
1654 f = open(smbconf, 'r')
1656 data = f.read().lstrip()
1659 if data is None or data == "":
1660 make_smbconf(smbconf, hostname, domain, realm,
1661 serverrole, targetdir, sid_generator, useeadb,
1662 lp=lp, server_services=server_services)
1664 make_smbconf(smbconf, hostname, domain, realm, serverrole,
1665 targetdir, sid_generator, useeadb, lp=lp,
1666 server_services=server_services)
1669 lp = samba.param.LoadParm()
1671 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1672 dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
1673 configdn=configdn, schemadn=schemadn, serverdn=serverdn,
1674 sitename=sitename, rootdn=rootdn)
1675 paths = provision_paths_from_lp(lp, names.dnsdomain)
1677 paths.bind_gid = bind_gid
1678 paths.wheel_gid = wheel_gid
1681 logger.info("Looking up IPv4 addresses")
1682 hostips = interface_ips_v4(lp)
1683 if len(hostips) > 0:
1685 if len(hostips) > 1:
1686 logger.warning("More than one IPv4 address found. Using %s",
1688 if hostip == "127.0.0.1":
1691 logger.warning("No IPv4 address will be assigned")
1694 logger.info("Looking up IPv6 addresses")
1695 hostips = interface_ips_v6(lp, linklocal=False)
1697 hostip6 = hostips[0]
1698 if len(hostips) > 1:
1699 logger.warning("More than one IPv6 address found. Using %s", hostip6)
1701 logger.warning("No IPv6 address will be assigned")
1703 names.hostip = hostip
1704 names.hostip6 = hostip6
1706 if serverrole is None:
1707 serverrole = lp.get("server role")
1709 if not os.path.exists(paths.private_dir):
1710 os.mkdir(paths.private_dir)
1711 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1712 os.mkdir(os.path.join(paths.private_dir, "tls"))
1714 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1716 schema = Schema(domainsid, invocationid=invocationid,
1717 schemadn=names.schemadn)
1719 if backend_type == "ldb":
1720 provision_backend = LDBBackend(backend_type, paths=paths,
1721 lp=lp, credentials=credentials,
1722 names=names, logger=logger)
1723 elif backend_type == "existing":
1724 # If support for this is ever added back, then the URI will need to be specified again
1725 provision_backend = ExistingBackend(backend_type, paths=paths,
1726 lp=lp, credentials=credentials,
1727 names=names, logger=logger,
1728 ldap_backend_forced_uri=None)
1729 elif backend_type == "fedora-ds":
1730 provision_backend = FDSBackend(backend_type, paths=paths,
1731 lp=lp, credentials=credentials,
1732 names=names, logger=logger, domainsid=domainsid,
1733 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1734 slapd_path=slapd_path,
1736 elif backend_type == "openldap":
1737 provision_backend = OpenLDAPBackend(backend_type, paths=paths,
1738 lp=lp, credentials=credentials,
1739 names=names, logger=logger, domainsid=domainsid,
1740 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1741 slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls)
1743 raise ValueError("Unknown LDAP backend type selected")
1745 provision_backend.init()
1746 provision_backend.start()
1748 # only install a new shares config db if there is none
1749 if not os.path.exists(paths.shareconf):
1750 logger.info("Setting up share.ldb")
1751 share_ldb = Ldb(paths.shareconf, session_info=session_info, lp=lp)
1752 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1754 logger.info("Setting up secrets.ldb")
1755 secrets_ldb = setup_secretsdb(paths,
1756 session_info=session_info,
1757 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1760 logger.info("Setting up the registry")
1761 setup_registry(paths.hklm, session_info, lp=lp)
1763 logger.info("Setting up the privileges database")
1764 setup_privileges(paths.privilege, session_info, lp=lp)
1766 logger.info("Setting up idmap db")
1767 idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp)
1769 setup_name_mappings(idmap, sid=str(domainsid),
1770 root_uid=root_uid, nobody_uid=nobody_uid,
1771 users_gid=users_gid, wheel_gid=wheel_gid)
1773 logger.info("Setting up SAM db")
1774 samdb = setup_samdb(paths.samdb, session_info,
1775 provision_backend, lp, names, logger=logger,
1776 serverrole=serverrole,
1777 schema=schema, fill=samdb_fill, am_rodc=am_rodc)
1779 if serverrole == "domain controller":
1780 if paths.netlogon is None:
1781 raise MissingShareError("netlogon", paths.smbconf,
1782 setup_path("provision.smb.conf.dc"))
1784 if paths.sysvol is None:
1785 raise MissingShareError("sysvol", paths.smbconf,
1786 setup_path("provision.smb.conf.dc"))
1788 if not os.path.isdir(paths.netlogon):
1789 os.makedirs(paths.netlogon, 0755)
1791 if adminpass is None:
1792 adminpass = samba.generate_random_password(12, 32)
1793 adminpass_generated = True
1795 adminpass_generated = False
1797 if samdb_fill == FILL_FULL:
1798 provision_fill(samdb, secrets_ldb, logger, names, paths,
1799 schema=schema, targetdir=targetdir, samdb_fill=samdb_fill,
1800 hostip=hostip, hostip6=hostip6, domainsid=domainsid,
1801 next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
1802 krbtgtpass=krbtgtpass, domainguid=domainguid,
1803 policyguid=policyguid, policyguid_dc=policyguid_dc,
1804 invocationid=invocationid, machinepass=machinepass,
1805 ntdsguid=ntdsguid, dns_backend=dns_backend,
1806 dnspass=dnspass, serverrole=serverrole,
1807 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1810 create_krb5_conf(paths.krb5conf,
1811 dnsdomain=names.dnsdomain, hostname=names.hostname,
1813 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1814 "generated at %s", paths.krb5conf)
1816 if serverrole == "domain controller":
1817 create_dns_update_list(lp, logger, paths)
1819 backend_result = provision_backend.post_setup()
1820 provision_backend.shutdown()
1822 create_phpldapadmin_config(paths.phpldapadminconfig,
1825 secrets_ldb.transaction_cancel()
1828 # Now commit the secrets.ldb to disk
1829 secrets_ldb.transaction_commit()
1831 # the commit creates the dns.keytab, now chown it
1832 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1833 if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
1835 os.chmod(dns_keytab_path, 0640)
1836 os.chown(dns_keytab_path, -1, paths.bind_gid)
1838 if not os.environ.has_key('SAMBA_SELFTEST'):
1839 logger.info("Failed to chown %s to bind gid %u",
1840 dns_keytab_path, paths.bind_gid)
1842 result = ProvisionResult()
1843 result.server_role = serverrole
1844 result.domaindn = domaindn
1845 result.paths = paths
1846 result.names = names
1848 result.samdb = samdb
1849 result.idmap = idmap
1850 result.domainsid = str(domainsid)
1852 if samdb_fill == FILL_FULL:
1853 result.adminpass_generated = adminpass_generated
1854 result.adminpass = adminpass
1856 result.adminpass_generated = False
1857 result.adminpass = None
1859 result.backend_result = backend_result
1864 def provision_become_dc(smbconf=None, targetdir=None,
1865 realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
1866 serverdn=None, domain=None, hostname=None, domainsid=None,
1867 adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
1868 policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
1869 dns_backend=None, root=None, nobody=None, users=None, wheel=None,
1870 backup=None, serverrole=None, ldap_backend=None,
1871 ldap_backend_type=None, sitename=None, debuglevel=1):
1873 logger = logging.getLogger("provision")
1874 samba.set_debug_level(debuglevel)
1876 res = provision(logger, system_session(), None,
1877 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1878 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1879 configdn=configdn, serverdn=serverdn, domain=domain,
1880 hostname=hostname, hostip=None, domainsid=domainsid,
1881 machinepass=machinepass, serverrole="domain controller",
1882 sitename=sitename, dns_backend=dns_backend, dnspass=dnspass)
1883 res.lp.set("debuglevel", str(debuglevel))
1887 def create_phpldapadmin_config(path, ldapi_uri):
1888 """Create a PHP LDAP admin configuration file.
1890 :param path: Path to write the configuration to.
1892 setup_file(setup_path("phpldapadmin-config.php"), path,
1893 {"S4_LDAPI_URI": ldapi_uri})
1896 def create_krb5_conf(path, dnsdomain, hostname, realm):
1897 """Write out a file containing zone statements suitable for inclusion in a
1898 named.conf file (including GSS-TSIG configuration).
1900 :param path: Path of the new named.conf file.
1901 :param dnsdomain: DNS Domain name
1902 :param hostname: Local hostname
1903 :param realm: Realm name
1905 setup_file(setup_path("krb5.conf"), path, {
1906 "DNSDOMAIN": dnsdomain,
1907 "HOSTNAME": hostname,
1912 class ProvisioningError(Exception):
1913 """A generic provision error."""
1915 def __init__(self, value):
1919 return "ProvisioningError: " + self.value
1922 class InvalidNetbiosName(Exception):
1923 """A specified name was not a valid NetBIOS name."""
1925 def __init__(self, name):
1926 super(InvalidNetbiosName, self).__init__(
1927 "The name '%r' is not a valid NetBIOS name" % name)
1930 class MissingShareError(ProvisioningError):
1932 def __init__(self, name, smbconf, smbconf_template):
1933 super(MissingShareError, self).__init__(
1934 "Existing smb.conf does not have a [%s] share, but you are "
1935 "configuring a DC. Please either remove %s or see the template "
1936 "at %s" % (name, smbconf, smbconf_template))