s4-provision: setup spn_update_list in provision
[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 time
33 import uuid
34 import socket
35 import param
36 import registry
37 import urllib
38 import shutil
39
40 import ldb
41
42 from samba.auth import system_session, admin_session
43 import samba
44 from samba import version, Ldb, substitute_var, valid_netbios_name
45 from samba import check_all_substituted, read_and_sub_file, setup_file
46 from samba.dsdb import DS_DOMAIN_FUNCTION_2003, DS_DC_FUNCTION_2008
47 from samba.dcerpc import security
48 from samba.dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
49 from samba.idmap import IDmapDB
50 from samba.ntacls import setntacl, dsacl2fsacl
51 from samba.ndr import ndr_pack,ndr_unpack
52 from samba.schema import Schema
53 from samba.samdb import SamDB
54 from ms_display_specifiers import read_ms_ldif
55 from samba.provisionbackend import LDBBackend, ExistingBackend, FDSBackend, OpenLDAPBackend
56
57 __docformat__ = "restructuredText"
58
59 def find_setup_dir():
60     """Find the setup directory used by provision."""
61     dirname = os.path.dirname(__file__)
62     if "/site-packages/" in dirname:
63         prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
64         for suffix in ["share/setup", "share/samba/setup", "setup"]:
65             ret = os.path.join(prefix, suffix)
66             if os.path.isdir(ret):
67                 return ret
68     # In source tree
69     ret = os.path.join(dirname, "../../../setup")
70     if os.path.isdir(ret):
71         return ret
72     raise Exception("Unable to find setup directory.")
73
74 # descriptors of the naming contexts
75 # hard coded at this point, but will probably be changed when
76 # we enable different fsmo roles
77
78 def get_config_descriptor(domain_sid):
79     sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
80            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
81            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
82            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
83            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
84            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
85            "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
86            "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
87            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
88            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
89            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
90            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
91            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
92            "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
93            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
94     sec = security.descriptor.from_sddl(sddl, domain_sid)
95     return ndr_pack(sec)
96
97 def get_domain_descriptor(domain_sid):
98     sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
99         "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
100     "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
101     "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
102     "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
103     "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
104     "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
105     "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
106     "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
107     "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
108     "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
109     "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \
110     "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
111     "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
112     "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
113     "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
114     "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
115     "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
116     "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
117     "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
118     "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
119     "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
120     "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
121     "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
122     "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
123     "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
124     "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
125     "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
126     "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
127     "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
128     "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
129     "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
130     "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
131     "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
132     "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
133     "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
134     "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
135     "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
136     "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
137     "(A;;RPRC;;;RU)" \
138     "(A;CI;LC;;;RU)" \
139     "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
140     "(A;;RP;;;WD)" \
141     "(A;;RPLCLORC;;;ED)" \
142     "(A;;RPLCLORC;;;AU)" \
143     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
144     "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
145     "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
146     "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)"
147     sec = security.descriptor.from_sddl(sddl, domain_sid)
148     return ndr_pack(sec)
149
150 DEFAULTSITE = "Default-First-Site-Name"
151
152 class ProvisionPaths(object):
153
154     def __init__(self):
155         self.shareconf = None
156         self.hklm = None
157         self.hkcu = None
158         self.hkcr = None
159         self.hku = None
160         self.hkpd = None
161         self.hkpt = None
162         self.samdb = None
163         self.idmapdb = None
164         self.secrets = None
165         self.keytab = None
166         self.dns_keytab = None
167         self.dns = None
168         self.winsdb = None
169         self.private_dir = None
170
171
172 class ProvisionNames(object):
173
174     def __init__(self):
175         self.rootdn = None
176         self.domaindn = None
177         self.configdn = None
178         self.schemadn = None
179         self.ldapmanagerdn = None
180         self.dnsdomain = None
181         self.realm = None
182         self.netbiosname = None
183         self.domain = None
184         self.hostname = None
185         self.sitename = None
186         self.smbconf = None
187     
188
189 class ProvisionResult(object):
190
191     def __init__(self):
192         self.paths = None
193         self.domaindn = None
194         self.lp = None
195         self.samdb = None
196
197
198 def check_install(lp, session_info, credentials):
199     """Check whether the current install seems ok.
200     
201     :param lp: Loadparm context
202     :param session_info: Session information
203     :param credentials: Credentials
204     """
205     if lp.get("realm") == "":
206         raise Exception("Realm empty")
207     samdb = Ldb(lp.get("sam database"), session_info=session_info, 
208             credentials=credentials, lp=lp)
209     if len(samdb.search("(cn=Administrator)")) != 1:
210         raise ProvisioningError("No administrator account found")
211
212
213 def findnss(nssfn, names):
214     """Find a user or group from a list of possibilities.
215     
216     :param nssfn: NSS Function to try (should raise KeyError if not found)
217     :param names: Names to check.
218     :return: Value return by first names list.
219     """
220     for name in names:
221         try:
222             return nssfn(name)
223         except KeyError:
224             pass
225     raise KeyError("Unable to find user/group in %r" % names)
226
227
228 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
229 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
230
231
232 def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
233     """Setup a ldb in the private dir.
234     
235     :param ldb: LDB file to import data into
236     :param ldif_path: Path of the LDIF file to load
237     :param subst_vars: Optional variables to subsitute in LDIF.
238     :param nocontrols: Optional list of controls, can be None for no controls
239     """
240     assert isinstance(ldif_path, str)
241     data = read_and_sub_file(ldif_path, subst_vars)
242     ldb.add_ldif(data, controls)
243
244
245 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
246     """Modify a ldb in the private dir.
247     
248     :param ldb: LDB object.
249     :param ldif_path: LDIF file path.
250     :param subst_vars: Optional dictionary with substitution variables.
251     """
252     data = read_and_sub_file(ldif_path, subst_vars)
253     ldb.modify_ldif(data)
254
255
256 def setup_ldb(ldb, ldif_path, subst_vars):
257     """Import a LDIF a file into a LDB handle, optionally substituting variables.
258
259     :note: Either all LDIF data will be added or none (using transactions).
260
261     :param ldb: LDB file to import into.
262     :param ldif_path: Path to the LDIF file.
263     :param subst_vars: Dictionary with substitution variables.
264     """
265     assert ldb is not None
266     ldb.transaction_start()
267     try:
268         setup_add_ldif(ldb, ldif_path, subst_vars)
269     except:
270         ldb.transaction_cancel()
271         raise
272     else:
273         ldb.transaction_commit()
274
275
276 def provision_paths_from_lp(lp, dnsdomain):
277     """Set the default paths for provisioning.
278
279     :param lp: Loadparm context.
280     :param dnsdomain: DNS Domain name
281     """
282     paths = ProvisionPaths()
283     paths.private_dir = lp.get("private dir")
284
285     # This is stored without path prefix for the "privateKeytab" attribute in
286     # "secrets_dns.ldif".
287     paths.dns_keytab = "dns.keytab"
288
289     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
290     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
291     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
292     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
293     paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
294     paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
295     paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
296     paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
297     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
298     paths.namedconf_update = os.path.join(paths.private_dir, "named.conf.update")
299     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
300     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
301     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
302     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
303     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
304                                             "phpldapadmin-config.php")
305     paths.hklm = "hklm.ldb"
306     paths.hkcr = "hkcr.ldb"
307     paths.hkcu = "hkcu.ldb"
308     paths.hku = "hku.ldb"
309     paths.hkpd = "hkpd.ldb"
310     paths.hkpt = "hkpt.ldb"
311     paths.sysvol = lp.get("path", "sysvol")
312     paths.netlogon = lp.get("path", "netlogon")
313     paths.smbconf = lp.configfile
314     return paths
315
316
317 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
318                 serverrole=None, rootdn=None, domaindn=None, configdn=None,
319                 schemadn=None, serverdn=None, sitename=None):
320     """Guess configuration settings to use."""
321
322     if hostname is None:
323         hostname = socket.gethostname().split(".")[0]
324
325     netbiosname = lp.get("netbios name")
326     if netbiosname is None:
327         netbiosname = hostname
328     assert netbiosname is not None
329     netbiosname = netbiosname.upper()
330     if not valid_netbios_name(netbiosname):
331         raise InvalidNetbiosName(netbiosname)
332
333     if dnsdomain is None:
334         dnsdomain = lp.get("realm")
335         if dnsdomain is None or dnsdomain == "":
336             raise ProvisioningError("guess_names: 'realm' not specified in supplied %s!", lp.configfile)
337
338     dnsdomain = dnsdomain.lower()
339
340     if serverrole is None:
341         serverrole = lp.get("server role")
342         if serverrole is None:
343             raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
344
345     serverrole = serverrole.lower()
346
347     realm = dnsdomain.upper()
348
349     if lp.get("realm") == "":
350         raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s.  Please remove the smb.conf file and let provision generate it" % lp.configfile)
351
352     if lp.get("realm").upper() != realm:
353         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))
354
355     if lp.get("server role").lower() != serverrole:
356         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))
357
358     if serverrole == "domain controller":
359         if domain is None:
360             # This will, for better or worse, default to 'WORKGROUP'
361             domain = lp.get("workgroup")
362         domain = domain.upper()
363
364         if lp.get("workgroup").upper() != domain:
365             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))
366
367         if domaindn is None:
368             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
369     else:
370         domain = netbiosname
371         if domaindn is None:
372             domaindn = "DC=" + netbiosname
373         
374     if not valid_netbios_name(domain):
375         raise InvalidNetbiosName(domain)
376         
377     if hostname.upper() == realm:
378         raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
379     if netbiosname == realm:
380         raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!" % (realm, netbiosname))
381     if domain == realm:
382         raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
383
384     if rootdn is None:
385        rootdn = domaindn
386        
387     if configdn is None:
388         configdn = "CN=Configuration," + rootdn
389     if schemadn is None:
390         schemadn = "CN=Schema," + configdn
391
392     if sitename is None:
393         sitename=DEFAULTSITE
394
395     names = ProvisionNames()
396     names.rootdn = rootdn
397     names.domaindn = domaindn
398     names.configdn = configdn
399     names.schemadn = schemadn
400     names.ldapmanagerdn = "CN=Manager," + rootdn
401     names.dnsdomain = dnsdomain
402     names.domain = domain
403     names.realm = realm
404     names.netbiosname = netbiosname
405     names.hostname = hostname
406     names.sitename = sitename
407     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
408  
409     return names
410     
411
412 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
413                  targetdir, sid_generator,eadb):
414     """Create a new smb.conf file based on a couple of basic settings.
415     """
416     assert smbconf is not None
417     if hostname is None:
418         hostname = socket.gethostname().split(".")[0]
419     netbiosname = hostname.upper()
420
421     if serverrole is None:
422         serverrole = "standalone"
423
424     assert serverrole in ("domain controller", "member server", "standalone")
425     if serverrole == "domain controller":
426         smbconfsuffix = "dc"
427     elif serverrole == "member server":
428         smbconfsuffix = "member"
429     elif serverrole == "standalone":
430         smbconfsuffix = "standalone"
431
432     if sid_generator is None:
433         sid_generator = "internal"
434
435     assert domain is not None
436     domain = domain.upper()
437
438     assert realm is not None
439     realm = realm.upper()
440
441     default_lp = param.LoadParm()
442     #Load non-existant file
443     if os.path.exists(smbconf):
444         default_lp.load(smbconf)
445     if eadb:
446         if targetdir is not None:
447             privdir = os.path.join(targetdir, "private")
448         else:
449             privdir = default_lp.get("private dir")
450         posixeadb_line = "posix:eadb = " + os.path.abspath(os.path.join(privdir,"eadb.tdb"))
451     else:
452         posixeadb_line = ""
453
454     if targetdir is not None:
455         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
456         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
457
458         default_lp.set("lock dir", os.path.abspath(targetdir))
459     else:
460         privatedir_line = ""
461         lockdir_line = ""
462
463     if sid_generator == "internal":
464         sid_generator_line = ""
465     else:
466         sid_generator_line = "sid generator = " + sid_generator
467
468     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
469     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
470
471     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
472                smbconf, {
473             "NETBIOS_NAME": netbiosname,
474             "DOMAIN": domain,
475             "REALM": realm,
476             "SERVERROLE": serverrole,
477             "NETLOGONPATH": netlogon,
478             "SYSVOLPATH": sysvol,
479             "SIDGENERATOR_LINE": sid_generator_line,
480             "PRIVATEDIR_LINE": privatedir_line,
481             "LOCKDIR_LINE": lockdir_line,
482             "POSIXEADB_LINE": posixeadb_line
483             })
484
485
486 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
487                         users_gid, wheel_gid):
488     """setup reasonable name mappings for sam names to unix names.
489
490     :param samdb: SamDB object.
491     :param idmap: IDmap db object.
492     :param sid: The domain sid.
493     :param domaindn: The domain DN.
494     :param root_uid: uid of the UNIX root user.
495     :param nobody_uid: uid of the UNIX nobody user.
496     :param users_gid: gid of the UNIX users group.
497     :param wheel_gid: gid of the UNIX wheel group."""
498     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
499     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
500     
501     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
502     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
503
504
505 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
506                            provision_backend, names, schema,
507                            serverrole, 
508                            erase=False):
509     """Setup the partitions for the SAM database. 
510     
511     Alternatively, provision() may call this, and then populate the database.
512     
513     :note: This will wipe the Sam Database!
514     
515     :note: This function always removes the local SAM LDB file. The erase 
516         parameter controls whether to erase the existing data, which 
517         may not be stored locally but in LDAP.
518
519     """
520     assert session_info is not None
521
522     # We use options=["modules:"] to stop the modules loading - we
523     # just want to wipe and re-initialise the database, not start it up
524
525     try:
526         os.unlink(samdb_path)
527     except OSError:
528         pass
529
530     samdb = Ldb(url=samdb_path, session_info=session_info, 
531                 lp=lp, options=["modules:"])
532
533     ldap_backend_line = "# No LDAP backend"
534     if provision_backend.type is not "ldb":
535         ldap_backend_line = "ldapBackend: %s" % provision_backend.ldapi_uri
536
537     samdb.transaction_start()
538     try:
539         message("Setting up sam.ldb partitions and settings")
540         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
541                 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), 
542                 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
543                 "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
544                 "LDAP_BACKEND_LINE": ldap_backend_line,
545         })
546
547         
548         setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
549                 "BACKEND_TYPE": provision_backend.type,
550                 "SERVER_ROLE": serverrole
551                 })
552
553         message("Setting up sam.ldb rootDSE")
554         setup_samdb_rootdse(samdb, setup_path, names)
555     except:
556         samdb.transaction_cancel()
557         raise
558     else:
559         samdb.transaction_commit()
560
561         
562 def secretsdb_self_join(secretsdb, domain, 
563                         netbiosname, machinepass, domainsid=None,
564                         realm=None, dnsdomain=None,
565                         keytab_path=None, 
566                         key_version_number=1,
567                         secure_channel_type=SEC_CHAN_WKSTA):
568     """Add domain join-specific bits to a secrets database.
569     
570     :param secretsdb: Ldb Handle to the secrets database
571     :param machinepass: Machine password
572     """
573     attrs=["whenChanged",
574            "secret",
575            "priorSecret",
576            "priorChanged",
577            "krb5Keytab",
578            "privateKeytab"]
579     
580
581     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain))
582     msg["secureChannelType"] = str(secure_channel_type)
583     msg["flatname"] = [domain]
584     msg["objectClass"] = ["top", "primaryDomain"]
585     if realm is not None:
586       if dnsdomain is None:
587         dnsdomain = realm.lower()
588       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
589       msg["realm"] = realm
590       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
591       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
592       msg["privateKeytab"] = ["secrets.keytab"]
593
594
595     msg["secret"] = [machinepass]
596     msg["samAccountName"] = ["%s$" % netbiosname]
597     msg["secureChannelType"] = [str(secure_channel_type)]
598     if domainsid is not None:
599         msg["objectSid"] = [ndr_pack(domainsid)]
600     
601     res = secretsdb.search(base="cn=Primary Domains", 
602                            attrs=attrs, 
603                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
604                            scope=ldb.SCOPE_ONELEVEL)
605     
606     for del_msg in res:
607       if del_msg.dn is not msg.dn:
608         secretsdb.delete(del_msg.dn)
609
610     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=ldb.SCOPE_BASE)
611
612     if len(res) == 1:
613       msg["priorSecret"] = res[0]["secret"]
614       msg["priorWhenChanged"] = res[0]["whenChanged"]
615
616       if res["privateKeytab"] is not None:
617         msg["privateKeytab"] = res[0]["privateKeytab"]
618
619       if res["krb5Keytab"] is not None:
620         msg["krb5Keytab"] = res[0]["krb5Keytab"]
621
622       for el in msg:
623         el.set_flags(ldb.FLAG_MOD_REPLACE)
624         secretsdb.modify(msg)
625     else:
626       secretsdb.add(msg)
627
628
629 def secretsdb_setup_dns(secretsdb, setup_path, private_dir,
630                         realm, dnsdomain,
631                         dns_keytab_path, dnspass):
632     """Add DNS specific bits to a secrets database.
633     
634     :param secretsdb: Ldb Handle to the secrets database
635     :param setup_path: Setup path function
636     :param machinepass: Machine password
637     """
638     try:
639         os.unlink(os.path.join(private_dir, dns_keytab_path))
640     except OSError:
641         pass
642
643     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
644             "REALM": realm,
645             "DNSDOMAIN": dnsdomain,
646             "DNS_KEYTAB": dns_keytab_path,
647             "DNSPASS_B64": b64encode(dnspass),
648             })
649
650
651 def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
652     """Setup the secrets database.
653
654     :param path: Path to the secrets database.
655     :param setup_path: Get the path to a setup file.
656     :param session_info: Session info.
657     :param credentials: Credentials
658     :param lp: Loadparm context
659     :return: LDB handle for the created secrets database
660     """
661     if os.path.exists(path):
662         os.unlink(path)
663     secrets_ldb = Ldb(path, session_info=session_info, 
664                       lp=lp)
665     secrets_ldb.erase()
666     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
667     secrets_ldb = Ldb(path, session_info=session_info, 
668                       lp=lp)
669     secrets_ldb.transaction_start()
670     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
671
672     if backend_credentials is not None and backend_credentials.authentication_requested():
673         if backend_credentials.get_bind_dn() is not None:
674             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
675                     "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
676                     "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
677                     })
678         else:
679             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
680                     "LDAPADMINUSER": backend_credentials.get_username(),
681                     "LDAPADMINREALM": backend_credentials.get_realm(),
682                     "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
683                     })
684
685     return secrets_ldb
686
687 def setup_privileges(path, setup_path, session_info, lp):
688     """Setup the privileges database.
689
690     :param path: Path to the privileges database.
691     :param setup_path: Get the path to a setup file.
692     :param session_info: Session info.
693     :param credentials: Credentials
694     :param lp: Loadparm context
695     :return: LDB handle for the created secrets database
696     """
697     if os.path.exists(path):
698         os.unlink(path)
699     privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
700     privilege_ldb.erase()
701     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
702
703
704 def setup_registry(path, setup_path, session_info, lp):
705     """Setup the registry.
706     
707     :param path: Path to the registry database
708     :param setup_path: Function that returns the path to a setup.
709     :param session_info: Session information
710     :param credentials: Credentials
711     :param lp: Loadparm context
712     """
713     reg = registry.Registry()
714     hive = registry.open_ldb(path, session_info=session_info, 
715                          lp_ctx=lp)
716     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
717     provision_reg = setup_path("provision.reg")
718     assert os.path.exists(provision_reg)
719     reg.diff_apply(provision_reg)
720
721
722 def setup_idmapdb(path, setup_path, session_info, lp):
723     """Setup the idmap database.
724
725     :param path: path to the idmap database
726     :param setup_path: Function that returns a path to a setup file
727     :param session_info: Session information
728     :param credentials: Credentials
729     :param lp: Loadparm context
730     """
731     if os.path.exists(path):
732         os.unlink(path)
733
734     idmap_ldb = IDmapDB(path, session_info=session_info,
735                         lp=lp)
736
737     idmap_ldb.erase()
738     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
739     return idmap_ldb
740
741
742 def setup_samdb_rootdse(samdb, setup_path, names):
743     """Setup the SamDB rootdse.
744
745     :param samdb: Sam Database handle
746     :param setup_path: Obtain setup path
747     """
748     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
749         "SCHEMADN": names.schemadn, 
750         "NETBIOSNAME": names.netbiosname,
751         "DNSDOMAIN": names.dnsdomain,
752         "REALM": names.realm,
753         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
754         "DOMAINDN": names.domaindn,
755         "ROOTDN": names.rootdn,
756         "CONFIGDN": names.configdn,
757         "SERVERDN": names.serverdn,
758         })
759         
760
761 def setup_self_join(samdb, names,
762                     machinepass, dnspass, 
763                     domainsid, invocationid, setup_path,
764                     policyguid, policyguid_dc, domainControllerFunctionality,
765                     ntdsguid):
766     """Join a host to its own domain."""
767     assert isinstance(invocationid, str)
768     if ntdsguid is not None:
769         ntdsguid_line = "objectGUID: %s\n"%ntdsguid
770     else:
771         ntdsguid_line = ""
772     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
773               "CONFIGDN": names.configdn, 
774               "SCHEMADN": names.schemadn,
775               "DOMAINDN": names.domaindn,
776               "SERVERDN": names.serverdn,
777               "INVOCATIONID": invocationid,
778               "NETBIOSNAME": names.netbiosname,
779               "DEFAULTSITE": names.sitename,
780               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
781               "MACHINEPASS_B64": b64encode(machinepass),
782               "REALM": names.realm,
783               "DOMAIN": names.domain,
784               "DOMAINSID": str(domainsid),
785               "DNSDOMAIN": names.dnsdomain,
786               "SAMBA_VERSION_STRING": version,
787               "NTDSGUID": ntdsguid_line,
788               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
789
790     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
791               "POLICYGUID": policyguid,
792               "POLICYGUID_DC": policyguid_dc,
793               "DNSDOMAIN": names.dnsdomain,
794               "DOMAINSID": str(domainsid),
795               "DOMAINDN": names.domaindn})
796     
797     # add the NTDSGUID based SPNs
798     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
799     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
800                                      expression="", scope=ldb.SCOPE_BASE)
801     assert isinstance(names.ntdsguid, str)
802
803     # Setup fSMORoleOwner entries to point at the newly created DC entry
804     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
805               "DOMAIN": names.domain,
806               "DNSDOMAIN": names.dnsdomain,
807               "DOMAINDN": names.domaindn,
808               "CONFIGDN": names.configdn,
809               "SCHEMADN": names.schemadn, 
810               "DEFAULTSITE": names.sitename,
811               "SERVERDN": names.serverdn,
812               "NETBIOSNAME": names.netbiosname,
813               "NTDSGUID": names.ntdsguid,
814               "DNSPASS_B64": b64encode(dnspass),
815               })
816
817 def getpolicypath(sysvolpath, dnsdomain, guid):
818     if guid[0] != "{":
819         guid = "{%s}" % guid
820     policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
821     return policy_path
822
823 def create_gpo_struct(policy_path):
824     os.makedirs(policy_path, 0755)
825     open(os.path.join(policy_path, "GPT.INI"), 'w').write(
826                       "[General]\r\nVersion=65543")
827     os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
828     os.makedirs(os.path.join(policy_path, "USER"), 0755)
829
830
831 def setup_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
832     policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
833     create_gpo_struct(policy_path)
834
835     policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
836     create_gpo_struct(policy_path)
837
838
839 def setup_samdb(path, setup_path, session_info, provision_backend, lp, 
840                 names, message, 
841                 domainsid, domainguid, policyguid, policyguid_dc,
842                 fill, adminpass, krbtgtpass, 
843                 machinepass, invocationid, dnspass, ntdsguid,
844                 serverrole, dom_for_fun_level=None,
845                 schema=None):
846     """Setup a complete SAM Database.
847     
848     :note: This will wipe the main SAM database file!
849     """
850
851     # ATTENTION: Do NOT change these default values without discussion with the
852     # team and/or release manager. They have a big impact on the whole program!
853     domainControllerFunctionality = DS_DC_FUNCTION_2008
854
855     if dom_for_fun_level is None:
856         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
857     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
858         message("You want to run SAMBA 4 on a domain and forest function level"
859                 " lower than Windows 2003 (Native). This is not recommended")
860
861     if dom_for_fun_level > domainControllerFunctionality:
862         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!")
863
864     domainFunctionality = dom_for_fun_level
865     forestFunctionality = dom_for_fun_level
866
867     # Also wipes the database
868     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
869         provision_backend=provision_backend, session_info=session_info,
870         names=names, serverrole=serverrole, schema=schema)
871
872     if schema is None:
873         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn)
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
879     message("Pre-loading the Samba 4 and AD schema")
880
881     # Load the schema from the one we computed earlier
882     samdb.set_schema_from_ldb(schema.ldb)
883
884     # And now we can connect to the DB - the schema won't be loaded from the DB
885     samdb.connect(path)
886
887     if fill == FILL_DRS:
888         return samdb
889         
890     samdb.transaction_start()
891     try:
892         # Set the domain functionality levels onto the database.
893         # Various module (the password_hash module in particular) need
894         # to know what level of AD we are emulating.
895
896         # These will be fixed into the database via the database
897         # modifictions below, but we need them set from the start.
898         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
899         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
900         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
901
902         samdb.set_domain_sid(str(domainsid))
903         samdb.set_invocation_id(invocationid)
904         samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
905
906         message("Adding DomainDN: %s" % names.domaindn)
907
908 #impersonate domain admin
909         admin_session_info = admin_session(lp, str(domainsid))
910         samdb.set_session_info(admin_session_info)
911         if domainguid is not None:
912             domainguid_line = "objectGUID: %s\n-" % domainguid
913         else:
914             domainguid_line = ""
915
916         descr = b64encode(get_domain_descriptor(domainsid))
917         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
918                 "DOMAINDN": names.domaindn,
919                 "DOMAINGUID": domainguid_line,
920                 "DESCRIPTOR": descr
921                 })
922
923
924         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
925             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
926             "DOMAINSID": str(domainsid),
927             "SCHEMADN": names.schemadn, 
928             "NETBIOSNAME": names.netbiosname,
929             "DEFAULTSITE": names.sitename,
930             "CONFIGDN": names.configdn,
931             "SERVERDN": names.serverdn,
932             "POLICYGUID": policyguid,
933             "DOMAINDN": names.domaindn,
934             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
935             "SAMBA_VERSION_STRING": version
936             })
937
938         message("Adding configuration container")
939         descr = b64encode(get_config_descriptor(domainsid))
940         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
941             "CONFIGDN": names.configdn, 
942             "DESCRIPTOR": descr,
943             })
944
945         # The LDIF here was created when the Schema object was constructed
946         message("Setting up sam.ldb schema")
947         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
948         samdb.modify_ldif(schema.schema_dn_modify)
949         # set schemaInfo to defalt value for a new Forest
950         samdb.set_schema_info()
951         samdb.write_prefixes_from_schema()
952         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
953         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
954                        {"SCHEMADN": names.schemadn})
955
956         message("Reopening sam.ldb with new schema")
957     except:
958         samdb.transaction_cancel()
959         raise
960     else:
961         samdb.transaction_commit()
962
963     samdb = SamDB(session_info=admin_session_info,
964                 credentials=provision_backend.credentials, lp=lp,
965                 global_schema=False)
966     samdb.connect(path)
967     samdb.transaction_start()
968     try:
969         samdb.invocation_id = invocationid
970
971         message("Setting up sam.ldb configuration data")
972         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
973             "CONFIGDN": names.configdn,
974             "NETBIOSNAME": names.netbiosname,
975             "DEFAULTSITE": names.sitename,
976             "DNSDOMAIN": names.dnsdomain,
977             "DOMAIN": names.domain,
978             "SCHEMADN": names.schemadn,
979             "DOMAINDN": names.domaindn,
980             "SERVERDN": names.serverdn,
981             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
982             })
983
984         message("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         message("Adding users container")
991         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
992                 "DOMAINDN": names.domaindn})
993         message("Modifying users container")
994         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
995                 "DOMAINDN": names.domaindn})
996         message("Adding computers container")
997         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
998                 "DOMAINDN": names.domaindn})
999         message("Modifying computers container")
1000         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1001                 "DOMAINDN": names.domaindn})
1002         message("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             message("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             message("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, message, 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):
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 = 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 = 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             message("No external IPv4 address has been found: I use the loopback.")
1210             hostip = '127.0.0.1'
1211         else:
1212             hostip = hostips[0]
1213             if len(hostips) > 1:
1214                 message("More than one IPv4 address found: I use " + 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)
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                                          message=message)
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                                          message=message,
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                                          message=message,
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                                          message=message,
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         message("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     message("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     message("Setting up the registry")
1306     setup_registry(paths.hklm, setup_path, session_info, 
1307                    lp=lp)
1308
1309     message("Setting up the privileges database")
1310     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1311
1312     message("Setting up idmap db")
1313     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1314                           lp=lp)
1315
1316     message("Setting up SAM db")
1317     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1318                         provision_backend, lp, names,
1319                         message, 
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)
1329
1330     if serverrole == "domain controller":
1331         if paths.netlogon is None:
1332             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1333             message("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             message("Existing smb.conf does not have a [sysvol] share, but you"
1339                     " are configuring a DC.")
1340             message("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         message("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, message, 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             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1393             message("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             message("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, message, 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             message("Failed to chown %s to bind gid %u" % (dns_keytab_path,
1422                 paths.bind_gid))
1423
1424
1425     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1426
1427     message("Once the above files are installed, your Samba4 server will be ready to use")
1428     message("Server Role:           %s" % serverrole)
1429     message("Hostname:              %s" % names.hostname)
1430     message("NetBIOS Domain:        %s" % names.domain)
1431     message("DNS Domain:            %s" % names.dnsdomain)
1432     message("DOMAIN SID:            %s" % str(domainsid))
1433     if samdb_fill == FILL_FULL:
1434         message("Admin password:        %s" % adminpass)
1435     if provision_backend.type is not "ldb":
1436         if provision_backend.credentials.get_bind_dn() is not None:
1437             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1438         else:
1439             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1440
1441         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1442
1443         if provision_backend.slapd_command_escaped is not None:
1444             # now display slapd_command_file.txt to show how slapd must be started next time
1445             message("Use later the following commandline to start slapd, then Samba:")
1446             message(provision_backend.slapd_command_escaped)
1447             message("This slapd-Commandline is also stored under: " + provision_backend.ldapdir + "/ldap_backend_startup.sh")
1448
1449
1450     result = ProvisionResult()
1451     result.domaindn = domaindn
1452     result.paths = paths
1453     result.lp = lp
1454     result.samdb = samdb
1455     return result
1456
1457
1458 def provision_become_dc(setup_dir=None,
1459                         smbconf=None, targetdir=None, realm=None, 
1460                         rootdn=None, domaindn=None, schemadn=None,
1461                         configdn=None, serverdn=None,
1462                         domain=None, hostname=None, domainsid=None, 
1463                         adminpass=None, krbtgtpass=None, domainguid=None, 
1464                         policyguid=None, policyguid_dc=None, invocationid=None,
1465                         machinepass=None, 
1466                         dnspass=None, root=None, nobody=None, users=None, 
1467                         wheel=None, backup=None, serverrole=None, 
1468                         ldap_backend=None, ldap_backend_type=None,
1469                         sitename=None, debuglevel=1):
1470
1471     def message(text):
1472         """print a message if quiet is not set."""
1473         print text
1474
1475     samba.set_debug_level(debuglevel)
1476
1477     return provision(setup_dir, message, system_session(), None,
1478               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1479               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1480               configdn=configdn, serverdn=serverdn, domain=domain,
1481               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1482               machinepass=machinepass, serverrole="domain controller",
1483               sitename=sitename)
1484
1485
1486 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1487     """Create a PHP LDAP admin configuration file.
1488
1489     :param path: Path to write the configuration to.
1490     :param setup_path: Function to generate setup paths.
1491     """
1492     setup_file(setup_path("phpldapadmin-config.php"), path, 
1493             {"S4_LDAPI_URI": ldapi_uri})
1494
1495
1496 def create_zone_file(lp, message, paths, targetdir, setup_path, dnsdomain,
1497                      hostip, hostip6, hostname, realm, domainguid,
1498                      ntdsguid):
1499     """Write out a DNS zone file, from the info in the current database.
1500
1501     :param paths: paths object
1502     :param setup_path: Setup path function.
1503     :param dnsdomain: DNS Domain name
1504     :param domaindn: DN of the Domain
1505     :param hostip: Local IPv4 IP
1506     :param hostip6: Local IPv6 IP
1507     :param hostname: Local hostname
1508     :param realm: Realm name
1509     :param domainguid: GUID of the domain.
1510     :param ntdsguid: GUID of the hosts nTDSDSA record.
1511     """
1512     assert isinstance(domainguid, str)
1513
1514     if hostip6 is not None:
1515         hostip6_base_line = "            IN AAAA    " + hostip6
1516         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1517     else:
1518         hostip6_base_line = ""
1519         hostip6_host_line = ""
1520
1521     if hostip is not None:
1522         hostip_base_line = "            IN A    " + hostip
1523         hostip_host_line = hostname + "        IN A    " + hostip
1524     else:
1525         hostip_base_line = ""
1526         hostip_host_line = ""
1527
1528     dns_dir = os.path.dirname(paths.dns)
1529
1530     try:
1531         shutil.rmtree(dns_dir, True)
1532     except OSError:
1533         pass
1534
1535     os.mkdir(dns_dir, 0775)
1536
1537     # we need to freeze the zone while we update the contents
1538     if targetdir is None:
1539         rndc = ' '.join(lp.get("rndc command"))
1540         os.system(rndc + " freeze " + lp.get("realm"))
1541
1542     setup_file(setup_path("provision.zone"), paths.dns, {
1543             "HOSTNAME": hostname,
1544             "DNSDOMAIN": dnsdomain,
1545             "REALM": realm,
1546             "HOSTIP_BASE_LINE": hostip_base_line,
1547             "HOSTIP_HOST_LINE": hostip_host_line,
1548             "DOMAINGUID": domainguid,
1549             "DATESTRING": time.strftime("%Y%m%d%H"),
1550             "DEFAULTSITE": DEFAULTSITE,
1551             "NTDSGUID": ntdsguid,
1552             "HOSTIP6_BASE_LINE": hostip6_base_line,
1553             "HOSTIP6_HOST_LINE": hostip6_host_line,
1554         })
1555
1556     # note that we use no variable substitution on this file
1557     # the substitution is done at runtime by samba_dnsupdate
1558     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1559
1560     # and the SPN update list
1561     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1562
1563     if paths.bind_gid is not None:
1564         try:
1565             os.chown(dns_dir, -1, paths.bind_gid)
1566             os.chown(paths.dns, -1, paths.bind_gid)
1567             # chmod needed to cope with umask
1568             os.chmod(dns_dir, 0775)
1569             os.chmod(paths.dns, 0664)
1570         except OSError:
1571             message("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
1572
1573     if targetdir is None:
1574         os.system(rndc + " unfreeze " + lp.get("realm"))
1575
1576
1577 def create_dns_update_list(lp, message, paths, setup_path):
1578     """Write out a dns_update_list file"""
1579     # note that we use no variable substitution on this file
1580     # the substitution is done at runtime by samba_dnsupdate
1581     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1582     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1583
1584 def create_named_conf(paths, setup_path, realm, dnsdomain,
1585                       private_dir):
1586     """Write out a file containing zone statements suitable for inclusion in a
1587     named.conf file (including GSS-TSIG configuration).
1588     
1589     :param paths: all paths
1590     :param setup_path: Setup path function.
1591     :param realm: Realm name
1592     :param dnsdomain: DNS Domain name
1593     :param private_dir: Path to private directory
1594     :param keytab_name: File name of DNS keytab file
1595     """
1596
1597     setup_file(setup_path("named.conf"), paths.namedconf, {
1598             "DNSDOMAIN": dnsdomain,
1599             "REALM": realm,
1600             "ZONE_FILE": paths.dns,
1601             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1602             "NAMED_CONF": paths.namedconf,
1603             "NAMED_CONF_UPDATE": paths.namedconf_update
1604             })
1605
1606     setup_file(setup_path("named.conf.update"), paths.namedconf_update)
1607
1608 def create_named_txt(path, setup_path, realm, dnsdomain,
1609                       private_dir, keytab_name):
1610     """Write out a file containing zone statements suitable for inclusion in a
1611     named.conf file (including GSS-TSIG configuration).
1612     
1613     :param path: Path of the new named.conf file.
1614     :param setup_path: Setup path function.
1615     :param realm: Realm name
1616     :param dnsdomain: DNS Domain name
1617     :param private_dir: Path to private directory
1618     :param keytab_name: File name of DNS keytab file
1619     """
1620
1621     setup_file(setup_path("named.txt"), path, {
1622             "DNSDOMAIN": dnsdomain,
1623             "REALM": realm,
1624             "DNS_KEYTAB": keytab_name,
1625             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1626             "PRIVATE_DIR": private_dir
1627         })
1628
1629 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1630     """Write out a file containing zone statements suitable for inclusion in a
1631     named.conf file (including GSS-TSIG configuration).
1632     
1633     :param path: Path of the new named.conf file.
1634     :param setup_path: Setup path function.
1635     :param dnsdomain: DNS Domain name
1636     :param hostname: Local hostname
1637     :param realm: Realm name
1638     """
1639     setup_file(setup_path("krb5.conf"), path, {
1640             "DNSDOMAIN": dnsdomain,
1641             "HOSTNAME": hostname,
1642             "REALM": realm,
1643         })
1644
1645
1646 class ProvisioningError(Exception):
1647     """A generic provision error."""
1648
1649     def __init__(self, value):
1650         self.value = value
1651
1652     def __str__(self):
1653         return "ProvisioningError: " + self.value
1654
1655
1656 class InvalidNetbiosName(Exception):
1657     """A specified name was not a valid NetBIOS name."""
1658     def __init__(self, name):
1659         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)