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