2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 """Functions for setting up a Samba configuration."""
28 __docformat__ = "restructuredText"
30 from base64 import b64encode
44 from samba.auth import system_session, admin_session
46 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
49 check_all_substituted,
55 from samba.dcerpc import security, misc
56 from samba.dcerpc.misc import (
60 from samba.dsdb import (
61 DS_DOMAIN_FUNCTION_2003,
62 DS_DOMAIN_FUNCTION_2008_R2,
65 from samba.idmap import IDmapDB
66 from samba.ms_display_specifiers import read_ms_ldif
67 from samba.ntacls import setntacl, dsacl2fsacl
68 from samba.ndr import ndr_pack, ndr_unpack
69 from samba.provision.backend import (
75 from samba.provision.descriptor import (
76 get_config_descriptor,
79 from samba.provision.common import (
84 from samba.provision.sambadns import (
86 create_dns_update_list
91 from samba.schema import Schema
92 from samba.samdb import SamDB
93 from samba.dbchecker import dbcheck
96 VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~"
97 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
98 DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
99 DEFAULTSITE = "Default-First-Site-Name"
100 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
103 class ProvisionPaths(object):
106 self.shareconf = None
117 self.dns_keytab = None
120 self.private_dir = None
123 class ProvisionNames(object):
130 self.ldapmanagerdn = None
131 self.dnsdomain = None
133 self.netbiosname = None
139 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
140 """Get key provision parameters (realm, domain, ...) from a given provision
142 :param samdb: An LDB object connected to the sam.ldb file
143 :param secretsdb: An LDB object connected to the secrets.ldb file
144 :param idmapdb: An LDB object connected to the idmap.ldb file
145 :param paths: A list of path to provision object
146 :param smbconf: Path to the smb.conf file
147 :param lp: A LoadParm object
148 :return: A list of key provision parameters
150 names = ProvisionNames()
151 names.adminpass = None
153 # NT domain, kerberos realm, root dn, domain dn, domain dns name
154 names.domain = string.upper(lp.get("workgroup"))
155 names.realm = lp.get("realm")
156 names.dnsdomain = names.realm.lower()
157 basedn = samba.dn_from_dns_name(names.dnsdomain)
158 names.realm = string.upper(names.realm)
160 # Get the netbiosname first (could be obtained from smb.conf in theory)
161 res = secretsdb.search(expression="(flatname=%s)" %
162 names.domain,base="CN=Primary Domains",
163 scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
164 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
166 names.smbconf = smbconf
168 # That's a bit simplistic but it's ok as long as we have only 3
170 current = samdb.search(expression="(objectClass=*)",
171 base="", scope=ldb.SCOPE_BASE,
172 attrs=["defaultNamingContext", "schemaNamingContext",
173 "configurationNamingContext","rootDomainNamingContext"])
175 names.configdn = current[0]["configurationNamingContext"]
176 configdn = str(names.configdn)
177 names.schemadn = current[0]["schemaNamingContext"]
178 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
179 current[0]["defaultNamingContext"][0]))):
180 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
181 "is not the same ..." % (paths.samdb,
182 str(current[0]["defaultNamingContext"][0]),
183 paths.smbconf, basedn)))
185 names.domaindn=current[0]["defaultNamingContext"]
186 names.rootdn=current[0]["rootDomainNamingContext"]
188 res3 = samdb.search(expression="(objectClass=site)",
189 base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
190 names.sitename = str(res3[0]["cn"])
192 # dns hostname and server dn
193 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
194 base="OU=Domain Controllers,%s" % basedn,
195 scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
196 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
198 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
199 attrs=[], base=configdn)
200 names.serverdn = server_res[0].dn
202 # invocation id/objectguid
203 res5 = samdb.search(expression="(objectClass=*)",
204 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=ldb.SCOPE_BASE,
205 attrs=["invocationID", "objectGUID"])
206 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
207 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
210 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
211 scope=ldb.SCOPE_BASE, attrs=["objectGUID",
212 "objectSid","msDS-Behavior-Version" ])
213 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
214 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
215 if res6[0].get("msDS-Behavior-Version") is None or \
216 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
217 names.domainlevel = DS_DOMAIN_FUNCTION_2000
219 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
222 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
223 base="CN=Policies,CN=System," + basedn,
224 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
225 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
227 res8 = samdb.search(expression="(displayName=Default Domain Controllers"
229 base="CN=Policies,CN=System," + basedn,
230 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
232 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
234 names.policyid_dc = None
235 res9 = idmapdb.search(expression="(cn=%s)" %
236 (security.SID_BUILTIN_ADMINISTRATORS),
239 names.wheel_gid = res9[0]["xidNumber"]
241 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
245 def update_provision_usn(samdb, low, high, id, replace=False):
246 """Update the field provisionUSN in sam.ldb
248 This field is used to track range of USN modified by provision and
250 This value is used afterward by next provision to figure out if
251 the field have been modified since last provision.
253 :param samdb: An LDB object connect to sam.ldb
254 :param low: The lowest USN modified by this upgrade
255 :param high: The highest USN modified by this upgrade
256 :param id: The invocation id of the samba's dc
257 :param replace: A boolean indicating if the range should replace any
258 existing one or appended (default)
263 entry = samdb.search(base="@PROVISION",
264 scope=ldb.SCOPE_BASE,
265 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
266 for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
267 if not re.search(';', e):
268 e = "%s;%s" % (e, id)
271 tab.append("%s-%s;%s" % (low, high, id))
272 delta = ldb.Message()
273 delta.dn = ldb.Dn(samdb, "@PROVISION")
274 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
275 ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
276 entry = samdb.search(expression='provisionnerID=*',
277 base="@PROVISION", scope=ldb.SCOPE_BASE,
278 attrs=["provisionnerID"])
279 if len(entry) == 0 or len(entry[0]) == 0:
280 delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
284 def set_provision_usn(samdb, low, high, id):
285 """Set the field provisionUSN in sam.ldb
286 This field is used to track range of USN modified by provision and
288 This value is used afterward by next provision to figure out if
289 the field have been modified since last provision.
291 :param samdb: An LDB object connect to sam.ldb
292 :param low: The lowest USN modified by this upgrade
293 :param high: The highest USN modified by this upgrade
294 :param id: The invocationId of the provision"""
297 tab.append("%s-%s;%s" % (low, high, id))
299 delta = ldb.Message()
300 delta.dn = ldb.Dn(samdb, "@PROVISION")
301 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
302 ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
306 def get_max_usn(samdb,basedn):
307 """ This function return the biggest USN present in the provision
309 :param samdb: A LDB object pointing to the sam.ldb
310 :param basedn: A string containing the base DN of the provision
312 :return: The biggest USN in the provision"""
314 res = samdb.search(expression="objectClass=*",base=basedn,
315 scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
316 controls=["search_options:1:2",
317 "server_sort:1:1:uSNChanged",
318 "paged_results:1:1"])
319 return res[0]["uSNChanged"]
322 def get_last_provision_usn(sam):
323 """Get USNs ranges modified by a provision or an upgradeprovision
325 :param sam: An LDB object pointing to the sam.ldb
326 :return: a dictionnary which keys are invocation id and values are an array
327 of integer representing the different ranges
330 entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
331 base="@PROVISION", scope=ldb.SCOPE_BASE,
332 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
333 except ldb.LdbError, (ecode, emsg):
334 if ecode == ldb.ERR_NO_SUCH_OBJECT:
341 if entry[0].get("provisionnerID"):
342 for e in entry[0]["provisionnerID"]:
344 for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
345 tab1 = str(r).split(';')
350 if (len(myids) > 0 and id not in myids):
352 tab2 = p.split(tab1[0])
353 if range.get(id) == None:
355 range[id].append(tab2[0])
356 range[id].append(tab2[1])
362 class ProvisionResult(object):
373 def check_install(lp, session_info, credentials):
374 """Check whether the current install seems ok.
376 :param lp: Loadparm context
377 :param session_info: Session information
378 :param credentials: Credentials
380 if lp.get("realm") == "":
381 raise Exception("Realm empty")
382 samdb = Ldb(lp.samdb_url(), session_info=session_info,
383 credentials=credentials, lp=lp)
384 if len(samdb.search("(cn=Administrator)")) != 1:
385 raise ProvisioningError("No administrator account found")
388 def findnss(nssfn, names):
389 """Find a user or group from a list of possibilities.
391 :param nssfn: NSS Function to try (should raise KeyError if not found)
392 :param names: Names to check.
393 :return: Value return by first names list.
400 raise KeyError("Unable to find user/group in %r" % names)
403 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
404 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
407 def provision_paths_from_lp(lp, dnsdomain):
408 """Set the default paths for provisioning.
410 :param lp: Loadparm context.
411 :param dnsdomain: DNS Domain name
413 paths = ProvisionPaths()
414 paths.private_dir = lp.get("private dir")
416 # This is stored without path prefix for the "privateKeytab" attribute in
417 # "secrets_dns.ldif".
418 paths.dns_keytab = "dns.keytab"
419 paths.keytab = "secrets.keytab"
421 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
422 paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
423 paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
424 paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
425 paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
426 paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
427 paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
428 paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
429 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
430 paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
431 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
432 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
433 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
434 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
435 paths.phpldapadminconfig = os.path.join(paths.private_dir,
436 "phpldapadmin-config.php")
437 paths.hklm = "hklm.ldb"
438 paths.hkcr = "hkcr.ldb"
439 paths.hkcu = "hkcu.ldb"
440 paths.hku = "hku.ldb"
441 paths.hkpd = "hkpd.ldb"
442 paths.hkpt = "hkpt.ldb"
443 paths.sysvol = lp.get("path", "sysvol")
444 paths.netlogon = lp.get("path", "netlogon")
445 paths.smbconf = lp.configfile
449 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
450 serverrole=None, rootdn=None, domaindn=None, configdn=None,
451 schemadn=None, serverdn=None, sitename=None):
452 """Guess configuration settings to use."""
455 hostname = socket.gethostname().split(".")[0]
457 netbiosname = lp.get("netbios name")
458 if netbiosname is None:
459 netbiosname = hostname
460 # remove forbidden chars
462 for x in netbiosname:
463 if x.isalnum() or x in VALID_NETBIOS_CHARS:
464 newnbname = "%s%c" % (newnbname, x)
465 # force the length to be <16
466 netbiosname = newnbname[0:15]
467 assert netbiosname is not None
468 netbiosname = netbiosname.upper()
469 if not valid_netbios_name(netbiosname):
470 raise InvalidNetbiosName(netbiosname)
472 if dnsdomain is None:
473 dnsdomain = lp.get("realm")
474 if dnsdomain is None or dnsdomain == "":
475 raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
477 dnsdomain = dnsdomain.lower()
479 if serverrole is None:
480 serverrole = lp.get("server role")
481 if serverrole is None:
482 raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
484 serverrole = serverrole.lower()
486 realm = dnsdomain.upper()
488 if lp.get("realm") == "":
489 raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile)
491 if lp.get("realm").upper() != realm:
492 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))
494 if lp.get("server role").lower() != serverrole:
495 raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role"), serverrole, lp.configfile))
497 if serverrole == "domain controller":
499 # This will, for better or worse, default to 'WORKGROUP'
500 domain = lp.get("workgroup")
501 domain = domain.upper()
503 if lp.get("workgroup").upper() != domain:
504 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))
507 domaindn = samba.dn_from_dns_name(dnsdomain)
509 if domain == netbiosname:
510 raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
514 domaindn = "DC=" + netbiosname
516 if not valid_netbios_name(domain):
517 raise InvalidNetbiosName(domain)
519 if hostname.upper() == realm:
520 raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
521 if netbiosname.upper() == realm:
522 raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
524 raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
530 configdn = "CN=Configuration," + rootdn
532 schemadn = "CN=Schema," + configdn
537 names = ProvisionNames()
538 names.rootdn = rootdn
539 names.domaindn = domaindn
540 names.configdn = configdn
541 names.schemadn = schemadn
542 names.ldapmanagerdn = "CN=Manager," + rootdn
543 names.dnsdomain = dnsdomain
544 names.domain = domain
546 names.netbiosname = netbiosname
547 names.hostname = hostname
548 names.sitename = sitename
549 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
550 netbiosname, sitename, configdn)
555 def make_smbconf(smbconf, hostname, domain, realm, serverrole,
556 targetdir, sid_generator="internal", eadb=False, lp=None,
557 server_services=None):
558 """Create a new smb.conf file based on a couple of basic settings.
560 assert smbconf is not None
562 hostname = socket.gethostname().split(".")[0]
563 netbiosname = hostname.upper()
564 # remove forbidden chars
566 for x in netbiosname:
567 if x.isalnum() or x in VALID_NETBIOS_CHARS:
568 newnbname = "%s%c" % (newnbname, x)
569 #force the length to be <16
570 netbiosname = newnbname[0:15]
572 netbiosname = hostname.upper()
574 if serverrole is None:
575 serverrole = "standalone"
577 assert serverrole in ("domain controller", "member server", "standalone")
578 if serverrole == "domain controller":
580 elif serverrole == "member server":
581 smbconfsuffix = "member"
582 elif serverrole == "standalone":
583 smbconfsuffix = "standalone"
585 if sid_generator is None:
586 sid_generator = "internal"
588 assert domain is not None
589 domain = domain.upper()
591 assert realm is not None
592 realm = realm.upper()
595 lp = samba.param.LoadParm()
596 #Load non-existant file
597 if os.path.exists(smbconf):
599 if eadb and not lp.get("posix:eadb"):
600 if targetdir is not None:
601 privdir = os.path.join(targetdir, "private")
603 privdir = lp.get("private dir")
604 lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
606 if server_services is not None:
607 server_services_line = "server services = " + " ".join(server_services)
609 server_services_line = ""
611 if targetdir is not None:
612 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
613 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
614 statedir_line = "state directory = " + os.path.abspath(targetdir)
615 cachedir_line = "cache directory = " + os.path.abspath(targetdir)
617 lp.set("lock dir", os.path.abspath(targetdir))
618 lp.set("state directory", os.path.abspath(targetdir))
619 lp.set("cache directory", os.path.abspath(targetdir))
626 sysvol = os.path.join(lp.get("state directory"), "sysvol")
627 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
629 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
631 "NETBIOS_NAME": netbiosname,
634 "SERVERROLE": serverrole,
635 "NETLOGONPATH": netlogon,
636 "SYSVOLPATH": sysvol,
637 "PRIVATEDIR_LINE": privatedir_line,
638 "LOCKDIR_LINE": lockdir_line,
639 "STATEDIR_LINE": statedir_line,
640 "CACHEDIR_LINE": cachedir_line,
641 "SERVER_SERVICES_LINE": server_services_line
644 # reload the smb.conf
647 # and dump it without any values that are the default
648 # this ensures that any smb.conf parameters that were set
649 # on the provision/join command line are set in the resulting smb.conf
650 f = open(smbconf, mode='w')
655 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
656 users_gid, wheel_gid):
657 """setup reasonable name mappings for sam names to unix names.
659 :param samdb: SamDB object.
660 :param idmap: IDmap db object.
661 :param sid: The domain sid.
662 :param domaindn: The domain DN.
663 :param root_uid: uid of the UNIX root user.
664 :param nobody_uid: uid of the UNIX nobody user.
665 :param users_gid: gid of the UNIX users group.
666 :param wheel_gid: gid of the UNIX wheel group.
668 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
669 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
671 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
672 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
675 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
676 provision_backend, names, schema, serverrole,
678 """Setup the partitions for the SAM database.
680 Alternatively, provision() may call this, and then populate the database.
682 :note: This will wipe the Sam Database!
684 :note: This function always removes the local SAM LDB file. The erase
685 parameter controls whether to erase the existing data, which
686 may not be stored locally but in LDAP.
689 assert session_info is not None
691 # We use options=["modules:"] to stop the modules loading - we
692 # just want to wipe and re-initialise the database, not start it up
695 os.unlink(samdb_path)
699 samdb = Ldb(url=samdb_path, session_info=session_info,
700 lp=lp, options=["modules:"])
702 ldap_backend_line = "# No LDAP backend"
703 if provision_backend.type is not "ldb":
704 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
706 samdb.transaction_start()
708 logger.info("Setting up sam.ldb partitions and settings")
709 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
710 "LDAP_BACKEND_LINE": ldap_backend_line
714 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
715 "BACKEND_TYPE": provision_backend.type,
716 "SERVER_ROLE": serverrole
719 logger.info("Setting up sam.ldb rootDSE")
720 setup_samdb_rootdse(samdb, names)
722 samdb.transaction_cancel()
725 samdb.transaction_commit()
728 def secretsdb_self_join(secretsdb, domain,
729 netbiosname, machinepass, domainsid=None,
730 realm=None, dnsdomain=None,
732 key_version_number=1,
733 secure_channel_type=SEC_CHAN_WKSTA):
734 """Add domain join-specific bits to a secrets database.
736 :param secretsdb: Ldb Handle to the secrets database
737 :param machinepass: Machine password
739 attrs = ["whenChanged",
746 if realm is not None:
747 if dnsdomain is None:
748 dnsdomain = realm.lower()
749 dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
752 shortname = netbiosname.lower()
754 # We don't need to set msg["flatname"] here, because rdn_name will handle
755 # it, and it causes problems for modifies anyway
756 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
757 msg["secureChannelType"] = [str(secure_channel_type)]
758 msg["objectClass"] = ["top", "primaryDomain"]
759 if dnsname is not None:
760 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
761 msg["realm"] = [realm]
762 msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
763 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
764 msg["privateKeytab"] = ["secrets.keytab"]
766 msg["secret"] = [machinepass]
767 msg["samAccountName"] = ["%s$" % netbiosname]
768 msg["secureChannelType"] = [str(secure_channel_type)]
769 if domainsid is not None:
770 msg["objectSid"] = [ndr_pack(domainsid)]
772 # This complex expression tries to ensure that we don't have more
773 # than one record for this SID, realm or netbios domain at a time,
774 # but we don't delete the old record that we are about to modify,
775 # because that would delete the keytab and previous password.
776 res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
777 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
778 scope=ldb.SCOPE_ONELEVEL)
781 secretsdb.delete(del_msg.dn)
783 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
786 msg["priorSecret"] = [res[0]["secret"][0]]
787 msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
790 msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
795 msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
801 msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
802 secretsdb.modify(msg)
803 secretsdb.rename(res[0].dn, msg.dn)
805 spn = [ 'HOST/%s' % shortname ]
806 if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
807 # we are a domain controller then we add servicePrincipalName
808 # entries for the keytab code to update.
809 spn.extend([ 'HOST/%s' % dnsname ])
810 msg["servicePrincipalName"] = spn
815 def setup_secretsdb(paths, session_info, backend_credentials, lp):
816 """Setup the secrets database.
818 :note: This function does not handle exceptions and transaction on purpose,
819 it's up to the caller to do this job.
821 :param path: Path to the secrets database.
822 :param session_info: Session info.
823 :param credentials: Credentials
824 :param lp: Loadparm context
825 :return: LDB handle for the created secrets database
827 if os.path.exists(paths.secrets):
828 os.unlink(paths.secrets)
830 keytab_path = os.path.join(paths.private_dir, paths.keytab)
831 if os.path.exists(keytab_path):
832 os.unlink(keytab_path)
834 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
835 if os.path.exists(dns_keytab_path):
836 os.unlink(dns_keytab_path)
840 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
842 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
843 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
844 secrets_ldb.transaction_start()
846 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
848 if (backend_credentials is not None and
849 backend_credentials.authentication_requested()):
850 if backend_credentials.get_bind_dn() is not None:
851 setup_add_ldif(secrets_ldb,
852 setup_path("secrets_simple_ldap.ldif"), {
853 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
854 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
857 setup_add_ldif(secrets_ldb,
858 setup_path("secrets_sasl_ldap.ldif"), {
859 "LDAPADMINUSER": backend_credentials.get_username(),
860 "LDAPADMINREALM": backend_credentials.get_realm(),
861 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
864 secrets_ldb.transaction_cancel()
869 def setup_privileges(path, session_info, lp):
870 """Setup the privileges database.
872 :param path: Path to the privileges database.
873 :param session_info: Session info.
874 :param credentials: Credentials
875 :param lp: Loadparm context
876 :return: LDB handle for the created secrets database
878 if os.path.exists(path):
880 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
881 privilege_ldb.erase()
882 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
885 def setup_registry(path, session_info, lp):
886 """Setup the registry.
888 :param path: Path to the registry database
889 :param session_info: Session information
890 :param credentials: Credentials
891 :param lp: Loadparm context
893 reg = samba.registry.Registry()
894 hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
895 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
896 provision_reg = setup_path("provision.reg")
897 assert os.path.exists(provision_reg)
898 reg.diff_apply(provision_reg)
901 def setup_idmapdb(path, session_info, lp):
902 """Setup the idmap database.
904 :param path: path to the idmap database
905 :param session_info: Session information
906 :param credentials: Credentials
907 :param lp: Loadparm context
909 if os.path.exists(path):
912 idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
914 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
918 def setup_samdb_rootdse(samdb, names):
919 """Setup the SamDB rootdse.
921 :param samdb: Sam Database handle
923 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
924 "SCHEMADN": names.schemadn,
925 "DOMAINDN": names.domaindn,
926 "ROOTDN" : names.rootdn,
927 "CONFIGDN": names.configdn,
928 "SERVERDN": names.serverdn,
932 def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
933 dnspass, domainsid, next_rid, invocationid, policyguid, policyguid_dc,
934 domainControllerFunctionality, ntdsguid=None, dc_rid=None):
935 """Join a host to its own domain."""
936 assert isinstance(invocationid, str)
937 if ntdsguid is not None:
938 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
945 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
946 "CONFIGDN": names.configdn,
947 "SCHEMADN": names.schemadn,
948 "DOMAINDN": names.domaindn,
949 "SERVERDN": names.serverdn,
950 "INVOCATIONID": invocationid,
951 "NETBIOSNAME": names.netbiosname,
952 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
953 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
954 "DOMAINSID": str(domainsid),
955 "DCRID": str(dc_rid),
956 "SAMBA_VERSION_STRING": version,
957 "NTDSGUID": ntdsguid_line,
958 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
959 domainControllerFunctionality),
960 "RIDALLOCATIONSTART": str(next_rid + 100),
961 "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
963 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
964 "POLICYGUID": policyguid,
965 "POLICYGUID_DC": policyguid_dc,
966 "DNSDOMAIN": names.dnsdomain,
967 "DOMAINDN": names.domaindn})
969 # If we are setting up a subdomain, then this has been replicated in, so we
970 # don't need to add it
971 if fill == FILL_FULL:
972 setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), {
973 "CONFIGDN": names.configdn,
974 "SCHEMADN": names.schemadn,
975 "DOMAINDN": names.domaindn,
976 "SERVERDN": names.serverdn,
977 "INVOCATIONID": invocationid,
978 "NETBIOSNAME": names.netbiosname,
979 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
980 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
981 "DOMAINSID": str(domainsid),
982 "DCRID": str(dc_rid),
983 "SAMBA_VERSION_STRING": version,
984 "NTDSGUID": ntdsguid_line,
985 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
986 domainControllerFunctionality)})
988 # Setup fSMORoleOwner entries to point at the newly created DC entry
989 setup_modify_ldif(samdb,
990 setup_path("provision_self_join_modify_config.ldif"), {
991 "CONFIGDN": names.configdn,
992 "SCHEMADN": names.schemadn,
993 "DEFAULTSITE": names.sitename,
994 "NETBIOSNAME": names.netbiosname,
995 "SERVERDN": names.serverdn,
998 system_session_info = system_session()
999 samdb.set_session_info(system_session_info)
1000 # Setup fSMORoleOwner entries to point at the newly created DC entry to
1001 # modify a serverReference under cn=config when we are a subdomain, we must
1002 # be system due to ACLs
1003 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1004 "DOMAINDN": names.domaindn,
1005 "SERVERDN": names.serverdn,
1006 "NETBIOSNAME": names.netbiosname,
1009 samdb.set_session_info(admin_session_info)
1011 # This is Samba4 specific and should be replaced by the correct
1012 # DNS AD-style setup
1013 setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
1014 "DNSDOMAIN": names.dnsdomain,
1015 "DOMAINDN": names.domaindn,
1016 "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
1017 "HOSTNAME" : names.hostname,
1018 "DNSNAME" : '%s.%s' % (
1019 names.netbiosname.lower(), names.dnsdomain.lower())
1023 def getpolicypath(sysvolpath, dnsdomain, guid):
1024 """Return the physical path of policy given its guid.
1026 :param sysvolpath: Path to the sysvol folder
1027 :param dnsdomain: DNS name of the AD domain
1028 :param guid: The GUID of the policy
1029 :return: A string with the complete path to the policy folder
1032 guid = "{%s}" % guid
1033 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
1037 def create_gpo_struct(policy_path):
1038 if not os.path.exists(policy_path):
1039 os.makedirs(policy_path, 0775)
1040 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1041 "[General]\r\nVersion=0")
1042 p = os.path.join(policy_path, "MACHINE")
1043 if not os.path.exists(p):
1044 os.makedirs(p, 0775)
1045 p = os.path.join(policy_path, "USER")
1046 if not os.path.exists(p):
1047 os.makedirs(p, 0775)
1050 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
1051 """Create the default GPO for a domain
1053 :param sysvolpath: Physical path for the sysvol folder
1054 :param dnsdomain: DNS domain name of the AD domain
1055 :param policyguid: GUID of the default domain policy
1056 :param policyguid_dc: GUID of the default domain controler policy
1058 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
1059 create_gpo_struct(policy_path)
1061 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
1062 create_gpo_struct(policy_path)
1065 def setup_samdb(path, session_info, provision_backend, lp, names,
1066 logger, fill, serverrole, schema, am_rodc=False):
1067 """Setup a complete SAM Database.
1069 :note: This will wipe the main SAM database file!
1072 # Also wipes the database
1073 setup_samdb_partitions(path, logger=logger, lp=lp,
1074 provision_backend=provision_backend, session_info=session_info,
1075 names=names, serverrole=serverrole, schema=schema)
1077 # Load the database, but don's load the global schema and don't connect
1079 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
1080 credentials=provision_backend.credentials, lp=lp,
1081 global_schema=False, am_rodc=am_rodc)
1083 logger.info("Pre-loading the Samba 4 and AD schema")
1085 # Load the schema from the one we computed earlier
1086 samdb.set_schema(schema)
1088 # Set the NTDS settings DN manually - in order to have it already around
1089 # before the provisioned tree exists and we connect
1090 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1092 # And now we can connect to the DB - the schema won't be loaded from the
1099 def fill_samdb(samdb, lp, names,
1100 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
1101 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
1102 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
1103 next_rid=None, dc_rid=None):
1105 if next_rid is None:
1108 # Provision does not make much sense values larger than 1000000000
1109 # as the upper range of the rIDAvailablePool is 1073741823 and
1110 # we don't want to create a domain that cannot allocate rids.
1111 if next_rid < 1000 or next_rid > 1000000000:
1112 error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
1113 error += "the valid range is %u-%u. The default is %u." % (
1114 1000, 1000000000, 1000)
1115 raise ProvisioningError(error)
1117 # ATTENTION: Do NOT change these default values without discussion with the
1118 # team and/or release manager. They have a big impact on the whole program!
1119 domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
1121 if dom_for_fun_level is None:
1122 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1124 if dom_for_fun_level > domainControllerFunctionality:
1125 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!")
1127 domainFunctionality = dom_for_fun_level
1128 forestFunctionality = dom_for_fun_level
1130 # Set the NTDS settings DN manually - in order to have it already around
1131 # before the provisioned tree exists and we connect
1132 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1134 samdb.transaction_start()
1136 # Set the domain functionality levels onto the database.
1137 # Various module (the password_hash module in particular) need
1138 # to know what level of AD we are emulating.
1140 # These will be fixed into the database via the database
1141 # modifictions below, but we need them set from the start.
1142 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1143 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1144 samdb.set_opaque_integer("domainControllerFunctionality",
1145 domainControllerFunctionality)
1147 samdb.set_domain_sid(str(domainsid))
1148 samdb.set_invocation_id(invocationid)
1150 logger.info("Adding DomainDN: %s" % names.domaindn)
1152 # impersonate domain admin
1153 admin_session_info = admin_session(lp, str(domainsid))
1154 samdb.set_session_info(admin_session_info)
1155 if domainguid is not None:
1156 domainguid_line = "objectGUID: %s\n-" % domainguid
1158 domainguid_line = ""
1160 descr = b64encode(get_domain_descriptor(domainsid))
1161 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1162 "DOMAINDN": names.domaindn,
1163 "DOMAINSID": str(domainsid),
1164 "DESCRIPTOR": descr,
1165 "DOMAINGUID": domainguid_line
1168 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1169 "DOMAINDN": names.domaindn,
1170 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1171 "NEXTRID": str(next_rid),
1172 "DEFAULTSITE": names.sitename,
1173 "CONFIGDN": names.configdn,
1174 "POLICYGUID": policyguid,
1175 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1176 "SAMBA_VERSION_STRING": version
1179 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1180 if fill == FILL_FULL:
1181 logger.info("Adding configuration container")
1182 descr = b64encode(get_config_descriptor(domainsid))
1183 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1184 "CONFIGDN": names.configdn,
1185 "DESCRIPTOR": descr,
1188 # The LDIF here was created when the Schema object was constructed
1189 logger.info("Setting up sam.ldb schema")
1190 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1191 samdb.modify_ldif(schema.schema_dn_modify)
1192 samdb.write_prefixes_from_schema()
1193 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1194 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1195 {"SCHEMADN": names.schemadn})
1197 # Now register this container in the root of the forest
1198 msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
1199 msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
1203 samdb.transaction_cancel()
1206 samdb.transaction_commit()
1208 samdb.transaction_start()
1210 samdb.invocation_id = invocationid
1212 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1213 if fill == FILL_FULL:
1214 logger.info("Setting up sam.ldb configuration data")
1215 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1216 "CONFIGDN": names.configdn,
1217 "NETBIOSNAME": names.netbiosname,
1218 "DEFAULTSITE": names.sitename,
1219 "DNSDOMAIN": names.dnsdomain,
1220 "DOMAIN": names.domain,
1221 "SCHEMADN": names.schemadn,
1222 "DOMAINDN": names.domaindn,
1223 "SERVERDN": names.serverdn,
1224 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1225 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1228 logger.info("Setting up display specifiers")
1229 display_specifiers_ldif = read_ms_ldif(
1230 setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1231 display_specifiers_ldif = substitute_var(display_specifiers_ldif,
1232 {"CONFIGDN": names.configdn})
1233 check_all_substituted(display_specifiers_ldif)
1234 samdb.add_ldif(display_specifiers_ldif)
1236 logger.info("Adding users container")
1237 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1238 "DOMAINDN": names.domaindn})
1239 logger.info("Modifying users container")
1240 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1241 "DOMAINDN": names.domaindn})
1242 logger.info("Adding computers container")
1243 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1244 "DOMAINDN": names.domaindn})
1245 logger.info("Modifying computers container")
1246 setup_modify_ldif(samdb,
1247 setup_path("provision_computers_modify.ldif"), {
1248 "DOMAINDN": names.domaindn})
1249 logger.info("Setting up sam.ldb data")
1250 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1251 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1252 "DOMAINDN": names.domaindn,
1253 "NETBIOSNAME": names.netbiosname,
1254 "DEFAULTSITE": names.sitename,
1255 "CONFIGDN": names.configdn,
1256 "SERVERDN": names.serverdn,
1257 "RIDAVAILABLESTART": str(next_rid + 600),
1258 "POLICYGUID_DC": policyguid_dc
1261 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1262 if fill == FILL_FULL:
1263 setup_modify_ldif(samdb,
1264 setup_path("provision_configuration_references.ldif"), {
1265 "CONFIGDN": names.configdn,
1266 "SCHEMADN": names.schemadn})
1268 logger.info("Setting up well known security principals")
1269 setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
1270 "CONFIGDN": names.configdn,
1273 if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
1274 setup_modify_ldif(samdb,
1275 setup_path("provision_basedn_references.ldif"),
1276 {"DOMAINDN": names.domaindn})
1278 logger.info("Setting up sam.ldb users and groups")
1279 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1280 "DOMAINDN": names.domaindn,
1281 "DOMAINSID": str(domainsid),
1282 "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
1283 "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
1286 logger.info("Setting up self join")
1287 setup_self_join(samdb, admin_session_info, names=names, fill=fill,
1288 invocationid=invocationid,
1290 machinepass=machinepass,
1291 domainsid=domainsid,
1294 policyguid=policyguid,
1295 policyguid_dc=policyguid_dc,
1296 domainControllerFunctionality=domainControllerFunctionality,
1299 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1300 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1301 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1302 assert isinstance(names.ntdsguid, str)
1304 samdb.transaction_cancel()
1307 samdb.transaction_commit()
1312 FILL_SUBDOMAIN = "SUBDOMAIN"
1313 FILL_NT4SYNC = "NT4SYNC"
1315 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1316 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)"
1319 def set_dir_acl(path, acl, lp, domsid):
1320 setntacl(lp, path, acl, domsid)
1321 for root, dirs, files in os.walk(path, topdown=False):
1323 setntacl(lp, os.path.join(root, name), acl, domsid)
1325 setntacl(lp, os.path.join(root, name), acl, domsid)
1328 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1329 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1332 :param sysvol: Physical path for the sysvol folder
1333 :param dnsdomain: The DNS name of the domain
1334 :param domainsid: The SID of the domain
1335 :param domaindn: The DN of the domain (ie. DC=...)
1336 :param samdb: An LDB object on the SAM db
1337 :param lp: an LP object
1340 # Set ACL for GPO root folder
1341 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1342 setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
1344 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1345 attrs=["cn", "nTSecurityDescriptor"],
1346 expression="", scope=ldb.SCOPE_ONELEVEL)
1349 acl = ndr_unpack(security.descriptor,
1350 str(policy["nTSecurityDescriptor"])).as_sddl()
1351 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1352 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1356 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1358 """Set the ACL for the sysvol share and the subfolders
1360 :param samdb: An LDB object on the SAM db
1361 :param netlogon: Physical path for the netlogon folder
1362 :param sysvol: Physical path for the sysvol folder
1363 :param gid: The GID of the "Domain adminstrators" group
1364 :param domainsid: The SID of the domain
1365 :param dnsdomain: The DNS name of the domain
1366 :param domaindn: The DN of the domain (ie. DC=...)
1370 os.chown(sysvol, -1, gid)
1376 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1377 setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
1378 for root, dirs, files in os.walk(sysvol, topdown=False):
1381 os.chown(os.path.join(root, name), -1, gid)
1382 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1385 os.chown(os.path.join(root, name), -1, gid)
1386 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1388 # Set acls on Policy folder and policies folders
1389 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
1392 def interface_ips_v4(lp):
1393 '''return only IPv4 IPs'''
1394 ips = samba.interface_ips(lp, False)
1397 if i.find(':') == -1:
1401 def interface_ips_v6(lp, linklocal=False):
1402 '''return only IPv6 IPs'''
1403 ips = samba.interface_ips(lp, False)
1406 if i.find(':') != -1 and (linklocal or i.find('%') == -1):
1411 def provision_fill(samdb, secrets_ldb, logger, names, paths,
1412 domainsid, schema=None,
1413 targetdir=None, samdb_fill=FILL_FULL,
1414 hostip=None, hostip6=None,
1415 next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
1416 domainguid=None, policyguid=None, policyguid_dc=None,
1417 invocationid=None, machinepass=None, ntdsguid=None,
1418 dns_backend=None, dnspass=None,
1419 serverrole=None, dom_for_fun_level=None,
1420 am_rodc=False, lp=None):
1421 # create/adapt the group policy GUIDs
1422 # Default GUID for default policy are described at
1423 # "How Core Group Policy Works"
1424 # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
1425 if policyguid is None:
1426 policyguid = DEFAULT_POLICY_GUID
1427 policyguid = policyguid.upper()
1428 if policyguid_dc is None:
1429 policyguid_dc = DEFAULT_DC_POLICY_GUID
1430 policyguid_dc = policyguid_dc.upper()
1432 if invocationid is None:
1433 invocationid = str(uuid.uuid4())
1435 if krbtgtpass is None:
1436 krbtgtpass = samba.generate_random_password(128, 255)
1437 if machinepass is None:
1438 machinepass = samba.generate_random_password(128, 255)
1440 dnspass = samba.generate_random_password(128, 255)
1442 samdb = fill_samdb(samdb, lp, names, logger=logger,
1443 domainsid=domainsid, schema=schema, domainguid=domainguid,
1444 policyguid=policyguid, policyguid_dc=policyguid_dc,
1445 fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
1446 invocationid=invocationid, machinepass=machinepass,
1447 dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
1448 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1449 next_rid=next_rid, dc_rid=dc_rid)
1451 if serverrole == "domain controller":
1452 # Set up group policies (domain policy and domain controller
1454 create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
1456 setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
1457 domainsid, names.dnsdomain, names.domaindn, lp)
1459 secretsdb_self_join(secrets_ldb, domain=names.domain,
1460 realm=names.realm, dnsdomain=names.dnsdomain,
1461 netbiosname=names.netbiosname, domainsid=domainsid,
1462 machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
1464 # Now set up the right msDS-SupportedEncryptionTypes into the DB
1465 # In future, this might be determined from some configuration
1466 kerberos_enctypes = str(ENC_ALL_TYPES)
1469 msg = ldb.Message(ldb.Dn(samdb,
1470 samdb.searchone("distinguishedName",
1471 expression="samAccountName=%s$" % names.netbiosname,
1472 scope=ldb.SCOPE_SUBTREE)))
1473 msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
1474 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
1475 name="msDS-SupportedEncryptionTypes")
1477 except ldb.LdbError, (enum, estr):
1478 if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
1479 # It might be that this attribute does not exist in this schema
1482 setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
1483 hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
1484 dnspass=dnspass, os_level=dom_for_fun_level,
1485 targetdir=targetdir, site=DEFAULTSITE)
1487 domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
1488 attribute="objectGUID")
1489 assert isinstance(domainguid, str)
1491 lastProvisionUSNs = get_last_provision_usn(samdb)
1492 maxUSN = get_max_usn(samdb, str(names.rootdn))
1493 if lastProvisionUSNs is not None:
1494 update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
1496 set_provision_usn(samdb, 0, maxUSN, invocationid)
1498 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1499 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
1500 { 'NTDSGUID' : names.ntdsguid })
1502 # fix any dangling GUIDs from the provision
1503 logger.info("Fixing provision GUIDs")
1504 chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True, quiet=True)
1505 samdb.transaction_start()
1507 # a small number of GUIDs are missing because of ordering issues in the
1509 for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
1510 chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
1511 scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
1512 chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
1513 scope=ldb.SCOPE_ONELEVEL,
1514 attrs=['ipsecOwnersReference',
1515 'ipsecFilterReference',
1516 'ipsecISAKMPReference',
1517 'ipsecNegotiationPolicyReference',
1518 'ipsecNFAReference'])
1520 samdb.transaction_cancel()
1523 samdb.transaction_commit()
1527 "ROLE_STANDALONE": "standalone",
1528 "ROLE_DOMAIN_MEMBER": "member server",
1529 "ROLE_DOMAIN_BDC": "domain controller",
1530 "ROLE_DOMAIN_PDC": "domain controller",
1531 "dc": "domain controller",
1532 "member": "member server",
1533 "domain controller": "domain controller",
1534 "member server": "member server",
1535 "standalone": "standalone",
1539 def sanitize_server_role(role):
1540 """Sanitize a server role name.
1542 :param role: Server role
1543 :raise ValueError: If the role can not be interpreted
1544 :return: Sanitized server role (one of "member server",
1545 "domain controller", "standalone")
1548 return _ROLES_MAP[role]
1550 raise ValueError(role)
1553 def provision(logger, session_info, credentials, smbconf=None,
1554 targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
1555 domaindn=None, schemadn=None, configdn=None, serverdn=None,
1556 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
1557 next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None,
1558 domainguid=None, policyguid=None, policyguid_dc=None,
1559 dns_backend=None, dnspass=None,
1560 invocationid=None, machinepass=None, ntdsguid=None,
1561 root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
1562 serverrole=None, dom_for_fun_level=None,
1563 backend_type=None, sitename=None,
1564 ol_mmr_urls=None, ol_olc=None, slapd_path=None,
1565 useeadb=False, am_rodc=False,
1569 :note: caution, this wipes all existing data!
1573 serverrole = sanitize_server_role(serverrole)
1575 raise ProvisioningError('server role (%s) should be one of "domain controller", "member server", "standalone"' % serverrole)
1577 if ldapadminpass is None:
1578 # Make a new, random password between Samba and it's LDAP server
1579 ldapadminpass=samba.generate_random_password(128, 255)
1581 if backend_type is None:
1582 backend_type = "ldb"
1584 if domainsid is None:
1585 domainsid = security.random_sid()
1587 domainsid = security.dom_sid(domainsid)
1589 sid_generator = "internal"
1590 if backend_type == "fedora-ds":
1591 sid_generator = "backend"
1593 root_uid = findnss_uid([root or "root"])
1594 nobody_uid = findnss_uid([nobody or "nobody"])
1595 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1597 wheel_gid = findnss_gid(["wheel", "adm"])
1599 wheel_gid = findnss_gid([wheel])
1601 bind_gid = findnss_gid(["bind", "named"])
1605 if targetdir is not None:
1606 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1607 elif smbconf is None:
1608 smbconf = samba.param.default_path()
1609 if not os.path.exists(os.path.dirname(smbconf)):
1610 os.makedirs(os.path.dirname(smbconf))
1612 server_services = None
1613 if dns_backend == "SAMBA_INTERNAL":
1614 server_services = [ "+dns" ]
1616 # only install a new smb.conf if there isn't one there already
1617 if os.path.exists(smbconf):
1618 # if Samba Team members can't figure out the weird errors
1619 # loading an empty smb.conf gives, then we need to be smarter.
1620 # Pretend it just didn't exist --abartlet
1621 data = open(smbconf, 'r').read()
1622 data = data.lstrip()
1623 if data is None or data == "":
1624 make_smbconf(smbconf, hostname, domain, realm,
1625 serverrole, targetdir, sid_generator, useeadb,
1626 lp=lp, server_services=server_services)
1628 make_smbconf(smbconf, hostname, domain, realm, serverrole,
1629 targetdir, sid_generator, useeadb, lp=lp,
1630 server_services=server_services)
1633 lp = samba.param.LoadParm()
1635 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1636 dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
1637 configdn=configdn, schemadn=schemadn, serverdn=serverdn,
1638 sitename=sitename, rootdn=rootdn)
1639 paths = provision_paths_from_lp(lp, names.dnsdomain)
1641 paths.bind_gid = bind_gid
1642 paths.wheel_gid = wheel_gid
1645 logger.info("Looking up IPv4 addresses")
1646 hostips = interface_ips_v4(lp)
1647 if len(hostips) > 0:
1649 if len(hostips) > 1:
1650 logger.warning("More than one IPv4 address found. Using %s",
1652 if hostip == "127.0.0.1":
1655 logger.warning("No IPv4 address will be assigned")
1658 logger.info("Looking up IPv6 addresses")
1659 hostips = interface_ips_v6(lp, linklocal=False)
1661 hostip6 = hostips[0]
1662 if len(hostips) > 1:
1663 logger.warning("More than one IPv6 address found. Using %s", hostip6)
1665 logger.warning("No IPv6 address will be assigned")
1667 names.hostip = hostip
1668 names.hostip6 = hostip6
1670 if serverrole is None:
1671 serverrole = lp.get("server role")
1673 if not os.path.exists(paths.private_dir):
1674 os.mkdir(paths.private_dir)
1675 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1676 os.mkdir(os.path.join(paths.private_dir, "tls"))
1678 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1680 schema = Schema(domainsid, invocationid=invocationid,
1681 schemadn=names.schemadn)
1683 if backend_type == "ldb":
1684 provision_backend = LDBBackend(backend_type, paths=paths,
1685 lp=lp, credentials=credentials,
1686 names=names, logger=logger)
1687 elif backend_type == "existing":
1688 # If support for this is ever added back, then the URI will need to be specified again
1689 provision_backend = ExistingBackend(backend_type, paths=paths,
1690 lp=lp, credentials=credentials,
1691 names=names, logger=logger,
1692 ldap_backend_forced_uri=None)
1693 elif backend_type == "fedora-ds":
1694 provision_backend = FDSBackend(backend_type, paths=paths,
1695 lp=lp, credentials=credentials,
1696 names=names, logger=logger, domainsid=domainsid,
1697 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1698 slapd_path=slapd_path,
1700 elif backend_type == "openldap":
1701 provision_backend = OpenLDAPBackend(backend_type, paths=paths,
1702 lp=lp, credentials=credentials,
1703 names=names, logger=logger, domainsid=domainsid,
1704 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1705 slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls)
1707 raise ValueError("Unknown LDAP backend type selected")
1709 provision_backend.init()
1710 provision_backend.start()
1712 # only install a new shares config db if there is none
1713 if not os.path.exists(paths.shareconf):
1714 logger.info("Setting up share.ldb")
1715 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1717 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1719 logger.info("Setting up secrets.ldb")
1720 secrets_ldb = setup_secretsdb(paths,
1721 session_info=session_info,
1722 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1725 logger.info("Setting up the registry")
1726 setup_registry(paths.hklm, session_info,
1729 logger.info("Setting up the privileges database")
1730 setup_privileges(paths.privilege, session_info, lp=lp)
1732 logger.info("Setting up idmap db")
1733 idmap = setup_idmapdb(paths.idmapdb,
1734 session_info=session_info, lp=lp)
1736 setup_name_mappings(idmap, sid=str(domainsid),
1737 root_uid=root_uid, nobody_uid=nobody_uid,
1738 users_gid=users_gid, wheel_gid=wheel_gid)
1740 logger.info("Setting up SAM db")
1741 samdb = setup_samdb(paths.samdb, session_info,
1742 provision_backend, lp, names, logger=logger,
1743 serverrole=serverrole,
1744 schema=schema, fill=samdb_fill, am_rodc=am_rodc)
1746 if serverrole == "domain controller":
1747 if paths.netlogon is None:
1748 logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1749 logger.info("Please either remove %s or see the template at %s" %
1750 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1751 assert paths.netlogon is not None
1753 if paths.sysvol is None:
1754 logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1755 " are configuring a DC.")
1756 logger.info("Please either remove %s or see the template at %s" %
1757 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1758 assert paths.sysvol is not None
1760 if not os.path.isdir(paths.netlogon):
1761 os.makedirs(paths.netlogon, 0755)
1763 if adminpass is None:
1764 adminpass = samba.generate_random_password(12, 32)
1765 adminpass_generated = True
1767 adminpass_generated = False
1769 if samdb_fill == FILL_FULL:
1770 provision_fill(samdb, secrets_ldb, logger,
1771 names, paths, schema=schema, targetdir=targetdir,
1772 samdb_fill=samdb_fill, hostip=hostip, hostip6=hostip6, domainsid=domainsid,
1773 next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
1774 krbtgtpass=krbtgtpass, domainguid=domainguid,
1775 policyguid=policyguid, policyguid_dc=policyguid_dc,
1776 invocationid=invocationid, machinepass=machinepass,
1777 ntdsguid=ntdsguid, dns_backend=dns_backend, dnspass=dnspass,
1778 serverrole=serverrole, dom_for_fun_level=dom_for_fun_level,
1779 am_rodc=am_rodc, lp=lp)
1781 create_krb5_conf(paths.krb5conf,
1782 dnsdomain=names.dnsdomain, hostname=names.hostname,
1784 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1785 "generated at %s", paths.krb5conf)
1787 if serverrole == "domain controller":
1788 create_dns_update_list(lp, logger, paths)
1790 provision_backend.post_setup()
1791 provision_backend.shutdown()
1793 create_phpldapadmin_config(paths.phpldapadminconfig,
1796 secrets_ldb.transaction_cancel()
1799 # Now commit the secrets.ldb to disk
1800 secrets_ldb.transaction_commit()
1802 # the commit creates the dns.keytab, now chown it
1803 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1804 if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
1806 os.chmod(dns_keytab_path, 0640)
1807 os.chown(dns_keytab_path, -1, paths.bind_gid)
1809 if not os.environ.has_key('SAMBA_SELFTEST'):
1810 logger.info("Failed to chown %s to bind gid %u",
1811 dns_keytab_path, paths.bind_gid)
1813 logger.info("A phpLDAPadmin configuration file suitable for administering the Samba 4 LDAP server has been created in %s .",
1814 paths.phpldapadminconfig)
1816 logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1817 logger.info("Server Role: %s" % serverrole)
1818 logger.info("Hostname: %s" % names.hostname)
1819 logger.info("NetBIOS Domain: %s" % names.domain)
1820 logger.info("DNS Domain: %s" % names.dnsdomain)
1821 logger.info("DOMAIN SID: %s" % str(domainsid))
1822 if samdb_fill == FILL_FULL:
1823 if adminpass_generated:
1824 logger.info("Admin password: %s" % adminpass)
1825 if provision_backend.type is not "ldb":
1826 if provision_backend.credentials.get_bind_dn() is not None:
1827 logger.info("LDAP Backend Admin DN: %s" %
1828 provision_backend.credentials.get_bind_dn())
1830 logger.info("LDAP Admin User: %s" %
1831 provision_backend.credentials.get_username())
1833 if provision_backend.slapd_command_escaped is not None:
1834 # now display slapd_command_file.txt to show how slapd must be
1836 logger.info("Use later the following commandline to start slapd, then Samba:")
1837 logger.info(provision_backend.slapd_command_escaped)
1838 logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
1839 provision_backend.ldapdir)
1841 result = ProvisionResult()
1842 result.domaindn = domaindn
1843 result.paths = paths
1844 result.names = names
1846 result.samdb = samdb
1847 result.idmap = idmap
1851 def provision_become_dc(smbconf=None, targetdir=None,
1852 realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
1853 serverdn=None, domain=None, hostname=None, domainsid=None,
1854 adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
1855 policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
1856 dns_backend=None, root=None, nobody=None, users=None, wheel=None,
1857 backup=None, serverrole=None, ldap_backend=None,
1858 ldap_backend_type=None, sitename=None, debuglevel=1):
1860 logger = logging.getLogger("provision")
1861 samba.set_debug_level(debuglevel)
1863 res = provision(logger, system_session(), None,
1864 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1865 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1866 configdn=configdn, serverdn=serverdn, domain=domain,
1867 hostname=hostname, hostip=None, domainsid=domainsid,
1868 machinepass=machinepass, serverrole="domain controller",
1869 sitename=sitename, dns_backend=dns_backend, dnspass=dnspass)
1870 res.lp.set("debuglevel", str(debuglevel))
1874 def create_phpldapadmin_config(path, ldapi_uri):
1875 """Create a PHP LDAP admin configuration file.
1877 :param path: Path to write the configuration to.
1879 setup_file(setup_path("phpldapadmin-config.php"), path,
1880 {"S4_LDAPI_URI": ldapi_uri})
1883 def create_krb5_conf(path, dnsdomain, hostname, realm):
1884 """Write out a file containing zone statements suitable for inclusion in a
1885 named.conf file (including GSS-TSIG configuration).
1887 :param path: Path of the new named.conf file.
1888 :param dnsdomain: DNS Domain name
1889 :param hostname: Local hostname
1890 :param realm: Realm name
1892 setup_file(setup_path("krb5.conf"), path, {
1893 "DNSDOMAIN": dnsdomain,
1894 "HOSTNAME": hostname,
1899 class ProvisioningError(Exception):
1900 """A generic provision error."""
1902 def __init__(self, value):
1906 return "ProvisioningError: " + self.value
1909 class InvalidNetbiosName(Exception):
1910 """A specified name was not a valid NetBIOS name."""
1912 def __init__(self, name):
1913 super(InvalidNetbiosName, self).__init__(
1914 "The name '%r' is not a valid NetBIOS name" % name)