s4:provision.py - we do now support the "Windows 2000 Native" domain function level
[mat/samba.git] / source4 / scripting / python / samba / provision.py
1
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
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
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
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.
16 #   
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.
21 #   
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/>.
24 #
25
26 """Functions for setting up a Samba configuration."""
27
28 from base64 import b64encode
29 import os
30 import pwd
31 import grp
32 import logging
33 import time
34 import uuid
35 import socket
36 import urllib
37 import shutil
38
39 import ldb
40
41 from samba.auth import system_session, admin_session
42 import samba
43 from samba import version, Ldb, substitute_var, valid_netbios_name
44 from samba import check_all_substituted, read_and_sub_file, setup_file
45 from samba.dsdb import DS_DOMAIN_FUNCTION_2003, DS_DC_FUNCTION_2008
46 from samba.dcerpc import security
47 from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
48 from samba.idmap import IDmapDB
49 from samba.ms_display_specifiers import read_ms_ldif
50 from samba.ntacls import setntacl, dsacl2fsacl
51 from samba.ndr import ndr_pack,ndr_unpack
52 from samba.provisionbackend import (
53     ExistingBackend,
54     FDSBackend,
55     LDBBackend,
56     OpenLDAPBackend,
57     )
58 import samba.param
59 import samba.registry
60 from samba.schema import Schema
61 from samba.samdb import SamDB
62
63 __docformat__ = "restructuredText"
64
65 def find_setup_dir():
66     """Find the setup directory used by provision."""
67     dirname = os.path.dirname(__file__)
68     if "/site-packages/" in dirname:
69         prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
70         for suffix in ["share/setup", "share/samba/setup", "setup"]:
71             ret = os.path.join(prefix, suffix)
72             if os.path.isdir(ret):
73                 return ret
74     # In source tree
75     ret = os.path.join(dirname, "../../../setup")
76     if os.path.isdir(ret):
77         return ret
78     raise Exception("Unable to find setup directory.")
79
80 # descriptors of the naming contexts
81 # hard coded at this point, but will probably be changed when
82 # we enable different fsmo roles
83
84 def get_config_descriptor(domain_sid):
85     sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
86            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
87            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
88            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
89            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
90            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
91            "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
92            "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
93            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
94            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
95            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
96            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
97            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
98            "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
99            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
100     sec = security.descriptor.from_sddl(sddl, domain_sid)
101     return ndr_pack(sec)
102
103 def get_domain_descriptor(domain_sid):
104     sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
105         "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
106     "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
107     "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
108     "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
109     "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
110     "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
111     "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
112     "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
113     "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
114     "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
115     "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \
116     "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
117     "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
118     "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
119     "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
120     "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
121     "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
122     "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
123     "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
124     "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
125     "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
126     "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
127     "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
128     "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
129     "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
130     "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
131     "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
132     "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
133     "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
134     "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
135     "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
136     "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
137     "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
138     "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
139     "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
140     "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
141     "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
142     "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
143     "(A;;RPRC;;;RU)" \
144     "(A;CI;LC;;;RU)" \
145     "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
146     "(A;;RP;;;WD)" \
147     "(A;;RPLCLORC;;;ED)" \
148     "(A;;RPLCLORC;;;AU)" \
149     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
150     "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
151     "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
152     "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)"
153     sec = security.descriptor.from_sddl(sddl, domain_sid)
154     return ndr_pack(sec)
155
156 DEFAULTSITE = "Default-First-Site-Name"
157
158 class ProvisionPaths(object):
159
160     def __init__(self):
161         self.shareconf = None
162         self.hklm = None
163         self.hkcu = None
164         self.hkcr = None
165         self.hku = None
166         self.hkpd = None
167         self.hkpt = None
168         self.samdb = None
169         self.idmapdb = None
170         self.secrets = None
171         self.keytab = None
172         self.dns_keytab = None
173         self.dns = None
174         self.winsdb = None
175         self.private_dir = None
176
177
178 class ProvisionNames(object):
179
180     def __init__(self):
181         self.rootdn = None
182         self.domaindn = None
183         self.configdn = None
184         self.schemadn = None
185         self.ldapmanagerdn = None
186         self.dnsdomain = None
187         self.realm = None
188         self.netbiosname = None
189         self.domain = None
190         self.hostname = None
191         self.sitename = None
192         self.smbconf = None
193     
194
195 class ProvisionResult(object):
196
197     def __init__(self):
198         self.paths = None
199         self.domaindn = None
200         self.lp = None
201         self.samdb = None
202
203
204 def check_install(lp, session_info, credentials):
205     """Check whether the current install seems ok.
206     
207     :param lp: Loadparm context
208     :param session_info: Session information
209     :param credentials: Credentials
210     """
211     if lp.get("realm") == "":
212         raise Exception("Realm empty")
213     samdb = Ldb(lp.get("sam database"), session_info=session_info, 
214             credentials=credentials, lp=lp)
215     if len(samdb.search("(cn=Administrator)")) != 1:
216         raise ProvisioningError("No administrator account found")
217
218
219 def findnss(nssfn, names):
220     """Find a user or group from a list of possibilities.
221     
222     :param nssfn: NSS Function to try (should raise KeyError if not found)
223     :param names: Names to check.
224     :return: Value return by first names list.
225     """
226     for name in names:
227         try:
228             return nssfn(name)
229         except KeyError:
230             pass
231     raise KeyError("Unable to find user/group in %r" % names)
232
233
234 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
235 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
236
237
238 def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
239     """Setup a ldb in the private dir.
240     
241     :param ldb: LDB file to import data into
242     :param ldif_path: Path of the LDIF file to load
243     :param subst_vars: Optional variables to subsitute in LDIF.
244     :param nocontrols: Optional list of controls, can be None for no controls
245     """
246     assert isinstance(ldif_path, str)
247     data = read_and_sub_file(ldif_path, subst_vars)
248     ldb.add_ldif(data, controls)
249
250
251 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
252     """Modify a ldb in the private dir.
253     
254     :param ldb: LDB object.
255     :param ldif_path: LDIF file path.
256     :param subst_vars: Optional dictionary with substitution variables.
257     """
258     data = read_and_sub_file(ldif_path, subst_vars)
259     ldb.modify_ldif(data)
260
261
262 def setup_ldb(ldb, ldif_path, subst_vars):
263     """Import a LDIF a file into a LDB handle, optionally substituting variables.
264
265     :note: Either all LDIF data will be added or none (using transactions).
266
267     :param ldb: LDB file to import into.
268     :param ldif_path: Path to the LDIF file.
269     :param subst_vars: Dictionary with substitution variables.
270     """
271     assert ldb is not None
272     ldb.transaction_start()
273     try:
274         setup_add_ldif(ldb, ldif_path, subst_vars)
275     except:
276         ldb.transaction_cancel()
277         raise
278     else:
279         ldb.transaction_commit()
280
281
282 def provision_paths_from_lp(lp, dnsdomain):
283     """Set the default paths for provisioning.
284
285     :param lp: Loadparm context.
286     :param dnsdomain: DNS Domain name
287     """
288     paths = ProvisionPaths()
289     paths.private_dir = lp.get("private dir")
290
291     # This is stored without path prefix for the "privateKeytab" attribute in
292     # "secrets_dns.ldif".
293     paths.dns_keytab = "dns.keytab"
294
295     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
296     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
297     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
298     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
299     paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
300     paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
301     paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
302     paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
303     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
304     paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
305     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
306     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
307     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
308     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
309     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
310                                             "phpldapadmin-config.php")
311     paths.hklm = "hklm.ldb"
312     paths.hkcr = "hkcr.ldb"
313     paths.hkcu = "hkcu.ldb"
314     paths.hku = "hku.ldb"
315     paths.hkpd = "hkpd.ldb"
316     paths.hkpt = "hkpt.ldb"
317     paths.sysvol = lp.get("path", "sysvol")
318     paths.netlogon = lp.get("path", "netlogon")
319     paths.smbconf = lp.configfile
320     return paths
321
322
323 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
324                 serverrole=None, rootdn=None, domaindn=None, configdn=None,
325                 schemadn=None, serverdn=None, sitename=None):
326     """Guess configuration settings to use."""
327
328     if hostname is None:
329         hostname = socket.gethostname().split(".")[0]
330
331     netbiosname = lp.get("netbios name")
332     if netbiosname is None:
333         netbiosname = hostname
334     assert netbiosname is not None
335     netbiosname = netbiosname.upper()
336     if not valid_netbios_name(netbiosname):
337         raise InvalidNetbiosName(netbiosname)
338
339     if dnsdomain is None:
340         dnsdomain = lp.get("realm")
341         if dnsdomain is None or dnsdomain == "":
342             raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
343
344     dnsdomain = dnsdomain.lower()
345
346     if serverrole is None:
347         serverrole = lp.get("server role")
348         if serverrole is None:
349             raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
350
351     serverrole = serverrole.lower()
352
353     realm = dnsdomain.upper()
354
355     if lp.get("realm") == "":
356         raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s.  Please remove the smb.conf file and let provision generate it" % lp.configfile)
357
358     if lp.get("realm").upper() != realm:
359         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))
360
361     if lp.get("server role").lower() != serverrole:
362         raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'!  Please remove the smb.conf file and let provision generate it" % (lp.get("server role").upper(), serverrole, lp.configfile))
363
364     if serverrole == "domain controller":
365         if domain is None:
366             # This will, for better or worse, default to 'WORKGROUP'
367             domain = lp.get("workgroup")
368         domain = domain.upper()
369
370         if lp.get("workgroup").upper() != domain:
371             raise ProvisioningError("guess_names: Workgroup '%s' in %s must match chosen domain '%s'!  Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
372
373         if domaindn is None:
374             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
375     else:
376         domain = netbiosname
377         if domaindn is None:
378             domaindn = "DC=" + netbiosname
379         
380     if not valid_netbios_name(domain):
381         raise InvalidNetbiosName(domain)
382         
383     if hostname.upper() == realm:
384         raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
385     if netbiosname == realm:
386         raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
387     if domain == realm:
388         raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
389
390     if rootdn is None:
391        rootdn = domaindn
392        
393     if configdn is None:
394         configdn = "CN=Configuration," + rootdn
395     if schemadn is None:
396         schemadn = "CN=Schema," + configdn
397
398     if sitename is None:
399         sitename=DEFAULTSITE
400
401     names = ProvisionNames()
402     names.rootdn = rootdn
403     names.domaindn = domaindn
404     names.configdn = configdn
405     names.schemadn = schemadn
406     names.ldapmanagerdn = "CN=Manager," + rootdn
407     names.dnsdomain = dnsdomain
408     names.domain = domain
409     names.realm = realm
410     names.netbiosname = netbiosname
411     names.hostname = hostname
412     names.sitename = sitename
413     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
414  
415     return names
416     
417
418 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
419                  targetdir, sid_generator="internal", eadb=False):
420     """Create a new smb.conf file based on a couple of basic settings.
421     """
422     assert smbconf is not None
423     if hostname is None:
424         hostname = socket.gethostname().split(".")[0]
425     netbiosname = hostname.upper()
426
427     if serverrole is None:
428         serverrole = "standalone"
429
430     assert serverrole in ("domain controller", "member server", "standalone")
431     if serverrole == "domain controller":
432         smbconfsuffix = "dc"
433     elif serverrole == "member server":
434         smbconfsuffix = "member"
435     elif serverrole == "standalone":
436         smbconfsuffix = "standalone"
437
438     if sid_generator is None:
439         sid_generator = "internal"
440
441     assert domain is not None
442     domain = domain.upper()
443
444     assert realm is not None
445     realm = realm.upper()
446
447     default_lp = samba.param.LoadParm()
448     #Load non-existant file
449     if os.path.exists(smbconf):
450         default_lp.load(smbconf)
451     if eadb:
452         if targetdir is not None:
453             privdir = os.path.join(targetdir, "private")
454         else:
455             privdir = default_lp.get("private dir")
456         posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(privdir,"eadb.tdb"))
457     else:
458         posixeadb_line = ""
459
460     if targetdir is not None:
461         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
462         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
463
464         default_lp.set("lock dir", os.path.abspath(targetdir))
465     else:
466         privatedir_line = ""
467         lockdir_line = ""
468
469     if sid_generator == "internal":
470         sid_generator_line = ""
471     else:
472         sid_generator_line = "sid generator = " + sid_generator
473
474     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
475     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
476
477     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
478                smbconf, {
479             "NETBIOS_NAME": netbiosname,
480             "DOMAIN": domain,
481             "REALM": realm,
482             "SERVERROLE": serverrole,
483             "NETLOGONPATH": netlogon,
484             "SYSVOLPATH": sysvol,
485             "SIDGENERATOR_LINE": sid_generator_line,
486             "PRIVATEDIR_LINE": privatedir_line,
487             "LOCKDIR_LINE": lockdir_line,
488             "POSIXEADB_LINE": posixeadb_line
489             })
490
491
492 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
493                         users_gid, wheel_gid):
494     """setup reasonable name mappings for sam names to unix names.
495
496     :param samdb: SamDB object.
497     :param idmap: IDmap db object.
498     :param sid: The domain sid.
499     :param domaindn: The domain DN.
500     :param root_uid: uid of the UNIX root user.
501     :param nobody_uid: uid of the UNIX nobody user.
502     :param users_gid: gid of the UNIX users group.
503     :param wheel_gid: gid of the UNIX wheel group."""
504     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
505     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
506     
507     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
508     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
509
510
511 def setup_samdb_partitions(samdb_path, setup_path, logger, lp, session_info, 
512                            provision_backend, names, schema, serverrole, 
513                            erase=False):
514     """Setup the partitions for the SAM database. 
515     
516     Alternatively, provision() may call this, and then populate the database.
517     
518     :note: This will wipe the Sam Database!
519     
520     :note: This function always removes the local SAM LDB file. The erase 
521         parameter controls whether to erase the existing data, which 
522         may not be stored locally but in LDAP.
523
524     """
525     assert session_info is not None
526
527     # We use options=["modules:"] to stop the modules loading - we
528     # just want to wipe and re-initialise the database, not start it up
529
530     try:
531         os.unlink(samdb_path)
532     except OSError:
533         pass
534
535     samdb = Ldb(url=samdb_path, session_info=session_info, 
536                 lp=lp, options=["modules:"])
537
538     ldap_backend_line = "# No LDAP backend"
539     if provision_backend.type is not "ldb":
540         ldap_backend_line = "ldapBackend: %s" % provision_backend.ldapi_uri
541
542     samdb.transaction_start()
543     try:
544         logger.info("Setting up sam.ldb partitions and settings")
545         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
546                 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), 
547                 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
548                 "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
549                 "LDAP_BACKEND_LINE": ldap_backend_line,
550         })
551
552         
553         setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
554                 "BACKEND_TYPE": provision_backend.type,
555                 "SERVER_ROLE": serverrole
556                 })
557
558         logger.info("Setting up sam.ldb rootDSE")
559         setup_samdb_rootdse(samdb, setup_path, names)
560     except:
561         samdb.transaction_cancel()
562         raise
563     else:
564         samdb.transaction_commit()
565
566         
567 def secretsdb_self_join(secretsdb, domain, 
568                         netbiosname, machinepass, domainsid=None,
569                         realm=None, dnsdomain=None,
570                         keytab_path=None, 
571                         key_version_number=1,
572                         secure_channel_type=SEC_CHAN_WKSTA):
573     """Add domain join-specific bits to a secrets database.
574     
575     :param secretsdb: Ldb Handle to the secrets database
576     :param machinepass: Machine password
577     """
578     attrs=["whenChanged",
579            "secret",
580            "priorSecret",
581            "priorChanged",
582            "krb5Keytab",
583            "privateKeytab"]
584     
585
586     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
587     msg["secureChannelType"] = str(secure_channel_type)
588     msg["flatname"] = [domain]
589     msg["objectClass"] = ["top", "primaryDomain"]
590     if realm is not None:
591       if dnsdomain is None:
592         dnsdomain = realm.lower()
593       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
594       msg["realm"] = realm
595       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
596       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
597       msg["privateKeytab"] = ["secrets.keytab"]
598
599
600     msg["secret"] = [machinepass]
601     msg["samAccountName"] = ["%s$" % netbiosname]
602     msg["secureChannelType"] = [str(secure_channel_type)]
603     if domainsid is not None:
604         msg["objectSid"] = [ndr_pack(domainsid)]
605     
606     res = secretsdb.search(base="cn=Primary Domains", 
607                            attrs=attrs, 
608                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
609                            scope=ldb.SCOPE_ONELEVEL)
610     
611     for del_msg in res:
612       if del_msg.dn is not msg.dn:
613         secretsdb.delete(del_msg.dn)
614
615     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
616
617     if len(res) == 1:
618       msg["priorSecret"] = res[0]["secret"]
619       msg["priorWhenChanged"] = res[0]["whenChanged"]
620
621       if res["privateKeytab"] is not None:
622         msg["privateKeytab"] = res[0]["privateKeytab"]
623
624       if res["krb5Keytab"] is not None:
625         msg["krb5Keytab"] = res[0]["krb5Keytab"]
626
627       for el in msg:
628         el.set_flags(ldb.FLAG_MOD_REPLACE)
629         secretsdb.modify(msg)
630     else:
631       secretsdb.add(msg)
632
633
634 def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
635                         realm, dnsdomain,
636                         dns_keytab_path, dnspass):
637     """Add DNS specific bits to a secrets database.
638     
639     :param secretsdb: Ldb Handle to the secrets database
640     :param setup_path: Setup path function
641     :param machinepass: Machine password
642     """
643     try:
644         os.unlink(os.path.join(private_dir, dns_keytab_path))
645     except OSError:
646         pass
647
648     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
649             "REALM": realm,
650             "DNSDOMAIN": dnsdomain,
651             "DNS_KEYTAB": dns_keytab_path,
652             "DNSPASS_B64": b64encode(dnspass),
653             })
654
655
656 def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
657     """Setup the secrets database.
658
659     :param path: Path to the secrets database.
660     :param setup_path: Get the path to a setup file.
661     :param session_info: Session info.
662     :param credentials: Credentials
663     :param lp: Loadparm context
664     :return: LDB handle for the created secrets database
665     """
666     if os.path.exists(path):
667         os.unlink(path)
668     secrets_ldb = Ldb(path, session_info=session_info, 
669                       lp=lp)
670     secrets_ldb.erase()
671     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
672     secrets_ldb = Ldb(path, session_info=session_info, 
673                       lp=lp)
674     secrets_ldb.transaction_start()
675     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
676
677     if backend_credentials is not None and backend_credentials.authentication_requested():
678         if backend_credentials.get_bind_dn() is not None:
679             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
680                     "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
681                     "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
682                     })
683         else:
684             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
685                     "LDAPADMINUSER": backend_credentials.get_username(),
686                     "LDAPADMINREALM": backend_credentials.get_realm(),
687                     "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
688                     })
689
690     return secrets_ldb
691
692 def setup_privileges(path, setup_path, session_info, lp):
693     """Setup the privileges database.
694
695     :param path: Path to the privileges database.
696     :param setup_path: Get the path to a setup file.
697     :param session_info: Session info.
698     :param credentials: Credentials
699     :param lp: Loadparm context
700     :return: LDB handle for the created secrets database
701     """
702     if os.path.exists(path):
703         os.unlink(path)
704     privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
705     privilege_ldb.erase()
706     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
707
708
709 def setup_registry(path, setup_path, session_info, lp):
710     """Setup the registry.
711     
712     :param path: Path to the registry database
713     :param setup_path: Function that returns the path to a setup.
714     :param session_info: Session information
715     :param credentials: Credentials
716     :param lp: Loadparm context
717     """
718     reg = samba.registry.Registry()
719     hive = samba.registry.open_ldb(path, session_info=session_info, 
720                          lp_ctx=lp)
721     reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
722     provision_reg = setup_path("provision.reg")
723     assert os.path.exists(provision_reg)
724     reg.diff_apply(provision_reg)
725
726
727 def setup_idmapdb(path, setup_path, session_info, lp):
728     """Setup the idmap database.
729
730     :param path: path to the idmap database
731     :param setup_path: Function that returns a path to a setup file
732     :param session_info: Session information
733     :param credentials: Credentials
734     :param lp: Loadparm context
735     """
736     if os.path.exists(path):
737         os.unlink(path)
738
739     idmap_ldb = IDmapDB(path, session_info=session_info,
740                         lp=lp)
741
742     idmap_ldb.erase()
743     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
744     return idmap_ldb
745
746
747 def setup_samdb_rootdse(samdb, setup_path, names):
748     """Setup the SamDB rootdse.
749
750     :param samdb: Sam Database handle
751     :param setup_path: Obtain setup path
752     """
753     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
754         "SCHEMADN": names.schemadn, 
755         "NETBIOSNAME": names.netbiosname,
756         "DNSDOMAIN": names.dnsdomain,
757         "REALM": names.realm,
758         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
759         "DOMAINDN": names.domaindn,
760         "ROOTDN": names.rootdn,
761         "CONFIGDN": names.configdn,
762         "SERVERDN": names.serverdn,
763         })
764         
765
766 def setup_self_join(samdb, names,
767                     machinepass, dnspass, 
768                     domainsid, invocationid, setup_path,
769                     policyguid, policyguid_dc, domainControllerFunctionality,
770                     ntdsguid):
771     """Join a host to its own domain."""
772     assert isinstance(invocationid, str)
773     if ntdsguid is not None:
774         ntdsguid_line = "objectGUID: %s\n"%ntdsguid
775     else:
776         ntdsguid_line = ""
777     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
778               "CONFIGDN": names.configdn, 
779               "SCHEMADN": names.schemadn,
780               "DOMAINDN": names.domaindn,
781               "SERVERDN": names.serverdn,
782               "INVOCATIONID": invocationid,
783               "NETBIOSNAME": names.netbiosname,
784               "DEFAULTSITE": names.sitename,
785               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
786               "MACHINEPASS_B64": b64encode(machinepass),
787               "REALM": names.realm,
788               "DOMAIN": names.domain,
789               "DOMAINSID": str(domainsid),
790               "DNSDOMAIN": names.dnsdomain,
791               "SAMBA_VERSION_STRING": version,
792               "NTDSGUID": ntdsguid_line,
793               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
794
795     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
796               "POLICYGUID": policyguid,
797               "POLICYGUID_DC": policyguid_dc,
798               "DNSDOMAIN": names.dnsdomain,
799               "DOMAINSID": str(domainsid),
800               "DOMAINDN": names.domaindn})
801     
802     # add the NTDSGUID based SPNs
803     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
804     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
805                                      expression="", scope=ldb.SCOPE_BASE)
806     assert isinstance(names.ntdsguid, str)
807
808     # Setup fSMORoleOwner entries to point at the newly created DC entry
809     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
810               "DOMAIN": names.domain,
811               "DNSDOMAIN": names.dnsdomain,
812               "DOMAINDN": names.domaindn,
813               "CONFIGDN": names.configdn,
814               "SCHEMADN": names.schemadn, 
815               "DEFAULTSITE": names.sitename,
816               "SERVERDN": names.serverdn,
817               "NETBIOSNAME": names.netbiosname,
818               "NTDSGUID": names.ntdsguid,
819               "DNSPASS_B64": b64encode(dnspass),
820               })
821
822 def getpolicypath(sysvolpath, dnsdomain, guid):
823     if guid[0] != "{":
824         guid = "{%s}" % guid
825     policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
826     return policy_path
827
828 def create_gpo_struct(policy_path):
829     os.makedirs(policy_path, 0755)
830     open(os.path.join(policy_path, "GPT.INI"), 'w').write(
831                       "[General]\r\nVersion=65543")
832     os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
833     os.makedirs(os.path.join(policy_path, "USER"), 0755)
834
835
836 def setup_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
837     policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
838     create_gpo_struct(policy_path)
839
840     policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
841     create_gpo_struct(policy_path)
842
843
844 def setup_samdb(path, setup_path, session_info, provision_backend, lp, names,
845         logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
846         adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
847         serverrole, am_rodc=False, dom_for_fun_level=None, schema=None):
848     """Setup a complete SAM Database.
849     
850     :note: This will wipe the main SAM database file!
851     """
852
853     # ATTENTION: Do NOT change these default values without discussion with the
854     # team and/or release manager. They have a big impact on the whole program!
855     domainControllerFunctionality = DS_DC_FUNCTION_2008
856
857     if dom_for_fun_level is None:
858         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
859
860     if dom_for_fun_level > domainControllerFunctionality:
861         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008). This won't work!")
862
863     domainFunctionality = dom_for_fun_level
864     forestFunctionality = dom_for_fun_level
865
866     # Also wipes the database
867     setup_samdb_partitions(path, setup_path, logger=logger, lp=lp,
868         provision_backend=provision_backend, session_info=session_info,
869         names=names, serverrole=serverrole, schema=schema)
870
871     if schema is None:
872         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
873                         am_rodc=am_rodc)
874
875     # Load the database, but don's load the global schema and don't connect quite yet
876     samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
877                   credentials=provision_backend.credentials, lp=lp, global_schema=False,
878                   am_rodc=am_rodc)
879
880     logger.info("Pre-loading the Samba 4 and AD schema")
881
882     # Load the schema from the one we computed earlier
883     samdb.set_schema_from_ldb(schema.ldb)
884
885     # And now we can connect to the DB - the schema won't be loaded from the DB
886     samdb.connect(path)
887
888     if fill == FILL_DRS:
889         return samdb
890         
891     samdb.transaction_start()
892     try:
893         # Set the domain functionality levels onto the database.
894         # Various module (the password_hash module in particular) need
895         # to know what level of AD we are emulating.
896
897         # These will be fixed into the database via the database
898         # modifictions below, but we need them set from the start.
899         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
900         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
901         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
902
903         samdb.set_domain_sid(str(domainsid))
904         samdb.set_invocation_id(invocationid)
905         samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
906
907         logger.info("Adding DomainDN: %s" % names.domaindn)
908
909 #impersonate domain admin
910         admin_session_info = admin_session(lp, str(domainsid))
911         samdb.set_session_info(admin_session_info)
912         if domainguid is not None:
913             domainguid_line = "objectGUID: %s\n-" % domainguid
914         else:
915             domainguid_line = ""
916
917         descr = b64encode(get_domain_descriptor(domainsid))
918         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
919                 "DOMAINDN": names.domaindn,
920                 "DOMAINGUID": domainguid_line,
921                 "DESCRIPTOR": descr
922                 })
923
924
925         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
926             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
927             "DOMAINSID": str(domainsid),
928             "SCHEMADN": names.schemadn, 
929             "NETBIOSNAME": names.netbiosname,
930             "DEFAULTSITE": names.sitename,
931             "CONFIGDN": names.configdn,
932             "SERVERDN": names.serverdn,
933             "POLICYGUID": policyguid,
934             "DOMAINDN": names.domaindn,
935             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
936             "SAMBA_VERSION_STRING": version
937             })
938
939         logger.info("Adding configuration container")
940         descr = b64encode(get_config_descriptor(domainsid))
941         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
942             "CONFIGDN": names.configdn, 
943             "DESCRIPTOR": descr,
944             })
945
946         # The LDIF here was created when the Schema object was constructed
947         logger.info("Setting up sam.ldb schema")
948         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
949         samdb.modify_ldif(schema.schema_dn_modify)
950         samdb.write_prefixes_from_schema()
951         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
952         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
953                        {"SCHEMADN": names.schemadn})
954
955         logger.info("Reopening sam.ldb with new schema")
956     except:
957         samdb.transaction_cancel()
958         raise
959     else:
960         samdb.transaction_commit()
961
962     samdb = SamDB(session_info=admin_session_info,
963                 credentials=provision_backend.credentials, lp=lp,
964                 global_schema=False, am_rodc=am_rodc)
965     samdb.connect(path)
966     samdb.transaction_start()
967     try:
968         samdb.invocation_id = invocationid
969
970         logger.info("Setting up sam.ldb configuration data")
971         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
972             "CONFIGDN": names.configdn,
973             "NETBIOSNAME": names.netbiosname,
974             "DEFAULTSITE": names.sitename,
975             "DNSDOMAIN": names.dnsdomain,
976             "DOMAIN": names.domain,
977             "SCHEMADN": names.schemadn,
978             "DOMAINDN": names.domaindn,
979             "SERVERDN": names.serverdn,
980             "FOREST_FUNCTIONALITY": str(forestFunctionality),
981             "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
982             })
983
984         logger.info("Setting up display specifiers")
985         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
986         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
987         check_all_substituted(display_specifiers_ldif)
988         samdb.add_ldif(display_specifiers_ldif)
989
990         logger.info("Adding users container")
991         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
992                 "DOMAINDN": names.domaindn})
993         logger.info("Modifying users container")
994         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
995                 "DOMAINDN": names.domaindn})
996         logger.info("Adding computers container")
997         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
998                 "DOMAINDN": names.domaindn})
999         logger.info("Modifying computers container")
1000         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1001                 "DOMAINDN": names.domaindn})
1002         logger.info("Setting up sam.ldb data")
1003         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1004             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1005             "DOMAINDN": names.domaindn,
1006             "NETBIOSNAME": names.netbiosname,
1007             "DEFAULTSITE": names.sitename,
1008             "CONFIGDN": names.configdn,
1009             "SERVERDN": names.serverdn,
1010             "POLICYGUID_DC": policyguid_dc
1011             })
1012
1013         setup_modify_ldif(samdb, setup_path("provision_basedn_references.ldif"), {
1014                 "DOMAINDN": names.domaindn})
1015
1016         setup_modify_ldif(samdb, setup_path("provision_configuration_references.ldif"), {
1017                 "CONFIGDN": names.configdn,
1018                 "SCHEMADN": names.schemadn})
1019         if fill == FILL_FULL:
1020             logger.info("Setting up sam.ldb users and groups")
1021             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1022                 "DOMAINDN": names.domaindn,
1023                 "DOMAINSID": str(domainsid),
1024                 "CONFIGDN": names.configdn,
1025                 "ADMINPASS_B64": b64encode(adminpass),
1026                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1027                 })
1028
1029             logger.info("Setting up self join")
1030             setup_self_join(samdb, names=names, invocationid=invocationid,
1031                             dnspass=dnspass,
1032                             machinepass=machinepass,
1033                             domainsid=domainsid, policyguid=policyguid,
1034                             policyguid_dc=policyguid_dc,
1035                             setup_path=setup_path,
1036                             domainControllerFunctionality=domainControllerFunctionality,
1037                             ntdsguid=ntdsguid)
1038
1039             ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1040             names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1041                 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1042             assert isinstance(names.ntdsguid, str)
1043     except:
1044         samdb.transaction_cancel()
1045         raise
1046     else:
1047         samdb.transaction_commit()
1048         return samdb
1049
1050
1051 FILL_FULL = "FULL"
1052 FILL_NT4SYNC = "NT4SYNC"
1053 FILL_DRS = "DRS"
1054 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1055 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)"
1056
1057 def set_dir_acl(path, acl, lp, domsid):
1058     setntacl(lp, path, acl, domsid)
1059     for root, dirs, files in os.walk(path, topdown=False):
1060         for name in files:
1061             setntacl(lp, os.path.join(root, name), acl, domsid)
1062         for name in dirs:
1063             setntacl(lp, os.path.join(root, name), acl, domsid)
1064
1065
1066 def set_gpo_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1067     # Set ACL for GPO
1068     policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1069     set_dir_acl(policy_path,dsacl2fsacl(POLICIES_ACL, str(domainsid)), 
1070         lp, str(domainsid))
1071     res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1072                         attrs=["cn","nTSecurityDescriptor"],
1073                         expression="", scope=ldb.SCOPE_ONELEVEL)
1074     for policy in res:
1075         acl = ndr_unpack(security.descriptor, 
1076                          str(policy["nTSecurityDescriptor"])).as_sddl()
1077         policy_path = getpolicypath(sysvol,dnsdomain,str(policy["cn"]))
1078         set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp, 
1079                     str(domainsid))
1080
1081 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1082     lp):
1083     try:
1084         os.chown(sysvol,-1,gid)
1085     except:
1086         canchown = False
1087     else:
1088         canchown = True
1089
1090     setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
1091     for root, dirs, files in os.walk(sysvol, topdown=False):
1092         for name in files:
1093             if canchown:
1094                 os.chown(os.path.join(root, name),-1,gid)
1095             setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1096         for name in dirs:
1097             if canchown:
1098                 os.chown(os.path.join(root, name),-1,gid)
1099             setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1100     set_gpo_acl(sysvol,dnsdomain,domainsid,domaindn,samdb,lp)
1101
1102
1103 def provision(setup_dir, logger, session_info, 
1104               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1105               realm=None, 
1106               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1107               serverdn=None,
1108               domain=None, hostname=None, hostip=None, hostip6=None, 
1109               domainsid=None, adminpass=None, ldapadminpass=None, 
1110               krbtgtpass=None, domainguid=None, 
1111               policyguid=None, policyguid_dc=None, invocationid=None,
1112               machinepass=None, ntdsguid=None,
1113               dnspass=None, root=None, nobody=None, users=None, 
1114               wheel=None, backup=None, aci=None, serverrole=None,
1115               dom_for_fun_level=None,
1116               ldap_backend_extra_port=None, backend_type=None,
1117               sitename=None,
1118               ol_mmr_urls=None, ol_olc=None, 
1119               setup_ds_path=None, slapd_path=None, nosync=False,
1120               ldap_dryrun_mode=False, useeadb=False, am_rodc=False):
1121     """Provision samba4
1122     
1123     :note: caution, this wipes all existing data!
1124     """
1125
1126     def setup_path(file):
1127       return os.path.join(setup_dir, file)
1128
1129     if domainsid is None:
1130       domainsid = security.random_sid()
1131     else:
1132       domainsid = security.dom_sid(domainsid)
1133
1134     # create/adapt the group policy GUIDs
1135     if policyguid is None:
1136         policyguid = str(uuid.uuid4())
1137     policyguid = policyguid.upper()
1138     if policyguid_dc is None:
1139         policyguid_dc = str(uuid.uuid4())
1140     policyguid_dc = policyguid_dc.upper()
1141
1142     if adminpass is None:
1143         adminpass = samba.generate_random_password(12, 32)
1144     if krbtgtpass is None:
1145         krbtgtpass = samba.generate_random_password(128, 255)
1146     if machinepass is None:
1147         machinepass  = samba.generate_random_password(128, 255)
1148     if dnspass is None:
1149         dnspass = samba.generate_random_password(128, 255)
1150     if ldapadminpass is None:
1151         #Make a new, random password between Samba and it's LDAP server
1152         ldapadminpass=samba.generate_random_password(128, 255)
1153
1154     if backend_type is None:
1155         backend_type = "ldb"
1156
1157     sid_generator = "internal"
1158     if backend_type == "fedora-ds":
1159         sid_generator = "backend"
1160
1161     root_uid = findnss_uid([root or "root"])
1162     nobody_uid = findnss_uid([nobody or "nobody"])
1163     users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1164     if wheel is None:
1165         wheel_gid = findnss_gid(["wheel", "adm"])
1166     else:
1167         wheel_gid = findnss_gid([wheel])
1168     try:
1169         bind_gid = findnss_gid(["bind", "named"])
1170     except KeyError:
1171         bind_gid = None
1172
1173     if targetdir is not None:
1174         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1175     elif smbconf is None:
1176         smbconf = samba.param.default_path()
1177     if not os.path.exists(os.path.dirname(smbconf)):
1178         os.makedirs(os.path.dirname(smbconf))
1179
1180     # only install a new smb.conf if there isn't one there already
1181     if os.path.exists(smbconf):
1182         # if Samba Team members can't figure out the weird errors
1183         # loading an empty smb.conf gives, then we need to be smarter.
1184         # Pretend it just didn't exist --abartlet
1185         data = open(smbconf, 'r').read()
1186         data = data.lstrip()
1187         if data is None or data == "":
1188             make_smbconf(smbconf, setup_path, hostname, domain, realm,
1189                          serverrole, targetdir, sid_generator, useeadb)
1190     else: 
1191         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1192                      targetdir, sid_generator, useeadb)
1193
1194     lp = samba.param.LoadParm()
1195     lp.load(smbconf)
1196
1197     names = guess_names(lp=lp, hostname=hostname, domain=domain,
1198                         dnsdomain=realm, serverrole=serverrole,
1199                         domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1200                         serverdn=serverdn, sitename=sitename)
1201
1202     paths = provision_paths_from_lp(lp, names.dnsdomain)
1203
1204     paths.bind_gid = bind_gid
1205
1206     if hostip is None:
1207         hostips = samba.interface_ips(lp, False)
1208         if len(hostips) == 0:
1209             logger.warning("No external IPv4 address has been found. Using loopback.")
1210             hostip = '127.0.0.1'
1211         else:
1212             hostip = hostips[0]
1213             if len(hostips) > 1:
1214                 logger.warning("More than one IPv4 address found. Using %s.", hostip)
1215
1216     if hostip6 is None:
1217         try:
1218             for ip in socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP):
1219                 if hostip6 is None:
1220                     hostip6 = ip[-1][0]
1221                 if hostip6 == '::1' and ip[-1][0] != '::1':
1222                     hostip6 = ip[-1][0]
1223         except socket.gaierror, (socket.EAI_NODATA, msg): 
1224             hostip6 = None
1225
1226     if serverrole is None:
1227         serverrole = lp.get("server role")
1228
1229     assert serverrole in ("domain controller", "member server", "standalone")
1230     if invocationid is None:
1231         invocationid = str(uuid.uuid4())
1232
1233     if not os.path.exists(paths.private_dir):
1234         os.mkdir(paths.private_dir)
1235     if not os.path.exists(os.path.join(paths.private_dir,"tls")):
1236         os.mkdir(os.path.join(paths.private_dir,"tls"))
1237
1238     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1239  
1240     schema = Schema(setup_path, domainsid, invocationid=invocationid, schemadn=names.schemadn,
1241                     serverdn=names.serverdn, am_rodc=am_rodc)
1242
1243     if backend_type == "ldb":
1244         provision_backend = LDBBackend(backend_type,
1245                                          paths=paths, setup_path=setup_path,
1246                                          lp=lp, credentials=credentials, 
1247                                          names=names,
1248                                          logger=logger)
1249     elif backend_type == "existing":
1250         provision_backend = ExistingBackend(backend_type,
1251                                          paths=paths, setup_path=setup_path,
1252                                          lp=lp, credentials=credentials, 
1253                                          names=names,
1254                                          logger=logger,
1255                                          ldapi_url=ldapi_url)
1256     elif backend_type == "fedora-ds":
1257         provision_backend = FDSBackend(backend_type,
1258                                          paths=paths, setup_path=setup_path,
1259                                          lp=lp, credentials=credentials, 
1260                                          names=names,
1261                                          logger=logger,
1262                                          domainsid=domainsid,
1263                                          schema=schema,
1264                                          hostname=hostname,
1265                                          ldapadminpass=ldapadminpass,
1266                                          slapd_path=slapd_path,
1267                                          ldap_backend_extra_port=ldap_backend_extra_port,
1268                                          ldap_dryrun_mode=ldap_dryrun_mode,
1269                                          root=root,
1270                                          setup_ds_path=setup_ds_path)
1271     elif backend_type == "openldap":
1272         provision_backend = OpenLDAPBackend(backend_type,
1273                                          paths=paths, setup_path=setup_path,
1274                                          lp=lp, credentials=credentials, 
1275                                          names=names,
1276                                          logger=logger,
1277                                          domainsid=domainsid,
1278                                          schema=schema,
1279                                          hostname=hostname,
1280                                          ldapadminpass=ldapadminpass,
1281                                          slapd_path=slapd_path,
1282                                          ldap_backend_extra_port=ldap_backend_extra_port,
1283                                          ldap_dryrun_mode=ldap_dryrun_mode,
1284                                          ol_mmr_urls=ol_mmr_urls, 
1285                                          nosync=nosync)
1286     else:
1287         raise ValueError("Unknown LDAP backend type selected")
1288
1289     provision_backend.init()
1290     provision_backend.start()
1291
1292     # only install a new shares config db if there is none
1293     if not os.path.exists(paths.shareconf):
1294         logger.info("Setting up share.ldb")
1295         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1296                         lp=lp)
1297         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1298
1299      
1300     logger.info("Setting up secrets.ldb")
1301     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1302         session_info=session_info,
1303         backend_credentials=provision_backend.secrets_credentials, lp=lp)
1304
1305     logger.info("Setting up the registry")
1306     setup_registry(paths.hklm, setup_path, session_info, 
1307                    lp=lp)
1308
1309     logger.info("Setting up the privileges database")
1310     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1311
1312     logger.info("Setting up idmap db")
1313     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1314                           lp=lp)
1315
1316     logger.info("Setting up SAM db")
1317     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1318                         provision_backend, lp, names,
1319                         logger=logger, 
1320                         domainsid=domainsid, 
1321                         schema=schema, domainguid=domainguid,
1322                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1323                         fill=samdb_fill, 
1324                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1325                         invocationid=invocationid, 
1326                         machinepass=machinepass, dnspass=dnspass, 
1327                         ntdsguid=ntdsguid, serverrole=serverrole,
1328                         dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc)
1329
1330     if serverrole == "domain controller":
1331         if paths.netlogon is None:
1332             logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1333             logger.info("Please either remove %s or see the template at %s" % 
1334                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1335             assert paths.netlogon is not None
1336
1337         if paths.sysvol is None:
1338             logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1339                     " are configuring a DC.")
1340             logger.info("Please either remove %s or see the template at %s" % 
1341                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1342             assert paths.sysvol is not None
1343
1344         if not os.path.isdir(paths.netlogon):
1345             os.makedirs(paths.netlogon, 0755)
1346
1347     if samdb_fill == FILL_FULL:
1348         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1349                             root_uid=root_uid, nobody_uid=nobody_uid,
1350                             users_gid=users_gid, wheel_gid=wheel_gid)
1351
1352         if serverrole == "domain controller":
1353             # Set up group policies (domain policy and domain controller policy)
1354             setup_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
1355             setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, 
1356                          domainsid, names.dnsdomain, names.domaindn, lp)
1357
1358         logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1359         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1360
1361         secretsdb_self_join(secrets_ldb, domain=names.domain,
1362                             realm=names.realm,
1363                             dnsdomain=names.dnsdomain,
1364                             netbiosname=names.netbiosname,
1365                             domainsid=domainsid, 
1366                             machinepass=machinepass,
1367                             secure_channel_type=SEC_CHAN_BDC)
1368
1369         if serverrole == "domain controller":
1370             secretsdb_setup_dns(secrets_ldb, setup_path,
1371                                 paths.private_dir,
1372                                 realm=names.realm, dnsdomain=names.dnsdomain,
1373                                 dns_keytab_path=paths.dns_keytab,
1374                                 dnspass=dnspass)
1375
1376             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1377             assert isinstance(domainguid, str)
1378
1379             # Only make a zone file on the first DC, it should be replicated
1380             # with DNS replication
1381             create_zone_file(lp, logger, paths, targetdir, setup_path,
1382                 dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
1383                 hostname=names.hostname, realm=names.realm, 
1384                 domainguid=domainguid, ntdsguid=names.ntdsguid)
1385
1386             create_named_conf(paths, setup_path, realm=names.realm,
1387                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1388
1389             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1390                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1391                               keytab_name=paths.dns_keytab)
1392             logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
1393             logger.info("and %s for further documentation required for secure DNS "
1394                     "updates", paths.namedtxt)
1395
1396             create_krb5_conf(paths.krb5conf, setup_path,
1397                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1398                              realm=names.realm)
1399             logger.info("A Kerberos configuration suitable for Samba 4 has been "
1400                     "generated at %s", paths.krb5conf)
1401
1402     if serverrole == "domain controller":
1403         create_dns_update_list(lp, logger, paths, setup_path)
1404
1405     provision_backend.post_setup()
1406     provision_backend.shutdown()
1407     
1408     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1409                                ldapi_url)
1410
1411     #Now commit the secrets.ldb to disk
1412     secrets_ldb.transaction_commit()
1413
1414     # the commit creates the dns.keytab, now chown it
1415     dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1416     if (os.path.isfile(dns_keytab_path) and paths.bind_gid is not None):
1417         try:
1418             os.chmod(dns_keytab_path, 0640)
1419             os.chown(dns_keytab_path, -1, paths.bind_gid)
1420         except OSError:
1421             logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
1422                 paths.bind_gid)
1423
1424
1425     logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
1426             paths.phpldapadminconfig)
1427
1428     logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1429     logger.info("Server Role:           %s" % serverrole)
1430     logger.info("Hostname:              %s" % names.hostname)
1431     logger.info("NetBIOS Domain:        %s" % names.domain)
1432     logger.info("DNS Domain:            %s" % names.dnsdomain)
1433     logger.info("DOMAIN SID:            %s" % str(domainsid))
1434     if samdb_fill == FILL_FULL:
1435         logger.info("Admin password:        %s" % adminpass)
1436     if provision_backend.type is not "ldb":
1437         if provision_backend.credentials.get_bind_dn() is not None:
1438             logger.info("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1439         else:
1440             logger.info("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1441
1442         logger.info("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1443
1444         if provision_backend.slapd_command_escaped is not None:
1445             # now display slapd_command_file.txt to show how slapd must be started next time
1446             logger.info("Use later the following commandline to start slapd, then Samba:")
1447             logger.info(provision_backend.slapd_command_escaped)
1448             logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh", 
1449                     provision_backend.ldapdir)
1450
1451
1452     result = ProvisionResult()
1453     result.domaindn = domaindn
1454     result.paths = paths
1455     result.lp = lp
1456     result.samdb = samdb
1457     return result
1458
1459
1460 def provision_become_dc(setup_dir=None,
1461                         smbconf=None, targetdir=None, realm=None, 
1462                         rootdn=None, domaindn=None, schemadn=None,
1463                         configdn=None, serverdn=None,
1464                         domain=None, hostname=None, domainsid=None, 
1465                         adminpass=None, krbtgtpass=None, domainguid=None, 
1466                         policyguid=None, policyguid_dc=None, invocationid=None,
1467                         machinepass=None, 
1468                         dnspass=None, root=None, nobody=None, users=None, 
1469                         wheel=None, backup=None, serverrole=None, 
1470                         ldap_backend=None, ldap_backend_type=None,
1471                         sitename=None, debuglevel=1):
1472
1473     logger = logging.getLogger("provision")
1474     samba.set_debug_level(debuglevel)
1475
1476     return provision(setup_dir, logger, system_session(), None,
1477               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1478               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1479               configdn=configdn, serverdn=serverdn, domain=domain,
1480               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1481               machinepass=machinepass, serverrole="domain controller",
1482               sitename=sitename)
1483
1484
1485 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1486     """Create a PHP LDAP admin configuration file.
1487
1488     :param path: Path to write the configuration to.
1489     :param setup_path: Function to generate setup paths.
1490     """
1491     setup_file(setup_path("phpldapadmin-config.php"), path, 
1492             {"S4_LDAPI_URI": ldapi_uri})
1493
1494
1495 def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain,
1496                      hostip, hostip6, hostname, realm, domainguid,
1497                      ntdsguid):
1498     """Write out a DNS zone file, from the info in the current database.
1499
1500     :param paths: paths object
1501     :param setup_path: Setup path function.
1502     :param dnsdomain: DNS Domain name
1503     :param domaindn: DN of the Domain
1504     :param hostip: Local IPv4 IP
1505     :param hostip6: Local IPv6 IP
1506     :param hostname: Local hostname
1507     :param realm: Realm name
1508     :param domainguid: GUID of the domain.
1509     :param ntdsguid: GUID of the hosts nTDSDSA record.
1510     """
1511     assert isinstance(domainguid, str)
1512
1513     if hostip6 is not None:
1514         hostip6_base_line = "            IN AAAA    " + hostip6
1515         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1516         gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
1517     else:
1518         hostip6_base_line = ""
1519         hostip6_host_line = ""
1520         gc_msdcs_ip6_line = ""
1521
1522     if hostip is not None:
1523         hostip_base_line = "            IN A    " + hostip
1524         hostip_host_line = hostname + "        IN A    " + hostip
1525         gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
1526     else:
1527         hostip_base_line = ""
1528         hostip_host_line = ""
1529         gc_msdcs_ip_line = ""
1530
1531     dns_dir = os.path.dirname(paths.dns)
1532
1533     try:
1534         shutil.rmtree(dns_dir, True)
1535     except OSError:
1536         pass
1537
1538     os.mkdir(dns_dir, 0775)
1539
1540     # we need to freeze the zone while we update the contents
1541     if targetdir is None:
1542         rndc = ' '.join(lp.get("rndc command"))
1543         os.system(rndc + " freeze " + lp.get("realm"))
1544
1545     setup_file(setup_path("provision.zone"), paths.dns, {
1546             "HOSTNAME": hostname,
1547             "DNSDOMAIN": dnsdomain,
1548             "REALM": realm,
1549             "HOSTIP_BASE_LINE": hostip_base_line,
1550             "HOSTIP_HOST_LINE": hostip_host_line,
1551             "DOMAINGUID": domainguid,
1552             "DATESTRING": time.strftime("%Y%m%d%H"),
1553             "DEFAULTSITE": DEFAULTSITE,
1554             "NTDSGUID": ntdsguid,
1555             "HOSTIP6_BASE_LINE": hostip6_base_line,
1556             "HOSTIP6_HOST_LINE": hostip6_host_line,
1557             "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
1558             "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
1559         })
1560
1561     # note that we use no variable substitution on this file
1562     # the substitution is done at runtime by samba_dnsupdate
1563     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1564
1565     # and the SPN update list
1566     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1567
1568     if paths.bind_gid is not None:
1569         try:
1570             os.chown(dns_dir, -1, paths.bind_gid)
1571             os.chown(paths.dns, -1, paths.bind_gid)
1572             # chmod needed to cope with umask
1573             os.chmod(dns_dir, 0775)
1574             os.chmod(paths.dns, 0664)
1575         except OSError:
1576             logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
1577
1578     if targetdir is None:
1579         os.system(rndc + " unfreeze " + lp.get("realm"))
1580
1581
1582 def create_dns_update_list(lp, logger, paths, setup_path):
1583     """Write out a dns_update_list file"""
1584     # note that we use no variable substitution on this file
1585     # the substitution is done at runtime by samba_dnsupdate
1586     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1587     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1588
1589
1590 def create_named_conf(paths, setup_path, realm, dnsdomain,
1591                       private_dir):
1592     """Write out a file containing zone statements suitable for inclusion in a
1593     named.conf file (including GSS-TSIG configuration).
1594     
1595     :param paths: all paths
1596     :param setup_path: Setup path function.
1597     :param realm: Realm name
1598     :param dnsdomain: DNS Domain name
1599     :param private_dir: Path to private directory
1600     :param keytab_name: File name of DNS keytab file
1601     """
1602
1603     setup_file(setup_path("named.conf"), paths.namedconf, {
1604             "DNSDOMAIN": dnsdomain,
1605             "REALM": realm,
1606             "ZONE_FILE": paths.dns,
1607             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1608             "NAMED_CONF": paths.namedconf,
1609             "NAMED_CONF_UPDATE": paths.namedconf_update
1610             })
1611
1612     setup_file(setup_path("named.conf.update"), paths.namedconf_update)
1613
1614 def create_named_txt(path, setup_path, realm, dnsdomain,
1615                       private_dir, keytab_name):
1616     """Write out a file containing zone statements suitable for inclusion in a
1617     named.conf file (including GSS-TSIG configuration).
1618     
1619     :param path: Path of the new named.conf file.
1620     :param setup_path: Setup path function.
1621     :param realm: Realm name
1622     :param dnsdomain: DNS Domain name
1623     :param private_dir: Path to private directory
1624     :param keytab_name: File name of DNS keytab file
1625     """
1626
1627     setup_file(setup_path("named.txt"), path, {
1628             "DNSDOMAIN": dnsdomain,
1629             "REALM": realm,
1630             "DNS_KEYTAB": keytab_name,
1631             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1632             "PRIVATE_DIR": private_dir
1633         })
1634
1635 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1636     """Write out a file containing zone statements suitable for inclusion in a
1637     named.conf file (including GSS-TSIG configuration).
1638     
1639     :param path: Path of the new named.conf file.
1640     :param setup_path: Setup path function.
1641     :param dnsdomain: DNS Domain name
1642     :param hostname: Local hostname
1643     :param realm: Realm name
1644     """
1645     setup_file(setup_path("krb5.conf"), path, {
1646             "DNSDOMAIN": dnsdomain,
1647             "HOSTNAME": hostname,
1648             "REALM": realm,
1649         })
1650
1651
1652 class ProvisioningError(Exception):
1653     """A generic provision error."""
1654
1655     def __init__(self, value):
1656         self.value = value
1657
1658     def __str__(self):
1659         return "ProvisioningError: " + self.value
1660
1661
1662 class InvalidNetbiosName(Exception):
1663     """A specified name was not a valid NetBIOS name."""
1664     def __init__(self, name):
1665         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)