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 from base64 import b64encode
42 from samba.auth import system_session, admin_session
44 from samba import version, Ldb, substitute_var, valid_netbios_name
45 from samba import check_all_substituted, read_and_sub_file, setup_file
46 from samba.dsdb import DS_DOMAIN_FUNCTION_2003, DS_DC_FUNCTION_2008
47 from samba.dcerpc import security
48 from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
49 from samba.idmap import IDmapDB
50 from samba.ms_display_specifiers import read_ms_ldif
51 from samba.ntacls import setntacl, dsacl2fsacl
52 from samba.ndr import ndr_pack,ndr_unpack
53 from samba.provisionbackend import (
61 from samba.schema import Schema
62 from samba.samdb import SamDB
64 __docformat__ = "restructuredText"
67 """Find the setup directory used by provision."""
69 for suffix in ["share/setup", "share/samba/setup", "setup"]:
70 ret = os.path.join(sys.prefix, suffix)
71 if os.path.isdir(ret):
74 dirname = os.path.dirname(__file__)
75 ret = os.path.join(dirname, "../../../setup")
76 if os.path.isdir(ret):
78 raise Exception("Unable to find setup directory.")
80 # descriptors of the naming contexts
81 # hard coded at this point, but will probably be changed when
82 # we enable different fsmo roles
85 def get_config_descriptor(domain_sid):
86 sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
87 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
88 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
89 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
90 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
91 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
92 "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
93 "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
94 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
95 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
96 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
97 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
98 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
99 "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
100 "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
101 sec = security.descriptor.from_sddl(sddl, domain_sid)
104 def get_domain_descriptor(domain_sid):
105 sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
106 "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
107 "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
108 "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
109 "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
110 "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
111 "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
112 "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
113 "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
114 "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
115 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
116 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \
117 "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
118 "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
119 "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
120 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
121 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
122 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
123 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
124 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
125 "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
126 "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
127 "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
128 "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
129 "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
130 "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
131 "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
132 "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
133 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
134 "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
135 "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
136 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
137 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
138 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
139 "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
140 "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
141 "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
142 "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
143 "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
146 "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
148 "(A;;RPLCLORC;;;ED)" \
149 "(A;;RPLCLORC;;;AU)" \
150 "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
151 "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
152 "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
153 "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)"
154 sec = security.descriptor.from_sddl(sddl, domain_sid)
157 DEFAULTSITE = "Default-First-Site-Name"
158 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
160 class ProvisionPaths(object):
163 self.shareconf = None
174 self.dns_keytab = None
177 self.private_dir = None
180 class ProvisionNames(object):
187 self.ldapmanagerdn = None
188 self.dnsdomain = None
190 self.netbiosname = None
197 def update_provision_usn(samdb, low, high, replace=False):
198 """Update the field provisionUSN in sam.ldb
200 This field is used to track range of USN modified by provision and
202 This value is used afterward by next provision to figure out if
203 the field have been modified since last provision.
205 :param samdb: An LDB object connect to sam.ldb
206 :param low: The lowest USN modified by this upgrade
207 :param high: The highest USN modified by this upgrade
208 :param replace: A boolean indicating if the range should replace any
209 existing one or appended (default)
214 entry = samdb.search(expression="(&(dn=@PROVISION)(%s=*))" % \
215 LAST_PROVISION_USN_ATTRIBUTE, base="",
216 scope=ldb.SCOPE_SUBTREE,
217 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
218 for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
221 tab.append("%s-%s" % (low, high))
222 delta = ldb.Message()
223 delta.dn = ldb.Dn(samdb, "@PROVISION")
224 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
225 ldb.FLAG_MOD_REPLACE,
226 LAST_PROVISION_USN_ATTRIBUTE)
230 def set_provision_usn(samdb, low, high):
231 """Set the field provisionUSN in sam.ldb
232 This field is used to track range of USN modified by provision and
234 This value is used afterward by next provision to figure out if
235 the field have been modified since last provision.
237 :param samdb: An LDB object connect to sam.ldb
238 :param low: The lowest USN modified by this upgrade
239 :param high: The highest USN modified by this upgrade"""
241 tab.append("%s-%s" % (low, high))
242 delta = ldb.Message()
243 delta.dn = ldb.Dn(samdb, "@PROVISION")
244 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
246 LAST_PROVISION_USN_ATTRIBUTE)
250 def get_max_usn(samdb,basedn):
251 """ This function return the biggest USN present in the provision
253 :param samdb: A LDB object pointing to the sam.ldb
254 :param basedn: A string containing the base DN of the provision
256 :return: The biggest USN in the provision"""
258 res = samdb.search(expression="objectClass=*",base=basedn,
259 scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
260 controls=["search_options:1:2",
261 "server_sort:1:1:uSNChanged",
262 "paged_results:1:1"])
263 return res[0]["uSNChanged"]
265 def get_last_provision_usn(sam):
266 """Get the lastest USN modified by a provision or an upgradeprovision
268 :param sam: An LDB object pointing to the sam.ldb
269 :return an integer corresponding to the highest USN modified by
270 (upgrade)provision, 0 is this value is unknown"""
272 entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" % \
273 LAST_PROVISION_USN_ATTRIBUTE,
274 base="", scope=ldb.SCOPE_SUBTREE,
275 attrs=[LAST_PROVISION_USN_ATTRIBUTE])
280 for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
281 tab = p.split(str(r))
289 class ProvisionResult(object):
298 def check_install(lp, session_info, credentials):
299 """Check whether the current install seems ok.
301 :param lp: Loadparm context
302 :param session_info: Session information
303 :param credentials: Credentials
305 if lp.get("realm") == "":
306 raise Exception("Realm empty")
307 samdb = Ldb(lp.get("sam database"), session_info=session_info,
308 credentials=credentials, lp=lp)
309 if len(samdb.search("(cn=Administrator)")) != 1:
310 raise ProvisioningError("No administrator account found")
313 def findnss(nssfn, names):
314 """Find a user or group from a list of possibilities.
316 :param nssfn: NSS Function to try (should raise KeyError if not found)
317 :param names: Names to check.
318 :return: Value return by first names list.
325 raise KeyError("Unable to find user/group in %r" % names)
328 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
329 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
332 def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
333 """Setup a ldb in the private dir.
335 :param ldb: LDB file to import data into
336 :param ldif_path: Path of the LDIF file to load
337 :param subst_vars: Optional variables to subsitute in LDIF.
338 :param nocontrols: Optional list of controls, can be None for no controls
340 assert isinstance(ldif_path, str)
341 data = read_and_sub_file(ldif_path, subst_vars)
342 ldb.add_ldif(data, controls)
345 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
346 """Modify a ldb in the private dir.
348 :param ldb: LDB object.
349 :param ldif_path: LDIF file path.
350 :param subst_vars: Optional dictionary with substitution variables.
352 data = read_and_sub_file(ldif_path, subst_vars)
353 ldb.modify_ldif(data)
356 def setup_ldb(ldb, ldif_path, subst_vars):
357 """Import a LDIF a file into a LDB handle, optionally substituting variables.
359 :note: Either all LDIF data will be added or none (using transactions).
361 :param ldb: LDB file to import into.
362 :param ldif_path: Path to the LDIF file.
363 :param subst_vars: Dictionary with substitution variables.
365 assert ldb is not None
366 ldb.transaction_start()
368 setup_add_ldif(ldb, ldif_path, subst_vars)
370 ldb.transaction_cancel()
373 ldb.transaction_commit()
376 def provision_paths_from_lp(lp, dnsdomain):
377 """Set the default paths for provisioning.
379 :param lp: Loadparm context.
380 :param dnsdomain: DNS Domain name
382 paths = ProvisionPaths()
383 paths.private_dir = lp.get("private dir")
385 # This is stored without path prefix for the "privateKeytab" attribute in
386 # "secrets_dns.ldif".
387 paths.dns_keytab = "dns.keytab"
389 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
390 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
391 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
392 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
393 paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
394 paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
395 paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
396 paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
397 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
398 paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
399 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
400 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
401 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
402 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
403 paths.phpldapadminconfig = os.path.join(paths.private_dir,
404 "phpldapadmin-config.php")
405 paths.hklm = "hklm.ldb"
406 paths.hkcr = "hkcr.ldb"
407 paths.hkcu = "hkcu.ldb"
408 paths.hku = "hku.ldb"
409 paths.hkpd = "hkpd.ldb"
410 paths.hkpt = "hkpt.ldb"
411 paths.sysvol = lp.get("path", "sysvol")
412 paths.netlogon = lp.get("path", "netlogon")
413 paths.smbconf = lp.configfile
417 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
418 serverrole=None, rootdn=None, domaindn=None, configdn=None,
419 schemadn=None, serverdn=None, sitename=None):
420 """Guess configuration settings to use."""
423 hostname = socket.gethostname().split(".")[0]
425 netbiosname = lp.get("netbios name")
426 if netbiosname is None:
427 netbiosname = hostname
428 assert netbiosname is not None
429 netbiosname = netbiosname.upper()
430 if not valid_netbios_name(netbiosname):
431 raise InvalidNetbiosName(netbiosname)
433 if dnsdomain is None:
434 dnsdomain = lp.get("realm")
435 if dnsdomain is None or dnsdomain == "":
436 raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
438 dnsdomain = dnsdomain.lower()
440 if serverrole is None:
441 serverrole = lp.get("server role")
442 if serverrole is None:
443 raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
445 serverrole = serverrole.lower()
447 realm = dnsdomain.upper()
449 if lp.get("realm") == "":
450 raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile)
452 if lp.get("realm").upper() != realm:
453 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))
455 if lp.get("server role").lower() != serverrole:
456 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").upper(), serverrole, lp.configfile))
458 if serverrole == "domain controller":
460 # This will, for better or worse, default to 'WORKGROUP'
461 domain = lp.get("workgroup")
462 domain = domain.upper()
464 if lp.get("workgroup").upper() != domain:
465 raise ProvisioningError("guess_names: Workgroup '%s' in %s must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
468 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
472 domaindn = "DC=" + netbiosname
474 if not valid_netbios_name(domain):
475 raise InvalidNetbiosName(domain)
477 if hostname.upper() == realm:
478 raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
479 if netbiosname == realm:
480 raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
482 raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
488 configdn = "CN=Configuration," + rootdn
490 schemadn = "CN=Schema," + configdn
495 names = ProvisionNames()
496 names.rootdn = rootdn
497 names.domaindn = domaindn
498 names.configdn = configdn
499 names.schemadn = schemadn
500 names.ldapmanagerdn = "CN=Manager," + rootdn
501 names.dnsdomain = dnsdomain
502 names.domain = domain
504 names.netbiosname = netbiosname
505 names.hostname = hostname
506 names.sitename = sitename
507 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
512 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
513 targetdir, sid_generator="internal", eadb=False):
514 """Create a new smb.conf file based on a couple of basic settings.
516 assert smbconf is not None
518 hostname = socket.gethostname().split(".")[0]
519 netbiosname = hostname.upper()
521 if serverrole is None:
522 serverrole = "standalone"
524 assert serverrole in ("domain controller", "member server", "standalone")
525 if serverrole == "domain controller":
527 elif serverrole == "member server":
528 smbconfsuffix = "member"
529 elif serverrole == "standalone":
530 smbconfsuffix = "standalone"
532 if sid_generator is None:
533 sid_generator = "internal"
535 assert domain is not None
536 domain = domain.upper()
538 assert realm is not None
539 realm = realm.upper()
541 default_lp = samba.param.LoadParm()
542 #Load non-existant file
543 if os.path.exists(smbconf):
544 default_lp.load(smbconf)
546 if targetdir is not None:
547 privdir = os.path.join(targetdir, "private")
549 privdir = default_lp.get("private dir")
550 posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(privdir, "eadb.tdb"))
554 if targetdir is not None:
555 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
556 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
558 default_lp.set("lock dir", os.path.abspath(targetdir))
563 if sid_generator == "internal":
564 sid_generator_line = ""
566 sid_generator_line = "sid generator = " + sid_generator
568 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
569 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
571 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
573 "NETBIOS_NAME": netbiosname,
576 "SERVERROLE": serverrole,
577 "NETLOGONPATH": netlogon,
578 "SYSVOLPATH": sysvol,
579 "SIDGENERATOR_LINE": sid_generator_line,
580 "PRIVATEDIR_LINE": privatedir_line,
581 "LOCKDIR_LINE": lockdir_line,
582 "POSIXEADB_LINE": posixeadb_line
586 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
587 users_gid, wheel_gid):
588 """setup reasonable name mappings for sam names to unix names.
590 :param samdb: SamDB object.
591 :param idmap: IDmap db object.
592 :param sid: The domain sid.
593 :param domaindn: The domain DN.
594 :param root_uid: uid of the UNIX root user.
595 :param nobody_uid: uid of the UNIX nobody user.
596 :param users_gid: gid of the UNIX users group.
597 :param wheel_gid: gid of the UNIX wheel group."""
598 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
599 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
601 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
602 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
605 def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info,
606 provision_backend, names, schema, serverrole,
608 """Setup the partitions for the SAM database.
610 Alternatively, provision() may call this, and then populate the database.
612 :note: This will wipe the Sam Database!
614 :note: This function always removes the local SAM LDB file. The erase
615 parameter controls whether to erase the existing data, which
616 may not be stored locally but in LDAP.
619 assert session_info is not None
621 # We use options=["modules:"] to stop the modules loading - we
622 # just want to wipe and re-initialise the database, not start it up
625 os.unlink(samdb_path)
629 samdb = Ldb(url=samdb_path, session_info=session_info,
630 lp=lp, options=["modules:"])
632 ldap_backend_line = "# No LDAP backend"
633 if provision_backend.type is not "ldb":
634 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldapi_uri
636 samdb.transaction_start()
638 logger.info("Setting up sam.ldb partitions and settings")
639 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
640 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(),
641 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
642 "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
643 "LDAP_BACKEND_LINE": ldap_backend_line,
647 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
648 "BACKEND_TYPE": provision_backend.type,
649 "SERVER_ROLE": serverrole
652 logger.info("Setting up sam.ldb rootDSE")
653 setup_samdb_rootdse(samdb, setup_path, names)
655 samdb.transaction_cancel()
658 samdb.transaction_commit()
661 def secretsdb_self_join(secretsdb, domain,
662 netbiosname, machinepass, domainsid=None,
663 realm=None, dnsdomain=None,
665 key_version_number=1,
666 secure_channel_type=SEC_CHAN_WKSTA):
667 """Add domain join-specific bits to a secrets database.
669 :param secretsdb: Ldb Handle to the secrets database
670 :param machinepass: Machine password
672 attrs=["whenChanged",
680 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
681 msg["secureChannelType"] = str(secure_channel_type)
682 msg["flatname"] = [domain]
683 msg["objectClass"] = ["top", "primaryDomain"]
684 if realm is not None:
685 if dnsdomain is None:
686 dnsdomain = realm.lower()
687 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
689 msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
690 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
691 msg["privateKeytab"] = ["secrets.keytab"]
694 msg["secret"] = [machinepass]
695 msg["samAccountName"] = ["%s$" % netbiosname]
696 msg["secureChannelType"] = [str(secure_channel_type)]
697 if domainsid is not None:
698 msg["objectSid"] = [ndr_pack(domainsid)]
700 res = secretsdb.search(base="cn=Primary Domains",
702 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))),
703 scope=ldb.SCOPE_ONELEVEL)
706 if del_msg.dn is not msg.dn:
707 secretsdb.delete(del_msg.dn)
709 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
712 msg["priorSecret"] = res[0]["secret"]
713 msg["priorWhenChanged"] = res[0]["whenChanged"]
715 if res["privateKeytab"] is not None:
716 msg["privateKeytab"] = res[0]["privateKeytab"]
718 if res["krb5Keytab"] is not None:
719 msg["krb5Keytab"] = res[0]["krb5Keytab"]
722 el.set_flags(ldb.FLAG_MOD_REPLACE)
723 secretsdb.modify(msg)
728 def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
730 dns_keytab_path, dnspass):
731 """Add DNS specific bits to a secrets database.
733 :param secretsdb: Ldb Handle to the secrets database
734 :param setup_path: Setup path function
735 :param machinepass: Machine password
738 os.unlink(os.path.join(private_dir, dns_keytab_path))
742 setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
744 "DNSDOMAIN": dnsdomain,
745 "DNS_KEYTAB": dns_keytab_path,
746 "DNSPASS_B64": b64encode(dnspass),
750 def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
751 """Setup the secrets database.
752 This function does not handle exceptions and transaction on purpose,
753 it's up to the caller to do this job.
755 :param path: Path to the secrets database.
756 :param setup_path: Get the path to a setup file.
757 :param session_info: Session info.
758 :param credentials: Credentials
759 :param lp: Loadparm context
760 :return: LDB handle for the created secrets database
762 if os.path.exists(path):
764 secrets_ldb = Ldb(path, session_info=session_info,
767 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
768 secrets_ldb = Ldb(path, session_info=session_info,
770 secrets_ldb.transaction_start()
771 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
773 if backend_credentials is not None and backend_credentials.authentication_requested():
774 if backend_credentials.get_bind_dn() is not None:
775 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
776 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
777 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
780 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
781 "LDAPADMINUSER": backend_credentials.get_username(),
782 "LDAPADMINREALM": backend_credentials.get_realm(),
783 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
788 def setup_privileges(path, setup_path, session_info, lp):
789 """Setup the privileges database.
791 :param path: Path to the privileges database.
792 :param setup_path: Get the path to a setup file.
793 :param session_info: Session info.
794 :param credentials: Credentials
795 :param lp: Loadparm context
796 :return: LDB handle for the created secrets database
798 if os.path.exists(path):
800 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
801 privilege_ldb.erase()
802 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
805 def setup_registry(path, setup_path, session_info, lp):
806 """Setup the registry.
808 :param path: Path to the registry database
809 :param setup_path: Function that returns the path to a setup.
810 :param session_info: Session information
811 :param credentials: Credentials
812 :param lp: Loadparm context
814 reg = samba.registry.Registry()
815 hive = samba.registry.open_ldb(path, session_info=session_info,
817 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
818 provision_reg = setup_path("provision.reg")
819 assert os.path.exists(provision_reg)
820 reg.diff_apply(provision_reg)
823 def setup_idmapdb(path, setup_path, session_info, lp):
824 """Setup the idmap database.
826 :param path: path to the idmap database
827 :param setup_path: Function that returns a path to a setup file
828 :param session_info: Session information
829 :param credentials: Credentials
830 :param lp: Loadparm context
832 if os.path.exists(path):
835 idmap_ldb = IDmapDB(path, session_info=session_info,
839 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
843 def setup_samdb_rootdse(samdb, setup_path, names):
844 """Setup the SamDB rootdse.
846 :param samdb: Sam Database handle
847 :param setup_path: Obtain setup path
849 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
850 "SCHEMADN": names.schemadn,
851 "NETBIOSNAME": names.netbiosname,
852 "DNSDOMAIN": names.dnsdomain,
853 "REALM": names.realm,
854 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
855 "DOMAINDN": names.domaindn,
856 "ROOTDN": names.rootdn,
857 "CONFIGDN": names.configdn,
858 "SERVERDN": names.serverdn,
862 def setup_self_join(samdb, names,
863 machinepass, dnspass,
864 domainsid, invocationid, setup_path,
865 policyguid, policyguid_dc, domainControllerFunctionality,
867 """Join a host to its own domain."""
868 assert isinstance(invocationid, str)
869 if ntdsguid is not None:
870 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
873 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
874 "CONFIGDN": names.configdn,
875 "SCHEMADN": names.schemadn,
876 "DOMAINDN": names.domaindn,
877 "SERVERDN": names.serverdn,
878 "INVOCATIONID": invocationid,
879 "NETBIOSNAME": names.netbiosname,
880 "DEFAULTSITE": names.sitename,
881 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
882 "MACHINEPASS_B64": b64encode(machinepass),
883 "REALM": names.realm,
884 "DOMAIN": names.domain,
885 "DOMAINSID": str(domainsid),
886 "DNSDOMAIN": names.dnsdomain,
887 "SAMBA_VERSION_STRING": version,
888 "NTDSGUID": ntdsguid_line,
889 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
891 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
892 "POLICYGUID": policyguid,
893 "POLICYGUID_DC": policyguid_dc,
894 "DNSDOMAIN": names.dnsdomain,
895 "DOMAINSID": str(domainsid),
896 "DOMAINDN": names.domaindn})
898 # add the NTDSGUID based SPNs
899 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
900 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
901 expression="", scope=ldb.SCOPE_BASE)
902 assert isinstance(names.ntdsguid, str)
904 # Setup fSMORoleOwner entries to point at the newly created DC entry
905 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
906 "DOMAIN": names.domain,
907 "DNSDOMAIN": names.dnsdomain,
908 "DOMAINDN": names.domaindn,
909 "CONFIGDN": names.configdn,
910 "SCHEMADN": names.schemadn,
911 "DEFAULTSITE": names.sitename,
912 "SERVERDN": names.serverdn,
913 "NETBIOSNAME": names.netbiosname,
914 "NTDSGUID": names.ntdsguid,
915 "DNSPASS_B64": b64encode(dnspass),
918 def getpolicypath(sysvolpath, dnsdomain, guid):
921 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
924 def create_gpo_struct(policy_path):
925 os.makedirs(policy_path, 0755)
926 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
927 "[General]\r\nVersion=65543")
928 os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
929 os.makedirs(os.path.join(policy_path, "USER"), 0755)
932 def setup_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
933 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
934 create_gpo_struct(policy_path)
936 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
937 create_gpo_struct(policy_path)
940 def setup_samdb(path, setup_path, session_info, provision_backend, lp, names,
941 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
942 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
943 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None):
944 """Setup a complete SAM Database.
946 :note: This will wipe the main SAM database file!
949 # ATTENTION: Do NOT change these default values without discussion with the
950 # team and/or release manager. They have a big impact on the whole program!
951 domainControllerFunctionality = DS_DC_FUNCTION_2008
953 if dom_for_fun_level is None:
954 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
956 if dom_for_fun_level > domainControllerFunctionality:
957 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). This won't work!")
959 domainFunctionality = dom_for_fun_level
960 forestFunctionality = dom_for_fun_level
962 # Also wipes the database
963 setup_samdb_partitions(path, setup_path, logger=logger, lp=lp,
964 provision_backend=provision_backend, session_info=session_info,
965 names=names, serverrole=serverrole, schema=schema)
968 schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
971 # Load the database, but don's load the global schema and don't connect quite yet
972 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
973 credentials=provision_backend.credentials, lp=lp, global_schema=False,
976 logger.info("Pre-loading the Samba 4 and AD schema")
978 # Load the schema from the one we computed earlier
979 samdb.set_schema(schema)
981 # And now we can connect to the DB - the schema won't be loaded from the DB
987 samdb.transaction_start()
989 # Set the domain functionality levels onto the database.
990 # Various module (the password_hash module in particular) need
991 # to know what level of AD we are emulating.
993 # These will be fixed into the database via the database
994 # modifictions below, but we need them set from the start.
995 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
996 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
997 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
999 samdb.set_domain_sid(str(domainsid))
1000 samdb.set_invocation_id(invocationid)
1001 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1003 logger.info("Adding DomainDN: %s" % names.domaindn)
1005 #impersonate domain admin
1006 admin_session_info = admin_session(lp, str(domainsid))
1007 samdb.set_session_info(admin_session_info)
1008 if domainguid is not None:
1009 domainguid_line = "objectGUID: %s\n-" % domainguid
1011 domainguid_line = ""
1013 descr = b64encode(get_domain_descriptor(domainsid))
1014 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1015 "DOMAINDN": names.domaindn,
1016 "DOMAINGUID": domainguid_line,
1021 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1022 "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1023 "DOMAINSID": str(domainsid),
1024 "SCHEMADN": names.schemadn,
1025 "NETBIOSNAME": names.netbiosname,
1026 "DEFAULTSITE": names.sitename,
1027 "CONFIGDN": names.configdn,
1028 "SERVERDN": names.serverdn,
1029 "POLICYGUID": policyguid,
1030 "DOMAINDN": names.domaindn,
1031 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1032 "SAMBA_VERSION_STRING": version
1035 logger.info("Adding configuration container")
1036 descr = b64encode(get_config_descriptor(domainsid))
1037 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1038 "CONFIGDN": names.configdn,
1039 "DESCRIPTOR": descr,
1042 # The LDIF here was created when the Schema object was constructed
1043 logger.info("Setting up sam.ldb schema")
1044 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1045 samdb.modify_ldif(schema.schema_dn_modify)
1046 samdb.write_prefixes_from_schema()
1047 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1048 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1049 {"SCHEMADN": names.schemadn})
1051 logger.info("Reopening sam.ldb with new schema")
1053 samdb.transaction_cancel()
1056 samdb.transaction_commit()
1058 samdb = SamDB(session_info=admin_session_info,
1059 credentials=provision_backend.credentials, lp=lp,
1060 global_schema=False, am_rodc=am_rodc)
1062 samdb.transaction_start()
1064 samdb.invocation_id = invocationid
1066 logger.info("Setting up sam.ldb configuration data")
1067 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1068 "CONFIGDN": names.configdn,
1069 "NETBIOSNAME": names.netbiosname,
1070 "DEFAULTSITE": names.sitename,
1071 "DNSDOMAIN": names.dnsdomain,
1072 "DOMAIN": names.domain,
1073 "SCHEMADN": names.schemadn,
1074 "DOMAINDN": names.domaindn,
1075 "SERVERDN": names.serverdn,
1076 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1077 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
1080 logger.info("Setting up display specifiers")
1081 display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1082 display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1083 check_all_substituted(display_specifiers_ldif)
1084 samdb.add_ldif(display_specifiers_ldif)
1086 logger.info("Adding users container")
1087 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1088 "DOMAINDN": names.domaindn})
1089 logger.info("Modifying users container")
1090 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1091 "DOMAINDN": names.domaindn})
1092 logger.info("Adding computers container")
1093 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1094 "DOMAINDN": names.domaindn})
1095 logger.info("Modifying computers container")
1096 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1097 "DOMAINDN": names.domaindn})
1098 logger.info("Setting up sam.ldb data")
1099 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1100 "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1101 "DOMAINDN": names.domaindn,
1102 "NETBIOSNAME": names.netbiosname,
1103 "DEFAULTSITE": names.sitename,
1104 "CONFIGDN": names.configdn,
1105 "SERVERDN": names.serverdn,
1106 "POLICYGUID_DC": policyguid_dc
1109 setup_modify_ldif(samdb, setup_path("provision_basedn_references.ldif"), {
1110 "DOMAINDN": names.domaindn})
1112 setup_modify_ldif(samdb, setup_path("provision_configuration_references.ldif"), {
1113 "CONFIGDN": names.configdn,
1114 "SCHEMADN": names.schemadn})
1115 if fill == FILL_FULL:
1116 logger.info("Setting up sam.ldb users and groups")
1117 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1118 "DOMAINDN": names.domaindn,
1119 "DOMAINSID": str(domainsid),
1120 "CONFIGDN": names.configdn,
1121 "ADMINPASS_B64": b64encode(adminpass),
1122 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1125 logger.info("Setting up self join")
1126 setup_self_join(samdb, names=names, invocationid=invocationid,
1128 machinepass=machinepass,
1129 domainsid=domainsid, policyguid=policyguid,
1130 policyguid_dc=policyguid_dc,
1131 setup_path=setup_path,
1132 domainControllerFunctionality=domainControllerFunctionality,
1135 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
1136 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1137 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1138 assert isinstance(names.ntdsguid, str)
1140 samdb.transaction_cancel()
1143 samdb.transaction_commit()
1148 FILL_NT4SYNC = "NT4SYNC"
1150 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1151 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)"
1153 def set_dir_acl(path, acl, lp, domsid):
1154 setntacl(lp, path, acl, domsid)
1155 for root, dirs, files in os.walk(path, topdown=False):
1157 setntacl(lp, os.path.join(root, name), acl, domsid)
1159 setntacl(lp, os.path.join(root, name), acl, domsid)
1162 def set_gpo_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1164 policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1165 set_dir_acl(policy_path,dsacl2fsacl(POLICIES_ACL, str(domainsid)),
1167 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1168 attrs=["cn", "nTSecurityDescriptor"],
1169 expression="", scope=ldb.SCOPE_ONELEVEL)
1171 acl = ndr_unpack(security.descriptor,
1172 str(policy["nTSecurityDescriptor"])).as_sddl()
1173 policy_path = getpolicypath(sysvol,dnsdomain,str(policy["cn"]))
1174 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1177 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1180 os.chown(sysvol,-1,gid)
1186 setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
1187 for root, dirs, files in os.walk(sysvol, topdown=False):
1190 os.chown(os.path.join(root, name),-1,gid)
1191 setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1194 os.chown(os.path.join(root, name),-1,gid)
1195 setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1196 set_gpo_acl(sysvol,dnsdomain,domainsid,domaindn,samdb,lp)
1199 def provision(setup_dir, logger, session_info,
1200 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1202 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1204 domain=None, hostname=None, hostip=None, hostip6=None,
1205 domainsid=None, adminpass=None, ldapadminpass=None,
1206 krbtgtpass=None, domainguid=None,
1207 policyguid=None, policyguid_dc=None, invocationid=None,
1208 machinepass=None, ntdsguid=None,
1209 dnspass=None, root=None, nobody=None, users=None,
1210 wheel=None, backup=None, aci=None, serverrole=None,
1211 dom_for_fun_level=None,
1212 ldap_backend_extra_port=None, backend_type=None,
1214 ol_mmr_urls=None, ol_olc=None,
1215 setup_ds_path=None, slapd_path=None, nosync=False,
1216 ldap_dryrun_mode=False, useeadb=False, am_rodc=False):
1219 :note: caution, this wipes all existing data!
1222 def setup_path(file):
1223 return os.path.join(setup_dir, file)
1225 if domainsid is None:
1226 domainsid = security.random_sid()
1228 domainsid = security.dom_sid(domainsid)
1230 # create/adapt the group policy GUIDs
1231 if policyguid is None:
1232 policyguid = str(uuid.uuid4())
1233 policyguid = policyguid.upper()
1234 if policyguid_dc is None:
1235 policyguid_dc = str(uuid.uuid4())
1236 policyguid_dc = policyguid_dc.upper()
1238 if adminpass is None:
1239 adminpass = samba.generate_random_password(12, 32)
1240 if krbtgtpass is None:
1241 krbtgtpass = samba.generate_random_password(128, 255)
1242 if machinepass is None:
1243 machinepass = samba.generate_random_password(128, 255)
1245 dnspass = samba.generate_random_password(128, 255)
1246 if ldapadminpass is None:
1247 #Make a new, random password between Samba and it's LDAP server
1248 ldapadminpass=samba.generate_random_password(128, 255)
1250 if backend_type is None:
1251 backend_type = "ldb"
1253 sid_generator = "internal"
1254 if backend_type == "fedora-ds":
1255 sid_generator = "backend"
1257 root_uid = findnss_uid([root or "root"])
1258 nobody_uid = findnss_uid([nobody or "nobody"])
1259 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1261 wheel_gid = findnss_gid(["wheel", "adm"])
1263 wheel_gid = findnss_gid([wheel])
1265 bind_gid = findnss_gid(["bind", "named"])
1269 if targetdir is not None:
1270 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1271 elif smbconf is None:
1272 smbconf = samba.param.default_path()
1273 if not os.path.exists(os.path.dirname(smbconf)):
1274 os.makedirs(os.path.dirname(smbconf))
1276 # only install a new smb.conf if there isn't one there already
1277 if os.path.exists(smbconf):
1278 # if Samba Team members can't figure out the weird errors
1279 # loading an empty smb.conf gives, then we need to be smarter.
1280 # Pretend it just didn't exist --abartlet
1281 data = open(smbconf, 'r').read()
1282 data = data.lstrip()
1283 if data is None or data == "":
1284 make_smbconf(smbconf, setup_path, hostname, domain, realm,
1285 serverrole, targetdir, sid_generator, useeadb)
1287 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1288 targetdir, sid_generator, useeadb)
1290 lp = samba.param.LoadParm()
1293 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1294 dnsdomain=realm, serverrole=serverrole,
1295 domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1296 serverdn=serverdn, sitename=sitename)
1298 paths = provision_paths_from_lp(lp, names.dnsdomain)
1300 paths.bind_gid = bind_gid
1303 hostips = samba.interface_ips(lp, False)
1304 if len(hostips) == 0:
1305 logger.warning("No external IPv4 address has been found. Using loopback.")
1306 hostip = '127.0.0.1'
1309 if len(hostips) > 1:
1310 logger.warning("More than one IPv4 address found. Using %s.", hostip)
1314 for ip in socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP):
1317 if hostip6 == '::1' and ip[-1][0] != '::1':
1319 except socket.gaierror, (socket.EAI_NODATA, msg):
1322 if serverrole is None:
1323 serverrole = lp.get("server role")
1325 assert serverrole in ("domain controller", "member server", "standalone")
1326 if invocationid is None:
1327 invocationid = str(uuid.uuid4())
1329 if not os.path.exists(paths.private_dir):
1330 os.mkdir(paths.private_dir)
1331 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1332 os.mkdir(os.path.join(paths.private_dir, "tls"))
1334 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1336 schema = Schema(setup_path, domainsid, invocationid=invocationid, schemadn=names.schemadn,
1337 serverdn=names.serverdn, am_rodc=am_rodc)
1339 if backend_type == "ldb":
1340 provision_backend = LDBBackend(backend_type,
1341 paths=paths, setup_path=setup_path,
1342 lp=lp, credentials=credentials,
1345 elif backend_type == "existing":
1346 provision_backend = ExistingBackend(backend_type,
1347 paths=paths, setup_path=setup_path,
1348 lp=lp, credentials=credentials,
1351 ldapi_url=ldapi_url)
1352 elif backend_type == "fedora-ds":
1353 provision_backend = FDSBackend(backend_type,
1354 paths=paths, setup_path=setup_path,
1355 lp=lp, credentials=credentials,
1358 domainsid=domainsid,
1361 ldapadminpass=ldapadminpass,
1362 slapd_path=slapd_path,
1363 ldap_backend_extra_port=ldap_backend_extra_port,
1364 ldap_dryrun_mode=ldap_dryrun_mode,
1366 setup_ds_path=setup_ds_path)
1367 elif backend_type == "openldap":
1368 provision_backend = OpenLDAPBackend(backend_type,
1369 paths=paths, setup_path=setup_path,
1370 lp=lp, credentials=credentials,
1373 domainsid=domainsid,
1376 ldapadminpass=ldapadminpass,
1377 slapd_path=slapd_path,
1378 ldap_backend_extra_port=ldap_backend_extra_port,
1379 ldap_dryrun_mode=ldap_dryrun_mode,
1380 ol_mmr_urls=ol_mmr_urls,
1383 raise ValueError("Unknown LDAP backend type selected")
1385 provision_backend.init()
1386 provision_backend.start()
1388 # only install a new shares config db if there is none
1389 if not os.path.exists(paths.shareconf):
1390 logger.info("Setting up share.ldb")
1391 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1393 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1396 logger.info("Setting up secrets.ldb")
1397 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1398 session_info=session_info,
1399 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1401 logger.info("Setting up the registry")
1402 setup_registry(paths.hklm, setup_path, session_info,
1405 logger.info("Setting up the privileges database")
1406 setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1408 logger.info("Setting up idmap db")
1409 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1412 logger.info("Setting up SAM db")
1413 samdb = setup_samdb(paths.samdb, setup_path, session_info,
1414 provision_backend, lp, names,
1416 domainsid=domainsid,
1417 schema=schema, domainguid=domainguid,
1418 policyguid=policyguid, policyguid_dc=policyguid_dc,
1420 adminpass=adminpass, krbtgtpass=krbtgtpass,
1421 invocationid=invocationid,
1422 machinepass=machinepass, dnspass=dnspass,
1423 ntdsguid=ntdsguid, serverrole=serverrole,
1424 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc)
1426 if serverrole == "domain controller":
1427 if paths.netlogon is None:
1428 logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1429 logger.info("Please either remove %s or see the template at %s" %
1430 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1431 assert paths.netlogon is not None
1433 if paths.sysvol is None:
1434 logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1435 " are configuring a DC.")
1436 logger.info("Please either remove %s or see the template at %s" %
1437 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1438 assert paths.sysvol is not None
1440 if not os.path.isdir(paths.netlogon):
1441 os.makedirs(paths.netlogon, 0755)
1443 if samdb_fill == FILL_FULL:
1444 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1445 root_uid=root_uid, nobody_uid=nobody_uid,
1446 users_gid=users_gid, wheel_gid=wheel_gid)
1448 if serverrole == "domain controller":
1449 # Set up group policies (domain policy and domain controller policy)
1450 setup_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
1451 setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid,
1452 domainsid, names.dnsdomain, names.domaindn, lp)
1454 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1455 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1457 secretsdb_self_join(secrets_ldb, domain=names.domain,
1459 dnsdomain=names.dnsdomain,
1460 netbiosname=names.netbiosname,
1461 domainsid=domainsid,
1462 machinepass=machinepass,
1463 secure_channel_type=SEC_CHAN_BDC)
1465 if serverrole == "domain controller":
1466 secretsdb_setup_dns(secrets_ldb, setup_path,
1468 realm=names.realm, dnsdomain=names.dnsdomain,
1469 dns_keytab_path=paths.dns_keytab,
1472 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1473 assert isinstance(domainguid, str)
1475 # Only make a zone file on the first DC, it should be replicated
1476 # with DNS replication
1477 create_zone_file(lp, logger, paths, targetdir, setup_path,
1478 dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
1479 hostname=names.hostname, realm=names.realm,
1480 domainguid=domainguid, ntdsguid=names.ntdsguid)
1482 create_named_conf(paths, setup_path, realm=names.realm,
1483 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1485 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1486 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1487 keytab_name=paths.dns_keytab)
1488 logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
1489 logger.info("and %s for further documentation required for secure DNS "
1490 "updates", paths.namedtxt)
1492 create_krb5_conf(paths.krb5conf, setup_path,
1493 dnsdomain=names.dnsdomain, hostname=names.hostname,
1495 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1496 "generated at %s", paths.krb5conf)
1498 lastProvisionUSNs = get_last_provision_usn(samdb)
1499 maxUSN = get_max_usn(samdb, str(names.rootdn))
1500 if lastProvisionUSNs is not None:
1501 update_provision_usn(samdb, 0, maxUSN, 1)
1503 set_provision_usn(samdb, 0, maxUSN)
1505 if serverrole == "domain controller":
1506 create_dns_update_list(lp, logger, paths, setup_path)
1508 provision_backend.post_setup()
1509 provision_backend.shutdown()
1511 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1514 #Now commit the secrets.ldb to disk
1515 secrets_ldb.transaction_commit()
1517 # the commit creates the dns.keytab, now chown it
1518 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1519 if (os.path.isfile(dns_keytab_path) and paths.bind_gid is not None):
1521 os.chmod(dns_keytab_path, 0640)
1522 os.chown(dns_keytab_path, -1, paths.bind_gid)
1524 logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
1528 logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
1529 paths.phpldapadminconfig)
1531 logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1532 logger.info("Server Role: %s" % serverrole)
1533 logger.info("Hostname: %s" % names.hostname)
1534 logger.info("NetBIOS Domain: %s" % names.domain)
1535 logger.info("DNS Domain: %s" % names.dnsdomain)
1536 logger.info("DOMAIN SID: %s" % str(domainsid))
1537 if samdb_fill == FILL_FULL:
1538 logger.info("Admin password: %s" % adminpass)
1539 if provision_backend.type is not "ldb":
1540 if provision_backend.credentials.get_bind_dn() is not None:
1541 logger.info("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1543 logger.info("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1545 logger.info("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1547 if provision_backend.slapd_command_escaped is not None:
1548 # now display slapd_command_file.txt to show how slapd must be started next time
1549 logger.info("Use later the following commandline to start slapd, then Samba:")
1550 logger.info(provision_backend.slapd_command_escaped)
1551 logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
1552 provision_backend.ldapdir)
1554 result = ProvisionResult()
1555 result.domaindn = domaindn
1556 result.paths = paths
1558 result.samdb = samdb
1562 def provision_become_dc(setup_dir=None,
1563 smbconf=None, targetdir=None, realm=None,
1564 rootdn=None, domaindn=None, schemadn=None,
1565 configdn=None, serverdn=None,
1566 domain=None, hostname=None, domainsid=None,
1567 adminpass=None, krbtgtpass=None, domainguid=None,
1568 policyguid=None, policyguid_dc=None, invocationid=None,
1570 dnspass=None, root=None, nobody=None, users=None,
1571 wheel=None, backup=None, serverrole=None,
1572 ldap_backend=None, ldap_backend_type=None,
1573 sitename=None, debuglevel=1):
1575 logger = logging.getLogger("provision")
1576 samba.set_debug_level(debuglevel)
1578 return provision(setup_dir, logger, system_session(), None,
1579 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1580 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1581 configdn=configdn, serverdn=serverdn, domain=domain,
1582 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1583 machinepass=machinepass, serverrole="domain controller",
1587 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1588 """Create a PHP LDAP admin configuration file.
1590 :param path: Path to write the configuration to.
1591 :param setup_path: Function to generate setup paths.
1593 setup_file(setup_path("phpldapadmin-config.php"), path,
1594 {"S4_LDAPI_URI": ldapi_uri})
1597 def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain,
1598 hostip, hostip6, hostname, realm, domainguid,
1600 """Write out a DNS zone file, from the info in the current database.
1602 :param paths: paths object
1603 :param setup_path: Setup path function.
1604 :param dnsdomain: DNS Domain name
1605 :param domaindn: DN of the Domain
1606 :param hostip: Local IPv4 IP
1607 :param hostip6: Local IPv6 IP
1608 :param hostname: Local hostname
1609 :param realm: Realm name
1610 :param domainguid: GUID of the domain.
1611 :param ntdsguid: GUID of the hosts nTDSDSA record.
1613 assert isinstance(domainguid, str)
1615 if hostip6 is not None:
1616 hostip6_base_line = " IN AAAA " + hostip6
1617 hostip6_host_line = hostname + " IN AAAA " + hostip6
1618 gc_msdcs_ip6_line = "gc._msdcs IN AAAA " + hostip6
1620 hostip6_base_line = ""
1621 hostip6_host_line = ""
1622 gc_msdcs_ip6_line = ""
1624 if hostip is not None:
1625 hostip_base_line = " IN A " + hostip
1626 hostip_host_line = hostname + " IN A " + hostip
1627 gc_msdcs_ip_line = "gc._msdcs IN A " + hostip
1629 hostip_base_line = ""
1630 hostip_host_line = ""
1631 gc_msdcs_ip_line = ""
1633 dns_dir = os.path.dirname(paths.dns)
1636 shutil.rmtree(dns_dir, True)
1640 os.mkdir(dns_dir, 0775)
1642 # we need to freeze the zone while we update the contents
1643 if targetdir is None:
1644 rndc = ' '.join(lp.get("rndc command"))
1645 os.system(rndc + " freeze " + lp.get("realm"))
1647 setup_file(setup_path("provision.zone"), paths.dns, {
1648 "HOSTNAME": hostname,
1649 "DNSDOMAIN": dnsdomain,
1651 "HOSTIP_BASE_LINE": hostip_base_line,
1652 "HOSTIP_HOST_LINE": hostip_host_line,
1653 "DOMAINGUID": domainguid,
1654 "DATESTRING": time.strftime("%Y%m%d%H"),
1655 "DEFAULTSITE": DEFAULTSITE,
1656 "NTDSGUID": ntdsguid,
1657 "HOSTIP6_BASE_LINE": hostip6_base_line,
1658 "HOSTIP6_HOST_LINE": hostip6_host_line,
1659 "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
1660 "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
1663 # note that we use no variable substitution on this file
1664 # the substitution is done at runtime by samba_dnsupdate
1665 setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1667 # and the SPN update list
1668 setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1670 if paths.bind_gid is not None:
1672 os.chown(dns_dir, -1, paths.bind_gid)
1673 os.chown(paths.dns, -1, paths.bind_gid)
1674 # chmod needed to cope with umask
1675 os.chmod(dns_dir, 0775)
1676 os.chmod(paths.dns, 0664)
1678 logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
1680 if targetdir is None:
1681 os.system(rndc + " unfreeze " + lp.get("realm"))
1684 def create_dns_update_list(lp, logger, paths, setup_path):
1685 """Write out a dns_update_list file"""
1686 # note that we use no variable substitution on this file
1687 # the substitution is done at runtime by samba_dnsupdate
1688 setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1689 setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1692 def create_named_conf(paths, setup_path, realm, dnsdomain,
1694 """Write out a file containing zone statements suitable for inclusion in a
1695 named.conf file (including GSS-TSIG configuration).
1697 :param paths: all paths
1698 :param setup_path: Setup path function.
1699 :param realm: Realm name
1700 :param dnsdomain: DNS Domain name
1701 :param private_dir: Path to private directory
1702 :param keytab_name: File name of DNS keytab file
1705 setup_file(setup_path("named.conf"), paths.namedconf, {
1706 "DNSDOMAIN": dnsdomain,
1708 "ZONE_FILE": paths.dns,
1709 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1710 "NAMED_CONF": paths.namedconf,
1711 "NAMED_CONF_UPDATE": paths.namedconf_update
1714 setup_file(setup_path("named.conf.update"), paths.namedconf_update)
1717 def create_named_txt(path, setup_path, realm, dnsdomain,
1718 private_dir, keytab_name):
1719 """Write out a file containing zone statements suitable for inclusion in a
1720 named.conf file (including GSS-TSIG configuration).
1722 :param path: Path of the new named.conf file.
1723 :param setup_path: Setup path function.
1724 :param realm: Realm name
1725 :param dnsdomain: DNS Domain name
1726 :param private_dir: Path to private directory
1727 :param keytab_name: File name of DNS keytab file
1730 setup_file(setup_path("named.txt"), path, {
1731 "DNSDOMAIN": dnsdomain,
1733 "DNS_KEYTAB": keytab_name,
1734 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1735 "PRIVATE_DIR": private_dir
1739 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1740 """Write out a file containing zone statements suitable for inclusion in a
1741 named.conf file (including GSS-TSIG configuration).
1743 :param path: Path of the new named.conf file.
1744 :param setup_path: Setup path function.
1745 :param dnsdomain: DNS Domain name
1746 :param hostname: Local hostname
1747 :param realm: Realm name
1749 setup_file(setup_path("krb5.conf"), path, {
1750 "DNSDOMAIN": dnsdomain,
1751 "HOSTNAME": hostname,
1756 class ProvisioningError(Exception):
1757 """A generic provision error."""
1759 def __init__(self, value):
1763 return "ProvisioningError: " + self.value
1766 class InvalidNetbiosName(Exception):
1767 """A specified name was not a valid NetBIOS name."""
1768 def __init__(self, name):
1769 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)