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