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
45 from samba.auth import system_session, admin_session
47 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
50 check_all_substituted,
56 from samba.dcerpc import security, misc
57 from samba.dcerpc.misc import (
61 from samba.dsdb import (
62 DS_DOMAIN_FUNCTION_2003,
63 DS_DOMAIN_FUNCTION_2008_R2,
66 from samba.idmap import IDmapDB
67 from samba.ms_display_specifiers import read_ms_ldif
68 from samba.ntacls import setntacl, dsacl2fsacl
69 from samba.ndr import ndr_pack, ndr_unpack
70 from samba.provision.backend import (
76 from samba.provision.descriptor import (
77 get_config_descriptor,
80 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 VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~"
99 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
100 DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
101 DEFAULTSITE = "Default-First-Site-Name"
102 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
105 class ProvisionPaths(object):
108 self.shareconf = None
119 self.dns_keytab = None
122 self.private_dir = 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")
246 def update_provision_usn(samdb, low, high, id, replace=False):
247 """Update the field provisionUSN in sam.ldb
249 This field is used to track range of USN modified by provision and
251 This value is used afterward by next provision to figure out if
252 the field have been modified since last provision.
254 :param samdb: An LDB object connect to sam.ldb
255 :param low: The lowest USN modified by this upgrade
256 :param high: The highest USN modified by this upgrade
257 :param id: The invocation id of the samba's dc
258 :param replace: A boolean indicating if the range should replace any
259 existing one or appended (default)
264 entry = samdb.search(base="@PROVISION",
265 scope=ldb.SCOPE_BASE,
266 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
267 for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
268 if not re.search(';', e):
269 e = "%s;%s" % (e, id)
272 tab.append("%s-%s;%s" % (low, high, id))
273 delta = ldb.Message()
274 delta.dn = ldb.Dn(samdb, "@PROVISION")
275 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
276 ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
277 entry = samdb.search(expression='provisionnerID=*',
278 base="@PROVISION", scope=ldb.SCOPE_BASE,
279 attrs=["provisionnerID"])
280 if len(entry) == 0 or len(entry[0]) == 0:
281 delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
285 def set_provision_usn(samdb, low, high, id):
286 """Set the field provisionUSN in sam.ldb
287 This field is used to track range of USN modified by provision and
289 This value is used afterward by next provision to figure out if
290 the field have been modified since last provision.
292 :param samdb: An LDB object connect to sam.ldb
293 :param low: The lowest USN modified by this upgrade
294 :param high: The highest USN modified by this upgrade
295 :param id: The invocationId of the provision"""
298 tab.append("%s-%s;%s" % (low, high, id))
300 delta = ldb.Message()
301 delta.dn = ldb.Dn(samdb, "@PROVISION")
302 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
303 ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
307 def get_max_usn(samdb,basedn):
308 """ This function return the biggest USN present in the provision
310 :param samdb: A LDB object pointing to the sam.ldb
311 :param basedn: A string containing the base DN of the provision
313 :return: The biggest USN in the provision"""
315 res = samdb.search(expression="objectClass=*",base=basedn,
316 scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
317 controls=["search_options:1:2",
318 "server_sort:1:1:uSNChanged",
319 "paged_results:1:1"])
320 return res[0]["uSNChanged"]
323 def get_last_provision_usn(sam):
324 """Get USNs ranges modified by a provision or an upgradeprovision
326 :param sam: An LDB object pointing to the sam.ldb
327 :return: a dictionnary which keys are invocation id and values are an array
328 of integer representing the different ranges
331 entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
332 base="@PROVISION", scope=ldb.SCOPE_BASE,
333 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
334 except ldb.LdbError, (ecode, emsg):
335 if ecode == ldb.ERR_NO_SUCH_OBJECT:
342 if entry[0].get("provisionnerID"):
343 for e in entry[0]["provisionnerID"]:
345 for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
346 tab1 = str(r).split(';')
351 if (len(myids) > 0 and id not in myids):
353 tab2 = p.split(tab1[0])
354 if range.get(id) == None:
356 range[id].append(tab2[0])
357 range[id].append(tab2[1])
363 class ProvisionResult(object):
374 def check_install(lp, session_info, credentials):
375 """Check whether the current install seems ok.
377 :param lp: Loadparm context
378 :param session_info: Session information
379 :param credentials: Credentials
381 if lp.get("realm") == "":
382 raise Exception("Realm empty")
383 samdb = Ldb(lp.samdb_url(), session_info=session_info,
384 credentials=credentials, lp=lp)
385 if len(samdb.search("(cn=Administrator)")) != 1:
386 raise ProvisioningError("No administrator account found")
389 def findnss(nssfn, names):
390 """Find a user or group from a list of possibilities.
392 :param nssfn: NSS Function to try (should raise KeyError if not found)
393 :param names: Names to check.
394 :return: Value return by first names list.
401 raise KeyError("Unable to find user/group in %r" % names)
404 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
405 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
408 def provision_paths_from_lp(lp, dnsdomain):
409 """Set the default paths for provisioning.
411 :param lp: Loadparm context.
412 :param dnsdomain: DNS Domain name
414 paths = ProvisionPaths()
415 paths.private_dir = lp.get("private dir")
417 # This is stored without path prefix for the "privateKeytab" attribute in
418 # "secrets_dns.ldif".
419 paths.dns_keytab = "dns.keytab"
420 paths.keytab = "secrets.keytab"
422 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
423 paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
424 paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
425 paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
426 paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
427 paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
428 paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
429 paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
430 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
431 paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
432 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
433 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
434 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
435 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
436 paths.phpldapadminconfig = os.path.join(paths.private_dir,
437 "phpldapadmin-config.php")
438 paths.hklm = "hklm.ldb"
439 paths.hkcr = "hkcr.ldb"
440 paths.hkcu = "hkcu.ldb"
441 paths.hku = "hku.ldb"
442 paths.hkpd = "hkpd.ldb"
443 paths.hkpt = "hkpt.ldb"
444 paths.sysvol = lp.get("path", "sysvol")
445 paths.netlogon = lp.get("path", "netlogon")
446 paths.smbconf = lp.configfile
450 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
451 serverrole=None, rootdn=None, domaindn=None, configdn=None,
452 schemadn=None, serverdn=None, sitename=None):
453 """Guess configuration settings to use."""
456 hostname = socket.gethostname().split(".")[0]
458 netbiosname = lp.get("netbios name")
459 if netbiosname is None:
460 netbiosname = hostname
461 # remove forbidden chars
463 for x in netbiosname:
464 if x.isalnum() or x in VALID_NETBIOS_CHARS:
465 newnbname = "%s%c" % (newnbname, x)
466 # force the length to be <16
467 netbiosname = newnbname[0:15]
468 assert netbiosname is not None
469 netbiosname = netbiosname.upper()
470 if not valid_netbios_name(netbiosname):
471 raise InvalidNetbiosName(netbiosname)
473 if dnsdomain is None:
474 dnsdomain = lp.get("realm")
475 if dnsdomain is None or dnsdomain == "":
476 raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
478 dnsdomain = dnsdomain.lower()
480 if serverrole is None:
481 serverrole = lp.get("server role")
482 if serverrole is None:
483 raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
485 serverrole = serverrole.lower()
487 realm = dnsdomain.upper()
489 if lp.get("realm") == "":
490 raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile)
492 if lp.get("realm").upper() != realm:
493 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))
495 if lp.get("server role").lower() != serverrole:
496 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))
498 if serverrole == "domain controller":
500 # This will, for better or worse, default to 'WORKGROUP'
501 domain = lp.get("workgroup")
502 domain = domain.upper()
504 if lp.get("workgroup").upper() != domain:
505 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))
508 domaindn = samba.dn_from_dns_name(dnsdomain)
510 if domain == netbiosname:
511 raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
515 domaindn = "DC=" + netbiosname
517 if not valid_netbios_name(domain):
518 raise InvalidNetbiosName(domain)
520 if hostname.upper() == realm:
521 raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
522 if netbiosname.upper() == realm:
523 raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
525 raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
531 configdn = "CN=Configuration," + rootdn
533 schemadn = "CN=Schema," + configdn
538 names = ProvisionNames()
539 names.rootdn = rootdn
540 names.domaindn = domaindn
541 names.configdn = configdn
542 names.schemadn = schemadn
543 names.ldapmanagerdn = "CN=Manager," + rootdn
544 names.dnsdomain = dnsdomain
545 names.domain = domain
547 names.netbiosname = netbiosname
548 names.hostname = hostname
549 names.sitename = sitename
550 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
551 netbiosname, sitename, configdn)
556 def make_smbconf(smbconf, hostname, domain, realm, serverrole,
557 targetdir, sid_generator="internal", eadb=False, lp=None,
558 server_services=None):
559 """Create a new smb.conf file based on a couple of basic settings.
561 assert smbconf is not None
563 hostname = socket.gethostname().split(".")[0]
564 netbiosname = hostname.upper()
565 # remove forbidden chars
567 for x in netbiosname:
568 if x.isalnum() or x in VALID_NETBIOS_CHARS:
569 newnbname = "%s%c" % (newnbname, x)
570 #force the length to be <16
571 netbiosname = newnbname[0:15]
573 netbiosname = hostname.upper()
575 if serverrole is None:
576 serverrole = "standalone"
578 assert serverrole in ("domain controller", "member server", "standalone")
579 if serverrole == "domain controller":
581 elif serverrole == "member server":
582 smbconfsuffix = "member"
583 elif serverrole == "standalone":
584 smbconfsuffix = "standalone"
586 if sid_generator is None:
587 sid_generator = "internal"
589 assert domain is not None
590 domain = domain.upper()
592 assert realm is not None
593 realm = realm.upper()
596 lp = samba.param.LoadParm()
597 #Load non-existant file
598 if os.path.exists(smbconf):
600 if eadb and not lp.get("posix:eadb"):
601 if targetdir is not None:
602 privdir = os.path.join(targetdir, "private")
604 privdir = lp.get("private dir")
605 lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
607 if server_services is not None:
608 server_services_line = "server services = " + " ".join(server_services)
610 server_services_line = ""
612 if targetdir is not None:
613 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
614 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
615 statedir_line = "state directory = " + os.path.abspath(targetdir)
616 cachedir_line = "cache directory = " + os.path.abspath(targetdir)
618 lp.set("lock dir", os.path.abspath(targetdir))
619 lp.set("state directory", os.path.abspath(targetdir))
620 lp.set("cache directory", os.path.abspath(targetdir))
627 sysvol = os.path.join(lp.get("state directory"), "sysvol")
628 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
630 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
632 "NETBIOS_NAME": netbiosname,
635 "SERVERROLE": serverrole,
636 "NETLOGONPATH": netlogon,
637 "SYSVOLPATH": sysvol,
638 "PRIVATEDIR_LINE": privatedir_line,
639 "LOCKDIR_LINE": lockdir_line,
640 "STATEDIR_LINE": statedir_line,
641 "CACHEDIR_LINE": cachedir_line,
642 "SERVER_SERVICES_LINE": server_services_line
645 # reload the smb.conf
648 # and dump it without any values that are the default
649 # this ensures that any smb.conf parameters that were set
650 # on the provision/join command line are set in the resulting smb.conf
651 f = open(smbconf, mode='w')
657 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
658 users_gid, wheel_gid):
659 """setup reasonable name mappings for sam names to unix names.
661 :param samdb: SamDB object.
662 :param idmap: IDmap db object.
663 :param sid: The domain sid.
664 :param domaindn: The domain DN.
665 :param root_uid: uid of the UNIX root user.
666 :param nobody_uid: uid of the UNIX nobody user.
667 :param users_gid: gid of the UNIX users group.
668 :param wheel_gid: gid of the UNIX wheel group.
670 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
671 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
673 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
674 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
677 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
678 provision_backend, names, schema, serverrole,
680 """Setup the partitions for the SAM database.
682 Alternatively, provision() may call this, and then populate the database.
684 :note: This will wipe the Sam Database!
686 :note: This function always removes the local SAM LDB file. The erase
687 parameter controls whether to erase the existing data, which
688 may not be stored locally but in LDAP.
691 assert session_info is not None
693 # We use options=["modules:"] to stop the modules loading - we
694 # just want to wipe and re-initialise the database, not start it up
697 os.unlink(samdb_path)
701 samdb = Ldb(url=samdb_path, session_info=session_info,
702 lp=lp, options=["modules:"])
704 ldap_backend_line = "# No LDAP backend"
705 if provision_backend.type is not "ldb":
706 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
708 samdb.transaction_start()
710 logger.info("Setting up sam.ldb partitions and settings")
711 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
712 "LDAP_BACKEND_LINE": ldap_backend_line
716 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
717 "BACKEND_TYPE": provision_backend.type,
718 "SERVER_ROLE": serverrole
721 logger.info("Setting up sam.ldb rootDSE")
722 setup_samdb_rootdse(samdb, names)
724 samdb.transaction_cancel()
727 samdb.transaction_commit()
730 def secretsdb_self_join(secretsdb, domain,
731 netbiosname, machinepass, domainsid=None,
732 realm=None, dnsdomain=None,
734 key_version_number=1,
735 secure_channel_type=SEC_CHAN_WKSTA):
736 """Add domain join-specific bits to a secrets database.
738 :param secretsdb: Ldb Handle to the secrets database
739 :param machinepass: Machine password
741 attrs = ["whenChanged",
748 if realm is not None:
749 if dnsdomain is None:
750 dnsdomain = realm.lower()
751 dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
754 shortname = netbiosname.lower()
756 # We don't need to set msg["flatname"] here, because rdn_name will handle
757 # it, and it causes problems for modifies anyway
758 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
759 msg["secureChannelType"] = [str(secure_channel_type)]
760 msg["objectClass"] = ["top", "primaryDomain"]
761 if dnsname is not None:
762 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
763 msg["realm"] = [realm]
764 msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
765 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
766 msg["privateKeytab"] = ["secrets.keytab"]
768 msg["secret"] = [machinepass]
769 msg["samAccountName"] = ["%s$" % netbiosname]
770 msg["secureChannelType"] = [str(secure_channel_type)]
771 if domainsid is not None:
772 msg["objectSid"] = [ndr_pack(domainsid)]
774 # This complex expression tries to ensure that we don't have more
775 # than one record for this SID, realm or netbios domain at a time,
776 # but we don't delete the old record that we are about to modify,
777 # because that would delete the keytab and previous password.
778 res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
779 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
780 scope=ldb.SCOPE_ONELEVEL)
783 secretsdb.delete(del_msg.dn)
785 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
788 msg["priorSecret"] = [res[0]["secret"][0]]
789 msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
792 msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
797 msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
803 msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
804 secretsdb.modify(msg)
805 secretsdb.rename(res[0].dn, msg.dn)
807 spn = [ 'HOST/%s' % shortname ]
808 if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
809 # we are a domain controller then we add servicePrincipalName
810 # entries for the keytab code to update.
811 spn.extend([ 'HOST/%s' % dnsname ])
812 msg["servicePrincipalName"] = spn
817 def setup_secretsdb(paths, session_info, backend_credentials, lp):
818 """Setup the secrets database.
820 :note: This function does not handle exceptions and transaction on purpose,
821 it's up to the caller to do this job.
823 :param path: Path to the secrets database.
824 :param session_info: Session info.
825 :param credentials: Credentials
826 :param lp: Loadparm context
827 :return: LDB handle for the created secrets database
829 if os.path.exists(paths.secrets):
830 os.unlink(paths.secrets)
832 keytab_path = os.path.join(paths.private_dir, paths.keytab)
833 if os.path.exists(keytab_path):
834 os.unlink(keytab_path)
836 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
837 if os.path.exists(dns_keytab_path):
838 os.unlink(dns_keytab_path)
842 secrets_ldb = Ldb(path, session_info=session_info,
845 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
846 secrets_ldb = Ldb(path, session_info=session_info,
848 secrets_ldb.transaction_start()
850 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
852 if (backend_credentials is not None and
853 backend_credentials.authentication_requested()):
854 if backend_credentials.get_bind_dn() is not None:
855 setup_add_ldif(secrets_ldb,
856 setup_path("secrets_simple_ldap.ldif"), {
857 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
858 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
861 setup_add_ldif(secrets_ldb,
862 setup_path("secrets_sasl_ldap.ldif"), {
863 "LDAPADMINUSER": backend_credentials.get_username(),
864 "LDAPADMINREALM": backend_credentials.get_realm(),
865 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
868 secrets_ldb.transaction_cancel()
874 def setup_privileges(path, session_info, lp):
875 """Setup the privileges database.
877 :param path: Path to the privileges database.
878 :param session_info: Session info.
879 :param credentials: Credentials
880 :param lp: Loadparm context
881 :return: LDB handle for the created secrets database
883 if os.path.exists(path):
885 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
886 privilege_ldb.erase()
887 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
890 def setup_registry(path, session_info, lp):
891 """Setup the registry.
893 :param path: Path to the registry database
894 :param session_info: Session information
895 :param credentials: Credentials
896 :param lp: Loadparm context
898 reg = samba.registry.Registry()
899 hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
900 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
901 provision_reg = setup_path("provision.reg")
902 assert os.path.exists(provision_reg)
903 reg.diff_apply(provision_reg)
906 def setup_idmapdb(path, session_info, lp):
907 """Setup the idmap database.
909 :param path: path to the idmap database
910 :param session_info: Session information
911 :param credentials: Credentials
912 :param lp: Loadparm context
914 if os.path.exists(path):
917 idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
919 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
923 def setup_samdb_rootdse(samdb, names):
924 """Setup the SamDB rootdse.
926 :param samdb: Sam Database handle
928 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
929 "SCHEMADN": names.schemadn,
930 "DOMAINDN": names.domaindn,
931 "ROOTDN" : names.rootdn,
932 "CONFIGDN": names.configdn,
933 "SERVERDN": names.serverdn,
937 def setup_self_join(samdb, admin_session_info, names, fill, machinepass, dnspass,
938 domainsid, next_rid, invocationid,
939 policyguid, policyguid_dc, domainControllerFunctionality,
940 ntdsguid, dc_rid=None):
941 """Join a host to its own domain."""
942 assert isinstance(invocationid, str)
943 if ntdsguid is not None:
944 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
951 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
952 "CONFIGDN": names.configdn,
953 "SCHEMADN": names.schemadn,
954 "DOMAINDN": names.domaindn,
955 "SERVERDN": names.serverdn,
956 "INVOCATIONID": invocationid,
957 "NETBIOSNAME": names.netbiosname,
958 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
959 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
960 "DOMAINSID": str(domainsid),
961 "DCRID": str(dc_rid),
962 "SAMBA_VERSION_STRING": version,
963 "NTDSGUID": ntdsguid_line,
964 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
965 domainControllerFunctionality),
966 "RIDALLOCATIONSTART": str(next_rid + 100),
967 "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
969 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
970 "POLICYGUID": policyguid,
971 "POLICYGUID_DC": policyguid_dc,
972 "DNSDOMAIN": names.dnsdomain,
973 "DOMAINDN": names.domaindn})
975 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
976 if fill == FILL_FULL:
977 setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), {
978 "CONFIGDN": names.configdn,
979 "SCHEMADN": names.schemadn,
980 "DOMAINDN": names.domaindn,
981 "SERVERDN": names.serverdn,
982 "INVOCATIONID": invocationid,
983 "NETBIOSNAME": names.netbiosname,
984 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
985 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
986 "DOMAINSID": str(domainsid),
987 "DCRID": str(dc_rid),
988 "SAMBA_VERSION_STRING": version,
989 "NTDSGUID": ntdsguid_line,
990 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
991 domainControllerFunctionality)})
993 # Setup fSMORoleOwner entries to point at the newly created DC entry
994 setup_modify_ldif(samdb, setup_path("provision_self_join_modify_config.ldif"), {
995 "CONFIGDN": names.configdn,
996 "SCHEMADN": names.schemadn,
997 "DEFAULTSITE": names.sitename,
998 "NETBIOSNAME": names.netbiosname,
999 "SERVERDN": names.serverdn,
1002 system_session_info = system_session()
1003 samdb.set_session_info(system_session_info)
1004 # Setup fSMORoleOwner entries to point at the newly created DC entry
1006 # to modify a serverReference under cn=config when we are a subdomain, we must
1007 # be system due to ACLs
1008 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1009 "DOMAINDN": names.domaindn,
1010 "SERVERDN": names.serverdn,
1011 "NETBIOSNAME": names.netbiosname,
1014 samdb.set_session_info(admin_session_info)
1016 # This is Samba4 specific and should be replaced by the correct
1017 # DNS AD-style setup
1018 setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
1019 "DNSDOMAIN": names.dnsdomain,
1020 "DOMAINDN": names.domaindn,
1021 "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
1022 "HOSTNAME" : names.hostname,
1023 "DNSNAME" : '%s.%s' % (
1024 names.netbiosname.lower(), names.dnsdomain.lower())
1028 def getpolicypath(sysvolpath, dnsdomain, guid):
1029 """Return the physical path of policy given its guid.
1031 :param sysvolpath: Path to the sysvol folder
1032 :param dnsdomain: DNS name of the AD domain
1033 :param guid: The GUID of the policy
1034 :return: A string with the complete path to the policy folder
1038 guid = "{%s}" % guid
1039 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
1043 def create_gpo_struct(policy_path):
1044 if not os.path.exists(policy_path):
1045 os.makedirs(policy_path, 0775)
1046 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1047 "[General]\r\nVersion=0")
1048 p = os.path.join(policy_path, "MACHINE")
1049 if not os.path.exists(p):
1050 os.makedirs(p, 0775)
1051 p = os.path.join(policy_path, "USER")
1052 if not os.path.exists(p):
1053 os.makedirs(p, 0775)
1056 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
1057 """Create the default GPO for a domain
1059 :param sysvolpath: Physical path for the sysvol folder
1060 :param dnsdomain: DNS domain name of the AD domain
1061 :param policyguid: GUID of the default domain policy
1062 :param policyguid_dc: GUID of the default domain controler policy
1064 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
1065 create_gpo_struct(policy_path)
1067 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
1068 create_gpo_struct(policy_path)
1071 def setup_samdb(path, session_info, provision_backend, lp, names,
1072 logger, fill, serverrole, schema, am_rodc=False):
1073 """Setup a complete SAM Database.
1075 :note: This will wipe the main SAM database file!
1078 # Also wipes the database
1079 setup_samdb_partitions(path, logger=logger, lp=lp,
1080 provision_backend=provision_backend, session_info=session_info,
1081 names=names, serverrole=serverrole, schema=schema)
1083 # Load the database, but don's load the global schema and don't connect
1085 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
1086 credentials=provision_backend.credentials, lp=lp,
1087 global_schema=False, am_rodc=am_rodc)
1089 logger.info("Pre-loading the Samba 4 and AD schema")
1091 # Load the schema from the one we computed earlier
1092 samdb.set_schema(schema)
1094 # Set the NTDS settings DN manually - in order to have it already around
1095 # before the provisioned tree exists and we connect
1096 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1098 # And now we can connect to the DB - the schema won't be loaded from the
1104 def fill_samdb(samdb, lp, names,
1105 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
1106 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
1107 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
1108 next_rid=None, dc_rid=None):
1110 if next_rid is None:
1113 # Provision does not make much sense values larger than 1000000000
1114 # as the upper range of the rIDAvailablePool is 1073741823 and
1115 # we don't want to create a domain that cannot allocate rids.
1116 if next_rid < 1000 or next_rid > 1000000000:
1117 error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
1118 error += "the valid range is %u-%u. The default is %u." % (
1119 1000, 1000000000, 1000)
1120 raise ProvisioningError(error)
1122 # ATTENTION: Do NOT change these default values without discussion with the
1123 # team and/or release manager. They have a big impact on the whole program!
1124 domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
1126 if dom_for_fun_level is None:
1127 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1129 if dom_for_fun_level > domainControllerFunctionality:
1130 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!")
1132 domainFunctionality = dom_for_fun_level
1133 forestFunctionality = dom_for_fun_level
1135 # Set the NTDS settings DN manually - in order to have it already around
1136 # before the provisioned tree exists and we connect
1137 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1139 samdb.transaction_start()
1141 # Set the domain functionality levels onto the database.
1142 # Various module (the password_hash module in particular) need
1143 # to know what level of AD we are emulating.
1145 # These will be fixed into the database via the database
1146 # modifictions below, but we need them set from the start.
1147 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1148 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1149 samdb.set_opaque_integer("domainControllerFunctionality",
1150 domainControllerFunctionality)
1152 samdb.set_domain_sid(str(domainsid))
1153 samdb.set_invocation_id(invocationid)
1155 logger.info("Adding DomainDN: %s" % names.domaindn)
1157 # impersonate domain admin
1158 admin_session_info = admin_session(lp, str(domainsid))
1159 samdb.set_session_info(admin_session_info)
1160 if domainguid is not None:
1161 domainguid_line = "objectGUID: %s\n-" % domainguid
1163 domainguid_line = ""
1165 descr = b64encode(get_domain_descriptor(domainsid))
1166 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1167 "DOMAINDN": names.domaindn,
1168 "DOMAINSID": str(domainsid),
1169 "DESCRIPTOR": descr,
1170 "DOMAINGUID": domainguid_line
1173 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1174 "DOMAINDN": names.domaindn,
1175 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1176 "NEXTRID": str(next_rid),
1177 "DEFAULTSITE": names.sitename,
1178 "CONFIGDN": names.configdn,
1179 "POLICYGUID": policyguid,
1180 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1181 "SAMBA_VERSION_STRING": version
1184 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1185 if fill == FILL_FULL:
1186 logger.info("Adding configuration container")
1187 descr = b64encode(get_config_descriptor(domainsid))
1188 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1189 "CONFIGDN": names.configdn,
1190 "DESCRIPTOR": descr,
1193 # The LDIF here was created when the Schema object was constructed
1194 logger.info("Setting up sam.ldb schema")
1195 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1196 samdb.modify_ldif(schema.schema_dn_modify)
1197 samdb.write_prefixes_from_schema()
1198 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1199 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1200 {"SCHEMADN": names.schemadn})
1202 # Now register this container in the root of the forest
1203 msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
1204 msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
1208 samdb.transaction_cancel()
1211 samdb.transaction_commit()
1213 samdb.transaction_start()
1215 samdb.invocation_id = invocationid
1217 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1218 if fill == FILL_FULL:
1219 logger.info("Setting up sam.ldb configuration data")
1220 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1221 "CONFIGDN": names.configdn,
1222 "NETBIOSNAME": names.netbiosname,
1223 "DEFAULTSITE": names.sitename,
1224 "DNSDOMAIN": names.dnsdomain,
1225 "DOMAIN": names.domain,
1226 "SCHEMADN": names.schemadn,
1227 "DOMAINDN": names.domaindn,
1228 "SERVERDN": names.serverdn,
1229 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1230 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1233 logger.info("Setting up display specifiers")
1234 display_specifiers_ldif = read_ms_ldif(
1235 setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1236 display_specifiers_ldif = substitute_var(display_specifiers_ldif,
1237 {"CONFIGDN": names.configdn})
1238 check_all_substituted(display_specifiers_ldif)
1239 samdb.add_ldif(display_specifiers_ldif)
1241 logger.info("Adding users container")
1242 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1243 "DOMAINDN": names.domaindn})
1244 logger.info("Modifying users container")
1245 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1246 "DOMAINDN": names.domaindn})
1247 logger.info("Adding computers container")
1248 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1249 "DOMAINDN": names.domaindn})
1250 logger.info("Modifying computers container")
1251 setup_modify_ldif(samdb,
1252 setup_path("provision_computers_modify.ldif"), {
1253 "DOMAINDN": names.domaindn})
1254 logger.info("Setting up sam.ldb data")
1255 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1256 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1257 "DOMAINDN": names.domaindn,
1258 "NETBIOSNAME": names.netbiosname,
1259 "DEFAULTSITE": names.sitename,
1260 "CONFIGDN": names.configdn,
1261 "SERVERDN": names.serverdn,
1262 "RIDAVAILABLESTART": str(next_rid + 600),
1263 "POLICYGUID_DC": policyguid_dc
1266 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1267 if fill == FILL_FULL:
1268 setup_modify_ldif(samdb,
1269 setup_path("provision_configuration_references.ldif"), {
1270 "CONFIGDN": names.configdn,
1271 "SCHEMADN": names.schemadn})
1273 logger.info("Setting up well known security principals")
1274 setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
1275 "CONFIGDN": names.configdn,
1278 if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
1279 setup_modify_ldif(samdb,
1280 setup_path("provision_basedn_references.ldif"),
1281 {"DOMAINDN": names.domaindn})
1283 logger.info("Setting up sam.ldb users and groups")
1284 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1285 "DOMAINDN": names.domaindn,
1286 "DOMAINSID": str(domainsid),
1287 "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
1288 "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
1291 logger.info("Setting up self join")
1292 setup_self_join(samdb, admin_session_info, names=names, fill=fill, invocationid=invocationid,
1294 machinepass=machinepass,
1295 domainsid=domainsid,
1298 policyguid=policyguid,
1299 policyguid_dc=policyguid_dc,
1300 domainControllerFunctionality=domainControllerFunctionality,
1303 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1304 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1305 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1306 assert isinstance(names.ntdsguid, str)
1308 samdb.transaction_cancel()
1311 samdb.transaction_commit()
1316 FILL_SUBDOMAIN = "SUBDOMAIN"
1317 FILL_NT4SYNC = "NT4SYNC"
1319 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1320 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)"
1323 def set_dir_acl(path, acl, lp, domsid):
1324 setntacl(lp, path, acl, domsid)
1325 for root, dirs, files in os.walk(path, topdown=False):
1327 setntacl(lp, os.path.join(root, name), acl, domsid)
1329 setntacl(lp, os.path.join(root, name), acl, domsid)
1332 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1333 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1336 :param sysvol: Physical path for the sysvol folder
1337 :param dnsdomain: The DNS name of the domain
1338 :param domainsid: The SID of the domain
1339 :param domaindn: The DN of the domain (ie. DC=...)
1340 :param samdb: An LDB object on the SAM db
1341 :param lp: an LP object
1344 # Set ACL for GPO root folder
1345 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1346 setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
1348 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1349 attrs=["cn", "nTSecurityDescriptor"],
1350 expression="", scope=ldb.SCOPE_ONELEVEL)
1353 acl = ndr_unpack(security.descriptor,
1354 str(policy["nTSecurityDescriptor"])).as_sddl()
1355 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1356 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1360 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1362 """Set the ACL for the sysvol share and the subfolders
1364 :param samdb: An LDB object on the SAM db
1365 :param netlogon: Physical path for the netlogon folder
1366 :param sysvol: Physical path for the sysvol folder
1367 :param gid: The GID of the "Domain adminstrators" group
1368 :param domainsid: The SID of the domain
1369 :param dnsdomain: The DNS name of the domain
1370 :param domaindn: The DN of the domain (ie. DC=...)
1374 os.chown(sysvol, -1, gid)
1380 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1381 setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
1382 for root, dirs, files in os.walk(sysvol, topdown=False):
1385 os.chown(os.path.join(root, name), -1, gid)
1386 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1389 os.chown(os.path.join(root, name), -1, gid)
1390 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1392 # Set acls on Policy folder and policies folders
1393 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
1396 def interface_ips_v4(lp):
1397 '''return only IPv4 IPs'''
1398 ips = samba.interface_ips(lp, False)
1401 if i.find(':') == -1:
1405 def interface_ips_v6(lp, linklocal=False):
1406 '''return only IPv6 IPs'''
1407 ips = samba.interface_ips(lp, False)
1410 if i.find(':') != -1 and (linklocal or i.find('%') == -1):
1415 def provision_fill(samdb, secrets_ldb, logger, names, paths,
1416 domainsid, schema=None,
1417 targetdir=None, samdb_fill=FILL_FULL,
1418 hostip=None, hostip6=None,
1419 next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
1420 domainguid=None, policyguid=None, policyguid_dc=None,
1421 invocationid=None, machinepass=None, ntdsguid=None,
1422 dns_backend=None, dnspass=None,
1423 serverrole=None, dom_for_fun_level=None,
1424 am_rodc=False, lp=None):
1425 # create/adapt the group policy GUIDs
1426 # Default GUID for default policy are described at
1427 # "How Core Group Policy Works"
1428 # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
1429 if policyguid is None:
1430 policyguid = DEFAULT_POLICY_GUID
1431 policyguid = policyguid.upper()
1432 if policyguid_dc is None:
1433 policyguid_dc = DEFAULT_DC_POLICY_GUID
1434 policyguid_dc = policyguid_dc.upper()
1436 if invocationid is None:
1437 invocationid = str(uuid.uuid4())
1439 if krbtgtpass is None:
1440 krbtgtpass = samba.generate_random_password(128, 255)
1441 if machinepass is None:
1442 machinepass = samba.generate_random_password(128, 255)
1444 dnspass = samba.generate_random_password(128, 255)
1446 samdb = fill_samdb(samdb, lp, names, logger=logger,
1447 domainsid=domainsid, schema=schema, domainguid=domainguid,
1448 policyguid=policyguid, policyguid_dc=policyguid_dc,
1449 fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
1450 invocationid=invocationid, machinepass=machinepass,
1451 dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
1452 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1453 next_rid=next_rid, dc_rid=dc_rid)
1455 if serverrole == "domain controller":
1456 # Set up group policies (domain policy and domain controller
1458 create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
1460 setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
1461 domainsid, names.dnsdomain, names.domaindn, lp)
1463 secretsdb_self_join(secrets_ldb, domain=names.domain,
1464 realm=names.realm, dnsdomain=names.dnsdomain,
1465 netbiosname=names.netbiosname, domainsid=domainsid,
1466 machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
1468 # Now set up the right msDS-SupportedEncryptionTypes into the DB
1469 # In future, this might be determined from some configuration
1470 kerberos_enctypes = str(ENC_ALL_TYPES)
1473 msg = ldb.Message(ldb.Dn(samdb,
1474 samdb.searchone("distinguishedName",
1475 expression="samAccountName=%s$" % names.netbiosname,
1476 scope=ldb.SCOPE_SUBTREE)))
1477 msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
1478 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
1479 name="msDS-SupportedEncryptionTypes")
1481 except ldb.LdbError, (enum, estr):
1482 if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
1483 # It might be that this attribute does not exist in this schema
1486 setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
1487 hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
1488 dnspass=dnspass, os_level=dom_for_fun_level,
1489 targetdir=targetdir, site=DEFAULTSITE)
1491 domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
1492 attribute="objectGUID")
1493 assert isinstance(domainguid, str)
1495 lastProvisionUSNs = get_last_provision_usn(samdb)
1496 maxUSN = get_max_usn(samdb, str(names.rootdn))
1497 if lastProvisionUSNs is not None:
1498 update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
1500 set_provision_usn(samdb, 0, maxUSN, invocationid)
1502 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1503 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
1504 { 'NTDSGUID' : names.ntdsguid })
1506 # fix any dangling GUIDs from the provision
1507 logger.info("Fixing provision GUIDs")
1508 chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True, quiet=True)
1509 samdb.transaction_start()
1510 # a small number of GUIDs are missing because of ordering issues in the
1512 for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
1513 chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
1514 scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
1515 chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
1516 scope=ldb.SCOPE_ONELEVEL,
1517 attrs=['ipsecOwnersReference',
1518 'ipsecFilterReference',
1519 'ipsecISAKMPReference',
1520 'ipsecNegotiationPolicyReference',
1521 'ipsecNFAReference'])
1522 samdb.transaction_commit()
1524 def provision(logger, session_info, credentials, smbconf=None,
1525 targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
1526 domaindn=None, schemadn=None, configdn=None, serverdn=None,
1527 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
1528 next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None, krbtgtpass=None,
1529 domainguid=None, policyguid=None, policyguid_dc=None,
1530 dns_backend=None, dnspass=None,
1531 invocationid=None, machinepass=None, ntdsguid=None,
1532 root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
1533 serverrole=None, dom_for_fun_level=None,
1534 backend_type=None, sitename=None,
1535 ol_mmr_urls=None, ol_olc=None, slapd_path=None,
1536 useeadb=False, am_rodc=False,
1540 :note: caution, this wipes all existing data!
1544 roles["ROLE_STANDALONE"] = "standalone"
1545 roles["ROLE_DOMAIN_MEMBER"] = "member server"
1546 roles["ROLE_DOMAIN_BDC"] = "domain controller"
1547 roles["ROLE_DOMAIN_PDC"] = "domain controller"
1548 roles["dc"] = "domain controller"
1549 roles["member"] = "member server"
1550 roles["domain controller"] = "domain controller"
1551 roles["member server"] = "member server"
1552 roles["standalone"] = "standalone"
1555 serverrole = roles[serverrole]
1557 raise ProvisioningError('server role (%s) should be one of "domain controller", "member server", "standalone"' % serverrole)
1559 if ldapadminpass is None:
1560 # Make a new, random password between Samba and it's LDAP server
1561 ldapadminpass=samba.generate_random_password(128, 255)
1563 if backend_type is None:
1564 backend_type = "ldb"
1566 if domainsid is None:
1567 domainsid = security.random_sid()
1569 domainsid = security.dom_sid(domainsid)
1571 sid_generator = "internal"
1572 if backend_type == "fedora-ds":
1573 sid_generator = "backend"
1575 root_uid = findnss_uid([root or "root"])
1576 nobody_uid = findnss_uid([nobody or "nobody"])
1577 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1579 wheel_gid = findnss_gid(["wheel", "adm"])
1581 wheel_gid = findnss_gid([wheel])
1583 bind_gid = findnss_gid(["bind", "named"])
1587 if targetdir is not None:
1588 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1589 elif smbconf is None:
1590 smbconf = samba.param.default_path()
1591 if not os.path.exists(os.path.dirname(smbconf)):
1592 os.makedirs(os.path.dirname(smbconf))
1594 server_services = None
1595 if dns_backend == "SAMBA_INTERNAL":
1596 server_services = [ "+dns" ]
1598 # only install a new smb.conf if there isn't one there already
1599 if os.path.exists(smbconf):
1600 # if Samba Team members can't figure out the weird errors
1601 # loading an empty smb.conf gives, then we need to be smarter.
1602 # Pretend it just didn't exist --abartlet
1603 data = open(smbconf, 'r').read()
1604 data = data.lstrip()
1605 if data is None or data == "":
1606 make_smbconf(smbconf, hostname, domain, realm,
1607 serverrole, targetdir, sid_generator, useeadb,
1608 lp=lp, server_services=server_services)
1610 make_smbconf(smbconf, hostname, domain, realm, serverrole,
1611 targetdir, sid_generator, useeadb, lp=lp,
1612 server_services=server_services)
1615 lp = samba.param.LoadParm()
1617 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1618 dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
1619 configdn=configdn, schemadn=schemadn, serverdn=serverdn,
1620 sitename=sitename, rootdn=rootdn)
1621 paths = provision_paths_from_lp(lp, names.dnsdomain)
1623 paths.bind_gid = bind_gid
1624 paths.wheel_gid = wheel_gid
1627 logger.info("Looking up IPv4 addresses")
1628 hostips = interface_ips_v4(lp)
1629 if len(hostips) > 0:
1631 if len(hostips) > 1:
1632 logger.warning("More than one IPv4 address found. Using %s",
1634 if hostip == "127.0.0.1":
1637 logger.warning("No IPv4 address will be assigned")
1640 logger.info("Looking up IPv6 addresses")
1641 hostips = interface_ips_v6(lp, linklocal=False)
1643 hostip6 = hostips[0]
1644 if len(hostips) > 1:
1645 logger.warning("More than one IPv6 address found. Using %s", hostip6)
1647 logger.warning("No IPv6 address will be assigned")
1649 names.hostip = hostip
1650 names.hostip6 = hostip6
1652 if serverrole is None:
1653 serverrole = lp.get("server role")
1655 if not os.path.exists(paths.private_dir):
1656 os.mkdir(paths.private_dir)
1657 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1658 os.mkdir(os.path.join(paths.private_dir, "tls"))
1660 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1662 schema = Schema(domainsid, invocationid=invocationid,
1663 schemadn=names.schemadn)
1665 if backend_type == "ldb":
1666 provision_backend = LDBBackend(backend_type, paths=paths,
1667 lp=lp, credentials=credentials,
1668 names=names, logger=logger)
1669 elif backend_type == "existing":
1670 # If support for this is ever added back, then the URI will need to be specified again
1671 provision_backend = ExistingBackend(backend_type, paths=paths,
1672 lp=lp, credentials=credentials,
1673 names=names, logger=logger,
1674 ldap_backend_forced_uri=None)
1675 elif backend_type == "fedora-ds":
1676 provision_backend = FDSBackend(backend_type, paths=paths,
1677 lp=lp, credentials=credentials,
1678 names=names, logger=logger, domainsid=domainsid,
1679 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1680 slapd_path=slapd_path,
1682 elif backend_type == "openldap":
1683 provision_backend = OpenLDAPBackend(backend_type, paths=paths,
1684 lp=lp, credentials=credentials,
1685 names=names, logger=logger, domainsid=domainsid,
1686 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1687 slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls)
1689 raise ValueError("Unknown LDAP backend type selected")
1691 provision_backend.init()
1692 provision_backend.start()
1694 # only install a new shares config db if there is none
1695 if not os.path.exists(paths.shareconf):
1696 logger.info("Setting up share.ldb")
1697 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1699 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1701 logger.info("Setting up secrets.ldb")
1702 secrets_ldb = setup_secretsdb(paths,
1703 session_info=session_info,
1704 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1707 logger.info("Setting up the registry")
1708 setup_registry(paths.hklm, session_info,
1711 logger.info("Setting up the privileges database")
1712 setup_privileges(paths.privilege, session_info, lp=lp)
1714 logger.info("Setting up idmap db")
1715 idmap = setup_idmapdb(paths.idmapdb,
1716 session_info=session_info, lp=lp)
1718 setup_name_mappings(idmap, sid=str(domainsid),
1719 root_uid=root_uid, nobody_uid=nobody_uid,
1720 users_gid=users_gid, wheel_gid=wheel_gid)
1722 logger.info("Setting up SAM db")
1723 samdb = setup_samdb(paths.samdb, session_info,
1724 provision_backend, lp, names, logger=logger,
1725 serverrole=serverrole,
1726 schema=schema, fill=samdb_fill, am_rodc=am_rodc)
1728 if serverrole == "domain controller":
1729 if paths.netlogon is None:
1730 logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1731 logger.info("Please either remove %s or see the template at %s" %
1732 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1733 assert paths.netlogon is not None
1735 if paths.sysvol is None:
1736 logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1737 " are configuring a DC.")
1738 logger.info("Please either remove %s or see the template at %s" %
1739 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1740 assert paths.sysvol is not None
1742 if not os.path.isdir(paths.netlogon):
1743 os.makedirs(paths.netlogon, 0755)
1745 if adminpass is None:
1746 adminpass = samba.generate_random_password(12, 32)
1747 adminpass_generated = True
1749 adminpass_generated = False
1751 if samdb_fill == FILL_FULL:
1752 provision_fill(samdb, secrets_ldb, logger,
1753 names, paths, schema=schema, targetdir=targetdir,
1754 samdb_fill=samdb_fill, hostip=hostip, hostip6=hostip6, domainsid=domainsid,
1755 next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
1756 krbtgtpass=krbtgtpass, domainguid=domainguid,
1757 policyguid=policyguid, policyguid_dc=policyguid_dc,
1758 invocationid=invocationid, machinepass=machinepass,
1759 ntdsguid=ntdsguid, dns_backend=dns_backend, dnspass=dnspass,
1760 serverrole=serverrole, dom_for_fun_level=dom_for_fun_level,
1761 am_rodc=am_rodc, lp=lp)
1763 create_krb5_conf(paths.krb5conf,
1764 dnsdomain=names.dnsdomain, hostname=names.hostname,
1766 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1767 "generated at %s", paths.krb5conf)
1769 if serverrole == "domain controller":
1770 create_dns_update_list(lp, logger, paths)
1772 provision_backend.post_setup()
1773 provision_backend.shutdown()
1775 create_phpldapadmin_config(paths.phpldapadminconfig,
1778 secrets_ldb.transaction_cancel()
1781 # Now commit the secrets.ldb to disk
1782 secrets_ldb.transaction_commit()
1784 # the commit creates the dns.keytab, now chown it
1785 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1786 if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
1788 os.chmod(dns_keytab_path, 0640)
1789 os.chown(dns_keytab_path, -1, paths.bind_gid)
1791 if not os.environ.has_key('SAMBA_SELFTEST'):
1792 logger.info("Failed to chown %s to bind gid %u",
1793 dns_keytab_path, paths.bind_gid)
1795 logger.info("A phpLDAPadmin configuration file suitable for administering the Samba 4 LDAP server has been created in %s .",
1796 paths.phpldapadminconfig)
1798 logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1799 logger.info("Server Role: %s" % serverrole)
1800 logger.info("Hostname: %s" % names.hostname)
1801 logger.info("NetBIOS Domain: %s" % names.domain)
1802 logger.info("DNS Domain: %s" % names.dnsdomain)
1803 logger.info("DOMAIN SID: %s" % str(domainsid))
1804 if samdb_fill == FILL_FULL:
1805 if adminpass_generated:
1806 logger.info("Admin password: %s" % adminpass)
1807 if provision_backend.type is not "ldb":
1808 if provision_backend.credentials.get_bind_dn() is not None:
1809 logger.info("LDAP Backend Admin DN: %s" %
1810 provision_backend.credentials.get_bind_dn())
1812 logger.info("LDAP Admin User: %s" %
1813 provision_backend.credentials.get_username())
1815 if provision_backend.slapd_command_escaped is not None:
1816 # now display slapd_command_file.txt to show how slapd must be
1818 logger.info("Use later the following commandline to start slapd, then Samba:")
1819 logger.info(provision_backend.slapd_command_escaped)
1820 logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
1821 provision_backend.ldapdir)
1823 result = ProvisionResult()
1824 result.domaindn = domaindn
1825 result.paths = paths
1826 result.names = names
1828 result.samdb = samdb
1829 result.idmap = idmap
1833 def provision_become_dc(smbconf=None, targetdir=None,
1834 realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
1835 serverdn=None, domain=None, hostname=None, domainsid=None,
1836 adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
1837 policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
1838 dns_backend=None, root=None, nobody=None, users=None, wheel=None, backup=None,
1839 serverrole=None, ldap_backend=None, ldap_backend_type=None,
1840 sitename=None, debuglevel=1):
1842 logger = logging.getLogger("provision")
1843 samba.set_debug_level(debuglevel)
1845 res = provision(logger, system_session(), None,
1846 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1847 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1848 configdn=configdn, serverdn=serverdn, domain=domain,
1849 hostname=hostname, hostip=None, domainsid=domainsid,
1850 machinepass=machinepass, serverrole="domain controller",
1851 sitename=sitename, dns_backend=dns_backend, dnspass=dnspass)
1852 res.lp.set("debuglevel", str(debuglevel))
1856 def create_phpldapadmin_config(path, ldapi_uri):
1857 """Create a PHP LDAP admin configuration file.
1859 :param path: Path to write the configuration to.
1861 setup_file(setup_path("phpldapadmin-config.php"), path,
1862 {"S4_LDAPI_URI": ldapi_uri})
1865 def create_krb5_conf(path, dnsdomain, hostname, realm):
1866 """Write out a file containing zone statements suitable for inclusion in a
1867 named.conf file (including GSS-TSIG configuration).
1869 :param path: Path of the new named.conf file.
1870 :param dnsdomain: DNS Domain name
1871 :param hostname: Local hostname
1872 :param realm: Realm name
1874 setup_file(setup_path("krb5.conf"), path, {
1875 "DNSDOMAIN": dnsdomain,
1876 "HOSTNAME": hostname,
1881 class ProvisioningError(Exception):
1882 """A generic provision error."""
1884 def __init__(self, value):
1888 return "ProvisioningError: " + self.value
1891 class InvalidNetbiosName(Exception):
1892 """A specified name was not a valid NetBIOS name."""
1893 def __init__(self, name):
1894 super(InvalidNetbiosName, self).__init__(
1895 "The name '%r' is not a valid NetBIOS name" % name)