Attempt to fix SamDB test infrastructure (not used in Samba anywhere, only in OpenCha...
[kamenim/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     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
860         logger.warning("Running SAMBA 4 on a domain and forest function level"
861                 " lower than Windows 2003 (Native) is not recommended.")
862
863     if dom_for_fun_level > domainControllerFunctionality:
864         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!")
865
866     domainFunctionality = dom_for_fun_level
867     forestFunctionality = dom_for_fun_level
868
869     # Also wipes the database
870     setup_samdb_partitions(path, setup_path, logger=logger, lp=lp,
871         provision_backend=provision_backend, session_info=session_info,
872         names=names, serverrole=serverrole, schema=schema)
873
874     if schema is None:
875         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
876                         am_rodc=am_rodc)
877
878     # Load the database, but don's load the global schema and don't connect quite yet
879     samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
880                   credentials=provision_backend.credentials, lp=lp, global_schema=False,
881                   am_rodc=am_rodc)
882
883     logger.info("Pre-loading the Samba 4 and AD schema")
884
885     # Load the schema from the one we computed earlier
886     samdb.set_schema_from_ldb(schema.ldb)
887
888     # And now we can connect to the DB - the schema won't be loaded from the DB
889     samdb.connect(path)
890
891     if fill == FILL_DRS:
892         return samdb
893         
894     samdb.transaction_start()
895     try:
896         # Set the domain functionality levels onto the database.
897         # Various module (the password_hash module in particular) need
898         # to know what level of AD we are emulating.
899
900         # These will be fixed into the database via the database
901         # modifictions below, but we need them set from the start.
902         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
903         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
904         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
905
906         samdb.set_domain_sid(str(domainsid))
907         samdb.set_invocation_id(invocationid)
908         samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
909
910         logger.info("Adding DomainDN: %s" % names.domaindn)
911
912 #impersonate domain admin
913         admin_session_info = admin_session(lp, str(domainsid))
914         samdb.set_session_info(admin_session_info)
915         if domainguid is not None:
916             domainguid_line = "objectGUID: %s\n-" % domainguid
917         else:
918             domainguid_line = ""
919
920         descr = b64encode(get_domain_descriptor(domainsid))
921         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
922                 "DOMAINDN": names.domaindn,
923                 "DOMAINGUID": domainguid_line,
924                 "DESCRIPTOR": descr
925                 })
926
927
928         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
929             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
930             "DOMAINSID": str(domainsid),
931             "SCHEMADN": names.schemadn, 
932             "NETBIOSNAME": names.netbiosname,
933             "DEFAULTSITE": names.sitename,
934             "CONFIGDN": names.configdn,
935             "SERVERDN": names.serverdn,
936             "POLICYGUID": policyguid,
937             "DOMAINDN": names.domaindn,
938             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
939             "SAMBA_VERSION_STRING": version
940             })
941
942         logger.info("Adding configuration container")
943         descr = b64encode(get_config_descriptor(domainsid))
944         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
945             "CONFIGDN": names.configdn, 
946             "DESCRIPTOR": descr,
947             })
948
949         # The LDIF here was created when the Schema object was constructed
950         logger.info("Setting up sam.ldb schema")
951         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
952         samdb.modify_ldif(schema.schema_dn_modify)
953         samdb.write_prefixes_from_schema()
954         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
955         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
956                        {"SCHEMADN": names.schemadn})
957
958         logger.info("Reopening sam.ldb with new schema")
959     except:
960         samdb.transaction_cancel()
961         raise
962     else:
963         samdb.transaction_commit()
964
965     samdb = SamDB(session_info=admin_session_info,
966                 credentials=provision_backend.credentials, lp=lp,
967                 global_schema=False, am_rodc=am_rodc)
968     samdb.connect(path)
969     samdb.transaction_start()
970     try:
971         samdb.invocation_id = invocationid
972
973         logger.info("Setting up sam.ldb configuration data")
974         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
975             "CONFIGDN": names.configdn,
976             "NETBIOSNAME": names.netbiosname,
977             "DEFAULTSITE": names.sitename,
978             "DNSDOMAIN": names.dnsdomain,
979             "DOMAIN": names.domain,
980             "SCHEMADN": names.schemadn,
981             "DOMAINDN": names.domaindn,
982             "SERVERDN": names.serverdn,
983             "FOREST_FUNCTIONALITY": str(forestFunctionality),
984             "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
985             })
986
987         logger.info("Setting up display specifiers")
988         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
989         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
990         check_all_substituted(display_specifiers_ldif)
991         samdb.add_ldif(display_specifiers_ldif)
992
993         logger.info("Adding users container")
994         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
995                 "DOMAINDN": names.domaindn})
996         logger.info("Modifying users container")
997         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
998                 "DOMAINDN": names.domaindn})
999         logger.info("Adding computers container")
1000         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1001                 "DOMAINDN": names.domaindn})
1002         logger.info("Modifying computers container")
1003         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1004                 "DOMAINDN": names.domaindn})
1005         logger.info("Setting up sam.ldb data")
1006         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1007             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1008             "DOMAINDN": names.domaindn,
1009             "NETBIOSNAME": names.netbiosname,
1010             "DEFAULTSITE": names.sitename,
1011             "CONFIGDN": names.configdn,
1012             "SERVERDN": names.serverdn,
1013             "POLICYGUID_DC": policyguid_dc
1014             })
1015
1016         setup_modify_ldif(samdb, setup_path("provision_basedn_references.ldif"), {
1017                 "DOMAINDN": names.domaindn})
1018
1019         setup_modify_ldif(samdb, setup_path("provision_configuration_references.ldif"), {
1020                 "CONFIGDN": names.configdn,
1021                 "SCHEMADN": names.schemadn})
1022         if fill == FILL_FULL:
1023             logger.info("Setting up sam.ldb users and groups")
1024             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1025                 "DOMAINDN": names.domaindn,
1026                 "DOMAINSID": str(domainsid),
1027                 "CONFIGDN": names.configdn,
1028                 "ADMINPASS_B64": b64encode(adminpass),
1029                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1030                 })
1031
1032             logger.info("Setting up self join")
1033             setup_self_join(samdb, names=names, invocationid=invocationid,
1034                             dnspass=dnspass,
1035                             machinepass=machinepass,
1036                             domainsid=domainsid, policyguid=policyguid,
1037                             policyguid_dc=policyguid_dc,
1038                             setup_path=setup_path,
1039                             domainControllerFunctionality=domainControllerFunctionality,
1040                             ntdsguid=ntdsguid)
1041
1042             ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1043             names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1044                 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1045             assert isinstance(names.ntdsguid, str)
1046     except:
1047         samdb.transaction_cancel()
1048         raise
1049     else:
1050         samdb.transaction_commit()
1051         return samdb
1052
1053
1054 FILL_FULL = "FULL"
1055 FILL_NT4SYNC = "NT4SYNC"
1056 FILL_DRS = "DRS"
1057 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1058 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)"
1059
1060 def set_dir_acl(path, acl, lp, domsid):
1061     setntacl(lp, path, acl, domsid)
1062     for root, dirs, files in os.walk(path, topdown=False):
1063         for name in files:
1064             setntacl(lp, os.path.join(root, name), acl, domsid)
1065         for name in dirs:
1066             setntacl(lp, os.path.join(root, name), acl, domsid)
1067
1068
1069 def set_gpo_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1070     # Set ACL for GPO
1071     policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1072     set_dir_acl(policy_path,dsacl2fsacl(POLICIES_ACL, str(domainsid)), 
1073         lp, str(domainsid))
1074     res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1075                         attrs=["cn","nTSecurityDescriptor"],
1076                         expression="", scope=ldb.SCOPE_ONELEVEL)
1077     for policy in res:
1078         acl = ndr_unpack(security.descriptor, 
1079                          str(policy["nTSecurityDescriptor"])).as_sddl()
1080         policy_path = getpolicypath(sysvol,dnsdomain,str(policy["cn"]))
1081         set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp, 
1082                     str(domainsid))
1083
1084 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1085     lp):
1086     try:
1087         os.chown(sysvol,-1,gid)
1088     except:
1089         canchown = False
1090     else:
1091         canchown = True
1092
1093     setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
1094     for root, dirs, files in os.walk(sysvol, topdown=False):
1095         for name in files:
1096             if canchown:
1097                 os.chown(os.path.join(root, name),-1,gid)
1098             setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1099         for name in dirs:
1100             if canchown:
1101                 os.chown(os.path.join(root, name),-1,gid)
1102             setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1103     set_gpo_acl(sysvol,dnsdomain,domainsid,domaindn,samdb,lp)
1104
1105
1106 def provision(setup_dir, logger, session_info, 
1107               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1108               realm=None, 
1109               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1110               serverdn=None,
1111               domain=None, hostname=None, hostip=None, hostip6=None, 
1112               domainsid=None, adminpass=None, ldapadminpass=None, 
1113               krbtgtpass=None, domainguid=None, 
1114               policyguid=None, policyguid_dc=None, invocationid=None,
1115               machinepass=None, ntdsguid=None,
1116               dnspass=None, root=None, nobody=None, users=None, 
1117               wheel=None, backup=None, aci=None, serverrole=None,
1118               dom_for_fun_level=None,
1119               ldap_backend_extra_port=None, backend_type=None,
1120               sitename=None,
1121               ol_mmr_urls=None, ol_olc=None, 
1122               setup_ds_path=None, slapd_path=None, nosync=False,
1123               ldap_dryrun_mode=False, useeadb=False, am_rodc=False):
1124     """Provision samba4
1125     
1126     :note: caution, this wipes all existing data!
1127     """
1128
1129     def setup_path(file):
1130       return os.path.join(setup_dir, file)
1131
1132     if domainsid is None:
1133       domainsid = security.random_sid()
1134     else:
1135       domainsid = security.dom_sid(domainsid)
1136
1137     # create/adapt the group policy GUIDs
1138     if policyguid is None:
1139         policyguid = str(uuid.uuid4())
1140     policyguid = policyguid.upper()
1141     if policyguid_dc is None:
1142         policyguid_dc = str(uuid.uuid4())
1143     policyguid_dc = policyguid_dc.upper()
1144
1145     if adminpass is None:
1146         adminpass = samba.generate_random_password(12, 32)
1147     if krbtgtpass is None:
1148         krbtgtpass = samba.generate_random_password(128, 255)
1149     if machinepass is None:
1150         machinepass  = samba.generate_random_password(128, 255)
1151     if dnspass is None:
1152         dnspass = samba.generate_random_password(128, 255)
1153     if ldapadminpass is None:
1154         #Make a new, random password between Samba and it's LDAP server
1155         ldapadminpass=samba.generate_random_password(128, 255)
1156
1157     if backend_type is None:
1158         backend_type = "ldb"
1159
1160     sid_generator = "internal"
1161     if backend_type == "fedora-ds":
1162         sid_generator = "backend"
1163
1164     root_uid = findnss_uid([root or "root"])
1165     nobody_uid = findnss_uid([nobody or "nobody"])
1166     users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1167     if wheel is None:
1168         wheel_gid = findnss_gid(["wheel", "adm"])
1169     else:
1170         wheel_gid = findnss_gid([wheel])
1171     try:
1172         bind_gid = findnss_gid(["bind", "named"])
1173     except KeyError:
1174         bind_gid = None
1175
1176     if targetdir is not None:
1177         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1178     elif smbconf is None:
1179         smbconf = samba.param.default_path()
1180     if not os.path.exists(os.path.dirname(smbconf)):
1181         os.makedirs(os.path.dirname(smbconf))
1182
1183     # only install a new smb.conf if there isn't one there already
1184     if os.path.exists(smbconf):
1185         # if Samba Team members can't figure out the weird errors
1186         # loading an empty smb.conf gives, then we need to be smarter.
1187         # Pretend it just didn't exist --abartlet
1188         data = open(smbconf, 'r').read()
1189         data = data.lstrip()
1190         if data is None or data == "":
1191             make_smbconf(smbconf, setup_path, hostname, domain, realm,
1192                          serverrole, targetdir, sid_generator, useeadb)
1193     else: 
1194         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1195                      targetdir, sid_generator, useeadb)
1196
1197     lp = samba.param.LoadParm()
1198     lp.load(smbconf)
1199
1200     names = guess_names(lp=lp, hostname=hostname, domain=domain,
1201                         dnsdomain=realm, serverrole=serverrole,
1202                         domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1203                         serverdn=serverdn, sitename=sitename)
1204
1205     paths = provision_paths_from_lp(lp, names.dnsdomain)
1206
1207     paths.bind_gid = bind_gid
1208
1209     if hostip is None:
1210         hostips = samba.interface_ips(lp, False)
1211         if len(hostips) == 0:
1212             logger.warning("No external IPv4 address has been found. Using loopback.")
1213             hostip = '127.0.0.1'
1214         else:
1215             hostip = hostips[0]
1216             if len(hostips) > 1:
1217                 logger.warning("More than one IPv4 address found. Using %s.", hostip)
1218
1219     if hostip6 is None:
1220         try:
1221             for ip in socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP):
1222                 if hostip6 is None:
1223                     hostip6 = ip[-1][0]
1224                 if hostip6 == '::1' and ip[-1][0] != '::1':
1225                     hostip6 = ip[-1][0]
1226         except socket.gaierror, (socket.EAI_NODATA, msg): 
1227             hostip6 = None
1228
1229     if serverrole is None:
1230         serverrole = lp.get("server role")
1231
1232     assert serverrole in ("domain controller", "member server", "standalone")
1233     if invocationid is None:
1234         invocationid = str(uuid.uuid4())
1235
1236     if not os.path.exists(paths.private_dir):
1237         os.mkdir(paths.private_dir)
1238     if not os.path.exists(os.path.join(paths.private_dir,"tls")):
1239         os.mkdir(os.path.join(paths.private_dir,"tls"))
1240
1241     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1242  
1243     schema = Schema(setup_path, domainsid, invocationid=invocationid, schemadn=names.schemadn,
1244                     serverdn=names.serverdn, am_rodc=am_rodc)
1245
1246     if backend_type == "ldb":
1247         provision_backend = LDBBackend(backend_type,
1248                                          paths=paths, setup_path=setup_path,
1249                                          lp=lp, credentials=credentials, 
1250                                          names=names,
1251                                          logger=logger)
1252     elif backend_type == "existing":
1253         provision_backend = ExistingBackend(backend_type,
1254                                          paths=paths, setup_path=setup_path,
1255                                          lp=lp, credentials=credentials, 
1256                                          names=names,
1257                                          logger=logger,
1258                                          ldapi_url=ldapi_url)
1259     elif backend_type == "fedora-ds":
1260         provision_backend = FDSBackend(backend_type,
1261                                          paths=paths, setup_path=setup_path,
1262                                          lp=lp, credentials=credentials, 
1263                                          names=names,
1264                                          logger=logger,
1265                                          domainsid=domainsid,
1266                                          schema=schema,
1267                                          hostname=hostname,
1268                                          ldapadminpass=ldapadminpass,
1269                                          slapd_path=slapd_path,
1270                                          ldap_backend_extra_port=ldap_backend_extra_port,
1271                                          ldap_dryrun_mode=ldap_dryrun_mode,
1272                                          root=root,
1273                                          setup_ds_path=setup_ds_path)
1274     elif backend_type == "openldap":
1275         provision_backend = OpenLDAPBackend(backend_type,
1276                                          paths=paths, setup_path=setup_path,
1277                                          lp=lp, credentials=credentials, 
1278                                          names=names,
1279                                          logger=logger,
1280                                          domainsid=domainsid,
1281                                          schema=schema,
1282                                          hostname=hostname,
1283                                          ldapadminpass=ldapadminpass,
1284                                          slapd_path=slapd_path,
1285                                          ldap_backend_extra_port=ldap_backend_extra_port,
1286                                          ldap_dryrun_mode=ldap_dryrun_mode,
1287                                          ol_mmr_urls=ol_mmr_urls, 
1288                                          nosync=nosync)
1289     else:
1290         raise ValueError("Unknown LDAP backend type selected")
1291
1292     provision_backend.init()
1293     provision_backend.start()
1294
1295     # only install a new shares config db if there is none
1296     if not os.path.exists(paths.shareconf):
1297         logger.info("Setting up share.ldb")
1298         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1299                         lp=lp)
1300         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1301
1302      
1303     logger.info("Setting up secrets.ldb")
1304     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1305         session_info=session_info,
1306         backend_credentials=provision_backend.secrets_credentials, lp=lp)
1307
1308     logger.info("Setting up the registry")
1309     setup_registry(paths.hklm, setup_path, session_info, 
1310                    lp=lp)
1311
1312     logger.info("Setting up the privileges database")
1313     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1314
1315     logger.info("Setting up idmap db")
1316     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1317                           lp=lp)
1318
1319     logger.info("Setting up SAM db")
1320     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1321                         provision_backend, lp, names,
1322                         logger=logger, 
1323                         domainsid=domainsid, 
1324                         schema=schema, domainguid=domainguid,
1325                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1326                         fill=samdb_fill, 
1327                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1328                         invocationid=invocationid, 
1329                         machinepass=machinepass, dnspass=dnspass, 
1330                         ntdsguid=ntdsguid, serverrole=serverrole,
1331                         dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc)
1332
1333     if serverrole == "domain controller":
1334         if paths.netlogon is None:
1335             logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1336             logger.info("Please either remove %s or see the template at %s" % 
1337                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1338             assert paths.netlogon is not None
1339
1340         if paths.sysvol is None:
1341             logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1342                     " are configuring a DC.")
1343             logger.info("Please either remove %s or see the template at %s" % 
1344                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1345             assert paths.sysvol is not None
1346
1347         if not os.path.isdir(paths.netlogon):
1348             os.makedirs(paths.netlogon, 0755)
1349
1350     if samdb_fill == FILL_FULL:
1351         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1352                             root_uid=root_uid, nobody_uid=nobody_uid,
1353                             users_gid=users_gid, wheel_gid=wheel_gid)
1354
1355         if serverrole == "domain controller":
1356             # Set up group policies (domain policy and domain controller policy)
1357             setup_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
1358             setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, 
1359                          domainsid, names.dnsdomain, names.domaindn, lp)
1360
1361         logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1362         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1363
1364         secretsdb_self_join(secrets_ldb, domain=names.domain,
1365                             realm=names.realm,
1366                             dnsdomain=names.dnsdomain,
1367                             netbiosname=names.netbiosname,
1368                             domainsid=domainsid, 
1369                             machinepass=machinepass,
1370                             secure_channel_type=SEC_CHAN_BDC)
1371
1372         if serverrole == "domain controller":
1373             secretsdb_setup_dns(secrets_ldb, setup_path,
1374                                 paths.private_dir,
1375                                 realm=names.realm, dnsdomain=names.dnsdomain,
1376                                 dns_keytab_path=paths.dns_keytab,
1377                                 dnspass=dnspass)
1378
1379             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1380             assert isinstance(domainguid, str)
1381
1382             # Only make a zone file on the first DC, it should be replicated
1383             # with DNS replication
1384             create_zone_file(lp, logger, paths, targetdir, setup_path,
1385                 dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
1386                 hostname=names.hostname, realm=names.realm, 
1387                 domainguid=domainguid, ntdsguid=names.ntdsguid)
1388
1389             create_named_conf(paths, setup_path, realm=names.realm,
1390                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1391
1392             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1393                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1394                               keytab_name=paths.dns_keytab)
1395             logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
1396             logger.info("and %s for further documentation required for secure DNS "
1397                     "updates", paths.namedtxt)
1398
1399             create_krb5_conf(paths.krb5conf, setup_path,
1400                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1401                              realm=names.realm)
1402             logger.info("A Kerberos configuration suitable for Samba 4 has been "
1403                     "generated at %s", paths.krb5conf)
1404
1405     if serverrole == "domain controller":
1406         create_dns_update_list(lp, logger, paths, setup_path)
1407
1408     provision_backend.post_setup()
1409     provision_backend.shutdown()
1410     
1411     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1412                                ldapi_url)
1413
1414     #Now commit the secrets.ldb to disk
1415     secrets_ldb.transaction_commit()
1416
1417     # the commit creates the dns.keytab, now chown it
1418     dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1419     if (os.path.isfile(dns_keytab_path) and paths.bind_gid is not None):
1420         try:
1421             os.chmod(dns_keytab_path, 0640)
1422             os.chown(dns_keytab_path, -1, paths.bind_gid)
1423         except OSError:
1424             logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
1425                 paths.bind_gid)
1426
1427
1428     logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
1429             paths.phpldapadminconfig)
1430
1431     logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1432     logger.info("Server Role:           %s" % serverrole)
1433     logger.info("Hostname:              %s" % names.hostname)
1434     logger.info("NetBIOS Domain:        %s" % names.domain)
1435     logger.info("DNS Domain:            %s" % names.dnsdomain)
1436     logger.info("DOMAIN SID:            %s" % str(domainsid))
1437     if samdb_fill == FILL_FULL:
1438         logger.info("Admin password:        %s" % adminpass)
1439     if provision_backend.type is not "ldb":
1440         if provision_backend.credentials.get_bind_dn() is not None:
1441             logger.info("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1442         else:
1443             logger.info("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1444
1445         logger.info("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1446
1447         if provision_backend.slapd_command_escaped is not None:
1448             # now display slapd_command_file.txt to show how slapd must be started next time
1449             logger.info("Use later the following commandline to start slapd, then Samba:")
1450             logger.info(provision_backend.slapd_command_escaped)
1451             logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh", 
1452                     provision_backend.ldapdir)
1453
1454
1455     result = ProvisionResult()
1456     result.domaindn = domaindn
1457     result.paths = paths
1458     result.lp = lp
1459     result.samdb = samdb
1460     return result
1461
1462
1463 def provision_become_dc(setup_dir=None,
1464                         smbconf=None, targetdir=None, realm=None, 
1465                         rootdn=None, domaindn=None, schemadn=None,
1466                         configdn=None, serverdn=None,
1467                         domain=None, hostname=None, domainsid=None, 
1468                         adminpass=None, krbtgtpass=None, domainguid=None, 
1469                         policyguid=None, policyguid_dc=None, invocationid=None,
1470                         machinepass=None, 
1471                         dnspass=None, root=None, nobody=None, users=None, 
1472                         wheel=None, backup=None, serverrole=None, 
1473                         ldap_backend=None, ldap_backend_type=None,
1474                         sitename=None, debuglevel=1):
1475
1476     logger = logging.getLogger("provision")
1477     samba.set_debug_level(debuglevel)
1478
1479     return provision(setup_dir, logger, system_session(), None,
1480               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1481               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1482               configdn=configdn, serverdn=serverdn, domain=domain,
1483               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1484               machinepass=machinepass, serverrole="domain controller",
1485               sitename=sitename)
1486
1487
1488 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1489     """Create a PHP LDAP admin configuration file.
1490
1491     :param path: Path to write the configuration to.
1492     :param setup_path: Function to generate setup paths.
1493     """
1494     setup_file(setup_path("phpldapadmin-config.php"), path, 
1495             {"S4_LDAPI_URI": ldapi_uri})
1496
1497
1498 def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain,
1499                      hostip, hostip6, hostname, realm, domainguid,
1500                      ntdsguid):
1501     """Write out a DNS zone file, from the info in the current database.
1502
1503     :param paths: paths object
1504     :param setup_path: Setup path function.
1505     :param dnsdomain: DNS Domain name
1506     :param domaindn: DN of the Domain
1507     :param hostip: Local IPv4 IP
1508     :param hostip6: Local IPv6 IP
1509     :param hostname: Local hostname
1510     :param realm: Realm name
1511     :param domainguid: GUID of the domain.
1512     :param ntdsguid: GUID of the hosts nTDSDSA record.
1513     """
1514     assert isinstance(domainguid, str)
1515
1516     if hostip6 is not None:
1517         hostip6_base_line = "            IN AAAA    " + hostip6
1518         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1519         gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
1520     else:
1521         hostip6_base_line = ""
1522         hostip6_host_line = ""
1523         gc_msdcs_ip6_line = ""
1524
1525     if hostip is not None:
1526         hostip_base_line = "            IN A    " + hostip
1527         hostip_host_line = hostname + "        IN A    " + hostip
1528         gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
1529     else:
1530         hostip_base_line = ""
1531         hostip_host_line = ""
1532         gc_msdcs_ip_line = ""
1533
1534     dns_dir = os.path.dirname(paths.dns)
1535
1536     try:
1537         shutil.rmtree(dns_dir, True)
1538     except OSError:
1539         pass
1540
1541     os.mkdir(dns_dir, 0775)
1542
1543     # we need to freeze the zone while we update the contents
1544     if targetdir is None:
1545         rndc = ' '.join(lp.get("rndc command"))
1546         os.system(rndc + " freeze " + lp.get("realm"))
1547
1548     setup_file(setup_path("provision.zone"), paths.dns, {
1549             "HOSTNAME": hostname,
1550             "DNSDOMAIN": dnsdomain,
1551             "REALM": realm,
1552             "HOSTIP_BASE_LINE": hostip_base_line,
1553             "HOSTIP_HOST_LINE": hostip_host_line,
1554             "DOMAINGUID": domainguid,
1555             "DATESTRING": time.strftime("%Y%m%d%H"),
1556             "DEFAULTSITE": DEFAULTSITE,
1557             "NTDSGUID": ntdsguid,
1558             "HOSTIP6_BASE_LINE": hostip6_base_line,
1559             "HOSTIP6_HOST_LINE": hostip6_host_line,
1560             "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
1561             "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
1562         })
1563
1564     # note that we use no variable substitution on this file
1565     # the substitution is done at runtime by samba_dnsupdate
1566     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1567
1568     # and the SPN update list
1569     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1570
1571     if paths.bind_gid is not None:
1572         try:
1573             os.chown(dns_dir, -1, paths.bind_gid)
1574             os.chown(paths.dns, -1, paths.bind_gid)
1575             # chmod needed to cope with umask
1576             os.chmod(dns_dir, 0775)
1577             os.chmod(paths.dns, 0664)
1578         except OSError:
1579             logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
1580
1581     if targetdir is None:
1582         os.system(rndc + " unfreeze " + lp.get("realm"))
1583
1584
1585 def create_dns_update_list(lp, logger, paths, setup_path):
1586     """Write out a dns_update_list file"""
1587     # note that we use no variable substitution on this file
1588     # the substitution is done at runtime by samba_dnsupdate
1589     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1590     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1591
1592
1593 def create_named_conf(paths, setup_path, realm, dnsdomain,
1594                       private_dir):
1595     """Write out a file containing zone statements suitable for inclusion in a
1596     named.conf file (including GSS-TSIG configuration).
1597     
1598     :param paths: all paths
1599     :param setup_path: Setup path function.
1600     :param realm: Realm name
1601     :param dnsdomain: DNS Domain name
1602     :param private_dir: Path to private directory
1603     :param keytab_name: File name of DNS keytab file
1604     """
1605
1606     setup_file(setup_path("named.conf"), paths.namedconf, {
1607             "DNSDOMAIN": dnsdomain,
1608             "REALM": realm,
1609             "ZONE_FILE": paths.dns,
1610             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1611             "NAMED_CONF": paths.namedconf,
1612             "NAMED_CONF_UPDATE": paths.namedconf_update
1613             })
1614
1615     setup_file(setup_path("named.conf.update"), paths.namedconf_update)
1616
1617 def create_named_txt(path, setup_path, realm, dnsdomain,
1618                       private_dir, keytab_name):
1619     """Write out a file containing zone statements suitable for inclusion in a
1620     named.conf file (including GSS-TSIG configuration).
1621     
1622     :param path: Path of the new named.conf file.
1623     :param setup_path: Setup path function.
1624     :param realm: Realm name
1625     :param dnsdomain: DNS Domain name
1626     :param private_dir: Path to private directory
1627     :param keytab_name: File name of DNS keytab file
1628     """
1629
1630     setup_file(setup_path("named.txt"), path, {
1631             "DNSDOMAIN": dnsdomain,
1632             "REALM": realm,
1633             "DNS_KEYTAB": keytab_name,
1634             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1635             "PRIVATE_DIR": private_dir
1636         })
1637
1638 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1639     """Write out a file containing zone statements suitable for inclusion in a
1640     named.conf file (including GSS-TSIG configuration).
1641     
1642     :param path: Path of the new named.conf file.
1643     :param setup_path: Setup path function.
1644     :param dnsdomain: DNS Domain name
1645     :param hostname: Local hostname
1646     :param realm: Realm name
1647     """
1648     setup_file(setup_path("krb5.conf"), path, {
1649             "DNSDOMAIN": dnsdomain,
1650             "HOSTNAME": hostname,
1651             "REALM": realm,
1652         })
1653
1654
1655 class ProvisioningError(Exception):
1656     """A generic provision error."""
1657
1658     def __init__(self, value):
1659         self.value = value
1660
1661     def __str__(self):
1662         return "ProvisioningError: " + self.value
1663
1664
1665 class InvalidNetbiosName(Exception):
1666     """A specified name was not a valid NetBIOS name."""
1667     def __init__(self, name):
1668         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)