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.
753 :note: This function does not handle exceptions and transaction on purpose,
754 it's up to the caller to do this job.
756 :param path: Path to the secrets database.
757 :param setup_path: Get the path to a setup file.
758 :param session_info: Session info.
759 :param credentials: Credentials
760 :param lp: Loadparm context
761 :return: LDB handle for the created secrets database
763 if os.path.exists(path):
765 secrets_ldb = Ldb(path, session_info=session_info,
768 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
769 secrets_ldb = Ldb(path, session_info=session_info,
771 secrets_ldb.transaction_start()
773 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
775 if backend_credentials is not None and backend_credentials.authentication_requested():
776 if backend_credentials.get_bind_dn() is not None:
777 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
778 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
779 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
782 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
783 "LDAPADMINUSER": backend_credentials.get_username(),
784 "LDAPADMINREALM": backend_credentials.get_realm(),
785 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
790 secrets_ldb.transaction_cancel()
793 def setup_privileges(path, setup_path, session_info, lp):
794 """Setup the privileges database.
796 :param path: Path to the privileges database.
797 :param setup_path: Get the path to a setup file.
798 :param session_info: Session info.
799 :param credentials: Credentials
800 :param lp: Loadparm context
801 :return: LDB handle for the created secrets database
803 if os.path.exists(path):
805 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
806 privilege_ldb.erase()
807 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
810 def setup_registry(path, setup_path, session_info, lp):
811 """Setup the registry.
813 :param path: Path to the registry database
814 :param setup_path: Function that returns the path to a setup.
815 :param session_info: Session information
816 :param credentials: Credentials
817 :param lp: Loadparm context
819 reg = samba.registry.Registry()
820 hive = samba.registry.open_ldb(path, session_info=session_info,
822 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
823 provision_reg = setup_path("provision.reg")
824 assert os.path.exists(provision_reg)
825 reg.diff_apply(provision_reg)
828 def setup_idmapdb(path, setup_path, session_info, lp):
829 """Setup the idmap database.
831 :param path: path to the idmap database
832 :param setup_path: Function that returns a path to a setup file
833 :param session_info: Session information
834 :param credentials: Credentials
835 :param lp: Loadparm context
837 if os.path.exists(path):
840 idmap_ldb = IDmapDB(path, session_info=session_info,
844 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
848 def setup_samdb_rootdse(samdb, setup_path, names):
849 """Setup the SamDB rootdse.
851 :param samdb: Sam Database handle
852 :param setup_path: Obtain setup path
854 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
855 "SCHEMADN": names.schemadn,
856 "NETBIOSNAME": names.netbiosname,
857 "DNSDOMAIN": names.dnsdomain,
858 "REALM": names.realm,
859 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
860 "DOMAINDN": names.domaindn,
861 "ROOTDN": names.rootdn,
862 "CONFIGDN": names.configdn,
863 "SERVERDN": names.serverdn,
867 def setup_self_join(samdb, names,
868 machinepass, dnspass,
869 domainsid, invocationid, setup_path,
870 policyguid, policyguid_dc, domainControllerFunctionality,
872 """Join a host to its own domain."""
873 assert isinstance(invocationid, str)
874 if ntdsguid is not None:
875 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
878 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
879 "CONFIGDN": names.configdn,
880 "SCHEMADN": names.schemadn,
881 "DOMAINDN": names.domaindn,
882 "SERVERDN": names.serverdn,
883 "INVOCATIONID": invocationid,
884 "NETBIOSNAME": names.netbiosname,
885 "DEFAULTSITE": names.sitename,
886 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
887 "MACHINEPASS_B64": b64encode(machinepass),
888 "REALM": names.realm,
889 "DOMAIN": names.domain,
890 "DOMAINSID": str(domainsid),
891 "DNSDOMAIN": names.dnsdomain,
892 "SAMBA_VERSION_STRING": version,
893 "NTDSGUID": ntdsguid_line,
894 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
896 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
897 "POLICYGUID": policyguid,
898 "POLICYGUID_DC": policyguid_dc,
899 "DNSDOMAIN": names.dnsdomain,
900 "DOMAINSID": str(domainsid),
901 "DOMAINDN": names.domaindn})
903 # add the NTDSGUID based SPNs
904 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
905 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
906 expression="", scope=ldb.SCOPE_BASE)
907 assert isinstance(names.ntdsguid, str)
909 # Setup fSMORoleOwner entries to point at the newly created DC entry
910 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
911 "DOMAIN": names.domain,
912 "DNSDOMAIN": names.dnsdomain,
913 "DOMAINDN": names.domaindn,
914 "CONFIGDN": names.configdn,
915 "SCHEMADN": names.schemadn,
916 "DEFAULTSITE": names.sitename,
917 "SERVERDN": names.serverdn,
918 "NETBIOSNAME": names.netbiosname,
919 "NTDSGUID": names.ntdsguid,
920 "DNSPASS_B64": b64encode(dnspass),
923 def getpolicypath(sysvolpath, dnsdomain, guid):
926 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
929 def create_gpo_struct(policy_path):
930 os.makedirs(policy_path, 0755)
931 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
932 "[General]\r\nVersion=65543")
933 os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
934 os.makedirs(os.path.join(policy_path, "USER"), 0755)
937 def setup_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
938 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
939 create_gpo_struct(policy_path)
941 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
942 create_gpo_struct(policy_path)
945 def setup_samdb(path, setup_path, session_info, provision_backend, lp, names,
946 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
947 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
948 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None):
949 """Setup a complete SAM Database.
951 :note: This will wipe the main SAM database file!
954 # ATTENTION: Do NOT change these default values without discussion with the
955 # team and/or release manager. They have a big impact on the whole program!
956 domainControllerFunctionality = DS_DC_FUNCTION_2008
958 if dom_for_fun_level is None:
959 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
961 if dom_for_fun_level > domainControllerFunctionality:
962 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!")
964 domainFunctionality = dom_for_fun_level
965 forestFunctionality = dom_for_fun_level
967 # Also wipes the database
968 setup_samdb_partitions(path, setup_path, logger=logger, lp=lp,
969 provision_backend=provision_backend, session_info=session_info,
970 names=names, serverrole=serverrole, schema=schema)
973 schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
976 # Load the database, but don's load the global schema and don't connect quite yet
977 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
978 credentials=provision_backend.credentials, lp=lp, global_schema=False,
981 logger.info("Pre-loading the Samba 4 and AD schema")
983 # Load the schema from the one we computed earlier
984 samdb.set_schema(schema)
986 # And now we can connect to the DB - the schema won't be loaded from the DB
992 samdb.transaction_start()
994 # Set the domain functionality levels onto the database.
995 # Various module (the password_hash module in particular) need
996 # to know what level of AD we are emulating.
998 # These will be fixed into the database via the database
999 # modifictions below, but we need them set from the start.
1000 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1001 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1002 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
1004 samdb.set_domain_sid(str(domainsid))
1005 samdb.set_invocation_id(invocationid)
1006 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1008 logger.info("Adding DomainDN: %s" % names.domaindn)
1010 #impersonate domain admin
1011 admin_session_info = admin_session(lp, str(domainsid))
1012 samdb.set_session_info(admin_session_info)
1013 if domainguid is not None:
1014 domainguid_line = "objectGUID: %s\n-" % domainguid
1016 domainguid_line = ""
1018 descr = b64encode(get_domain_descriptor(domainsid))
1019 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1020 "DOMAINDN": names.domaindn,
1021 "DOMAINGUID": domainguid_line,
1026 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1027 "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1028 "DOMAINSID": str(domainsid),
1029 "SCHEMADN": names.schemadn,
1030 "NETBIOSNAME": names.netbiosname,
1031 "DEFAULTSITE": names.sitename,
1032 "CONFIGDN": names.configdn,
1033 "SERVERDN": names.serverdn,
1034 "POLICYGUID": policyguid,
1035 "DOMAINDN": names.domaindn,
1036 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1037 "SAMBA_VERSION_STRING": version
1040 logger.info("Adding configuration container")
1041 descr = b64encode(get_config_descriptor(domainsid))
1042 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1043 "CONFIGDN": names.configdn,
1044 "DESCRIPTOR": descr,
1047 # The LDIF here was created when the Schema object was constructed
1048 logger.info("Setting up sam.ldb schema")
1049 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1050 samdb.modify_ldif(schema.schema_dn_modify)
1051 samdb.write_prefixes_from_schema()
1052 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1053 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1054 {"SCHEMADN": names.schemadn})
1056 logger.info("Reopening sam.ldb with new schema")
1058 samdb.transaction_cancel()
1061 samdb.transaction_commit()
1063 samdb = SamDB(session_info=admin_session_info,
1064 credentials=provision_backend.credentials, lp=lp,
1065 global_schema=False, am_rodc=am_rodc)
1067 samdb.transaction_start()
1069 samdb.invocation_id = invocationid
1071 logger.info("Setting up sam.ldb configuration data")
1072 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1073 "CONFIGDN": names.configdn,
1074 "NETBIOSNAME": names.netbiosname,
1075 "DEFAULTSITE": names.sitename,
1076 "DNSDOMAIN": names.dnsdomain,
1077 "DOMAIN": names.domain,
1078 "SCHEMADN": names.schemadn,
1079 "DOMAINDN": names.domaindn,
1080 "SERVERDN": names.serverdn,
1081 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1082 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
1085 logger.info("Setting up display specifiers")
1086 display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1087 display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1088 check_all_substituted(display_specifiers_ldif)
1089 samdb.add_ldif(display_specifiers_ldif)
1091 logger.info("Adding users container")
1092 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1093 "DOMAINDN": names.domaindn})
1094 logger.info("Modifying users container")
1095 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1096 "DOMAINDN": names.domaindn})
1097 logger.info("Adding computers container")
1098 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1099 "DOMAINDN": names.domaindn})
1100 logger.info("Modifying computers container")
1101 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1102 "DOMAINDN": names.domaindn})
1103 logger.info("Setting up sam.ldb data")
1104 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1105 "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1106 "DOMAINDN": names.domaindn,
1107 "NETBIOSNAME": names.netbiosname,
1108 "DEFAULTSITE": names.sitename,
1109 "CONFIGDN": names.configdn,
1110 "SERVERDN": names.serverdn,
1111 "POLICYGUID_DC": policyguid_dc
1114 setup_modify_ldif(samdb, setup_path("provision_basedn_references.ldif"), {
1115 "DOMAINDN": names.domaindn})
1117 setup_modify_ldif(samdb, setup_path("provision_configuration_references.ldif"), {
1118 "CONFIGDN": names.configdn,
1119 "SCHEMADN": names.schemadn})
1120 if fill == FILL_FULL:
1121 logger.info("Setting up sam.ldb users and groups")
1122 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1123 "DOMAINDN": names.domaindn,
1124 "DOMAINSID": str(domainsid),
1125 "CONFIGDN": names.configdn,
1126 "ADMINPASS_B64": b64encode(adminpass),
1127 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1130 logger.info("Setting up self join")
1131 setup_self_join(samdb, names=names, invocationid=invocationid,
1133 machinepass=machinepass,
1134 domainsid=domainsid, policyguid=policyguid,
1135 policyguid_dc=policyguid_dc,
1136 setup_path=setup_path,
1137 domainControllerFunctionality=domainControllerFunctionality,
1140 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
1141 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1142 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1143 assert isinstance(names.ntdsguid, str)
1145 samdb.transaction_cancel()
1148 samdb.transaction_commit()
1153 FILL_NT4SYNC = "NT4SYNC"
1155 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1156 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)"
1158 def set_dir_acl(path, acl, lp, domsid):
1159 setntacl(lp, path, acl, domsid)
1160 for root, dirs, files in os.walk(path, topdown=False):
1162 setntacl(lp, os.path.join(root, name), acl, domsid)
1164 setntacl(lp, os.path.join(root, name), acl, domsid)
1167 def set_gpo_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1169 policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1170 set_dir_acl(policy_path,dsacl2fsacl(POLICIES_ACL, str(domainsid)),
1172 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1173 attrs=["cn", "nTSecurityDescriptor"],
1174 expression="", scope=ldb.SCOPE_ONELEVEL)
1176 acl = ndr_unpack(security.descriptor,
1177 str(policy["nTSecurityDescriptor"])).as_sddl()
1178 policy_path = getpolicypath(sysvol,dnsdomain,str(policy["cn"]))
1179 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1182 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1185 os.chown(sysvol,-1,gid)
1191 setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
1192 for root, dirs, files in os.walk(sysvol, topdown=False):
1195 os.chown(os.path.join(root, name),-1,gid)
1196 setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1199 os.chown(os.path.join(root, name),-1,gid)
1200 setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1201 set_gpo_acl(sysvol,dnsdomain,domainsid,domaindn,samdb,lp)
1204 def provision(setup_dir, logger, session_info,
1205 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1207 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1209 domain=None, hostname=None, hostip=None, hostip6=None,
1210 domainsid=None, adminpass=None, ldapadminpass=None,
1211 krbtgtpass=None, domainguid=None,
1212 policyguid=None, policyguid_dc=None, invocationid=None,
1213 machinepass=None, ntdsguid=None,
1214 dnspass=None, root=None, nobody=None, users=None,
1215 wheel=None, backup=None, aci=None, serverrole=None,
1216 dom_for_fun_level=None,
1217 ldap_backend_extra_port=None, backend_type=None,
1219 ol_mmr_urls=None, ol_olc=None,
1220 setup_ds_path=None, slapd_path=None, nosync=False,
1221 ldap_dryrun_mode=False, useeadb=False, am_rodc=False):
1224 :note: caution, this wipes all existing data!
1227 def setup_path(file):
1228 return os.path.join(setup_dir, file)
1230 if domainsid is None:
1231 domainsid = security.random_sid()
1233 domainsid = security.dom_sid(domainsid)
1235 # create/adapt the group policy GUIDs
1236 if policyguid is None:
1237 policyguid = str(uuid.uuid4())
1238 policyguid = policyguid.upper()
1239 if policyguid_dc is None:
1240 policyguid_dc = str(uuid.uuid4())
1241 policyguid_dc = policyguid_dc.upper()
1243 if adminpass is None:
1244 adminpass = samba.generate_random_password(12, 32)
1245 if krbtgtpass is None:
1246 krbtgtpass = samba.generate_random_password(128, 255)
1247 if machinepass is None:
1248 machinepass = samba.generate_random_password(128, 255)
1250 dnspass = samba.generate_random_password(128, 255)
1251 if ldapadminpass is None:
1252 #Make a new, random password between Samba and it's LDAP server
1253 ldapadminpass=samba.generate_random_password(128, 255)
1255 if backend_type is None:
1256 backend_type = "ldb"
1258 sid_generator = "internal"
1259 if backend_type == "fedora-ds":
1260 sid_generator = "backend"
1262 root_uid = findnss_uid([root or "root"])
1263 nobody_uid = findnss_uid([nobody or "nobody"])
1264 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1266 wheel_gid = findnss_gid(["wheel", "adm"])
1268 wheel_gid = findnss_gid([wheel])
1270 bind_gid = findnss_gid(["bind", "named"])
1274 if targetdir is not None:
1275 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1276 elif smbconf is None:
1277 smbconf = samba.param.default_path()
1278 if not os.path.exists(os.path.dirname(smbconf)):
1279 os.makedirs(os.path.dirname(smbconf))
1281 # only install a new smb.conf if there isn't one there already
1282 if os.path.exists(smbconf):
1283 # if Samba Team members can't figure out the weird errors
1284 # loading an empty smb.conf gives, then we need to be smarter.
1285 # Pretend it just didn't exist --abartlet
1286 data = open(smbconf, 'r').read()
1287 data = data.lstrip()
1288 if data is None or data == "":
1289 make_smbconf(smbconf, setup_path, hostname, domain, realm,
1290 serverrole, targetdir, sid_generator, useeadb)
1292 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1293 targetdir, sid_generator, useeadb)
1295 lp = samba.param.LoadParm()
1298 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1299 dnsdomain=realm, serverrole=serverrole,
1300 domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1301 serverdn=serverdn, sitename=sitename)
1303 paths = provision_paths_from_lp(lp, names.dnsdomain)
1305 paths.bind_gid = bind_gid
1308 hostips = samba.interface_ips(lp, False)
1309 if len(hostips) == 0:
1310 logger.warning("No external IPv4 address has been found. Using loopback.")
1311 hostip = '127.0.0.1'
1314 if len(hostips) > 1:
1315 logger.warning("More than one IPv4 address found. Using %s.", hostip)
1319 for ip in socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP):
1322 if hostip6 == '::1' and ip[-1][0] != '::1':
1324 except socket.gaierror, (socket.EAI_NODATA, msg):
1327 if serverrole is None:
1328 serverrole = lp.get("server role")
1330 assert serverrole in ("domain controller", "member server", "standalone")
1331 if invocationid is None:
1332 invocationid = str(uuid.uuid4())
1334 if not os.path.exists(paths.private_dir):
1335 os.mkdir(paths.private_dir)
1336 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1337 os.mkdir(os.path.join(paths.private_dir, "tls"))
1339 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1341 schema = Schema(setup_path, domainsid, invocationid=invocationid, schemadn=names.schemadn,
1342 serverdn=names.serverdn, am_rodc=am_rodc)
1344 if backend_type == "ldb":
1345 provision_backend = LDBBackend(backend_type,
1346 paths=paths, setup_path=setup_path,
1347 lp=lp, credentials=credentials,
1350 elif backend_type == "existing":
1351 provision_backend = ExistingBackend(backend_type,
1352 paths=paths, setup_path=setup_path,
1353 lp=lp, credentials=credentials,
1356 ldapi_url=ldapi_url)
1357 elif backend_type == "fedora-ds":
1358 provision_backend = FDSBackend(backend_type,
1359 paths=paths, setup_path=setup_path,
1360 lp=lp, credentials=credentials,
1363 domainsid=domainsid,
1366 ldapadminpass=ldapadminpass,
1367 slapd_path=slapd_path,
1368 ldap_backend_extra_port=ldap_backend_extra_port,
1369 ldap_dryrun_mode=ldap_dryrun_mode,
1371 setup_ds_path=setup_ds_path)
1372 elif backend_type == "openldap":
1373 provision_backend = OpenLDAPBackend(backend_type,
1374 paths=paths, setup_path=setup_path,
1375 lp=lp, credentials=credentials,
1378 domainsid=domainsid,
1381 ldapadminpass=ldapadminpass,
1382 slapd_path=slapd_path,
1383 ldap_backend_extra_port=ldap_backend_extra_port,
1384 ldap_dryrun_mode=ldap_dryrun_mode,
1385 ol_mmr_urls=ol_mmr_urls,
1388 raise ValueError("Unknown LDAP backend type selected")
1390 provision_backend.init()
1391 provision_backend.start()
1393 # only install a new shares config db if there is none
1394 if not os.path.exists(paths.shareconf):
1395 logger.info("Setting up share.ldb")
1396 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1398 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1401 logger.info("Setting up secrets.ldb")
1402 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1403 session_info=session_info,
1404 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1407 logger.info("Setting up the registry")
1408 setup_registry(paths.hklm, setup_path, session_info,
1411 logger.info("Setting up the privileges database")
1412 setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1414 logger.info("Setting up idmap db")
1415 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1418 logger.info("Setting up SAM db")
1419 samdb = setup_samdb(paths.samdb, setup_path, session_info,
1420 provision_backend, lp, names,
1422 domainsid=domainsid,
1423 schema=schema, domainguid=domainguid,
1424 policyguid=policyguid, policyguid_dc=policyguid_dc,
1426 adminpass=adminpass, krbtgtpass=krbtgtpass,
1427 invocationid=invocationid,
1428 machinepass=machinepass, dnspass=dnspass,
1429 ntdsguid=ntdsguid, serverrole=serverrole,
1430 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc)
1432 if serverrole == "domain controller":
1433 if paths.netlogon is None:
1434 logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1435 logger.info("Please either remove %s or see the template at %s" %
1436 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1437 assert paths.netlogon is not None
1439 if paths.sysvol is None:
1440 logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1441 " are configuring a DC.")
1442 logger.info("Please either remove %s or see the template at %s" %
1443 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1444 assert paths.sysvol is not None
1446 if not os.path.isdir(paths.netlogon):
1447 os.makedirs(paths.netlogon, 0755)
1449 if samdb_fill == FILL_FULL:
1450 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1451 root_uid=root_uid, nobody_uid=nobody_uid,
1452 users_gid=users_gid, wheel_gid=wheel_gid)
1454 if serverrole == "domain controller":
1455 # Set up group policies (domain policy and domain controller policy)
1456 setup_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
1457 setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid,
1458 domainsid, names.dnsdomain, names.domaindn, lp)
1460 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1461 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1463 secretsdb_self_join(secrets_ldb, domain=names.domain,
1465 dnsdomain=names.dnsdomain,
1466 netbiosname=names.netbiosname,
1467 domainsid=domainsid,
1468 machinepass=machinepass,
1469 secure_channel_type=SEC_CHAN_BDC)
1471 if serverrole == "domain controller":
1472 secretsdb_setup_dns(secrets_ldb, setup_path,
1474 realm=names.realm, dnsdomain=names.dnsdomain,
1475 dns_keytab_path=paths.dns_keytab,
1478 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1479 assert isinstance(domainguid, str)
1481 # Only make a zone file on the first DC, it should be replicated
1482 # with DNS replication
1483 create_zone_file(lp, logger, paths, targetdir, setup_path,
1484 dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
1485 hostname=names.hostname, realm=names.realm,
1486 domainguid=domainguid, ntdsguid=names.ntdsguid)
1488 create_named_conf(paths, setup_path, realm=names.realm,
1489 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1491 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1492 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1493 keytab_name=paths.dns_keytab)
1494 logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
1495 logger.info("and %s for further documentation required for secure DNS "
1496 "updates", paths.namedtxt)
1498 create_krb5_conf(paths.krb5conf, setup_path,
1499 dnsdomain=names.dnsdomain, hostname=names.hostname,
1501 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1502 "generated at %s", paths.krb5conf)
1504 lastProvisionUSNs = get_last_provision_usn(samdb)
1505 maxUSN = get_max_usn(samdb, str(names.rootdn))
1506 if lastProvisionUSNs is not None:
1507 update_provision_usn(samdb, 0, maxUSN, 1)
1509 set_provision_usn(samdb, 0, maxUSN)
1511 if serverrole == "domain controller":
1512 create_dns_update_list(lp, logger, paths, setup_path)
1514 provision_backend.post_setup()
1515 provision_backend.shutdown()
1517 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1520 secrets_ldb.transaction_cancel()
1523 #Now commit the secrets.ldb to disk
1524 secrets_ldb.transaction_commit()
1526 # the commit creates the dns.keytab, now chown it
1527 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1528 if (os.path.isfile(dns_keytab_path) and paths.bind_gid is not None):
1530 os.chmod(dns_keytab_path, 0640)
1531 os.chown(dns_keytab_path, -1, paths.bind_gid)
1533 logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
1537 logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
1538 paths.phpldapadminconfig)
1540 logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1541 logger.info("Server Role: %s" % serverrole)
1542 logger.info("Hostname: %s" % names.hostname)
1543 logger.info("NetBIOS Domain: %s" % names.domain)
1544 logger.info("DNS Domain: %s" % names.dnsdomain)
1545 logger.info("DOMAIN SID: %s" % str(domainsid))
1546 if samdb_fill == FILL_FULL:
1547 logger.info("Admin password: %s" % adminpass)
1548 if provision_backend.type is not "ldb":
1549 if provision_backend.credentials.get_bind_dn() is not None:
1550 logger.info("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1552 logger.info("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1554 logger.info("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1556 if provision_backend.slapd_command_escaped is not None:
1557 # now display slapd_command_file.txt to show how slapd must be started next time
1558 logger.info("Use later the following commandline to start slapd, then Samba:")
1559 logger.info(provision_backend.slapd_command_escaped)
1560 logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
1561 provision_backend.ldapdir)
1563 result = ProvisionResult()
1564 result.domaindn = domaindn
1565 result.paths = paths
1567 result.samdb = samdb
1571 def provision_become_dc(setup_dir=None,
1572 smbconf=None, targetdir=None, realm=None,
1573 rootdn=None, domaindn=None, schemadn=None,
1574 configdn=None, serverdn=None,
1575 domain=None, hostname=None, domainsid=None,
1576 adminpass=None, krbtgtpass=None, domainguid=None,
1577 policyguid=None, policyguid_dc=None, invocationid=None,
1579 dnspass=None, root=None, nobody=None, users=None,
1580 wheel=None, backup=None, serverrole=None,
1581 ldap_backend=None, ldap_backend_type=None,
1582 sitename=None, debuglevel=1):
1584 logger = logging.getLogger("provision")
1585 samba.set_debug_level(debuglevel)
1587 return provision(setup_dir, logger, system_session(), None,
1588 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1589 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1590 configdn=configdn, serverdn=serverdn, domain=domain,
1591 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1592 machinepass=machinepass, serverrole="domain controller",
1596 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1597 """Create a PHP LDAP admin configuration file.
1599 :param path: Path to write the configuration to.
1600 :param setup_path: Function to generate setup paths.
1602 setup_file(setup_path("phpldapadmin-config.php"), path,
1603 {"S4_LDAPI_URI": ldapi_uri})
1606 def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain,
1607 hostip, hostip6, hostname, realm, domainguid,
1609 """Write out a DNS zone file, from the info in the current database.
1611 :param paths: paths object
1612 :param setup_path: Setup path function.
1613 :param dnsdomain: DNS Domain name
1614 :param domaindn: DN of the Domain
1615 :param hostip: Local IPv4 IP
1616 :param hostip6: Local IPv6 IP
1617 :param hostname: Local hostname
1618 :param realm: Realm name
1619 :param domainguid: GUID of the domain.
1620 :param ntdsguid: GUID of the hosts nTDSDSA record.
1622 assert isinstance(domainguid, str)
1624 if hostip6 is not None:
1625 hostip6_base_line = " IN AAAA " + hostip6
1626 hostip6_host_line = hostname + " IN AAAA " + hostip6
1627 gc_msdcs_ip6_line = "gc._msdcs IN AAAA " + hostip6
1629 hostip6_base_line = ""
1630 hostip6_host_line = ""
1631 gc_msdcs_ip6_line = ""
1633 if hostip is not None:
1634 hostip_base_line = " IN A " + hostip
1635 hostip_host_line = hostname + " IN A " + hostip
1636 gc_msdcs_ip_line = "gc._msdcs IN A " + hostip
1638 hostip_base_line = ""
1639 hostip_host_line = ""
1640 gc_msdcs_ip_line = ""
1642 dns_dir = os.path.dirname(paths.dns)
1645 shutil.rmtree(dns_dir, True)
1649 os.mkdir(dns_dir, 0775)
1651 # we need to freeze the zone while we update the contents
1652 if targetdir is None:
1653 rndc = ' '.join(lp.get("rndc command"))
1654 os.system(rndc + " freeze " + lp.get("realm"))
1656 setup_file(setup_path("provision.zone"), paths.dns, {
1657 "HOSTNAME": hostname,
1658 "DNSDOMAIN": dnsdomain,
1660 "HOSTIP_BASE_LINE": hostip_base_line,
1661 "HOSTIP_HOST_LINE": hostip_host_line,
1662 "DOMAINGUID": domainguid,
1663 "DATESTRING": time.strftime("%Y%m%d%H"),
1664 "DEFAULTSITE": DEFAULTSITE,
1665 "NTDSGUID": ntdsguid,
1666 "HOSTIP6_BASE_LINE": hostip6_base_line,
1667 "HOSTIP6_HOST_LINE": hostip6_host_line,
1668 "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
1669 "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
1672 # note that we use no variable substitution on this file
1673 # the substitution is done at runtime by samba_dnsupdate
1674 setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1676 # and the SPN update list
1677 setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1679 if paths.bind_gid is not None:
1681 os.chown(dns_dir, -1, paths.bind_gid)
1682 os.chown(paths.dns, -1, paths.bind_gid)
1683 # chmod needed to cope with umask
1684 os.chmod(dns_dir, 0775)
1685 os.chmod(paths.dns, 0664)
1687 logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
1689 if targetdir is None:
1690 os.system(rndc + " unfreeze " + lp.get("realm"))
1693 def create_dns_update_list(lp, logger, paths, setup_path):
1694 """Write out a dns_update_list file"""
1695 # note that we use no variable substitution on this file
1696 # the substitution is done at runtime by samba_dnsupdate
1697 setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1698 setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1701 def create_named_conf(paths, setup_path, realm, dnsdomain,
1703 """Write out a file containing zone statements suitable for inclusion in a
1704 named.conf file (including GSS-TSIG configuration).
1706 :param paths: all paths
1707 :param setup_path: Setup path function.
1708 :param realm: Realm name
1709 :param dnsdomain: DNS Domain name
1710 :param private_dir: Path to private directory
1711 :param keytab_name: File name of DNS keytab file
1714 setup_file(setup_path("named.conf"), paths.namedconf, {
1715 "DNSDOMAIN": dnsdomain,
1717 "ZONE_FILE": paths.dns,
1718 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1719 "NAMED_CONF": paths.namedconf,
1720 "NAMED_CONF_UPDATE": paths.namedconf_update
1723 setup_file(setup_path("named.conf.update"), paths.namedconf_update)
1726 def create_named_txt(path, setup_path, realm, dnsdomain,
1727 private_dir, keytab_name):
1728 """Write out a file containing zone statements suitable for inclusion in a
1729 named.conf file (including GSS-TSIG configuration).
1731 :param path: Path of the new named.conf file.
1732 :param setup_path: Setup path function.
1733 :param realm: Realm name
1734 :param dnsdomain: DNS Domain name
1735 :param private_dir: Path to private directory
1736 :param keytab_name: File name of DNS keytab file
1739 setup_file(setup_path("named.txt"), path, {
1740 "DNSDOMAIN": dnsdomain,
1742 "DNS_KEYTAB": keytab_name,
1743 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1744 "PRIVATE_DIR": private_dir
1748 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1749 """Write out a file containing zone statements suitable for inclusion in a
1750 named.conf file (including GSS-TSIG configuration).
1752 :param path: Path of the new named.conf file.
1753 :param setup_path: Setup path function.
1754 :param dnsdomain: DNS Domain name
1755 :param hostname: Local hostname
1756 :param realm: Realm name
1758 setup_file(setup_path("krb5.conf"), path, {
1759 "DNSDOMAIN": dnsdomain,
1760 "HOSTNAME": hostname,
1765 class ProvisioningError(Exception):
1766 """A generic provision error."""
1768 def __init__(self, value):
1772 return "ProvisioningError: " + self.value
1775 class InvalidNetbiosName(Exception):
1776 """A specified name was not a valid NetBIOS name."""
1777 def __init__(self, name):
1778 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)