samba-tool: skip chown in sysvolreset when it would fail on a GID
[Samba/bb.git] / source4 / scripting / python / samba / provision / __init__.py
blob9966192a1973594656fb8669b98076dcb71ee673
1 # Unix SMB/CIFS implementation.
2 # backend code for provisioning a Samba4 server
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
5 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
6 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
8 # Based on the original in EJS:
9 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 """Functions for setting up a Samba configuration."""
27 __docformat__ = "restructuredText"
29 from base64 import b64encode
30 import os
31 import re
32 import pwd
33 import grp
34 import logging
35 import time
36 import uuid
37 import socket
38 import urllib
39 import string
40 import tempfile
42 import ldb
44 from samba.auth import system_session, admin_session
45 import samba
46 from samba.samba3 import smbd, passdb
47 from samba.samba3 import param as s3param
48 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
49 from samba import (
50 Ldb,
51 MAX_NETBIOS_NAME_LEN,
52 check_all_substituted,
53 is_valid_netbios_char,
54 setup_file,
55 substitute_var,
56 valid_netbios_name,
57 version,
59 from samba.dcerpc import security, misc
60 from samba.dcerpc.misc import (
61 SEC_CHAN_BDC,
62 SEC_CHAN_WKSTA,
64 from samba.dsdb import (
65 DS_DOMAIN_FUNCTION_2003,
66 DS_DOMAIN_FUNCTION_2008_R2,
67 ENC_ALL_TYPES,
69 from samba.idmap import IDmapDB
70 from samba.ms_display_specifiers import read_ms_ldif
71 from samba.ntacls import setntacl, getntacl, dsacl2fsacl
72 from samba.ndr import ndr_pack, ndr_unpack
73 from samba.provision.backend import (
74 ExistingBackend,
75 FDSBackend,
76 LDBBackend,
77 OpenLDAPBackend,
79 from samba.provision.descriptor import (
80 get_config_descriptor,
81 get_domain_descriptor
83 from samba.provision.common import (
84 setup_path,
85 setup_add_ldif,
86 setup_modify_ldif,
88 from samba.provision.sambadns import (
89 setup_ad_dns,
90 create_dns_update_list
93 import samba.param
94 import samba.registry
95 from samba.schema import Schema
96 from samba.samdb import SamDB
97 from samba.dbchecker import dbcheck
100 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
101 DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
102 DEFAULTSITE = "Default-First-Site-Name"
103 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
106 class ProvisionPaths(object):
108 def __init__(self):
109 self.shareconf = None
110 self.hklm = None
111 self.hkcu = None
112 self.hkcr = None
113 self.hku = None
114 self.hkpd = None
115 self.hkpt = None
116 self.samdb = None
117 self.idmapdb = None
118 self.secrets = None
119 self.keytab = None
120 self.dns_keytab = None
121 self.dns = None
122 self.winsdb = None
123 self.private_dir = None
124 self.state_dir = None
125 self.phpldapadminconfig = None
128 class ProvisionNames(object):
130 def __init__(self):
131 self.rootdn = None
132 self.domaindn = None
133 self.configdn = None
134 self.schemadn = None
135 self.ldapmanagerdn = None
136 self.dnsdomain = None
137 self.realm = None
138 self.netbiosname = None
139 self.domain = None
140 self.hostname = None
141 self.sitename = None
142 self.smbconf = None
145 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
146 lp):
147 """Get key provision parameters (realm, domain, ...) from a given provision
149 :param samdb: An LDB object connected to the sam.ldb file
150 :param secretsdb: An LDB object connected to the secrets.ldb file
151 :param idmapdb: An LDB object connected to the idmap.ldb file
152 :param paths: A list of path to provision object
153 :param smbconf: Path to the smb.conf file
154 :param lp: A LoadParm object
155 :return: A list of key provision parameters
157 names = ProvisionNames()
158 names.adminpass = None
160 # NT domain, kerberos realm, root dn, domain dn, domain dns name
161 names.domain = string.upper(lp.get("workgroup"))
162 names.realm = lp.get("realm")
163 names.dnsdomain = names.realm.lower()
164 basedn = samba.dn_from_dns_name(names.dnsdomain)
165 names.realm = string.upper(names.realm)
166 # netbiosname
167 # Get the netbiosname first (could be obtained from smb.conf in theory)
168 res = secretsdb.search(expression="(flatname=%s)" %
169 names.domain,base="CN=Primary Domains",
170 scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
171 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
173 names.smbconf = smbconf
175 # That's a bit simplistic but it's ok as long as we have only 3
176 # partitions
177 current = samdb.search(expression="(objectClass=*)",
178 base="", scope=ldb.SCOPE_BASE,
179 attrs=["defaultNamingContext", "schemaNamingContext",
180 "configurationNamingContext","rootDomainNamingContext"])
182 names.configdn = current[0]["configurationNamingContext"]
183 configdn = str(names.configdn)
184 names.schemadn = current[0]["schemaNamingContext"]
185 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
186 current[0]["defaultNamingContext"][0]))):
187 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
188 "is not the same ..." % (paths.samdb,
189 str(current[0]["defaultNamingContext"][0]),
190 paths.smbconf, basedn)))
192 names.domaindn=current[0]["defaultNamingContext"]
193 names.rootdn=current[0]["rootDomainNamingContext"]
194 # default site name
195 res3 = samdb.search(expression="(objectClass=site)",
196 base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
197 names.sitename = str(res3[0]["cn"])
199 # dns hostname and server dn
200 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
201 base="OU=Domain Controllers,%s" % basedn,
202 scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
203 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain, "")
205 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
206 attrs=[], base=configdn)
207 names.serverdn = server_res[0].dn
209 # invocation id/objectguid
210 res5 = samdb.search(expression="(objectClass=*)",
211 base="CN=NTDS Settings,%s" % str(names.serverdn),
212 scope=ldb.SCOPE_BASE,
213 attrs=["invocationID", "objectGUID"])
214 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
215 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
217 # domain guid/sid
218 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
219 scope=ldb.SCOPE_BASE, attrs=["objectGUID",
220 "objectSid","msDS-Behavior-Version" ])
221 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
222 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
223 if res6[0].get("msDS-Behavior-Version") is None or \
224 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
225 names.domainlevel = DS_DOMAIN_FUNCTION_2000
226 else:
227 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
229 # policy guid
230 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
231 base="CN=Policies,CN=System," + basedn,
232 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
233 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
234 # dc policy guid
235 res8 = samdb.search(expression="(displayName=Default Domain Controllers"
236 " Policy)",
237 base="CN=Policies,CN=System," + basedn,
238 scope=ldb.SCOPE_ONELEVEL,
239 attrs=["cn","displayName"])
240 if len(res8) == 1:
241 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
242 else:
243 names.policyid_dc = None
244 res9 = idmapdb.search(expression="(cn=%s)" %
245 (security.SID_BUILTIN_ADMINISTRATORS),
246 attrs=["xidNumber"])
247 if len(res9) != 1:
248 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
249 names.wheel_gid = res9[0]["xidNumber"]
250 return names
253 def update_provision_usn(samdb, low, high, id, replace=False):
254 """Update the field provisionUSN in sam.ldb
256 This field is used to track range of USN modified by provision and
257 upgradeprovision.
258 This value is used afterward by next provision to figure out if
259 the field have been modified since last provision.
261 :param samdb: An LDB object connect to sam.ldb
262 :param low: The lowest USN modified by this upgrade
263 :param high: The highest USN modified by this upgrade
264 :param id: The invocation id of the samba's dc
265 :param replace: A boolean indicating if the range should replace any
266 existing one or appended (default)
269 tab = []
270 if not replace:
271 entry = samdb.search(base="@PROVISION",
272 scope=ldb.SCOPE_BASE,
273 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
274 for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
275 if not re.search(';', e):
276 e = "%s;%s" % (e, id)
277 tab.append(str(e))
279 tab.append("%s-%s;%s" % (low, high, id))
280 delta = ldb.Message()
281 delta.dn = ldb.Dn(samdb, "@PROVISION")
282 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
283 ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
284 entry = samdb.search(expression='provisionnerID=*',
285 base="@PROVISION", scope=ldb.SCOPE_BASE,
286 attrs=["provisionnerID"])
287 if len(entry) == 0 or len(entry[0]) == 0:
288 delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
289 samdb.modify(delta)
292 def set_provision_usn(samdb, low, high, id):
293 """Set the field provisionUSN in sam.ldb
294 This field is used to track range of USN modified by provision and
295 upgradeprovision.
296 This value is used afterward by next provision to figure out if
297 the field have been modified since last provision.
299 :param samdb: An LDB object connect to sam.ldb
300 :param low: The lowest USN modified by this upgrade
301 :param high: The highest USN modified by this upgrade
302 :param id: The invocationId of the provision"""
304 tab = []
305 tab.append("%s-%s;%s" % (low, high, id))
307 delta = ldb.Message()
308 delta.dn = ldb.Dn(samdb, "@PROVISION")
309 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
310 ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
311 samdb.add(delta)
314 def get_max_usn(samdb,basedn):
315 """ This function return the biggest USN present in the provision
317 :param samdb: A LDB object pointing to the sam.ldb
318 :param basedn: A string containing the base DN of the provision
319 (ie. DC=foo, DC=bar)
320 :return: The biggest USN in the provision"""
322 res = samdb.search(expression="objectClass=*",base=basedn,
323 scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
324 controls=["search_options:1:2",
325 "server_sort:1:1:uSNChanged",
326 "paged_results:1:1"])
327 return res[0]["uSNChanged"]
330 def get_last_provision_usn(sam):
331 """Get USNs ranges modified by a provision or an upgradeprovision
333 :param sam: An LDB object pointing to the sam.ldb
334 :return: a dictionary which keys are invocation id and values are an array
335 of integer representing the different ranges
337 try:
338 entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
339 base="@PROVISION", scope=ldb.SCOPE_BASE,
340 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
341 except ldb.LdbError, (ecode, emsg):
342 if ecode == ldb.ERR_NO_SUCH_OBJECT:
343 return None
344 raise
345 if len(entry) > 0:
346 myids = []
347 range = {}
348 p = re.compile(r'-')
349 if entry[0].get("provisionnerID"):
350 for e in entry[0]["provisionnerID"]:
351 myids.append(str(e))
352 for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
353 tab1 = str(r).split(';')
354 if len(tab1) == 2:
355 id = tab1[1]
356 else:
357 id = "default"
358 if (len(myids) > 0 and id not in myids):
359 continue
360 tab2 = p.split(tab1[0])
361 if range.get(id) is None:
362 range[id] = []
363 range[id].append(tab2[0])
364 range[id].append(tab2[1])
365 return range
366 else:
367 return None
370 class ProvisionResult(object):
371 """Result of a provision.
373 :ivar server_role: The server role
374 :ivar paths: ProvisionPaths instance
375 :ivar domaindn: The domain dn, as string
378 def __init__(self):
379 self.server_role = None
380 self.paths = None
381 self.domaindn = None
382 self.lp = None
383 self.samdb = None
384 self.idmap = None
385 self.names = None
386 self.domainsid = None
387 self.adminpass_generated = None
388 self.adminpass = None
389 self.backend_result = None
391 def report_logger(self, logger):
392 """Report this provision result to a logger."""
393 logger.info(
394 "Once the above files are installed, your Samba4 server will "
395 "be ready to use")
396 if self.adminpass_generated:
397 logger.info("Admin password: %s", self.adminpass)
398 logger.info("Server Role: %s", self.server_role)
399 logger.info("Hostname: %s", self.names.hostname)
400 logger.info("NetBIOS Domain: %s", self.names.domain)
401 logger.info("DNS Domain: %s", self.names.dnsdomain)
402 logger.info("DOMAIN SID: %s", self.domainsid)
404 if self.paths.phpldapadminconfig is not None:
405 logger.info(
406 "A phpLDAPadmin configuration file suitable for administering "
407 "the Samba 4 LDAP server has been created in %s.",
408 self.paths.phpldapadminconfig)
410 if self.backend_result:
411 self.backend_result.report_logger(logger)
414 def check_install(lp, session_info, credentials):
415 """Check whether the current install seems ok.
417 :param lp: Loadparm context
418 :param session_info: Session information
419 :param credentials: Credentials
421 if lp.get("realm") == "":
422 raise Exception("Realm empty")
423 samdb = Ldb(lp.samdb_url(), session_info=session_info,
424 credentials=credentials, lp=lp)
425 if len(samdb.search("(cn=Administrator)")) != 1:
426 raise ProvisioningError("No administrator account found")
429 def findnss(nssfn, names):
430 """Find a user or group from a list of possibilities.
432 :param nssfn: NSS Function to try (should raise KeyError if not found)
433 :param names: Names to check.
434 :return: Value return by first names list.
436 for name in names:
437 try:
438 return nssfn(name)
439 except KeyError:
440 pass
441 raise KeyError("Unable to find user/group in %r" % names)
444 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
445 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
448 def provision_paths_from_lp(lp, dnsdomain):
449 """Set the default paths for provisioning.
451 :param lp: Loadparm context.
452 :param dnsdomain: DNS Domain name
454 paths = ProvisionPaths()
455 paths.private_dir = lp.get("private dir")
456 paths.state_dir = lp.get("state directory")
458 # This is stored without path prefix for the "privateKeytab" attribute in
459 # "secrets_dns.ldif".
460 paths.dns_keytab = "dns.keytab"
461 paths.keytab = "secrets.keytab"
463 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
464 paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
465 paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
466 paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
467 paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
468 paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
469 paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
470 paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
471 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
472 paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
473 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
474 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
475 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
476 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
477 paths.phpldapadminconfig = os.path.join(paths.private_dir,
478 "phpldapadmin-config.php")
479 paths.hklm = "hklm.ldb"
480 paths.hkcr = "hkcr.ldb"
481 paths.hkcu = "hkcu.ldb"
482 paths.hku = "hku.ldb"
483 paths.hkpd = "hkpd.ldb"
484 paths.hkpt = "hkpt.ldb"
485 paths.sysvol = lp.get("path", "sysvol")
486 paths.netlogon = lp.get("path", "netlogon")
487 paths.smbconf = lp.configfile
488 return paths
491 def determine_netbios_name(hostname):
492 """Determine a netbios name from a hostname."""
493 # remove forbidden chars and force the length to be <16
494 netbiosname = "".join([x for x in hostname if is_valid_netbios_char(x)])
495 return netbiosname[:MAX_NETBIOS_NAME_LEN].upper()
498 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
499 serverrole=None, rootdn=None, domaindn=None, configdn=None,
500 schemadn=None, serverdn=None, sitename=None):
501 """Guess configuration settings to use."""
503 if hostname is None:
504 hostname = socket.gethostname().split(".")[0]
506 netbiosname = lp.get("netbios name")
507 if netbiosname is None:
508 netbiosname = determine_netbios_name(hostname)
509 netbiosname = netbiosname.upper()
510 if not valid_netbios_name(netbiosname):
511 raise InvalidNetbiosName(netbiosname)
513 if dnsdomain is None:
514 dnsdomain = lp.get("realm")
515 if dnsdomain is None or dnsdomain == "":
516 raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
518 dnsdomain = dnsdomain.lower()
520 if serverrole is None:
521 serverrole = lp.get("server role")
522 if serverrole is None:
523 raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
525 serverrole = serverrole.lower()
527 realm = dnsdomain.upper()
529 if lp.get("realm") == "":
530 raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile)
532 if lp.get("realm").upper() != realm:
533 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))
535 if lp.get("server role").lower() != serverrole:
536 raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role"), lp.configfile, serverrole))
538 if serverrole == "active directory domain controller":
539 if domain is None:
540 # This will, for better or worse, default to 'WORKGROUP'
541 domain = lp.get("workgroup")
542 domain = domain.upper()
544 if lp.get("workgroup").upper() != domain:
545 raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
547 if domaindn is None:
548 domaindn = samba.dn_from_dns_name(dnsdomain)
550 if domain == netbiosname:
551 raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
552 else:
553 domain = netbiosname
554 if domaindn is None:
555 domaindn = "DC=" + netbiosname
557 if not valid_netbios_name(domain):
558 raise InvalidNetbiosName(domain)
560 if hostname.upper() == realm:
561 raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
562 if netbiosname.upper() == realm:
563 raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
564 if domain == realm:
565 raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
567 if rootdn is None:
568 rootdn = domaindn
570 if configdn is None:
571 configdn = "CN=Configuration," + rootdn
572 if schemadn is None:
573 schemadn = "CN=Schema," + configdn
575 if sitename is None:
576 sitename = DEFAULTSITE
578 names = ProvisionNames()
579 names.rootdn = rootdn
580 names.domaindn = domaindn
581 names.configdn = configdn
582 names.schemadn = schemadn
583 names.ldapmanagerdn = "CN=Manager," + rootdn
584 names.dnsdomain = dnsdomain
585 names.domain = domain
586 names.realm = realm
587 names.netbiosname = netbiosname
588 names.hostname = hostname
589 names.sitename = sitename
590 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
591 netbiosname, sitename, configdn)
593 return names
596 def make_smbconf(smbconf, hostname, domain, realm, targetdir,
597 serverrole=None, eadb=False, use_ntvfs=False, lp=None,
598 global_param=None):
599 """Create a new smb.conf file based on a couple of basic settings.
601 assert smbconf is not None
603 if hostname is None:
604 hostname = socket.gethostname().split(".")[0]
606 netbiosname = determine_netbios_name(hostname)
608 if serverrole is None:
609 serverrole = "standalone server"
611 assert domain is not None
612 domain = domain.upper()
614 assert realm is not None
615 realm = realm.upper()
617 global_settings = {
618 "netbios name": netbiosname,
619 "workgroup": domain,
620 "realm": realm,
621 "server role": serverrole,
624 if lp is None:
625 lp = samba.param.LoadParm()
626 #Load non-existent file
627 if os.path.exists(smbconf):
628 lp.load(smbconf)
630 if global_param is not None:
631 for ent in global_param:
632 if global_param[ent] is not None:
633 global_settings[ent] = " ".join(global_param[ent])
635 if targetdir is not None:
636 global_settings["private dir"] = os.path.abspath(os.path.join(targetdir, "private"))
637 global_settings["lock dir"] = os.path.abspath(targetdir)
638 global_settings["state directory"] = os.path.abspath(os.path.join(targetdir, "state"))
639 global_settings["cache directory"] = os.path.abspath(os.path.join(targetdir, "cache"))
641 lp.set("lock dir", os.path.abspath(targetdir))
642 lp.set("state directory", global_settings["state directory"])
643 lp.set("cache directory", global_settings["cache directory"])
645 if eadb:
646 if use_ntvfs and not lp.get("posix:eadb"):
647 if targetdir is not None:
648 privdir = os.path.join(targetdir, "private")
649 else:
650 privdir = lp.get("private dir")
651 lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
652 elif not use_ntvfs and not lp.get("xattr_tdb:file"):
653 if targetdir is not None:
654 statedir = os.path.join(targetdir, "state")
655 else:
656 statedir = lp.get("state directory")
657 lp.set("xattr_tdb:file", os.path.abspath(os.path.join(statedir, "xattr.tdb")))
659 shares = {}
660 if serverrole == "active directory domain controller":
661 shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol")
662 shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(),
663 "scripts")
664 else:
665 global_settings["passdb backend"] = "samba_dsdb"
667 f = open(smbconf, 'w')
668 try:
669 f.write("[globals]\n")
670 for key, val in global_settings.iteritems():
671 f.write("\t%s = %s\n" % (key, val))
672 f.write("\n")
674 for name, path in shares.iteritems():
675 f.write("[%s]\n" % name)
676 f.write("\tpath = %s\n" % path)
677 f.write("\tread only = no\n")
678 f.write("\n")
679 finally:
680 f.close()
681 # reload the smb.conf
682 lp.load(smbconf)
684 # and dump it without any values that are the default
685 # this ensures that any smb.conf parameters that were set
686 # on the provision/join command line are set in the resulting smb.conf
687 f = open(smbconf, mode='w')
688 try:
689 lp.dump(f, False)
690 finally:
691 f.close()
694 def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
695 users_gid, wheel_gid):
696 """setup reasonable name mappings for sam names to unix names.
698 :param samdb: SamDB object.
699 :param idmap: IDmap db object.
700 :param sid: The domain sid.
701 :param domaindn: The domain DN.
702 :param root_uid: uid of the UNIX root user.
703 :param nobody_uid: uid of the UNIX nobody user.
704 :param users_gid: gid of the UNIX users group.
705 :param wheel_gid: gid of the UNIX wheel group.
707 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
708 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
710 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
711 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
714 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
715 provision_backend, names, schema, serverrole,
716 erase=False):
717 """Setup the partitions for the SAM database.
719 Alternatively, provision() may call this, and then populate the database.
721 :note: This will wipe the Sam Database!
723 :note: This function always removes the local SAM LDB file. The erase
724 parameter controls whether to erase the existing data, which
725 may not be stored locally but in LDAP.
728 assert session_info is not None
730 # We use options=["modules:"] to stop the modules loading - we
731 # just want to wipe and re-initialise the database, not start it up
733 try:
734 os.unlink(samdb_path)
735 except OSError:
736 pass
738 samdb = Ldb(url=samdb_path, session_info=session_info,
739 lp=lp, options=["modules:"])
741 ldap_backend_line = "# No LDAP backend"
742 if provision_backend.type != "ldb":
743 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
745 samdb.transaction_start()
746 try:
747 logger.info("Setting up sam.ldb partitions and settings")
748 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
749 "LDAP_BACKEND_LINE": ldap_backend_line
753 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
754 "BACKEND_TYPE": provision_backend.type,
755 "SERVER_ROLE": serverrole
758 logger.info("Setting up sam.ldb rootDSE")
759 setup_samdb_rootdse(samdb, names)
760 except:
761 samdb.transaction_cancel()
762 raise
763 else:
764 samdb.transaction_commit()
767 def secretsdb_self_join(secretsdb, domain,
768 netbiosname, machinepass, domainsid=None,
769 realm=None, dnsdomain=None,
770 keytab_path=None,
771 key_version_number=1,
772 secure_channel_type=SEC_CHAN_WKSTA):
773 """Add domain join-specific bits to a secrets database.
775 :param secretsdb: Ldb Handle to the secrets database
776 :param machinepass: Machine password
778 attrs = ["whenChanged",
779 "secret",
780 "priorSecret",
781 "priorChanged",
782 "krb5Keytab",
783 "privateKeytab"]
785 if realm is not None:
786 if dnsdomain is None:
787 dnsdomain = realm.lower()
788 dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
789 else:
790 dnsname = None
791 shortname = netbiosname.lower()
793 # We don't need to set msg["flatname"] here, because rdn_name will handle
794 # it, and it causes problems for modifies anyway
795 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
796 msg["secureChannelType"] = [str(secure_channel_type)]
797 msg["objectClass"] = ["top", "primaryDomain"]
798 if dnsname is not None:
799 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
800 msg["realm"] = [realm]
801 msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
802 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
803 msg["privateKeytab"] = ["secrets.keytab"]
805 msg["secret"] = [machinepass]
806 msg["samAccountName"] = ["%s$" % netbiosname]
807 msg["secureChannelType"] = [str(secure_channel_type)]
808 if domainsid is not None:
809 msg["objectSid"] = [ndr_pack(domainsid)]
811 # This complex expression tries to ensure that we don't have more
812 # than one record for this SID, realm or netbios domain at a time,
813 # but we don't delete the old record that we are about to modify,
814 # because that would delete the keytab and previous password.
815 res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
816 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(distinguishedName=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
817 scope=ldb.SCOPE_ONELEVEL)
819 for del_msg in res:
820 secretsdb.delete(del_msg.dn)
822 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
824 if len(res) == 1:
825 msg["priorSecret"] = [res[0]["secret"][0]]
826 msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
828 try:
829 msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
830 except KeyError:
831 pass
833 try:
834 msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
835 except KeyError:
836 pass
838 for el in msg:
839 if el != 'dn':
840 msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
841 secretsdb.modify(msg)
842 secretsdb.rename(res[0].dn, msg.dn)
843 else:
844 spn = [ 'HOST/%s' % shortname ]
845 if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
846 # we are a domain controller then we add servicePrincipalName
847 # entries for the keytab code to update.
848 spn.extend([ 'HOST/%s' % dnsname ])
849 msg["servicePrincipalName"] = spn
851 secretsdb.add(msg)
854 def setup_secretsdb(paths, session_info, backend_credentials, lp):
855 """Setup the secrets database.
857 :note: This function does not handle exceptions and transaction on purpose,
858 it's up to the caller to do this job.
860 :param path: Path to the secrets database.
861 :param session_info: Session info.
862 :param credentials: Credentials
863 :param lp: Loadparm context
864 :return: LDB handle for the created secrets database
866 if os.path.exists(paths.secrets):
867 os.unlink(paths.secrets)
869 keytab_path = os.path.join(paths.private_dir, paths.keytab)
870 if os.path.exists(keytab_path):
871 os.unlink(keytab_path)
873 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
874 if os.path.exists(dns_keytab_path):
875 os.unlink(dns_keytab_path)
877 path = paths.secrets
879 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
880 secrets_ldb.erase()
881 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
882 secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
883 secrets_ldb.transaction_start()
884 try:
885 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
887 if (backend_credentials is not None and
888 backend_credentials.authentication_requested()):
889 if backend_credentials.get_bind_dn() is not None:
890 setup_add_ldif(secrets_ldb,
891 setup_path("secrets_simple_ldap.ldif"), {
892 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
893 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
895 else:
896 setup_add_ldif(secrets_ldb,
897 setup_path("secrets_sasl_ldap.ldif"), {
898 "LDAPADMINUSER": backend_credentials.get_username(),
899 "LDAPADMINREALM": backend_credentials.get_realm(),
900 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
902 except:
903 secrets_ldb.transaction_cancel()
904 raise
905 return secrets_ldb
908 def setup_privileges(path, session_info, lp):
909 """Setup the privileges database.
911 :param path: Path to the privileges database.
912 :param session_info: Session info.
913 :param credentials: Credentials
914 :param lp: Loadparm context
915 :return: LDB handle for the created secrets database
917 if os.path.exists(path):
918 os.unlink(path)
919 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
920 privilege_ldb.erase()
921 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
924 def setup_registry(path, session_info, lp):
925 """Setup the registry.
927 :param path: Path to the registry database
928 :param session_info: Session information
929 :param credentials: Credentials
930 :param lp: Loadparm context
932 reg = samba.registry.Registry()
933 hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
934 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
935 provision_reg = setup_path("provision.reg")
936 assert os.path.exists(provision_reg)
937 reg.diff_apply(provision_reg)
940 def setup_idmapdb(path, session_info, lp):
941 """Setup the idmap database.
943 :param path: path to the idmap database
944 :param session_info: Session information
945 :param credentials: Credentials
946 :param lp: Loadparm context
948 if os.path.exists(path):
949 os.unlink(path)
951 idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
952 idmap_ldb.erase()
953 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
954 return idmap_ldb
957 def setup_samdb_rootdse(samdb, names):
958 """Setup the SamDB rootdse.
960 :param samdb: Sam Database handle
962 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
963 "SCHEMADN": names.schemadn,
964 "DOMAINDN": names.domaindn,
965 "ROOTDN" : names.rootdn,
966 "CONFIGDN": names.configdn,
967 "SERVERDN": names.serverdn,
971 def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
972 dns_backend, dnspass, domainsid, next_rid, invocationid,
973 policyguid, policyguid_dc,
974 domainControllerFunctionality, ntdsguid=None, dc_rid=None):
975 """Join a host to its own domain."""
976 assert isinstance(invocationid, str)
977 if ntdsguid is not None:
978 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
979 else:
980 ntdsguid_line = ""
982 if dc_rid is None:
983 dc_rid = next_rid
985 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
986 "CONFIGDN": names.configdn,
987 "SCHEMADN": names.schemadn,
988 "DOMAINDN": names.domaindn,
989 "SERVERDN": names.serverdn,
990 "INVOCATIONID": invocationid,
991 "NETBIOSNAME": names.netbiosname,
992 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
993 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
994 "DOMAINSID": str(domainsid),
995 "DCRID": str(dc_rid),
996 "SAMBA_VERSION_STRING": version,
997 "NTDSGUID": ntdsguid_line,
998 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
999 domainControllerFunctionality),
1000 "RIDALLOCATIONSTART": str(next_rid + 100),
1001 "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
1003 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
1004 "POLICYGUID": policyguid,
1005 "POLICYGUID_DC": policyguid_dc,
1006 "DNSDOMAIN": names.dnsdomain,
1007 "DOMAINDN": names.domaindn})
1009 # If we are setting up a subdomain, then this has been replicated in, so we
1010 # don't need to add it
1011 if fill == FILL_FULL:
1012 setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), {
1013 "CONFIGDN": names.configdn,
1014 "SCHEMADN": names.schemadn,
1015 "DOMAINDN": names.domaindn,
1016 "SERVERDN": names.serverdn,
1017 "INVOCATIONID": invocationid,
1018 "NETBIOSNAME": names.netbiosname,
1019 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
1020 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
1021 "DOMAINSID": str(domainsid),
1022 "DCRID": str(dc_rid),
1023 "SAMBA_VERSION_STRING": version,
1024 "NTDSGUID": ntdsguid_line,
1025 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
1026 domainControllerFunctionality)})
1028 # Setup fSMORoleOwner entries to point at the newly created DC entry
1029 setup_modify_ldif(samdb,
1030 setup_path("provision_self_join_modify_config.ldif"), {
1031 "CONFIGDN": names.configdn,
1032 "SCHEMADN": names.schemadn,
1033 "DEFAULTSITE": names.sitename,
1034 "NETBIOSNAME": names.netbiosname,
1035 "SERVERDN": names.serverdn,
1038 system_session_info = system_session()
1039 samdb.set_session_info(system_session_info)
1040 # Setup fSMORoleOwner entries to point at the newly created DC entry to
1041 # modify a serverReference under cn=config when we are a subdomain, we must
1042 # be system due to ACLs
1043 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1044 "DOMAINDN": names.domaindn,
1045 "SERVERDN": names.serverdn,
1046 "NETBIOSNAME": names.netbiosname,
1049 samdb.set_session_info(admin_session_info)
1051 if dns_backend != "SAMBA_INTERNAL":
1052 # This is Samba4 specific and should be replaced by the correct
1053 # DNS AD-style setup
1054 setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
1055 "DNSDOMAIN": names.dnsdomain,
1056 "DOMAINDN": names.domaindn,
1057 "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
1058 "HOSTNAME" : names.hostname,
1059 "DNSNAME" : '%s.%s' % (
1060 names.netbiosname.lower(), names.dnsdomain.lower())
1064 def getpolicypath(sysvolpath, dnsdomain, guid):
1065 """Return the physical path of policy given its guid.
1067 :param sysvolpath: Path to the sysvol folder
1068 :param dnsdomain: DNS name of the AD domain
1069 :param guid: The GUID of the policy
1070 :return: A string with the complete path to the policy folder
1072 if guid[0] != "{":
1073 guid = "{%s}" % guid
1074 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
1075 return policy_path
1078 def create_gpo_struct(policy_path):
1079 if not os.path.exists(policy_path):
1080 os.makedirs(policy_path, 0775)
1081 f = open(os.path.join(policy_path, "GPT.INI"), 'w')
1082 try:
1083 f.write("[General]\r\nVersion=0")
1084 finally:
1085 f.close()
1086 p = os.path.join(policy_path, "MACHINE")
1087 if not os.path.exists(p):
1088 os.makedirs(p, 0775)
1089 p = os.path.join(policy_path, "USER")
1090 if not os.path.exists(p):
1091 os.makedirs(p, 0775)
1094 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
1095 """Create the default GPO for a domain
1097 :param sysvolpath: Physical path for the sysvol folder
1098 :param dnsdomain: DNS domain name of the AD domain
1099 :param policyguid: GUID of the default domain policy
1100 :param policyguid_dc: GUID of the default domain controler policy
1102 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
1103 create_gpo_struct(policy_path)
1105 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
1106 create_gpo_struct(policy_path)
1109 def setup_samdb(path, session_info, provision_backend, lp, names,
1110 logger, fill, serverrole, schema, am_rodc=False):
1111 """Setup a complete SAM Database.
1113 :note: This will wipe the main SAM database file!
1116 # Also wipes the database
1117 setup_samdb_partitions(path, logger=logger, lp=lp,
1118 provision_backend=provision_backend, session_info=session_info,
1119 names=names, serverrole=serverrole, schema=schema)
1121 # Load the database, but don's load the global schema and don't connect
1122 # quite yet
1123 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
1124 credentials=provision_backend.credentials, lp=lp,
1125 global_schema=False, am_rodc=am_rodc)
1127 logger.info("Pre-loading the Samba 4 and AD schema")
1129 # Load the schema from the one we computed earlier
1130 samdb.set_schema(schema, write_indices_and_attributes=False)
1132 # Set the NTDS settings DN manually - in order to have it already around
1133 # before the provisioned tree exists and we connect
1134 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1136 # And now we can connect to the DB - the schema won't be loaded from the
1137 # DB
1138 samdb.connect(path)
1140 # But we have to give it one more kick to have it use the schema
1141 # during provision - it needs, now that it is connected, to write
1142 # the schema @ATTRIBUTES and @INDEXLIST records to the database.
1143 samdb.set_schema(schema, write_indices_and_attributes=True)
1145 return samdb
1148 def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid,
1149 policyguid_dc, fill, adminpass, krbtgtpass, machinepass, dns_backend,
1150 dnspass, invocationid, ntdsguid, serverrole, am_rodc=False,
1151 dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None):
1153 if next_rid is None:
1154 next_rid = 1000
1156 # Provision does not make much sense values larger than 1000000000
1157 # as the upper range of the rIDAvailablePool is 1073741823 and
1158 # we don't want to create a domain that cannot allocate rids.
1159 if next_rid < 1000 or next_rid > 1000000000:
1160 error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
1161 error += "the valid range is %u-%u. The default is %u." % (
1162 1000, 1000000000, 1000)
1163 raise ProvisioningError(error)
1165 # ATTENTION: Do NOT change these default values without discussion with the
1166 # team and/or release manager. They have a big impact on the whole program!
1167 domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
1169 if dom_for_fun_level is None:
1170 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1172 if dom_for_fun_level > domainControllerFunctionality:
1173 raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008_R2). This won't work!")
1175 domainFunctionality = dom_for_fun_level
1176 forestFunctionality = dom_for_fun_level
1178 # Set the NTDS settings DN manually - in order to have it already around
1179 # before the provisioned tree exists and we connect
1180 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1182 samdb.transaction_start()
1183 try:
1184 # Set the domain functionality levels onto the database.
1185 # Various module (the password_hash module in particular) need
1186 # to know what level of AD we are emulating.
1188 # These will be fixed into the database via the database
1189 # modifictions below, but we need them set from the start.
1190 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1191 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1192 samdb.set_opaque_integer("domainControllerFunctionality",
1193 domainControllerFunctionality)
1195 samdb.set_domain_sid(str(domainsid))
1196 samdb.set_invocation_id(invocationid)
1198 logger.info("Adding DomainDN: %s" % names.domaindn)
1200 # impersonate domain admin
1201 admin_session_info = admin_session(lp, str(domainsid))
1202 samdb.set_session_info(admin_session_info)
1203 if domainguid is not None:
1204 domainguid_line = "objectGUID: %s\n-" % domainguid
1205 else:
1206 domainguid_line = ""
1208 descr = b64encode(get_domain_descriptor(domainsid))
1209 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1210 "DOMAINDN": names.domaindn,
1211 "DOMAINSID": str(domainsid),
1212 "DESCRIPTOR": descr,
1213 "DOMAINGUID": domainguid_line
1216 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1217 "DOMAINDN": names.domaindn,
1218 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1219 "NEXTRID": str(next_rid),
1220 "DEFAULTSITE": names.sitename,
1221 "CONFIGDN": names.configdn,
1222 "POLICYGUID": policyguid,
1223 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1224 "SAMBA_VERSION_STRING": version
1227 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1228 if fill == FILL_FULL:
1229 logger.info("Adding configuration container")
1230 descr = b64encode(get_config_descriptor(domainsid))
1231 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1232 "CONFIGDN": names.configdn,
1233 "DESCRIPTOR": descr,
1236 # The LDIF here was created when the Schema object was constructed
1237 logger.info("Setting up sam.ldb schema")
1238 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1239 samdb.modify_ldif(schema.schema_dn_modify)
1240 samdb.write_prefixes_from_schema()
1241 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1242 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1243 {"SCHEMADN": names.schemadn})
1245 # Now register this container in the root of the forest
1246 msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
1247 msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
1248 "subRefs")
1250 except:
1251 samdb.transaction_cancel()
1252 raise
1253 else:
1254 samdb.transaction_commit()
1256 samdb.transaction_start()
1257 try:
1258 samdb.invocation_id = invocationid
1260 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1261 if fill == FILL_FULL:
1262 logger.info("Setting up sam.ldb configuration data")
1263 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1264 "CONFIGDN": names.configdn,
1265 "NETBIOSNAME": names.netbiosname,
1266 "DEFAULTSITE": names.sitename,
1267 "DNSDOMAIN": names.dnsdomain,
1268 "DOMAIN": names.domain,
1269 "SCHEMADN": names.schemadn,
1270 "DOMAINDN": names.domaindn,
1271 "SERVERDN": names.serverdn,
1272 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1273 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1276 logger.info("Setting up display specifiers")
1277 display_specifiers_ldif = read_ms_ldif(
1278 setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1279 display_specifiers_ldif = substitute_var(display_specifiers_ldif,
1280 {"CONFIGDN": names.configdn})
1281 check_all_substituted(display_specifiers_ldif)
1282 samdb.add_ldif(display_specifiers_ldif)
1284 logger.info("Adding users container")
1285 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1286 "DOMAINDN": names.domaindn})
1287 logger.info("Modifying users container")
1288 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1289 "DOMAINDN": names.domaindn})
1290 logger.info("Adding computers container")
1291 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1292 "DOMAINDN": names.domaindn})
1293 logger.info("Modifying computers container")
1294 setup_modify_ldif(samdb,
1295 setup_path("provision_computers_modify.ldif"), {
1296 "DOMAINDN": names.domaindn})
1297 logger.info("Setting up sam.ldb data")
1298 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1299 "CREATTIME": str(samba.unix2nttime(int(time.time()))),
1300 "DOMAINDN": names.domaindn,
1301 "NETBIOSNAME": names.netbiosname,
1302 "DEFAULTSITE": names.sitename,
1303 "CONFIGDN": names.configdn,
1304 "SERVERDN": names.serverdn,
1305 "RIDAVAILABLESTART": str(next_rid + 600),
1306 "POLICYGUID_DC": policyguid_dc
1309 # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
1310 if fill == FILL_FULL:
1311 setup_modify_ldif(samdb,
1312 setup_path("provision_configuration_references.ldif"), {
1313 "CONFIGDN": names.configdn,
1314 "SCHEMADN": names.schemadn})
1316 logger.info("Setting up well known security principals")
1317 setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
1318 "CONFIGDN": names.configdn,
1321 if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
1322 setup_modify_ldif(samdb,
1323 setup_path("provision_basedn_references.ldif"),
1324 {"DOMAINDN": names.domaindn})
1326 logger.info("Setting up sam.ldb users and groups")
1327 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1328 "DOMAINDN": names.domaindn,
1329 "DOMAINSID": str(domainsid),
1330 "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
1331 "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
1334 logger.info("Setting up self join")
1335 setup_self_join(samdb, admin_session_info, names=names, fill=fill,
1336 invocationid=invocationid,
1337 dns_backend=dns_backend,
1338 dnspass=dnspass,
1339 machinepass=machinepass,
1340 domainsid=domainsid,
1341 next_rid=next_rid,
1342 dc_rid=dc_rid,
1343 policyguid=policyguid,
1344 policyguid_dc=policyguid_dc,
1345 domainControllerFunctionality=domainControllerFunctionality,
1346 ntdsguid=ntdsguid)
1348 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1349 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1350 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1351 assert isinstance(names.ntdsguid, str)
1352 except:
1353 samdb.transaction_cancel()
1354 raise
1355 else:
1356 samdb.transaction_commit()
1357 return samdb
1360 FILL_FULL = "FULL"
1361 FILL_SUBDOMAIN = "SUBDOMAIN"
1362 FILL_NT4SYNC = "NT4SYNC"
1363 FILL_DRS = "DRS"
1364 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1365 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)"
1368 def set_dir_acl(path, acl, lp, domsid, use_ntvfs, passdb):
1369 setntacl(lp, path, acl, domsid, use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
1370 for root, dirs, files in os.walk(path, topdown=False):
1371 for name in files:
1372 setntacl(lp, os.path.join(root, name), acl, domsid,
1373 use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
1374 for name in dirs:
1375 setntacl(lp, os.path.join(root, name), acl, domsid,
1376 use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
1379 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb):
1380 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1381 folders beneath.
1383 :param sysvol: Physical path for the sysvol folder
1384 :param dnsdomain: The DNS name of the domain
1385 :param domainsid: The SID of the domain
1386 :param domaindn: The DN of the domain (ie. DC=...)
1387 :param samdb: An LDB object on the SAM db
1388 :param lp: an LP object
1391 # Set ACL for GPO root folder
1392 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1393 setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid),
1394 use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
1396 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1397 attrs=["cn", "nTSecurityDescriptor"],
1398 expression="", scope=ldb.SCOPE_ONELEVEL)
1400 for policy in res:
1401 acl = ndr_unpack(security.descriptor,
1402 str(policy["nTSecurityDescriptor"])).as_sddl()
1403 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1404 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1405 str(domainsid), use_ntvfs,
1406 passdb=passdb)
1409 def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
1410 domaindn, lp, use_ntvfs):
1411 """Set the ACL for the sysvol share and the subfolders
1413 :param samdb: An LDB object on the SAM db
1414 :param netlogon: Physical path for the netlogon folder
1415 :param sysvol: Physical path for the sysvol folder
1416 :param uid: The UID of the "Administrator" user
1417 :param gid: The GID of the "Domain adminstrators" group
1418 :param domainsid: The SID of the domain
1419 :param dnsdomain: The DNS name of the domain
1420 :param domaindn: The DN of the domain (ie. DC=...)
1422 s4_passdb = None
1424 if not use_ntvfs:
1425 # This will ensure that the smbd code we are running when setting ACLs
1426 # is initialised with the smb.conf
1427 s3conf = s3param.get_context()
1428 s3conf.load(lp.configfile)
1429 # ensure we are using the right samba_dsdb passdb backend, no matter what
1430 s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
1431 passdb.reload_static_pdb()
1433 # ensure that we init the samba_dsdb backend, so the domain sid is
1434 # marked in secrets.tdb
1435 s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
1437 # now ensure everything matches correctly, to avoid wierd issues
1438 if passdb.get_global_sam_sid() != domainsid:
1439 raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
1441 domain_info = s4_passdb.domain_info()
1442 if domain_info["dom_sid"] != domainsid:
1443 raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
1445 if domain_info["dns_domain"].upper() != dnsdomain.upper():
1446 raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper()))
1449 try:
1450 if use_ntvfs:
1451 os.chown(sysvol, -1, gid)
1452 except OSError:
1453 canchown = False
1454 else:
1455 canchown = True
1457 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1458 setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb)
1459 for root, dirs, files in os.walk(sysvol, topdown=False):
1460 for name in files:
1461 if use_ntvfs and canchown:
1462 os.chown(os.path.join(root, name), -1, gid)
1463 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb)
1464 for name in dirs:
1465 if use_ntvfs and canchown:
1466 os.chown(os.path.join(root, name), -1, gid)
1467 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb)
1469 # Set acls on Policy folder and policies folders
1470 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb=s4_passdb)
1472 def acl_type(direct_db_access):
1473 if direct_db_access:
1474 return "DB"
1475 else:
1476 return "VFS"
1478 def check_dir_acl(path, acl, lp, domainsid, direct_db_access):
1479 fsacl = getntacl(lp, path, direct_db_access=direct_db_access)
1480 fsacl_sddl = fsacl.as_sddl(domainsid)
1481 if fsacl_sddl != acl:
1482 raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), path, fsacl_sddl, acl))
1484 for root, dirs, files in os.walk(path, topdown=False):
1485 for name in files:
1486 fsacl = getntacl(lp, os.path.join(root, name), direct_db_access=direct_db_access)
1487 if fsacl is None:
1488 raise ProvisioningError('%s ACL on GPO file %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name)))
1489 fsacl_sddl = fsacl.as_sddl(domainsid)
1490 if fsacl_sddl != acl:
1491 raise ProvisioningError('%s ACL on GPO file %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
1493 for name in files:
1494 fsacl = getntacl(lp, os.path.join(root, name), direct_db_access=direct_db_access)
1495 if fsacl is None:
1496 raise ProvisioningError('%s ACL on GPO directory %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name)))
1497 fsacl_sddl = fsacl.as_sddl(domainsid)
1498 if fsacl_sddl != acl:
1499 raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
1502 def check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
1503 direct_db_access):
1504 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1505 folders beneath.
1507 :param sysvol: Physical path for the sysvol folder
1508 :param dnsdomain: The DNS name of the domain
1509 :param domainsid: The SID of the domain
1510 :param domaindn: The DN of the domain (ie. DC=...)
1511 :param samdb: An LDB object on the SAM db
1512 :param lp: an LP object
1515 # Set ACL for GPO root folder
1516 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1517 fsacl = getntacl(lp, root_policy_path, direct_db_access=direct_db_access)
1518 if fsacl is None:
1519 raise ProvisioningError('DB ACL on policy root %s %s not found!' % (acl_type(direct_db_access), root_policy_path))
1520 fsacl_sddl = fsacl.as_sddl(domainsid)
1521 if fsacl_sddl != POLICIES_ACL:
1522 raise ProvisioningError('%s ACL on policy root %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), root_policy_path, fsacl_sddl, fsacl))
1523 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1524 attrs=["cn", "nTSecurityDescriptor"],
1525 expression="", scope=ldb.SCOPE_ONELEVEL)
1527 for policy in res:
1528 acl = ndr_unpack(security.descriptor,
1529 str(policy["nTSecurityDescriptor"])).as_sddl()
1530 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1531 check_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1532 domainsid, direct_db_access)
1535 def checksysvolacl(samdb, netlogon, sysvol, domainsid, dnsdomain, domaindn,
1536 lp):
1537 """Set the ACL for the sysvol share and the subfolders
1539 :param samdb: An LDB object on the SAM db
1540 :param netlogon: Physical path for the netlogon folder
1541 :param sysvol: Physical path for the sysvol folder
1542 :param uid: The UID of the "Administrator" user
1543 :param gid: The GID of the "Domain adminstrators" group
1544 :param domainsid: The SID of the domain
1545 :param dnsdomain: The DNS name of the domain
1546 :param domaindn: The DN of the domain (ie. DC=...)
1549 # This will ensure that the smbd code we are running when setting ACLs is initialised with the smb.conf
1550 s3conf = s3param.get_context()
1551 s3conf.load(lp.configfile)
1552 # ensure we are using the right samba_dsdb passdb backend, no matter what
1553 s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
1554 # ensure that we init the samba_dsdb backend, so the domain sid is marked in secrets.tdb
1555 s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
1557 # now ensure everything matches correctly, to avoid wierd issues
1558 if passdb.get_global_sam_sid() != domainsid:
1559 raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
1561 domain_info = s4_passdb.domain_info()
1562 if domain_info["dom_sid"] != domainsid:
1563 raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
1565 if domain_info["dns_domain"].upper() != dnsdomain.upper():
1566 raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper()))
1568 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1569 for direct_db_access in [True, False]:
1570 for dir_path in [os.path.join(sysvol, dnsdomain), netlogon]:
1571 fsacl = getntacl(lp, dir_path, direct_db_access=direct_db_access)
1572 if fsacl is None:
1573 raise ProvisioningError('%s ACL on sysvol directory %s not found!' % (acl_type(direct_db_access), dir_path))
1574 fsacl_sddl = fsacl.as_sddl(domainsid)
1575 if fsacl_sddl != SYSVOL_ACL:
1576 raise ProvisioningError('%s ACL on sysvol directory %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), dir_path, fsacl_sddl, SYSVOL_ACL))
1578 # Check acls on Policy folder and policies folders
1579 check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
1580 direct_db_access)
1583 def interface_ips_v4(lp):
1584 """return only IPv4 IPs"""
1585 ips = samba.interface_ips(lp, False)
1586 ret = []
1587 for i in ips:
1588 if i.find(':') == -1:
1589 ret.append(i)
1590 return ret
1593 def interface_ips_v6(lp, linklocal=False):
1594 """return only IPv6 IPs"""
1595 ips = samba.interface_ips(lp, False)
1596 ret = []
1597 for i in ips:
1598 if i.find(':') != -1 and (linklocal or i.find('%') == -1):
1599 ret.append(i)
1600 return ret
1603 def provision_fill(samdb, secrets_ldb, logger, names, paths,
1604 domainsid, schema=None,
1605 targetdir=None, samdb_fill=FILL_FULL,
1606 hostip=None, hostip6=None,
1607 next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
1608 domainguid=None, policyguid=None, policyguid_dc=None,
1609 invocationid=None, machinepass=None, ntdsguid=None,
1610 dns_backend=None, dnspass=None,
1611 serverrole=None, dom_for_fun_level=None,
1612 am_rodc=False, lp=None, use_ntvfs=False, skip_sysvolacl=False):
1613 # create/adapt the group policy GUIDs
1614 # Default GUID for default policy are described at
1615 # "How Core Group Policy Works"
1616 # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
1617 if policyguid is None:
1618 policyguid = DEFAULT_POLICY_GUID
1619 policyguid = policyguid.upper()
1620 if policyguid_dc is None:
1621 policyguid_dc = DEFAULT_DC_POLICY_GUID
1622 policyguid_dc = policyguid_dc.upper()
1624 if invocationid is None:
1625 invocationid = str(uuid.uuid4())
1627 if krbtgtpass is None:
1628 krbtgtpass = samba.generate_random_password(128, 255)
1629 if machinepass is None:
1630 machinepass = samba.generate_random_password(128, 255)
1631 if dnspass is None:
1632 dnspass = samba.generate_random_password(128, 255)
1634 samdb = fill_samdb(samdb, lp, names, logger=logger,
1635 domainsid=domainsid, schema=schema, domainguid=domainguid,
1636 policyguid=policyguid, policyguid_dc=policyguid_dc,
1637 fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
1638 invocationid=invocationid, machinepass=machinepass,
1639 dns_backend=dns_backend, dnspass=dnspass,
1640 ntdsguid=ntdsguid, serverrole=serverrole,
1641 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1642 next_rid=next_rid, dc_rid=dc_rid)
1644 if serverrole == "active directory domain controller":
1646 # Set up group policies (domain policy and domain controller
1647 # policy)
1648 create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
1649 policyguid_dc)
1650 if not skip_sysvolacl:
1651 setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.root_uid,
1652 paths.wheel_gid, domainsid, names.dnsdomain,
1653 names.domaindn, lp, use_ntvfs)
1654 else:
1655 logger.info("Setting acl on sysvol skipped")
1657 secretsdb_self_join(secrets_ldb, domain=names.domain,
1658 realm=names.realm, dnsdomain=names.dnsdomain,
1659 netbiosname=names.netbiosname, domainsid=domainsid,
1660 machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
1662 # Now set up the right msDS-SupportedEncryptionTypes into the DB
1663 # In future, this might be determined from some configuration
1664 kerberos_enctypes = str(ENC_ALL_TYPES)
1666 try:
1667 msg = ldb.Message(ldb.Dn(samdb,
1668 samdb.searchone("distinguishedName",
1669 expression="samAccountName=%s$" % names.netbiosname,
1670 scope=ldb.SCOPE_SUBTREE)))
1671 msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
1672 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
1673 name="msDS-SupportedEncryptionTypes")
1674 samdb.modify(msg)
1675 except ldb.LdbError, (enum, estr):
1676 if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
1677 # It might be that this attribute does not exist in this schema
1678 raise
1680 setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
1681 hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
1682 dnspass=dnspass, os_level=dom_for_fun_level,
1683 targetdir=targetdir, site=DEFAULTSITE)
1685 domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
1686 attribute="objectGUID")
1687 assert isinstance(domainguid, str)
1689 lastProvisionUSNs = get_last_provision_usn(samdb)
1690 maxUSN = get_max_usn(samdb, str(names.rootdn))
1691 if lastProvisionUSNs is not None:
1692 update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
1693 else:
1694 set_provision_usn(samdb, 0, maxUSN, invocationid)
1696 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1697 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
1698 { 'NTDSGUID' : names.ntdsguid })
1700 # fix any dangling GUIDs from the provision
1701 logger.info("Fixing provision GUIDs")
1702 chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True,
1703 quiet=True)
1704 samdb.transaction_start()
1705 try:
1706 # a small number of GUIDs are missing because of ordering issues in the
1707 # provision code
1708 for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
1709 chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
1710 scope=ldb.SCOPE_BASE,
1711 attrs=['defaultObjectCategory'])
1712 chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
1713 scope=ldb.SCOPE_ONELEVEL,
1714 attrs=['ipsecOwnersReference',
1715 'ipsecFilterReference',
1716 'ipsecISAKMPReference',
1717 'ipsecNegotiationPolicyReference',
1718 'ipsecNFAReference'])
1719 except:
1720 samdb.transaction_cancel()
1721 raise
1722 else:
1723 samdb.transaction_commit()
1726 _ROLES_MAP = {
1727 "ROLE_STANDALONE": "standalone server",
1728 "ROLE_DOMAIN_MEMBER": "member server",
1729 "ROLE_DOMAIN_BDC": "active directory domain controller",
1730 "ROLE_DOMAIN_PDC": "active directory domain controller",
1731 "dc": "active directory domain controller",
1732 "member": "member server",
1733 "domain controller": "active directory domain controller",
1734 "active directory domain controller": "active directory domain controller",
1735 "member server": "member server",
1736 "standalone": "standalone server",
1737 "standalone server": "standalone server",
1741 def sanitize_server_role(role):
1742 """Sanitize a server role name.
1744 :param role: Server role
1745 :raise ValueError: If the role can not be interpreted
1746 :return: Sanitized server role (one of "member server",
1747 "active directory domain controller", "standalone server")
1749 try:
1750 return _ROLES_MAP[role]
1751 except KeyError:
1752 raise ValueError(role)
1755 def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
1756 maxuid, maxgid):
1757 """Create AD entries for the fake ypserver.
1759 This is needed for being able to manipulate posix attrs via ADUC.
1761 samdb.transaction_start()
1762 try:
1763 logger.info("Setting up fake yp server settings")
1764 setup_add_ldif(samdb, setup_path("ypServ30.ldif"), {
1765 "DOMAINDN": domaindn,
1766 "NETBIOSNAME": netbiosname,
1767 "NISDOMAIN": nisdomain,
1769 except:
1770 samdb.transaction_cancel()
1771 raise
1772 else:
1773 samdb.transaction_commit()
1776 def provision(logger, session_info, credentials, smbconf=None,
1777 targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
1778 domaindn=None, schemadn=None, configdn=None, serverdn=None,
1779 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
1780 next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None,
1781 krbtgtpass=None, domainguid=None, policyguid=None, policyguid_dc=None,
1782 dns_backend=None, dns_forwarder=None, dnspass=None,
1783 invocationid=None, machinepass=None, ntdsguid=None,
1784 root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
1785 serverrole=None, dom_for_fun_level=None, backend_type=None,
1786 sitename=None, ol_mmr_urls=None, ol_olc=None, slapd_path="/bin/false",
1787 useeadb=False, am_rodc=False, lp=None, use_ntvfs=False,
1788 use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True):
1789 """Provision samba4
1791 :note: caution, this wipes all existing data!
1794 try:
1795 serverrole = sanitize_server_role(serverrole)
1796 except ValueError:
1797 raise ProvisioningError('server role (%s) should be one of "active directory domain controller", "member server", "standalone server"' % serverrole)
1799 if ldapadminpass is None:
1800 # Make a new, random password between Samba and it's LDAP server
1801 ldapadminpass = samba.generate_random_password(128, 255)
1803 if backend_type is None:
1804 backend_type = "ldb"
1806 if domainsid is None:
1807 domainsid = security.random_sid()
1808 else:
1809 domainsid = security.dom_sid(domainsid)
1811 root_uid = findnss_uid([root or "root"])
1812 nobody_uid = findnss_uid([nobody or "nobody"])
1813 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1814 if wheel is None:
1815 wheel_gid = findnss_gid(["wheel", "adm"])
1816 else:
1817 wheel_gid = findnss_gid([wheel])
1818 try:
1819 bind_gid = findnss_gid(["bind", "named"])
1820 except KeyError:
1821 bind_gid = None
1823 if targetdir is not None:
1824 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1825 elif smbconf is None:
1826 smbconf = samba.param.default_path()
1827 if not os.path.exists(os.path.dirname(smbconf)):
1828 os.makedirs(os.path.dirname(smbconf))
1830 server_services = []
1831 global_param = {}
1832 if use_rfc2307:
1833 global_param["idmap_ldb:use rfc2307"] = ["yes"]
1835 if dns_backend != "SAMBA_INTERNAL":
1836 server_services.append("-dns")
1837 else:
1838 if dns_forwarder is not None:
1839 global_param["dns forwarder"] = [dns_forwarder]
1841 if use_ntvfs:
1842 server_services.append("+smb")
1843 server_services.append("-s3fs")
1844 global_param["dcerpc endpoint servers"] = ["+winreg", "+srvsvc"]
1846 if len(server_services) > 0:
1847 global_param["server services"] = server_services
1849 # only install a new smb.conf if there isn't one there already
1850 if os.path.exists(smbconf):
1851 # if Samba Team members can't figure out the weird errors
1852 # loading an empty smb.conf gives, then we need to be smarter.
1853 # Pretend it just didn't exist --abartlet
1854 f = open(smbconf, 'r')
1855 try:
1856 data = f.read().lstrip()
1857 finally:
1858 f.close()
1859 if data is None or data == "":
1860 make_smbconf(smbconf, hostname, domain, realm,
1861 targetdir, serverrole=serverrole,
1862 eadb=useeadb, use_ntvfs=use_ntvfs,
1863 lp=lp, global_param=global_param)
1864 else:
1865 make_smbconf(smbconf, hostname, domain, realm, targetdir,
1866 serverrole=serverrole,
1867 eadb=useeadb, use_ntvfs=use_ntvfs, lp=lp, global_param=global_param)
1869 if lp is None:
1870 lp = samba.param.LoadParm()
1871 lp.load(smbconf)
1872 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1873 dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
1874 configdn=configdn, schemadn=schemadn, serverdn=serverdn,
1875 sitename=sitename, rootdn=rootdn)
1876 paths = provision_paths_from_lp(lp, names.dnsdomain)
1878 paths.bind_gid = bind_gid
1879 paths.root_uid = root_uid;
1880 paths.wheel_gid = wheel_gid
1882 if hostip is None:
1883 logger.info("Looking up IPv4 addresses")
1884 hostips = interface_ips_v4(lp)
1885 if len(hostips) > 0:
1886 hostip = hostips[0]
1887 if len(hostips) > 1:
1888 logger.warning("More than one IPv4 address found. Using %s",
1889 hostip)
1890 if hostip == "127.0.0.1":
1891 hostip = None
1892 if hostip is None:
1893 logger.warning("No IPv4 address will be assigned")
1895 if hostip6 is None:
1896 logger.info("Looking up IPv6 addresses")
1897 hostips = interface_ips_v6(lp, linklocal=False)
1898 if hostips:
1899 hostip6 = hostips[0]
1900 if len(hostips) > 1:
1901 logger.warning("More than one IPv6 address found. Using %s", hostip6)
1902 if hostip6 is None:
1903 logger.warning("No IPv6 address will be assigned")
1905 names.hostip = hostip
1906 names.hostip6 = hostip6
1908 if serverrole is None:
1909 serverrole = lp.get("server role")
1911 if not os.path.exists(paths.private_dir):
1912 os.mkdir(paths.private_dir)
1913 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1914 os.mkdir(os.path.join(paths.private_dir, "tls"))
1915 if not os.path.exists(paths.state_dir):
1916 os.mkdir(paths.state_dir)
1918 if paths.sysvol and not os.path.exists(paths.sysvol):
1919 os.makedirs(paths.sysvol, 0775)
1921 if not use_ntvfs and serverrole == "active directory domain controller":
1922 s3conf = s3param.get_context()
1923 s3conf.load(lp.configfile)
1925 if paths.sysvol is None:
1926 raise MissingShareError("sysvol", paths.smbconf)
1928 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(paths.sysvol))
1929 try:
1930 try:
1931 smbd.set_simple_acl(file.name, 0755, wheel_gid)
1932 except Exception:
1933 if not smbd.have_posix_acls():
1934 # This clue is only strictly correct for RPM and
1935 # Debian-like Linux systems, but hopefully other users
1936 # will get enough clue from it.
1937 raise ProvisioningError("Samba was compiled without the posix ACL support that s3fs requires. Try installing libacl1-dev or libacl-devel, then re-run configure and make.")
1939 raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires. Try the mounting the filesystem with the 'acl' option.")
1940 try:
1941 smbd.chown(file.name, root_uid, wheel_gid)
1942 except Exception:
1943 raise ProvisioningError("Unable to chown a file on your filesystem. You may not be running provision as root.")
1944 finally:
1945 file.close()
1947 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1949 schema = Schema(domainsid, invocationid=invocationid,
1950 schemadn=names.schemadn)
1952 if backend_type == "ldb":
1953 provision_backend = LDBBackend(backend_type, paths=paths,
1954 lp=lp, credentials=credentials,
1955 names=names, logger=logger)
1956 elif backend_type == "existing":
1957 # If support for this is ever added back, then the URI will need to be
1958 # specified again
1959 provision_backend = ExistingBackend(backend_type, paths=paths,
1960 lp=lp, credentials=credentials,
1961 names=names, logger=logger,
1962 ldap_backend_forced_uri=None)
1963 elif backend_type == "fedora-ds":
1964 provision_backend = FDSBackend(backend_type, paths=paths,
1965 lp=lp, credentials=credentials,
1966 names=names, logger=logger, domainsid=domainsid,
1967 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1968 slapd_path=slapd_path,
1969 root=root)
1970 elif backend_type == "openldap":
1971 provision_backend = OpenLDAPBackend(backend_type, paths=paths,
1972 lp=lp, credentials=credentials,
1973 names=names, logger=logger, domainsid=domainsid,
1974 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1975 slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls)
1976 else:
1977 raise ValueError("Unknown LDAP backend type selected")
1979 provision_backend.init()
1980 provision_backend.start()
1982 # only install a new shares config db if there is none
1983 if not os.path.exists(paths.shareconf):
1984 logger.info("Setting up share.ldb")
1985 share_ldb = Ldb(paths.shareconf, session_info=session_info, lp=lp)
1986 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1988 logger.info("Setting up secrets.ldb")
1989 secrets_ldb = setup_secretsdb(paths,
1990 session_info=session_info,
1991 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1993 try:
1994 logger.info("Setting up the registry")
1995 setup_registry(paths.hklm, session_info, lp=lp)
1997 logger.info("Setting up the privileges database")
1998 setup_privileges(paths.privilege, session_info, lp=lp)
2000 logger.info("Setting up idmap db")
2001 idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp)
2003 setup_name_mappings(idmap, sid=str(domainsid),
2004 root_uid=root_uid, nobody_uid=nobody_uid,
2005 users_gid=users_gid, wheel_gid=wheel_gid)
2007 logger.info("Setting up SAM db")
2008 samdb = setup_samdb(paths.samdb, session_info,
2009 provision_backend, lp, names, logger=logger,
2010 serverrole=serverrole,
2011 schema=schema, fill=samdb_fill, am_rodc=am_rodc)
2013 if serverrole == "active directory domain controller":
2014 if paths.netlogon is None:
2015 raise MissingShareError("netlogon", paths.smbconf)
2017 if paths.sysvol is None:
2018 raise MissingShareError("sysvol", paths.smbconf)
2020 if not os.path.isdir(paths.netlogon):
2021 os.makedirs(paths.netlogon, 0755)
2023 if adminpass is None:
2024 adminpass = samba.generate_random_password(12, 32)
2025 adminpass_generated = True
2026 else:
2027 adminpass_generated = False
2029 if samdb_fill == FILL_FULL:
2030 provision_fill(samdb, secrets_ldb, logger, names, paths,
2031 schema=schema, targetdir=targetdir, samdb_fill=samdb_fill,
2032 hostip=hostip, hostip6=hostip6, domainsid=domainsid,
2033 next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
2034 krbtgtpass=krbtgtpass, domainguid=domainguid,
2035 policyguid=policyguid, policyguid_dc=policyguid_dc,
2036 invocationid=invocationid, machinepass=machinepass,
2037 ntdsguid=ntdsguid, dns_backend=dns_backend,
2038 dnspass=dnspass, serverrole=serverrole,
2039 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
2040 lp=lp, use_ntvfs=use_ntvfs,
2041 skip_sysvolacl=skip_sysvolacl)
2043 create_krb5_conf(paths.krb5conf,
2044 dnsdomain=names.dnsdomain, hostname=names.hostname,
2045 realm=names.realm)
2046 logger.info("A Kerberos configuration suitable for Samba 4 has been "
2047 "generated at %s", paths.krb5conf)
2049 if serverrole == "active directory domain controller":
2050 create_dns_update_list(lp, logger, paths)
2052 backend_result = provision_backend.post_setup()
2053 provision_backend.shutdown()
2055 create_phpldapadmin_config(paths.phpldapadminconfig,
2056 ldapi_url)
2057 except:
2058 secrets_ldb.transaction_cancel()
2059 raise
2061 # Now commit the secrets.ldb to disk
2062 secrets_ldb.transaction_commit()
2064 # the commit creates the dns.keytab, now chown it
2065 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
2066 if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
2067 try:
2068 os.chmod(dns_keytab_path, 0640)
2069 os.chown(dns_keytab_path, -1, paths.bind_gid)
2070 except OSError:
2071 if not os.environ.has_key('SAMBA_SELFTEST'):
2072 logger.info("Failed to chown %s to bind gid %u",
2073 dns_keytab_path, paths.bind_gid)
2075 result = ProvisionResult()
2076 result.server_role = serverrole
2077 result.domaindn = domaindn
2078 result.paths = paths
2079 result.names = names
2080 result.lp = lp
2081 result.samdb = samdb
2082 result.idmap = idmap
2083 result.domainsid = str(domainsid)
2085 if samdb_fill == FILL_FULL:
2086 result.adminpass_generated = adminpass_generated
2087 result.adminpass = adminpass
2088 else:
2089 result.adminpass_generated = False
2090 result.adminpass = None
2092 result.backend_result = backend_result
2094 if use_rfc2307:
2095 provision_fake_ypserver(logger=logger, samdb=samdb,
2096 domaindn=names.domaindn, netbiosname=names.netbiosname,
2097 nisdomain=names.domain.lower(), maxuid=maxuid, maxgid=maxgid)
2099 return result
2102 def provision_become_dc(smbconf=None, targetdir=None,
2103 realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
2104 serverdn=None, domain=None, hostname=None, domainsid=None,
2105 adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
2106 policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
2107 dns_backend=None, root=None, nobody=None, users=None, wheel=None,
2108 backup=None, serverrole=None, ldap_backend=None,
2109 ldap_backend_type=None, sitename=None, debuglevel=1, use_ntvfs=False):
2111 logger = logging.getLogger("provision")
2112 samba.set_debug_level(debuglevel)
2114 res = provision(logger, system_session(), None,
2115 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
2116 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
2117 configdn=configdn, serverdn=serverdn, domain=domain,
2118 hostname=hostname, hostip=None, domainsid=domainsid,
2119 machinepass=machinepass,
2120 serverrole="active directory domain controller",
2121 sitename=sitename, dns_backend=dns_backend, dnspass=dnspass,
2122 use_ntvfs=use_ntvfs)
2123 res.lp.set("debuglevel", str(debuglevel))
2124 return res
2127 def create_phpldapadmin_config(path, ldapi_uri):
2128 """Create a PHP LDAP admin configuration file.
2130 :param path: Path to write the configuration to.
2132 setup_file(setup_path("phpldapadmin-config.php"), path,
2133 {"S4_LDAPI_URI": ldapi_uri})
2136 def create_krb5_conf(path, dnsdomain, hostname, realm):
2137 """Write out a file containing zone statements suitable for inclusion in a
2138 named.conf file (including GSS-TSIG configuration).
2140 :param path: Path of the new named.conf file.
2141 :param dnsdomain: DNS Domain name
2142 :param hostname: Local hostname
2143 :param realm: Realm name
2145 setup_file(setup_path("krb5.conf"), path, {
2146 "DNSDOMAIN": dnsdomain,
2147 "HOSTNAME": hostname,
2148 "REALM": realm,
2152 class ProvisioningError(Exception):
2153 """A generic provision error."""
2155 def __init__(self, value):
2156 self.value = value
2158 def __str__(self):
2159 return "ProvisioningError: " + self.value
2162 class InvalidNetbiosName(Exception):
2163 """A specified name was not a valid NetBIOS name."""
2165 def __init__(self, name):
2166 super(InvalidNetbiosName, self).__init__(
2167 "The name '%r' is not a valid NetBIOS name" % name)
2170 class MissingShareError(ProvisioningError):
2172 def __init__(self, name, smbconf):
2173 super(MissingShareError, self).__init__(
2174 "Existing smb.conf does not have a [%s] share, but you are "
2175 "configuring a DC. Please remove %s or add the share manually." %
2176 (name, smbconf))