s4-param Remove 'sam database' parameter
[Samba.git] / source4 / scripting / python / samba / provision / __init__.py
blobfffd352071ff759eb0eb4c059da76b595d9f008a
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 """Functions for setting up a Samba configuration."""
28 __docformat__ = "restructuredText"
30 from base64 import b64encode
31 import os
32 import re
33 import pwd
34 import grp
35 import logging
36 import time
37 import uuid
38 import socket
39 import urllib
40 import shutil
41 import string
43 import ldb
45 from samba.auth import system_session, admin_session
46 import samba
47 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
48 from samba import (
49 Ldb,
50 check_all_substituted,
51 read_and_sub_file,
52 setup_file,
53 substitute_var,
54 valid_netbios_name,
55 version,
57 from samba.dcerpc import security, misc
58 from samba.dcerpc.misc import (
59 SEC_CHAN_BDC,
60 SEC_CHAN_WKSTA,
62 from samba.dsdb import (
63 DS_DOMAIN_FUNCTION_2003,
64 DS_DOMAIN_FUNCTION_2008_R2,
65 ENC_ALL_TYPES,
67 from samba.idmap import IDmapDB
68 from samba.ms_display_specifiers import read_ms_ldif
69 from samba.ntacls import setntacl, dsacl2fsacl
70 from samba.ndr import ndr_pack, ndr_unpack
71 from samba.provision.backend import (
72 ExistingBackend,
73 FDSBackend,
74 LDBBackend,
75 OpenLDAPBackend,
77 import samba.param
78 import samba.registry
79 from samba.schema import Schema
80 from samba.samdb import SamDB
82 VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~"
83 DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
84 DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
85 DEFAULTSITE = "Default-First-Site-Name"
86 LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
89 def setup_path(file):
90 """Return an absolute path to the provision tempate file specified by file"""
91 return os.path.join(samba.param.setup_dir(), file)
93 # Descriptors of naming contexts and other important objects
95 # "get_schema_descriptor" is located in "schema.py"
97 def get_sites_descriptor(domain_sid):
98 sddl = "D:(A;;RPLCLORC;;;AU)" \
99 "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \
100 "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
101 "S:AI(AU;CISA;CCDCSDDT;;;WD)" \
102 "(OU;CIIOSA;CR;;f0f8ffab-1191-11d0-a060-00aa006c33ed;WD)" \
103 "(OU;CIIOSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
104 "(OU;CIIOSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
105 "(OU;CIIOSA;WP;3e10944c-c354-11d0-aff8-0000f80367c1;b7b13124-b82e-11d0-afee-0000f80367c1;WD)"
106 sec = security.descriptor.from_sddl(sddl, domain_sid)
107 return ndr_pack(sec)
110 def get_config_descriptor(domain_sid):
111 sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
112 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
113 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
114 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
115 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
116 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
117 "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
118 "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
119 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
120 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
121 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
122 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
123 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
124 "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
125 "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
126 sec = security.descriptor.from_sddl(sddl, domain_sid)
127 return ndr_pack(sec)
130 def get_domain_descriptor(domain_sid):
131 sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
132 "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
133 "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
134 "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
135 "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
136 "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
137 "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
138 "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
139 "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
140 "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
141 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
142 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \
143 "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
144 "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
145 "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
146 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
147 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
148 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
149 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
150 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
151 "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
152 "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
153 "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
154 "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
155 "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
156 "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
157 "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
158 "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
159 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
160 "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
161 "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
162 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
163 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
164 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
165 "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
166 "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
167 "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
168 "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
169 "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
170 "(A;;RPRC;;;RU)" \
171 "(A;CI;LC;;;RU)" \
172 "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
173 "(A;;RP;;;WD)" \
174 "(A;;RPLCLORC;;;ED)" \
175 "(A;;RPLCLORC;;;AU)" \
176 "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
177 "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
178 "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
179 "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)"
180 sec = security.descriptor.from_sddl(sddl, domain_sid)
181 return ndr_pack(sec)
184 class ProvisionPaths(object):
186 def __init__(self):
187 self.shareconf = None
188 self.hklm = None
189 self.hkcu = None
190 self.hkcr = None
191 self.hku = None
192 self.hkpd = None
193 self.hkpt = None
194 self.samdb = None
195 self.idmapdb = None
196 self.secrets = None
197 self.keytab = None
198 self.dns_keytab = None
199 self.dns = None
200 self.winsdb = None
201 self.private_dir = None
204 class ProvisionNames(object):
206 def __init__(self):
207 self.rootdn = None
208 self.domaindn = None
209 self.configdn = None
210 self.schemadn = None
211 self.ldapmanagerdn = None
212 self.dnsdomain = None
213 self.realm = None
214 self.netbiosname = None
215 self.domain = None
216 self.hostname = None
217 self.sitename = None
218 self.smbconf = None
220 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
221 """Get key provision parameters (realm, domain, ...) from a given provision
223 :param samdb: An LDB object connected to the sam.ldb file
224 :param secretsdb: An LDB object connected to the secrets.ldb file
225 :param idmapdb: An LDB object connected to the idmap.ldb file
226 :param paths: A list of path to provision object
227 :param smbconf: Path to the smb.conf file
228 :param lp: A LoadParm object
229 :return: A list of key provision parameters
231 names = ProvisionNames()
232 names.adminpass = None
234 # NT domain, kerberos realm, root dn, domain dn, domain dns name
235 names.domain = string.upper(lp.get("workgroup"))
236 names.realm = lp.get("realm")
237 basedn = "DC=" + names.realm.replace(".",",DC=")
238 names.dnsdomain = names.realm.lower()
239 names.realm = string.upper(names.realm)
240 # netbiosname
241 # Get the netbiosname first (could be obtained from smb.conf in theory)
242 res = secretsdb.search(expression="(flatname=%s)" %
243 names.domain,base="CN=Primary Domains",
244 scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
245 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
247 names.smbconf = smbconf
249 # That's a bit simplistic but it's ok as long as we have only 3
250 # partitions
251 current = samdb.search(expression="(objectClass=*)",
252 base="", scope=ldb.SCOPE_BASE,
253 attrs=["defaultNamingContext", "schemaNamingContext",
254 "configurationNamingContext","rootDomainNamingContext"])
256 names.configdn = current[0]["configurationNamingContext"]
257 configdn = str(names.configdn)
258 names.schemadn = current[0]["schemaNamingContext"]
259 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
260 current[0]["defaultNamingContext"][0]))):
261 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
262 "is not the same ..." % (paths.samdb,
263 str(current[0]["defaultNamingContext"][0]),
264 paths.smbconf, basedn)))
266 names.domaindn=current[0]["defaultNamingContext"]
267 names.rootdn=current[0]["rootDomainNamingContext"]
268 # default site name
269 res3 = samdb.search(expression="(objectClass=*)",
270 base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
271 names.sitename = str(res3[0]["cn"])
273 # dns hostname and server dn
274 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
275 base="OU=Domain Controllers,%s" % basedn,
276 scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
277 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
279 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
280 attrs=[], base=configdn)
281 names.serverdn = server_res[0].dn
283 # invocation id/objectguid
284 res5 = samdb.search(expression="(objectClass=*)",
285 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=ldb.SCOPE_BASE,
286 attrs=["invocationID", "objectGUID"])
287 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
288 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
290 # domain guid/sid
291 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
292 scope=ldb.SCOPE_BASE, attrs=["objectGUID",
293 "objectSid","msDS-Behavior-Version" ])
294 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
295 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
296 if res6[0].get("msDS-Behavior-Version") is None or \
297 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
298 names.domainlevel = DS_DOMAIN_FUNCTION_2000
299 else:
300 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
302 # policy guid
303 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
304 base="CN=Policies,CN=System," + basedn,
305 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
306 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
307 # dc policy guid
308 res8 = samdb.search(expression="(displayName=Default Domain Controllers"
309 " Policy)",
310 base="CN=Policies,CN=System," + basedn,
311 scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
312 if len(res8) == 1:
313 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
314 else:
315 names.policyid_dc = None
316 res9 = idmapdb.search(expression="(cn=%s)" %
317 (security.SID_BUILTIN_ADMINISTRATORS),
318 attrs=["xidNumber"])
319 if len(res9) == 1:
320 names.wheel_gid = res9[0]["xidNumber"]
321 else:
322 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
323 return names
325 def update_provision_usn(samdb, low, high, replace=False):
326 """Update the field provisionUSN in sam.ldb
328 This field is used to track range of USN modified by provision and
329 upgradeprovision.
330 This value is used afterward by next provision to figure out if
331 the field have been modified since last provision.
333 :param samdb: An LDB object connect to sam.ldb
334 :param low: The lowest USN modified by this upgrade
335 :param high: The highest USN modified by this upgrade
336 :param replace: A boolean indicating if the range should replace any
337 existing one or appended (default)
340 tab = []
341 if not replace:
342 entry = samdb.search(expression="(&(dn=@PROVISION)(%s=*))" %
343 LAST_PROVISION_USN_ATTRIBUTE, base="",
344 scope=ldb.SCOPE_SUBTREE,
345 attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
346 for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
347 tab.append(str(e))
349 tab.append("%s-%s" % (low, high))
350 delta = ldb.Message()
351 delta.dn = ldb.Dn(samdb, "@PROVISION")
352 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
353 ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
354 samdb.modify(delta)
357 def set_provision_usn(samdb, low, high):
358 """Set the field provisionUSN in sam.ldb
359 This field is used to track range of USN modified by provision and
360 upgradeprovision.
361 This value is used afterward by next provision to figure out if
362 the field have been modified since last provision.
364 :param samdb: An LDB object connect to sam.ldb
365 :param low: The lowest USN modified by this upgrade
366 :param high: The highest USN modified by this upgrade"""
367 tab = []
368 tab.append("%s-%s" % (low, high))
369 delta = ldb.Message()
370 delta.dn = ldb.Dn(samdb, "@PROVISION")
371 delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
372 ldb.FLAG_MOD_ADD, LAST_PROVISION_USN_ATTRIBUTE)
373 samdb.add(delta)
376 def get_max_usn(samdb,basedn):
377 """ This function return the biggest USN present in the provision
379 :param samdb: A LDB object pointing to the sam.ldb
380 :param basedn: A string containing the base DN of the provision
381 (ie. DC=foo, DC=bar)
382 :return: The biggest USN in the provision"""
384 res = samdb.search(expression="objectClass=*",base=basedn,
385 scope=ldb.SCOPE_SUBTREE,attrs=["uSNChanged"],
386 controls=["search_options:1:2",
387 "server_sort:1:1:uSNChanged",
388 "paged_results:1:1"])
389 return res[0]["uSNChanged"]
392 def get_last_provision_usn(sam):
393 """Get the lastest USN modified by a provision or an upgradeprovision
395 :param sam: An LDB object pointing to the sam.ldb
396 :return: an integer corresponding to the highest USN modified by
397 (upgrade)provision, 0 is this value is unknown
399 entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" %
400 LAST_PROVISION_USN_ATTRIBUTE,
401 base="", scope=ldb.SCOPE_SUBTREE,
402 attrs=[LAST_PROVISION_USN_ATTRIBUTE])
403 if len(entry):
404 range = []
405 idx = 0
406 p = re.compile(r'-')
407 for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
408 tab = p.split(str(r))
409 range.append(tab[0])
410 range.append(tab[1])
411 idx = idx + 1
412 return range
413 else:
414 return None
417 class ProvisionResult(object):
419 def __init__(self):
420 self.paths = None
421 self.domaindn = None
422 self.lp = None
423 self.samdb = None
426 def check_install(lp, session_info, credentials):
427 """Check whether the current install seems ok.
429 :param lp: Loadparm context
430 :param session_info: Session information
431 :param credentials: Credentials
433 if lp.get("realm") == "":
434 raise Exception("Realm empty")
435 samdb = Ldb(lp.samdb_url(), session_info=session_info,
436 credentials=credentials, lp=lp)
437 if len(samdb.search("(cn=Administrator)")) != 1:
438 raise ProvisioningError("No administrator account found")
441 def findnss(nssfn, names):
442 """Find a user or group from a list of possibilities.
444 :param nssfn: NSS Function to try (should raise KeyError if not found)
445 :param names: Names to check.
446 :return: Value return by first names list.
448 for name in names:
449 try:
450 return nssfn(name)
451 except KeyError:
452 pass
453 raise KeyError("Unable to find user/group in %r" % names)
456 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
457 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
460 def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
461 """Setup a ldb in the private dir.
463 :param ldb: LDB file to import data into
464 :param ldif_path: Path of the LDIF file to load
465 :param subst_vars: Optional variables to subsitute in LDIF.
466 :param nocontrols: Optional list of controls, can be None for no controls
468 assert isinstance(ldif_path, str)
469 data = read_and_sub_file(ldif_path, subst_vars)
470 ldb.add_ldif(data, controls)
473 def setup_modify_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
474 """Modify a ldb in the private dir.
476 :param ldb: LDB object.
477 :param ldif_path: LDIF file path.
478 :param subst_vars: Optional dictionary with substitution variables.
480 data = read_and_sub_file(ldif_path, subst_vars)
481 ldb.modify_ldif(data, controls)
484 def setup_ldb(ldb, ldif_path, subst_vars):
485 """Import a LDIF a file into a LDB handle, optionally substituting
486 variables.
488 :note: Either all LDIF data will be added or none (using transactions).
490 :param ldb: LDB file to import into.
491 :param ldif_path: Path to the LDIF file.
492 :param subst_vars: Dictionary with substitution variables.
494 assert ldb is not None
495 ldb.transaction_start()
496 try:
497 setup_add_ldif(ldb, ldif_path, subst_vars)
498 except Exception:
499 ldb.transaction_cancel()
500 raise
501 else:
502 ldb.transaction_commit()
505 def provision_paths_from_lp(lp, dnsdomain):
506 """Set the default paths for provisioning.
508 :param lp: Loadparm context.
509 :param dnsdomain: DNS Domain name
511 paths = ProvisionPaths()
512 paths.private_dir = lp.get("private dir")
514 # This is stored without path prefix for the "privateKeytab" attribute in
515 # "secrets_dns.ldif".
516 paths.dns_keytab = "dns.keytab"
517 paths.keytab = "secrets.keytab"
519 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
520 paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
521 paths.idmapdb = os.path.join(paths.private_dir,
522 lp.get("idmap database") or "idmap.ldb")
523 paths.secrets = os.path.join(paths.private_dir,
524 lp.get("secrets database") or "secrets.ldb")
525 paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
526 paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
527 paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
528 paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
529 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
530 paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
531 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
532 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
533 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
534 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
535 paths.phpldapadminconfig = os.path.join(paths.private_dir,
536 "phpldapadmin-config.php")
537 paths.hklm = "hklm.ldb"
538 paths.hkcr = "hkcr.ldb"
539 paths.hkcu = "hkcu.ldb"
540 paths.hku = "hku.ldb"
541 paths.hkpd = "hkpd.ldb"
542 paths.hkpt = "hkpt.ldb"
543 paths.sysvol = lp.get("path", "sysvol")
544 paths.netlogon = lp.get("path", "netlogon")
545 paths.smbconf = lp.configfile
546 return paths
549 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
550 serverrole=None, rootdn=None, domaindn=None, configdn=None,
551 schemadn=None, serverdn=None, sitename=None):
552 """Guess configuration settings to use."""
554 if hostname is None:
555 hostname = socket.gethostname().split(".")[0]
557 netbiosname = lp.get("netbios name")
558 if netbiosname is None:
559 netbiosname = hostname
560 # remove forbidden chars
561 newnbname = ""
562 for x in netbiosname:
563 if x.isalnum() or x in VALID_NETBIOS_CHARS:
564 newnbname = "%s%c" % (newnbname, x)
565 # force the length to be <16
566 netbiosname = newnbname[0:15]
567 assert netbiosname is not None
568 netbiosname = netbiosname.upper()
569 if not valid_netbios_name(netbiosname):
570 raise InvalidNetbiosName(netbiosname)
572 if dnsdomain is None:
573 dnsdomain = lp.get("realm")
574 if dnsdomain is None or dnsdomain == "":
575 raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
577 dnsdomain = dnsdomain.lower()
579 if serverrole is None:
580 serverrole = lp.get("server role")
581 if serverrole is None:
582 raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
584 serverrole = serverrole.lower()
586 realm = dnsdomain.upper()
588 if lp.get("realm") == "":
589 raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s. Please remove the smb.conf file and let provision generate it" % lp.configfile)
591 if lp.get("realm").upper() != realm:
592 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))
594 if lp.get("server role").lower() != serverrole:
595 raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role"), serverrole, lp.configfile))
597 if serverrole == "domain controller":
598 if domain is None:
599 # This will, for better or worse, default to 'WORKGROUP'
600 domain = lp.get("workgroup")
601 domain = domain.upper()
603 if lp.get("workgroup").upper() != domain:
604 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))
606 if domaindn is None:
607 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
609 if domain == netbiosname:
610 raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
611 else:
612 domain = netbiosname
613 if domaindn is None:
614 domaindn = "DC=" + netbiosname
616 if not valid_netbios_name(domain):
617 raise InvalidNetbiosName(domain)
619 if hostname.upper() == realm:
620 raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
621 if netbiosname.upper() == realm:
622 raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
623 if domain == realm:
624 raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
626 if rootdn is None:
627 rootdn = domaindn
629 if configdn is None:
630 configdn = "CN=Configuration," + rootdn
631 if schemadn is None:
632 schemadn = "CN=Schema," + configdn
634 if sitename is None:
635 sitename=DEFAULTSITE
637 names = ProvisionNames()
638 names.rootdn = rootdn
639 names.domaindn = domaindn
640 names.configdn = configdn
641 names.schemadn = schemadn
642 names.ldapmanagerdn = "CN=Manager," + rootdn
643 names.dnsdomain = dnsdomain
644 names.domain = domain
645 names.realm = realm
646 names.netbiosname = netbiosname
647 names.hostname = hostname
648 names.sitename = sitename
649 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
650 netbiosname, sitename, configdn)
652 return names
655 def make_smbconf(smbconf, hostname, domain, realm, serverrole,
656 targetdir, sid_generator="internal", eadb=False, lp=None):
657 """Create a new smb.conf file based on a couple of basic settings.
659 assert smbconf is not None
660 if hostname is None:
661 hostname = socket.gethostname().split(".")[0]
662 netbiosname = hostname.upper()
663 # remove forbidden chars
664 newnbname = ""
665 for x in netbiosname:
666 if x.isalnum() or x in VALID_NETBIOS_CHARS:
667 newnbname = "%s%c" % (newnbname, x)
668 #force the length to be <16
669 netbiosname = newnbname[0:15]
670 else:
671 netbiosname = hostname.upper()
673 if serverrole is None:
674 serverrole = "standalone"
676 assert serverrole in ("domain controller", "member server", "standalone")
677 if serverrole == "domain controller":
678 smbconfsuffix = "dc"
679 elif serverrole == "member server":
680 smbconfsuffix = "member"
681 elif serverrole == "standalone":
682 smbconfsuffix = "standalone"
684 if sid_generator is None:
685 sid_generator = "internal"
687 assert domain is not None
688 domain = domain.upper()
690 assert realm is not None
691 realm = realm.upper()
693 if lp is None:
694 lp = samba.param.LoadParm()
695 #Load non-existant file
696 if os.path.exists(smbconf):
697 lp.load(smbconf)
698 if eadb and not lp.get("posix:eadb"):
699 if targetdir is not None:
700 privdir = os.path.join(targetdir, "private")
701 else:
702 privdir = lp.get("private dir")
703 lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
705 if targetdir is not None:
706 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
707 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
709 lp.set("lock dir", os.path.abspath(targetdir))
710 else:
711 privatedir_line = ""
712 lockdir_line = ""
714 if sid_generator == "internal":
715 sid_generator_line = ""
716 else:
717 sid_generator_line = "sid generator = " + sid_generator
719 sysvol = os.path.join(lp.get("lock dir"), "sysvol")
720 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
722 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
723 smbconf, {
724 "NETBIOS_NAME": netbiosname,
725 "DOMAIN": domain,
726 "REALM": realm,
727 "SERVERROLE": serverrole,
728 "NETLOGONPATH": netlogon,
729 "SYSVOLPATH": sysvol,
730 "SIDGENERATOR_LINE": sid_generator_line,
731 "PRIVATEDIR_LINE": privatedir_line,
732 "LOCKDIR_LINE": lockdir_line
735 # reload the smb.conf
736 lp.load(smbconf)
738 # and dump it without any values that are the default
739 # this ensures that any smb.conf parameters that were set
740 # on the provision/join command line are set in the resulting smb.conf
741 f = open(smbconf, mode='w')
742 lp.dump(f, False)
743 f.close()
747 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
748 users_gid, wheel_gid):
749 """setup reasonable name mappings for sam names to unix names.
751 :param samdb: SamDB object.
752 :param idmap: IDmap db object.
753 :param sid: The domain sid.
754 :param domaindn: The domain DN.
755 :param root_uid: uid of the UNIX root user.
756 :param nobody_uid: uid of the UNIX nobody user.
757 :param users_gid: gid of the UNIX users group.
758 :param wheel_gid: gid of the UNIX wheel group.
760 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
761 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
763 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
764 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
767 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
768 provision_backend, names, schema, serverrole,
769 erase=False):
770 """Setup the partitions for the SAM database.
772 Alternatively, provision() may call this, and then populate the database.
774 :note: This will wipe the Sam Database!
776 :note: This function always removes the local SAM LDB file. The erase
777 parameter controls whether to erase the existing data, which
778 may not be stored locally but in LDAP.
781 assert session_info is not None
783 # We use options=["modules:"] to stop the modules loading - we
784 # just want to wipe and re-initialise the database, not start it up
786 try:
787 os.unlink(samdb_path)
788 except OSError:
789 pass
791 samdb = Ldb(url=samdb_path, session_info=session_info,
792 lp=lp, options=["modules:"])
794 ldap_backend_line = "# No LDAP backend"
795 if provision_backend.type is not "ldb":
796 ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
798 samdb.transaction_start()
799 try:
800 logger.info("Setting up sam.ldb partitions and settings")
801 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
802 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(),
803 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
804 "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
805 "LDAP_BACKEND_LINE": ldap_backend_line,
809 setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
810 "BACKEND_TYPE": provision_backend.type,
811 "SERVER_ROLE": serverrole
814 logger.info("Setting up sam.ldb rootDSE")
815 setup_samdb_rootdse(samdb, names)
816 except Exception:
817 samdb.transaction_cancel()
818 raise
819 else:
820 samdb.transaction_commit()
823 def secretsdb_self_join(secretsdb, domain,
824 netbiosname, machinepass, domainsid=None,
825 realm=None, dnsdomain=None,
826 keytab_path=None,
827 key_version_number=1,
828 secure_channel_type=SEC_CHAN_WKSTA):
829 """Add domain join-specific bits to a secrets database.
831 :param secretsdb: Ldb Handle to the secrets database
832 :param machinepass: Machine password
834 attrs = ["whenChanged",
835 "secret",
836 "priorSecret",
837 "priorChanged",
838 "krb5Keytab",
839 "privateKeytab"]
841 if realm is not None:
842 if dnsdomain is None:
843 dnsdomain = realm.lower()
844 dnsname = '%s.%s' % (netbiosname.lower(), dnsdomain.lower())
845 else:
846 dnsname = None
847 shortname = netbiosname.lower()
849 # We don't need to set msg["flatname"] here, because rdn_name will handle
850 # it, and it causes problems for modifies anyway
851 msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
852 msg["secureChannelType"] = [str(secure_channel_type)]
853 msg["objectClass"] = ["top", "primaryDomain"]
854 if dnsname is not None:
855 msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
856 msg["realm"] = [realm]
857 msg["saltPrincipal"] = ["host/%s@%s" % (dnsname, realm.upper())]
858 msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
859 msg["privateKeytab"] = ["secrets.keytab"]
861 msg["secret"] = [machinepass]
862 msg["samAccountName"] = ["%s$" % netbiosname]
863 msg["secureChannelType"] = [str(secure_channel_type)]
864 if domainsid is not None:
865 msg["objectSid"] = [ndr_pack(domainsid)]
867 # This complex expression tries to ensure that we don't have more
868 # than one record for this SID, realm or netbios domain at a time,
869 # but we don't delete the old record that we are about to modify,
870 # because that would delete the keytab and previous password.
871 res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
872 expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
873 scope=ldb.SCOPE_ONELEVEL)
875 for del_msg in res:
876 secretsdb.delete(del_msg.dn)
878 res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
880 if len(res) == 1:
881 msg["priorSecret"] = [res[0]["secret"][0]]
882 msg["priorWhenChanged"] = [res[0]["whenChanged"][0]]
884 try:
885 msg["privateKeytab"] = [res[0]["privateKeytab"][0]]
886 except KeyError:
887 pass
889 try:
890 msg["krb5Keytab"] = [res[0]["krb5Keytab"][0]]
891 except KeyError:
892 pass
894 for el in msg:
895 if el != 'dn':
896 msg[el].set_flags(ldb.FLAG_MOD_REPLACE)
897 secretsdb.modify(msg)
898 secretsdb.rename(res[0].dn, msg.dn)
899 else:
900 spn = [ 'HOST/%s' % shortname ]
901 if secure_channel_type == SEC_CHAN_BDC and dnsname is not None:
902 # we are a domain controller then we add servicePrincipalName
903 # entries for the keytab code to update.
904 spn.extend([ 'HOST/%s' % dnsname ])
905 msg["servicePrincipalName"] = spn
907 secretsdb.add(msg)
910 def secretsdb_setup_dns(secretsdb, names, private_dir, realm,
911 dnsdomain, dns_keytab_path, dnspass):
912 """Add DNS specific bits to a secrets database.
914 :param secretsdb: Ldb Handle to the secrets database
915 :param machinepass: Machine password
917 try:
918 os.unlink(os.path.join(private_dir, dns_keytab_path))
919 except OSError:
920 pass
922 setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
923 "REALM": realm,
924 "DNSDOMAIN": dnsdomain,
925 "DNS_KEYTAB": dns_keytab_path,
926 "DNSPASS_B64": b64encode(dnspass),
927 "HOSTNAME": names.hostname,
928 "DNSNAME" : '%s.%s' % (
929 names.netbiosname.lower(), names.dnsdomain.lower())
933 def setup_secretsdb(paths, session_info, backend_credentials, lp):
934 """Setup the secrets database.
936 :note: This function does not handle exceptions and transaction on purpose,
937 it's up to the caller to do this job.
939 :param path: Path to the secrets database.
940 :param session_info: Session info.
941 :param credentials: Credentials
942 :param lp: Loadparm context
943 :return: LDB handle for the created secrets database
945 if os.path.exists(paths.secrets):
946 os.unlink(paths.secrets)
948 keytab_path = os.path.join(paths.private_dir, paths.keytab)
949 if os.path.exists(keytab_path):
950 os.unlink(keytab_path)
952 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
953 if os.path.exists(dns_keytab_path):
954 os.unlink(dns_keytab_path)
956 path = paths.secrets
958 secrets_ldb = Ldb(path, session_info=session_info,
959 lp=lp)
960 secrets_ldb.erase()
961 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
962 secrets_ldb = Ldb(path, session_info=session_info,
963 lp=lp)
964 secrets_ldb.transaction_start()
965 try:
966 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
968 if (backend_credentials is not None and
969 backend_credentials.authentication_requested()):
970 if backend_credentials.get_bind_dn() is not None:
971 setup_add_ldif(secrets_ldb,
972 setup_path("secrets_simple_ldap.ldif"), {
973 "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
974 "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
976 else:
977 setup_add_ldif(secrets_ldb,
978 setup_path("secrets_sasl_ldap.ldif"), {
979 "LDAPADMINUSER": backend_credentials.get_username(),
980 "LDAPADMINREALM": backend_credentials.get_realm(),
981 "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
984 return secrets_ldb
985 except Exception:
986 secrets_ldb.transaction_cancel()
987 raise
990 def setup_privileges(path, session_info, lp):
991 """Setup the privileges database.
993 :param path: Path to the privileges database.
994 :param session_info: Session info.
995 :param credentials: Credentials
996 :param lp: Loadparm context
997 :return: LDB handle for the created secrets database
999 if os.path.exists(path):
1000 os.unlink(path)
1001 privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
1002 privilege_ldb.erase()
1003 privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
1006 def setup_registry(path, session_info, lp):
1007 """Setup the registry.
1009 :param path: Path to the registry database
1010 :param session_info: Session information
1011 :param credentials: Credentials
1012 :param lp: Loadparm context
1014 reg = samba.registry.Registry()
1015 hive = samba.registry.open_ldb(path, session_info=session_info, lp_ctx=lp)
1016 reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
1017 provision_reg = setup_path("provision.reg")
1018 assert os.path.exists(provision_reg)
1019 reg.diff_apply(provision_reg)
1022 def setup_idmapdb(path, session_info, lp):
1023 """Setup the idmap database.
1025 :param path: path to the idmap database
1026 :param session_info: Session information
1027 :param credentials: Credentials
1028 :param lp: Loadparm context
1030 if os.path.exists(path):
1031 os.unlink(path)
1033 idmap_ldb = IDmapDB(path, session_info=session_info, lp=lp)
1034 idmap_ldb.erase()
1035 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
1036 return idmap_ldb
1039 def setup_samdb_rootdse(samdb, names):
1040 """Setup the SamDB rootdse.
1042 :param samdb: Sam Database handle
1044 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
1045 "SCHEMADN": names.schemadn,
1046 "DOMAINDN": names.domaindn,
1047 "ROOTDN": names.rootdn,
1048 "CONFIGDN": names.configdn,
1049 "SERVERDN": names.serverdn,
1053 def setup_self_join(samdb, names, machinepass, dnspass,
1054 domainsid, next_rid, invocationid,
1055 policyguid, policyguid_dc, domainControllerFunctionality,
1056 ntdsguid):
1057 """Join a host to its own domain."""
1058 assert isinstance(invocationid, str)
1059 if ntdsguid is not None:
1060 ntdsguid_line = "objectGUID: %s\n"%ntdsguid
1061 else:
1062 ntdsguid_line = ""
1063 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
1064 "CONFIGDN": names.configdn,
1065 "SCHEMADN": names.schemadn,
1066 "DOMAINDN": names.domaindn,
1067 "SERVERDN": names.serverdn,
1068 "INVOCATIONID": invocationid,
1069 "NETBIOSNAME": names.netbiosname,
1070 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
1071 "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
1072 "DOMAINSID": str(domainsid),
1073 "DCRID": str(next_rid),
1074 "SAMBA_VERSION_STRING": version,
1075 "NTDSGUID": ntdsguid_line,
1076 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
1077 domainControllerFunctionality)})
1079 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
1080 "POLICYGUID": policyguid,
1081 "POLICYGUID_DC": policyguid_dc,
1082 "DNSDOMAIN": names.dnsdomain,
1083 "DOMAINDN": names.domaindn})
1085 # add the NTDSGUID based SPNs
1086 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1087 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
1088 expression="", scope=ldb.SCOPE_BASE)
1089 assert isinstance(names.ntdsguid, str)
1091 # Setup fSMORoleOwner entries to point at the newly created DC entry
1092 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1093 "DOMAINDN": names.domaindn,
1094 "CONFIGDN": names.configdn,
1095 "SCHEMADN": names.schemadn,
1096 "DEFAULTSITE": names.sitename,
1097 "SERVERDN": names.serverdn,
1098 "NETBIOSNAME": names.netbiosname,
1099 "RIDALLOCATIONSTART": str(next_rid + 100),
1100 "RIDALLOCATIONEND": str(next_rid + 100 + 499),
1103 # This is partially Samba4 specific and should be replaced by the correct
1104 # DNS AD-style setup
1105 setup_add_ldif(samdb, setup_path("provision_dns_add.ldif"), {
1106 "DNSDOMAIN": names.dnsdomain,
1107 "DOMAINDN": names.domaindn,
1108 "DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
1109 "HOSTNAME" : names.hostname,
1110 "DNSNAME" : '%s.%s' % (
1111 names.netbiosname.lower(), names.dnsdomain.lower())
1115 def getpolicypath(sysvolpath, dnsdomain, guid):
1116 """Return the physical path of policy given its guid.
1118 :param sysvolpath: Path to the sysvol folder
1119 :param dnsdomain: DNS name of the AD domain
1120 :param guid: The GUID of the policy
1121 :return: A string with the complete path to the policy folder
1124 if guid[0] != "{":
1125 guid = "{%s}" % guid
1126 policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
1127 return policy_path
1130 def create_gpo_struct(policy_path):
1131 if not os.path.exists(policy_path):
1132 os.makedirs(policy_path, 0775)
1133 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1134 "[General]\r\nVersion=0")
1135 p = os.path.join(policy_path, "MACHINE")
1136 if not os.path.exists(p):
1137 os.makedirs(p, 0775)
1138 p = os.path.join(policy_path, "USER")
1139 if not os.path.exists(p):
1140 os.makedirs(p, 0775)
1143 def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
1144 """Create the default GPO for a domain
1146 :param sysvolpath: Physical path for the sysvol folder
1147 :param dnsdomain: DNS domain name of the AD domain
1148 :param policyguid: GUID of the default domain policy
1149 :param policyguid_dc: GUID of the default domain controler policy
1151 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
1152 create_gpo_struct(policy_path)
1154 policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
1155 create_gpo_struct(policy_path)
1158 def setup_samdb(path, session_info, provision_backend, lp, names,
1159 logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
1160 adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
1161 serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
1162 next_rid=1000):
1163 """Setup a complete SAM Database.
1165 :note: This will wipe the main SAM database file!
1168 # Provision does not make much sense values larger than 1000000000
1169 # as the upper range of the rIDAvailablePool is 1073741823 and
1170 # we don't want to create a domain that cannot allocate rids.
1171 if next_rid < 1000 or next_rid > 1000000000:
1172 error = "You want to run SAMBA 4 with a next_rid of %u, " % (next_rid)
1173 error += "the valid range is %u-%u. The default is %u." % (
1174 1000, 1000000000, 1000)
1175 raise ProvisioningError(error)
1177 # ATTENTION: Do NOT change these default values without discussion with the
1178 # team and/or release manager. They have a big impact on the whole program!
1179 domainControllerFunctionality = DS_DOMAIN_FUNCTION_2008_R2
1181 if dom_for_fun_level is None:
1182 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1184 if dom_for_fun_level > domainControllerFunctionality:
1185 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!")
1187 domainFunctionality = dom_for_fun_level
1188 forestFunctionality = dom_for_fun_level
1190 # Also wipes the database
1191 setup_samdb_partitions(path, logger=logger, lp=lp,
1192 provision_backend=provision_backend, session_info=session_info,
1193 names=names, serverrole=serverrole, schema=schema)
1195 if schema is None:
1196 schema = Schema(domainsid, schemadn=names.schemadn)
1198 # Load the database, but don's load the global schema and don't connect
1199 # quite yet
1200 samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
1201 credentials=provision_backend.credentials, lp=lp,
1202 global_schema=False, am_rodc=am_rodc)
1204 logger.info("Pre-loading the Samba 4 and AD schema")
1206 # Load the schema from the one we computed earlier
1207 samdb.set_schema(schema)
1209 # Set the NTDS settings DN manually - in order to have it already around
1210 # before the provisioned tree exists and we connect
1211 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1213 # And now we can connect to the DB - the schema won't be loaded from the
1214 # DB
1215 samdb.connect(path)
1217 if fill == FILL_DRS:
1218 return samdb
1220 samdb.transaction_start()
1221 try:
1222 # Set the domain functionality levels onto the database.
1223 # Various module (the password_hash module in particular) need
1224 # to know what level of AD we are emulating.
1226 # These will be fixed into the database via the database
1227 # modifictions below, but we need them set from the start.
1228 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1229 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1230 samdb.set_opaque_integer("domainControllerFunctionality",
1231 domainControllerFunctionality)
1233 samdb.set_domain_sid(str(domainsid))
1234 samdb.set_invocation_id(invocationid)
1236 logger.info("Adding DomainDN: %s" % names.domaindn)
1238 # impersonate domain admin
1239 admin_session_info = admin_session(lp, str(domainsid))
1240 samdb.set_session_info(admin_session_info)
1241 if domainguid is not None:
1242 domainguid_line = "objectGUID: %s\n-" % domainguid
1243 else:
1244 domainguid_line = ""
1246 descr = b64encode(get_domain_descriptor(domainsid))
1247 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1248 "DOMAINDN": names.domaindn,
1249 "DOMAINSID": str(domainsid),
1250 "DESCRIPTOR": descr,
1251 "DOMAINGUID": domainguid_line
1254 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1255 "DOMAINDN": names.domaindn,
1256 "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1257 "NEXTRID": str(next_rid),
1258 "DEFAULTSITE": names.sitename,
1259 "CONFIGDN": names.configdn,
1260 "POLICYGUID": policyguid,
1261 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1262 "SAMBA_VERSION_STRING": version
1265 logger.info("Adding configuration container")
1266 descr = b64encode(get_config_descriptor(domainsid))
1267 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1268 "CONFIGDN": names.configdn,
1269 "DESCRIPTOR": descr,
1272 # Now register this container in the root of the forest
1273 msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
1274 msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
1275 "subRefs")
1277 # The LDIF here was created when the Schema object was constructed
1278 logger.info("Setting up sam.ldb schema")
1279 samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1280 samdb.modify_ldif(schema.schema_dn_modify)
1281 samdb.write_prefixes_from_schema()
1282 samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1283 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
1284 {"SCHEMADN": names.schemadn})
1286 logger.info("Reopening sam.ldb with new schema")
1287 except Exception:
1288 samdb.transaction_cancel()
1289 raise
1290 else:
1291 samdb.transaction_commit()
1293 samdb = SamDB(session_info=admin_session_info, auto_connect=False,
1294 credentials=provision_backend.credentials, lp=lp,
1295 global_schema=False, am_rodc=am_rodc)
1297 # Set the NTDS settings DN manually - in order to have it already around
1298 # before the provisioned tree exists and we connect
1299 samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1300 samdb.connect(path)
1302 samdb.transaction_start()
1303 try:
1304 samdb.invocation_id = invocationid
1306 logger.info("Setting up sam.ldb configuration data")
1307 descr = b64encode(get_sites_descriptor(domainsid))
1308 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1309 "CONFIGDN": names.configdn,
1310 "NETBIOSNAME": names.netbiosname,
1311 "DEFAULTSITE": names.sitename,
1312 "DNSDOMAIN": names.dnsdomain,
1313 "DOMAIN": names.domain,
1314 "SCHEMADN": names.schemadn,
1315 "DOMAINDN": names.domaindn,
1316 "SERVERDN": names.serverdn,
1317 "FOREST_FUNCTIONALITY": str(forestFunctionality),
1318 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1319 "SITES_DESCRIPTOR": descr
1322 logger.info("Setting up display specifiers")
1323 display_specifiers_ldif = read_ms_ldif(
1324 setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1325 display_specifiers_ldif = substitute_var(display_specifiers_ldif,
1326 {"CONFIGDN": names.configdn})
1327 check_all_substituted(display_specifiers_ldif)
1328 samdb.add_ldif(display_specifiers_ldif)
1330 logger.info("Adding users container")
1331 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1332 "DOMAINDN": names.domaindn})
1333 logger.info("Modifying users container")
1334 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1335 "DOMAINDN": names.domaindn})
1336 logger.info("Adding computers container")
1337 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1338 "DOMAINDN": names.domaindn})
1339 logger.info("Modifying computers container")
1340 setup_modify_ldif(samdb,
1341 setup_path("provision_computers_modify.ldif"), {
1342 "DOMAINDN": names.domaindn})
1343 logger.info("Setting up sam.ldb data")
1344 setup_add_ldif(samdb, setup_path("provision.ldif"), {
1345 "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1346 "DOMAINDN": names.domaindn,
1347 "NETBIOSNAME": names.netbiosname,
1348 "DEFAULTSITE": names.sitename,
1349 "CONFIGDN": names.configdn,
1350 "SERVERDN": names.serverdn,
1351 "RIDAVAILABLESTART": str(next_rid + 600),
1352 "POLICYGUID_DC": policyguid_dc
1355 setup_modify_ldif(samdb,
1356 setup_path("provision_basedn_references.ldif"), {
1357 "DOMAINDN": names.domaindn})
1359 setup_modify_ldif(samdb,
1360 setup_path("provision_configuration_references.ldif"), {
1361 "CONFIGDN": names.configdn,
1362 "SCHEMADN": names.schemadn})
1363 if fill == FILL_FULL:
1364 logger.info("Setting up sam.ldb users and groups")
1365 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1366 "DOMAINDN": names.domaindn,
1367 "DOMAINSID": str(domainsid),
1368 "CONFIGDN": names.configdn,
1369 "ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
1370 "KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
1373 logger.info("Setting up self join")
1374 setup_self_join(samdb, names=names, invocationid=invocationid,
1375 dnspass=dnspass,
1376 machinepass=machinepass,
1377 domainsid=domainsid,
1378 next_rid=next_rid,
1379 policyguid=policyguid,
1380 policyguid_dc=policyguid_dc,
1381 domainControllerFunctionality=domainControllerFunctionality,
1382 ntdsguid=ntdsguid)
1384 ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
1385 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1386 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1387 assert isinstance(names.ntdsguid, str)
1388 except Exception:
1389 samdb.transaction_cancel()
1390 raise
1391 else:
1392 samdb.transaction_commit()
1393 return samdb
1396 FILL_FULL = "FULL"
1397 FILL_NT4SYNC = "NT4SYNC"
1398 FILL_DRS = "DRS"
1399 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1400 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)"
1403 def set_dir_acl(path, acl, lp, domsid):
1404 setntacl(lp, path, acl, domsid)
1405 for root, dirs, files in os.walk(path, topdown=False):
1406 for name in files:
1407 setntacl(lp, os.path.join(root, name), acl, domsid)
1408 for name in dirs:
1409 setntacl(lp, os.path.join(root, name), acl, domsid)
1412 def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1413 """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
1414 folders beneath.
1416 :param sysvol: Physical path for the sysvol folder
1417 :param dnsdomain: The DNS name of the domain
1418 :param domainsid: The SID of the domain
1419 :param domaindn: The DN of the domain (ie. DC=...)
1420 :param samdb: An LDB object on the SAM db
1421 :param lp: an LP object
1424 # Set ACL for GPO root folder
1425 root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1426 setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
1428 res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1429 attrs=["cn", "nTSecurityDescriptor"],
1430 expression="", scope=ldb.SCOPE_ONELEVEL)
1432 for policy in res:
1433 acl = ndr_unpack(security.descriptor,
1434 str(policy["nTSecurityDescriptor"])).as_sddl()
1435 policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
1436 set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
1437 str(domainsid))
1440 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1441 lp):
1442 """Set the ACL for the sysvol share and the subfolders
1444 :param samdb: An LDB object on the SAM db
1445 :param netlogon: Physical path for the netlogon folder
1446 :param sysvol: Physical path for the sysvol folder
1447 :param gid: The GID of the "Domain adminstrators" group
1448 :param domainsid: The SID of the domain
1449 :param dnsdomain: The DNS name of the domain
1450 :param domaindn: The DN of the domain (ie. DC=...)
1453 try:
1454 os.chown(sysvol, -1, gid)
1455 except OSError:
1456 canchown = False
1457 else:
1458 canchown = True
1460 # Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
1461 setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
1462 for root, dirs, files in os.walk(sysvol, topdown=False):
1463 for name in files:
1464 if canchown:
1465 os.chown(os.path.join(root, name), -1, gid)
1466 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1467 for name in dirs:
1468 if canchown:
1469 os.chown(os.path.join(root, name), -1, gid)
1470 setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
1472 # Set acls on Policy folder and policies folders
1473 set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
1476 def provision(logger, session_info, credentials, smbconf=None,
1477 targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
1478 domaindn=None, schemadn=None, configdn=None, serverdn=None,
1479 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
1480 next_rid=1000, adminpass=None, ldapadminpass=None, krbtgtpass=None,
1481 domainguid=None, policyguid=None, policyguid_dc=None,
1482 invocationid=None, machinepass=None, ntdsguid=None, dnspass=None,
1483 root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
1484 serverrole=None, dom_for_fun_level=None, ldap_backend_extra_port=None,
1485 ldap_backend_forced_uri=None, backend_type=None, sitename=None,
1486 ol_mmr_urls=None, ol_olc=None, setup_ds_path=None, slapd_path=None,
1487 nosync=False, ldap_dryrun_mode=False, useeadb=False, am_rodc=False,
1488 lp=None):
1489 """Provision samba4
1491 :note: caution, this wipes all existing data!
1494 if domainsid is None:
1495 domainsid = security.random_sid()
1496 else:
1497 domainsid = security.dom_sid(domainsid)
1499 # create/adapt the group policy GUIDs
1500 # Default GUID for default policy are described at
1501 # "How Core Group Policy Works"
1502 # http://technet.microsoft.com/en-us/library/cc784268%28WS.10%29.aspx
1503 if policyguid is None:
1504 policyguid = DEFAULT_POLICY_GUID
1505 policyguid = policyguid.upper()
1506 if policyguid_dc is None:
1507 policyguid_dc = DEFAULT_DC_POLICY_GUID
1508 policyguid_dc = policyguid_dc.upper()
1510 if adminpass is None:
1511 adminpass = samba.generate_random_password(12, 32)
1512 if krbtgtpass is None:
1513 krbtgtpass = samba.generate_random_password(128, 255)
1514 if machinepass is None:
1515 machinepass = samba.generate_random_password(128, 255)
1516 if dnspass is None:
1517 dnspass = samba.generate_random_password(128, 255)
1518 if ldapadminpass is None:
1519 # Make a new, random password between Samba and it's LDAP server
1520 ldapadminpass=samba.generate_random_password(128, 255)
1522 if backend_type is None:
1523 backend_type = "ldb"
1525 sid_generator = "internal"
1526 if backend_type == "fedora-ds":
1527 sid_generator = "backend"
1529 root_uid = findnss_uid([root or "root"])
1530 nobody_uid = findnss_uid([nobody or "nobody"])
1531 users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1532 if wheel is None:
1533 wheel_gid = findnss_gid(["wheel", "adm"])
1534 else:
1535 wheel_gid = findnss_gid([wheel])
1536 try:
1537 bind_gid = findnss_gid(["bind", "named"])
1538 except KeyError:
1539 bind_gid = None
1541 if targetdir is not None:
1542 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1543 elif smbconf is None:
1544 smbconf = samba.param.default_path()
1545 if not os.path.exists(os.path.dirname(smbconf)):
1546 os.makedirs(os.path.dirname(smbconf))
1548 # only install a new smb.conf if there isn't one there already
1549 if os.path.exists(smbconf):
1550 # if Samba Team members can't figure out the weird errors
1551 # loading an empty smb.conf gives, then we need to be smarter.
1552 # Pretend it just didn't exist --abartlet
1553 data = open(smbconf, 'r').read()
1554 data = data.lstrip()
1555 if data is None or data == "":
1556 make_smbconf(smbconf, hostname, domain, realm,
1557 serverrole, targetdir, sid_generator, useeadb,
1558 lp=lp)
1559 else:
1560 make_smbconf(smbconf, hostname, domain, realm, serverrole,
1561 targetdir, sid_generator, useeadb, lp=lp)
1563 if lp is None:
1564 lp = samba.param.LoadParm()
1565 lp.load(smbconf)
1566 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1567 dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
1568 configdn=configdn, schemadn=schemadn, serverdn=serverdn,
1569 sitename=sitename)
1570 paths = provision_paths_from_lp(lp, names.dnsdomain)
1572 paths.bind_gid = bind_gid
1574 if hostip is None:
1575 logger.info("Looking up IPv4 addresses")
1576 hostips = samba.interface_ips(lp, False)
1577 if len(hostips) == 0:
1578 logger.warning("No external IPv4 address has been found. Using loopback.")
1579 hostip = '127.0.0.1'
1580 else:
1581 hostip = hostips[0]
1582 if len(hostips) > 1:
1583 logger.warning("More than one IPv4 address found. Using %s.",
1584 hostip)
1586 if serverrole is None:
1587 serverrole = lp.get("server role")
1589 assert serverrole in ("domain controller", "member server", "standalone")
1590 if invocationid is None:
1591 invocationid = str(uuid.uuid4())
1593 if not os.path.exists(paths.private_dir):
1594 os.mkdir(paths.private_dir)
1595 if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1596 os.mkdir(os.path.join(paths.private_dir, "tls"))
1598 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1600 schema = Schema(domainsid, invocationid=invocationid,
1601 schemadn=names.schemadn)
1603 if backend_type == "ldb":
1604 provision_backend = LDBBackend(backend_type, paths=paths,
1605 lp=lp, credentials=credentials,
1606 names=names, logger=logger)
1607 elif backend_type == "existing":
1608 provision_backend = ExistingBackend(backend_type, paths=paths,
1609 lp=lp, credentials=credentials,
1610 names=names, logger=logger,
1611 ldap_backend_forced_uri=ldap_backend_forced_uri)
1612 elif backend_type == "fedora-ds":
1613 provision_backend = FDSBackend(backend_type, paths=paths,
1614 lp=lp, credentials=credentials,
1615 names=names, logger=logger, domainsid=domainsid,
1616 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1617 slapd_path=slapd_path,
1618 ldap_backend_extra_port=ldap_backend_extra_port,
1619 ldap_dryrun_mode=ldap_dryrun_mode, root=root,
1620 setup_ds_path=setup_ds_path,
1621 ldap_backend_forced_uri=ldap_backend_forced_uri)
1622 elif backend_type == "openldap":
1623 provision_backend = OpenLDAPBackend(backend_type, paths=paths,
1624 lp=lp, credentials=credentials,
1625 names=names, logger=logger, domainsid=domainsid,
1626 schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
1627 slapd_path=slapd_path,
1628 ldap_backend_extra_port=ldap_backend_extra_port,
1629 ldap_dryrun_mode=ldap_dryrun_mode, ol_mmr_urls=ol_mmr_urls,
1630 nosync=nosync,
1631 ldap_backend_forced_uri=ldap_backend_forced_uri)
1632 else:
1633 raise ValueError("Unknown LDAP backend type selected")
1635 provision_backend.init()
1636 provision_backend.start()
1638 # only install a new shares config db if there is none
1639 if not os.path.exists(paths.shareconf):
1640 logger.info("Setting up share.ldb")
1641 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1642 lp=lp)
1643 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1645 logger.info("Setting up secrets.ldb")
1646 secrets_ldb = setup_secretsdb(paths,
1647 session_info=session_info,
1648 backend_credentials=provision_backend.secrets_credentials, lp=lp)
1650 try:
1651 logger.info("Setting up the registry")
1652 setup_registry(paths.hklm, session_info,
1653 lp=lp)
1655 logger.info("Setting up the privileges database")
1656 setup_privileges(paths.privilege, session_info, lp=lp)
1658 logger.info("Setting up idmap db")
1659 idmap = setup_idmapdb(paths.idmapdb,
1660 session_info=session_info, lp=lp)
1662 logger.info("Setting up SAM db")
1663 samdb = setup_samdb(paths.samdb, session_info,
1664 provision_backend, lp, names, logger=logger,
1665 domainsid=domainsid, schema=schema, domainguid=domainguid,
1666 policyguid=policyguid, policyguid_dc=policyguid_dc,
1667 fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
1668 invocationid=invocationid, machinepass=machinepass,
1669 dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
1670 dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
1671 next_rid=next_rid)
1673 if serverrole == "domain controller":
1674 if paths.netlogon is None:
1675 logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1676 logger.info("Please either remove %s or see the template at %s" %
1677 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1678 assert paths.netlogon is not None
1680 if paths.sysvol is None:
1681 logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1682 " are configuring a DC.")
1683 logger.info("Please either remove %s or see the template at %s" %
1684 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1685 assert paths.sysvol is not None
1687 if not os.path.isdir(paths.netlogon):
1688 os.makedirs(paths.netlogon, 0755)
1690 if samdb_fill == FILL_FULL:
1691 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1692 root_uid=root_uid, nobody_uid=nobody_uid,
1693 users_gid=users_gid, wheel_gid=wheel_gid)
1695 if serverrole == "domain controller":
1696 # Set up group policies (domain policy and domain controller
1697 # policy)
1698 create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
1699 policyguid_dc)
1700 setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid,
1701 domainsid, names.dnsdomain, names.domaindn, lp)
1703 logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1704 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1706 secretsdb_self_join(secrets_ldb, domain=names.domain,
1707 realm=names.realm, dnsdomain=names.dnsdomain,
1708 netbiosname=names.netbiosname, domainsid=domainsid,
1709 machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
1711 # Now set up the right msDS-SupportedEncryptionTypes into the DB
1712 # In future, this might be determined from some configuration
1713 kerberos_enctypes = str(ENC_ALL_TYPES)
1715 try:
1716 msg = ldb.Message(ldb.Dn(samdb,
1717 samdb.searchone("distinguishedName",
1718 expression="samAccountName=%s$" % names.netbiosname,
1719 scope=ldb.SCOPE_SUBTREE)))
1720 msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
1721 elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
1722 name="msDS-SupportedEncryptionTypes")
1723 samdb.modify(msg)
1724 except ldb.LdbError, (enum, estr):
1725 if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
1726 # It might be that this attribute does not exist in this schema
1727 raise
1729 if serverrole == "domain controller":
1730 secretsdb_setup_dns(secrets_ldb, names,
1731 paths.private_dir, realm=names.realm,
1732 dnsdomain=names.dnsdomain,
1733 dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
1735 domainguid = samdb.searchone(basedn=domaindn,
1736 attribute="objectGUID")
1737 assert isinstance(domainguid, str)
1739 # Only make a zone file on the first DC, it should be
1740 # replicated with DNS replication
1741 create_zone_file(lp, logger, paths, targetdir,
1742 dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
1743 hostname=names.hostname, realm=names.realm,
1744 domainguid=domainguid, ntdsguid=names.ntdsguid)
1746 create_named_conf(paths, realm=names.realm,
1747 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1749 create_named_txt(paths.namedtxt,
1750 realm=names.realm, dnsdomain=names.dnsdomain,
1751 dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
1752 private_dir=paths.private_dir,
1753 keytab_name=paths.dns_keytab)
1754 logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
1755 logger.info("and %s for further documentation required for secure DNS "
1756 "updates", paths.namedtxt)
1758 lastProvisionUSNs = get_last_provision_usn(samdb)
1759 maxUSN = get_max_usn(samdb, str(names.rootdn))
1760 if lastProvisionUSNs is not None:
1761 update_provision_usn(samdb, 0, maxUSN, 1)
1762 else:
1763 set_provision_usn(samdb, 0, maxUSN)
1765 create_krb5_conf(paths.krb5conf,
1766 dnsdomain=names.dnsdomain, hostname=names.hostname,
1767 realm=names.realm)
1768 logger.info("A Kerberos configuration suitable for Samba 4 has been "
1769 "generated at %s", paths.krb5conf)
1771 if serverrole == "domain controller":
1772 create_dns_update_list(lp, logger, paths)
1774 provision_backend.post_setup()
1775 provision_backend.shutdown()
1777 create_phpldapadmin_config(paths.phpldapadminconfig,
1778 ldapi_url)
1779 except Exception:
1780 secrets_ldb.transaction_cancel()
1781 raise
1783 # Now commit the secrets.ldb to disk
1784 secrets_ldb.transaction_commit()
1786 # the commit creates the dns.keytab, now chown it
1787 dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1788 if os.path.isfile(dns_keytab_path) and paths.bind_gid is not None:
1789 try:
1790 os.chmod(dns_keytab_path, 0640)
1791 os.chown(dns_keytab_path, -1, paths.bind_gid)
1792 except OSError:
1793 if not os.environ.has_key('SAMBA_SELFTEST'):
1794 logger.info("Failed to chown %s to bind gid %u",
1795 dns_keytab_path, paths.bind_gid)
1798 logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
1799 paths.phpldapadminconfig)
1801 logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1802 logger.info("Server Role: %s" % serverrole)
1803 logger.info("Hostname: %s" % names.hostname)
1804 logger.info("NetBIOS Domain: %s" % names.domain)
1805 logger.info("DNS Domain: %s" % names.dnsdomain)
1806 logger.info("DOMAIN SID: %s" % str(domainsid))
1807 if samdb_fill == FILL_FULL:
1808 logger.info("Admin password: %s" % adminpass)
1809 if provision_backend.type is not "ldb":
1810 if provision_backend.credentials.get_bind_dn() is not None:
1811 logger.info("LDAP Backend Admin DN: %s" %
1812 provision_backend.credentials.get_bind_dn())
1813 else:
1814 logger.info("LDAP Admin User: %s" %
1815 provision_backend.credentials.get_username())
1817 logger.info("LDAP Admin Password: %s" %
1818 provision_backend.credentials.get_password())
1820 if provision_backend.slapd_command_escaped is not None:
1821 # now display slapd_command_file.txt to show how slapd must be
1822 # started next time
1823 logger.info("Use later the following commandline to start slapd, then Samba:")
1824 logger.info(provision_backend.slapd_command_escaped)
1825 logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
1826 provision_backend.ldapdir)
1828 result = ProvisionResult()
1829 result.domaindn = domaindn
1830 result.paths = paths
1831 result.lp = lp
1832 result.samdb = samdb
1833 return result
1836 def provision_become_dc(smbconf=None, targetdir=None,
1837 realm=None, rootdn=None, domaindn=None, schemadn=None, configdn=None,
1838 serverdn=None, domain=None, hostname=None, domainsid=None,
1839 adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
1840 policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
1841 root=None, nobody=None, users=None, wheel=None, backup=None,
1842 serverrole=None, ldap_backend=None, ldap_backend_type=None,
1843 sitename=None, debuglevel=1):
1845 logger = logging.getLogger("provision")
1846 samba.set_debug_level(debuglevel)
1848 res = provision(logger, system_session(), None,
1849 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1850 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1851 configdn=configdn, serverdn=serverdn, domain=domain,
1852 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1853 machinepass=machinepass, serverrole="domain controller",
1854 sitename=sitename)
1855 res.lp.set("debuglevel", str(debuglevel))
1856 return res
1859 def create_phpldapadmin_config(path, ldapi_uri):
1860 """Create a PHP LDAP admin configuration file.
1862 :param path: Path to write the configuration to.
1864 setup_file(setup_path("phpldapadmin-config.php"), path,
1865 {"S4_LDAPI_URI": ldapi_uri})
1868 def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
1869 hostip, hostip6, hostname, realm, domainguid,
1870 ntdsguid):
1871 """Write out a DNS zone file, from the info in the current database.
1873 :param paths: paths object
1874 :param dnsdomain: DNS Domain name
1875 :param domaindn: DN of the Domain
1876 :param hostip: Local IPv4 IP
1877 :param hostip6: Local IPv6 IP
1878 :param hostname: Local hostname
1879 :param realm: Realm name
1880 :param domainguid: GUID of the domain.
1881 :param ntdsguid: GUID of the hosts nTDSDSA record.
1883 assert isinstance(domainguid, str)
1885 if hostip6 is not None:
1886 hostip6_base_line = " IN AAAA " + hostip6
1887 hostip6_host_line = hostname + " IN AAAA " + hostip6
1888 gc_msdcs_ip6_line = "gc._msdcs IN AAAA " + hostip6
1889 else:
1890 hostip6_base_line = ""
1891 hostip6_host_line = ""
1892 gc_msdcs_ip6_line = ""
1894 if hostip is not None:
1895 hostip_base_line = " IN A " + hostip
1896 hostip_host_line = hostname + " IN A " + hostip
1897 gc_msdcs_ip_line = "gc._msdcs IN A " + hostip
1898 else:
1899 hostip_base_line = ""
1900 hostip_host_line = ""
1901 gc_msdcs_ip_line = ""
1903 dns_dir = os.path.dirname(paths.dns)
1905 try:
1906 shutil.rmtree(dns_dir, True)
1907 except OSError:
1908 pass
1910 os.mkdir(dns_dir, 0775)
1912 # we need to freeze the zone while we update the contents
1913 if targetdir is None:
1914 rndc = ' '.join(lp.get("rndc command"))
1915 os.system(rndc + " freeze " + lp.get("realm"))
1917 setup_file(setup_path("provision.zone"), paths.dns, {
1918 "HOSTNAME": hostname,
1919 "DNSDOMAIN": dnsdomain,
1920 "REALM": realm,
1921 "HOSTIP_BASE_LINE": hostip_base_line,
1922 "HOSTIP_HOST_LINE": hostip_host_line,
1923 "DOMAINGUID": domainguid,
1924 "DATESTRING": time.strftime("%Y%m%d%H"),
1925 "DEFAULTSITE": DEFAULTSITE,
1926 "NTDSGUID": ntdsguid,
1927 "HOSTIP6_BASE_LINE": hostip6_base_line,
1928 "HOSTIP6_HOST_LINE": hostip6_host_line,
1929 "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
1930 "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
1933 # note that we use no variable substitution on this file
1934 # the substitution is done at runtime by samba_dnsupdate
1935 setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1937 # and the SPN update list
1938 setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1940 if paths.bind_gid is not None:
1941 try:
1942 os.chown(dns_dir, -1, paths.bind_gid)
1943 os.chown(paths.dns, -1, paths.bind_gid)
1944 # chmod needed to cope with umask
1945 os.chmod(dns_dir, 0775)
1946 os.chmod(paths.dns, 0664)
1947 except OSError:
1948 if not os.environ.has_key('SAMBA_SELFTEST'):
1949 logger.error("Failed to chown %s to bind gid %u" % (
1950 dns_dir, paths.bind_gid))
1952 if targetdir is None:
1953 os.system(rndc + " unfreeze " + lp.get("realm"))
1956 def create_dns_update_list(lp, logger, paths):
1957 """Write out a dns_update_list file"""
1958 # note that we use no variable substitution on this file
1959 # the substitution is done at runtime by samba_dnsupdate
1960 setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1961 setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1964 def create_named_conf(paths, realm, dnsdomain,
1965 private_dir):
1966 """Write out a file containing zone statements suitable for inclusion in a
1967 named.conf file (including GSS-TSIG configuration).
1969 :param paths: all paths
1970 :param realm: Realm name
1971 :param dnsdomain: DNS Domain name
1972 :param private_dir: Path to private directory
1973 :param keytab_name: File name of DNS keytab file
1976 setup_file(setup_path("named.conf"), paths.namedconf, {
1977 "DNSDOMAIN": dnsdomain,
1978 "REALM": realm,
1979 "ZONE_FILE": paths.dns,
1980 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1981 "NAMED_CONF": paths.namedconf,
1982 "NAMED_CONF_UPDATE": paths.namedconf_update
1985 setup_file(setup_path("named.conf.update"), paths.namedconf_update)
1988 def create_named_txt(path, realm, dnsdomain, dnsname, private_dir,
1989 keytab_name):
1990 """Write out a file containing zone statements suitable for inclusion in a
1991 named.conf file (including GSS-TSIG configuration).
1993 :param path: Path of the new named.conf file.
1994 :param realm: Realm name
1995 :param dnsdomain: DNS Domain name
1996 :param private_dir: Path to private directory
1997 :param keytab_name: File name of DNS keytab file
1999 setup_file(setup_path("named.txt"), path, {
2000 "DNSDOMAIN": dnsdomain,
2001 "DNSNAME" : dnsname,
2002 "REALM": realm,
2003 "DNS_KEYTAB": keytab_name,
2004 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
2005 "PRIVATE_DIR": private_dir
2009 def create_krb5_conf(path, dnsdomain, hostname, realm):
2010 """Write out a file containing zone statements suitable for inclusion in a
2011 named.conf file (including GSS-TSIG configuration).
2013 :param path: Path of the new named.conf file.
2014 :param dnsdomain: DNS Domain name
2015 :param hostname: Local hostname
2016 :param realm: Realm name
2018 setup_file(setup_path("krb5.conf"), path, {
2019 "DNSDOMAIN": dnsdomain,
2020 "HOSTNAME": hostname,
2021 "REALM": realm,
2025 class ProvisioningError(Exception):
2026 """A generic provision error."""
2028 def __init__(self, value):
2029 self.value = value
2031 def __str__(self):
2032 return "ProvisioningError: " + self.value
2035 class InvalidNetbiosName(Exception):
2036 """A specified name was not a valid NetBIOS name."""
2037 def __init__(self, name):
2038 super(InvalidNetbiosName, self).__init__(
2039 "The name '%r' is not a valid NetBIOS name" % name)