s4:provision Make 'linked_attributes' and 'dnsyntax_attributes' a property of the...
[ira/wip.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 sys
31 import pwd
32 import grp
33 import time
34 import uuid, glue
35 import socket
36 import param
37 import registry
38 import samba
39 import subprocess
40 import ldb
41
42 import shutil
43 from credentials import Credentials, DONT_USE_KERBEROS
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name
46 from samba import check_all_substituted
47 from samba import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008
48 from samba.samdb import SamDB
49 from samba.idmap import IDmapDB
50 from samba.dcerpc import security
51 from samba.ndr import ndr_pack
52 import urllib
53 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
54 from ms_schema import read_ms_schema
55 from ms_display_specifiers import read_ms_ldif
56 from signal import SIGTERM
57 from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
58
59 __docformat__ = "restructuredText"
60
61 def find_setup_dir():
62     """Find the setup directory used by provision."""
63     dirname = os.path.dirname(__file__)
64     if "/site-packages/" in dirname:
65         prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
66         for suffix in ["share/setup", "share/samba/setup", "setup"]:
67             ret = os.path.join(prefix, suffix)
68             if os.path.isdir(ret):
69                 return ret
70     # In source tree
71     ret = os.path.join(dirname, "../../../setup")
72     if os.path.isdir(ret):
73         return ret
74     raise Exception("Unable to find setup directory.")
75
76 def get_schema_descriptor(domain_sid):
77     sddl = "O:SAG:SAD:(A;CI;RPLCLORC;;;AU)(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \
78            "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
79            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
80            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
81            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
82            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
83            "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \
84            "(AU;CISA;WP;;;WD)(AU;SA;CR;;;BA)" \
85            "(AU;SA;CR;;;DU)(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \
86            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
87     sec = security.descriptor.from_sddl(sddl, domain_sid)
88     return b64encode(ndr_pack(sec))
89
90 def get_config_descriptor(domain_sid):
91     sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
92            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
93            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
94            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
95            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
96            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
97            "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
98            "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
99            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
100            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
101            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
102            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
103            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;S-1-5-21-3191434175-1265308384-3577286990-498)" \
104            "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
105            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
106     sec = security.descriptor.from_sddl(sddl, domain_sid)
107     return b64encode(ndr_pack(sec))
108
109
110 DEFAULTSITE = "Default-First-Site-Name"
111
112 # Exception classes
113
114 class ProvisioningError(Exception):
115     """A generic provision error."""
116
117 class InvalidNetbiosName(Exception):
118     """A specified name was not a valid NetBIOS name."""
119     def __init__(self, name):
120         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
121
122
123 class ProvisionPaths(object):
124     def __init__(self):
125         self.shareconf = None
126         self.hklm = None
127         self.hkcu = None
128         self.hkcr = None
129         self.hku = None
130         self.hkpd = None
131         self.hkpt = None
132         self.samdb = None
133         self.idmapdb = None
134         self.secrets = None
135         self.keytab = None
136         self.dns_keytab = None
137         self.dns = None
138         self.winsdb = None
139         self.private_dir = None
140         self.ldapdir = None
141         self.slapdconf = None
142         self.modulesconf = None
143         self.memberofconf = None
144         self.fedoradsinf = None
145         self.fedoradspartitions = None
146         self.fedoradssasl = None
147         self.fedoradsdna = None
148         self.fedoradspam = None
149         self.fedoradsrefint = None
150         self.fedoradslinkedattributes = None
151         self.fedoradsindex = None
152         self.fedoradssamba = None
153         self.olmmron = None
154         self.olmmrserveridsconf = None
155         self.olmmrsyncreplconf = None
156         self.olcdir = None
157         self.olslapd = None
158         self.olcseedldif = None
159
160
161 class ProvisionNames(object):
162     def __init__(self):
163         self.rootdn = None
164         self.domaindn = None
165         self.configdn = None
166         self.schemadn = None
167         self.sambadn = None
168         self.ldapmanagerdn = None
169         self.dnsdomain = None
170         self.realm = None
171         self.netbiosname = None
172         self.domain = None
173         self.hostname = None
174         self.sitename = None
175         self.smbconf = None
176     
177
178 class ProvisionResult(object):
179     def __init__(self):
180         self.paths = None
181         self.domaindn = None
182         self.lp = None
183         self.samdb = None
184
185         
186 class Schema(object):
187     def __init__(self, setup_path, domain_sid, schemadn=None,
188                  serverdn=None, sambadn=None):
189         """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
190         
191         :param samdb: Load a schema into a SamDB.
192         :param setup_path: Setup path function.
193         :param schemadn: DN of the schema
194         :param serverdn: DN of the server
195         
196         Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
197         """
198
199         self.schemadn = schemadn
200         self.ldb = Ldb()
201         self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
202                                           setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
203         self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
204         self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
205         check_all_substituted(self.schema_data)
206
207         self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
208                                                   {"SCHEMADN": schemadn,
209                                                    "SERVERDN": serverdn,
210                                                    })
211
212         descr = get_schema_descriptor(domain_sid)
213         self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
214                                                {"SCHEMADN": schemadn,
215                                                 "DESCRIPTOR": descr
216                                                 })
217
218         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
219         prefixmap = b64encode(prefixmap)
220
221         
222
223         # We don't actually add this ldif, just parse it
224         prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
225         self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
226
227     def write_to_tmp_ldb(self, schemadb_path):
228         self.ldb.connect(schemadb_path)
229         self.ldb.transaction_start()
230     
231         self.ldb.add_ldif("""dn: @ATTRIBUTES
232 linkID: INTEGER
233
234 dn: @INDEXLIST
235 @IDXATTR: linkID
236 @IDXATTR: attributeSyntax
237 """)
238         # These bits of LDIF are supplied when the Schema object is created
239         self.ldb.add_ldif(self.schema_dn_add)
240         self.ldb.modify_ldif(self.schema_dn_modify)
241         self.ldb.add_ldif(self.schema_data)
242         self.ldb.transaction_commit()
243
244     # Return a hash with the forward attribute as a key and the back as the value 
245     def linked_attributes(self):
246         return get_linked_attributes(self.schemadn, self.ldb)
247
248     def dnsyntax_attributes(self):
249         return get_dnsyntax_attributes(self.schemadn, self.ldb)
250
251 # Return a hash with the forward attribute as a key and the back as the value 
252 def get_linked_attributes(schemadn,schemaldb):
253     attrs = ["linkID", "lDAPDisplayName"]
254     res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
255     attributes = {}
256     for i in range (0, len(res)):
257         expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
258         target = schemaldb.searchone(basedn=schemadn, 
259                                      expression=expression, 
260                                      attribute="lDAPDisplayName", 
261                                      scope=SCOPE_SUBTREE)
262         if target is not None:
263             attributes[str(res[i]["lDAPDisplayName"])]=str(target)
264             
265     return attributes
266
267 def get_dnsyntax_attributes(schemadn,schemaldb):
268     attrs = ["linkID", "lDAPDisplayName"]
269     res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
270     attributes = []
271     for i in range (0, len(res)):
272         attributes.append(str(res[i]["lDAPDisplayName"]))
273         
274     return attributes
275
276 def check_install(lp, session_info, credentials):
277     """Check whether the current install seems ok.
278     
279     :param lp: Loadparm context
280     :param session_info: Session information
281     :param credentials: Credentials
282     """
283     if lp.get("realm") == "":
284         raise Exception("Realm empty")
285     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
286             credentials=credentials, lp=lp)
287     if len(ldb.search("(cn=Administrator)")) != 1:
288         raise ProvisioningError("No administrator account found")
289
290
291 def findnss(nssfn, names):
292     """Find a user or group from a list of possibilities.
293     
294     :param nssfn: NSS Function to try (should raise KeyError if not found)
295     :param names: Names to check.
296     :return: Value return by first names list.
297     """
298     for name in names:
299         try:
300             return nssfn(name)
301         except KeyError:
302             pass
303     raise KeyError("Unable to find user/group %r" % names)
304
305
306 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
307 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
308
309
310 def read_and_sub_file(file, subst_vars):
311     """Read a file and sub in variables found in it
312     
313     :param file: File to be read (typically from setup directory)
314      param subst_vars: Optional variables to subsitute in the file.
315     """
316     data = open(file, 'r').read()
317     if subst_vars is not None:
318         data = substitute_var(data, subst_vars)
319     check_all_substituted(data)
320     return data
321
322
323 def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
324     """Setup a ldb in the private dir.
325     
326     :param ldb: LDB file to import data into
327     :param ldif_path: Path of the LDIF file to load
328     :param subst_vars: Optional variables to subsitute in LDIF.
329     :param nocontrols: Optional list of controls, can be None for no controls
330     """
331     assert isinstance(ldif_path, str)
332     data = read_and_sub_file(ldif_path, subst_vars)
333     ldb.add_ldif(data,controls)
334
335
336 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
337     """Modify a ldb in the private dir.
338     
339     :param ldb: LDB object.
340     :param ldif_path: LDIF file path.
341     :param subst_vars: Optional dictionary with substitution variables.
342     """
343     data = read_and_sub_file(ldif_path, subst_vars)
344
345     ldb.modify_ldif(data)
346
347
348 def setup_ldb(ldb, ldif_path, subst_vars):
349     """Import a LDIF a file into a LDB handle, optionally substituting variables.
350
351     :note: Either all LDIF data will be added or none (using transactions).
352
353     :param ldb: LDB file to import into.
354     :param ldif_path: Path to the LDIF file.
355     :param subst_vars: Dictionary with substitution variables.
356     """
357     assert ldb is not None
358     ldb.transaction_start()
359     try:
360         setup_add_ldif(ldb, ldif_path, subst_vars)
361     except:
362         ldb.transaction_cancel()
363         raise
364     ldb.transaction_commit()
365
366
367 def setup_file(template, fname, subst_vars=None):
368     """Setup a file in the private dir.
369
370     :param template: Path of the template file.
371     :param fname: Path of the file to create.
372     :param subst_vars: Substitution variables.
373     """
374     f = fname
375
376     if os.path.exists(f):
377         os.unlink(f)
378
379     data = read_and_sub_file(template, subst_vars)
380     open(f, 'w').write(data)
381
382
383 def provision_paths_from_lp(lp, dnsdomain):
384     """Set the default paths for provisioning.
385
386     :param lp: Loadparm context.
387     :param dnsdomain: DNS Domain name
388     """
389     paths = ProvisionPaths()
390     paths.private_dir = lp.get("private dir")
391     paths.dns_keytab = "dns.keytab"
392
393     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
394     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
395     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
396     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
397     paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
398     paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
399     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
400     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
401     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
402     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
403     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
404     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
405                                             "phpldapadmin-config.php")
406     paths.ldapdir = os.path.join(paths.private_dir, 
407                                  "ldap")
408     paths.slapdconf = os.path.join(paths.ldapdir, 
409                                    "slapd.conf")
410     paths.slapdpid = os.path.join(paths.ldapdir, 
411                                    "slapd.pid")
412     paths.modulesconf = os.path.join(paths.ldapdir, 
413                                      "modules.conf")
414     paths.memberofconf = os.path.join(paths.ldapdir, 
415                                       "memberof.conf")
416     paths.fedoradsinf = os.path.join(paths.ldapdir, 
417                                      "fedorads.inf")
418     paths.fedoradspartitions = os.path.join(paths.ldapdir, 
419                                             "fedorads-partitions.ldif")
420     paths.fedoradssasl = os.path.join(paths.ldapdir, 
421                                       "fedorads-sasl.ldif")
422     paths.fedoradsdna = os.path.join(paths.ldapdir, 
423                                      "fedorads-dna.ldif")
424     paths.fedoradspam = os.path.join(paths.ldapdir,
425                                      "fedorads-pam.ldif")
426     paths.fedoradsrefint = os.path.join(paths.ldapdir,
427                                         "fedorads-refint.ldif")
428     paths.fedoradslinkedattributes = os.path.join(paths.ldapdir,
429                                                   "fedorads-linked-attributes.ldif")
430     paths.fedoradsindex = os.path.join(paths.ldapdir,
431                                        "fedorads-index.ldif")
432     paths.fedoradssamba = os.path.join(paths.ldapdir, 
433                                        "fedorads-samba.ldif")
434     paths.olmmrserveridsconf = os.path.join(paths.ldapdir, 
435                                             "mmr_serverids.conf")
436     paths.olmmrsyncreplconf = os.path.join(paths.ldapdir, 
437                                            "mmr_syncrepl.conf")
438     paths.olcdir = os.path.join(paths.ldapdir, 
439                                  "slapd.d")
440     paths.olcseedldif = os.path.join(paths.ldapdir, 
441                                  "olc_seed.ldif")
442     paths.hklm = "hklm.ldb"
443     paths.hkcr = "hkcr.ldb"
444     paths.hkcu = "hkcu.ldb"
445     paths.hku = "hku.ldb"
446     paths.hkpd = "hkpd.ldb"
447     paths.hkpt = "hkpt.ldb"
448
449     paths.sysvol = lp.get("path", "sysvol")
450
451     paths.netlogon = lp.get("path", "netlogon")
452
453     paths.smbconf = lp.configfile
454
455     return paths
456
457
458 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
459                 serverrole=None, rootdn=None, domaindn=None, configdn=None,
460                 schemadn=None, serverdn=None, sitename=None, sambadn=None):
461     """Guess configuration settings to use."""
462
463     if hostname is None:
464         hostname = socket.gethostname().split(".")[0]
465
466     netbiosname = lp.get("netbios name")
467     if netbiosname is None:
468         netbiosname = hostname
469     assert netbiosname is not None
470     netbiosname = netbiosname.upper()
471     if not valid_netbios_name(netbiosname):
472         raise InvalidNetbiosName(netbiosname)
473
474     if dnsdomain is None:
475         dnsdomain = lp.get("realm")
476     assert dnsdomain is not None
477     dnsdomain = dnsdomain.lower()
478
479     if serverrole is None:
480         serverrole = lp.get("server role")
481     assert serverrole is not None
482     serverrole = serverrole.lower()
483
484     realm = dnsdomain.upper()
485
486     if lp.get("realm").upper() != realm:
487         raise ProvisioningError("guess_names: Realm '%s' in smb.conf must match chosen realm '%s'!", lp.get("realm").upper(), realm)
488
489     if serverrole == "domain controller":
490         if domain is None:
491             domain = lp.get("workgroup")
492         assert domain is not None
493         domain = domain.upper()
494
495         if lp.get("workgroup").upper() != domain:
496             raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'!", lp.get("workgroup").upper(), domain)
497
498         if domaindn is None:
499             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
500     else:
501         domain = netbiosname
502         if domaindn is None:
503             domaindn = "DC=" + netbiosname
504         
505     if not valid_netbios_name(domain):
506         raise InvalidNetbiosName(domain)
507         
508     if hostname.upper() == realm:
509         raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!", realm, hostname)
510     if netbiosname == realm:
511         raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!", realm, netbiosname)
512     if domain == realm:
513         raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!", realm, domain)
514
515     if rootdn is None:
516        rootdn = domaindn
517        
518     if configdn is None:
519         configdn = "CN=Configuration," + rootdn
520     if schemadn is None:
521         schemadn = "CN=Schema," + configdn
522     if sambadn is None:
523         sambadn = "CN=Samba"
524
525     if sitename is None:
526         sitename=DEFAULTSITE
527
528     names = ProvisionNames()
529     names.rootdn = rootdn
530     names.domaindn = domaindn
531     names.configdn = configdn
532     names.schemadn = schemadn
533     names.sambadn = sambadn
534     names.ldapmanagerdn = "CN=Manager," + rootdn
535     names.dnsdomain = dnsdomain
536     names.domain = domain
537     names.realm = realm
538     names.netbiosname = netbiosname
539     names.hostname = hostname
540     names.sitename = sitename
541     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
542  
543     return names
544     
545
546 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
547                  targetdir, sid_generator):
548     """Create a new smb.conf file based on a couple of basic settings.
549     """
550     assert smbconf is not None
551     if hostname is None:
552         hostname = socket.gethostname().split(".")[0]
553     netbiosname = hostname.upper()
554
555     if serverrole is None:
556         serverrole = "standalone"
557
558     assert serverrole in ("domain controller", "member server", "standalone")
559     if serverrole == "domain controller":
560         smbconfsuffix = "dc"
561     elif serverrole == "member server":
562         smbconfsuffix = "member"
563     elif serverrole == "standalone":
564         smbconfsuffix = "standalone"
565
566     if sid_generator is None:
567         sid_generator = "internal"
568
569     assert domain is not None
570     domain = domain.upper()
571
572     assert realm is not None
573     realm = realm.upper()
574
575     default_lp = param.LoadParm()
576     #Load non-existant file
577     if os.path.exists(smbconf):
578         default_lp.load(smbconf)
579     
580     if targetdir is not None:
581         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
582         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
583
584         default_lp.set("lock dir", os.path.abspath(targetdir))
585     else:
586         privatedir_line = ""
587         lockdir_line = ""
588
589     if sid_generator == "internal":
590         sid_generator_line = ""
591     else:
592         sid_generator_line = "sid generator = " + sid_generator
593
594     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
595     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
596
597     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
598                smbconf, {
599             "NETBIOS_NAME": netbiosname,
600             "DOMAIN": domain,
601             "REALM": realm,
602             "SERVERROLE": serverrole,
603             "NETLOGONPATH": netlogon,
604             "SYSVOLPATH": sysvol,
605             "SIDGENERATOR_LINE": sid_generator_line,
606             "PRIVATEDIR_LINE": privatedir_line,
607             "LOCKDIR_LINE": lockdir_line
608             })
609
610
611 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
612                         users_gid, wheel_gid):
613     """setup reasonable name mappings for sam names to unix names.
614
615     :param samdb: SamDB object.
616     :param idmap: IDmap db object.
617     :param sid: The domain sid.
618     :param domaindn: The domain DN.
619     :param root_uid: uid of the UNIX root user.
620     :param nobody_uid: uid of the UNIX nobody user.
621     :param users_gid: gid of the UNIX users group.
622     :param wheel_gid: gid of the UNIX wheel group."""
623
624     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
625     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
626     
627     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
628     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
629
630 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
631                            provision_backend, names, schema,
632                            serverrole, 
633                            erase=False):
634     """Setup the partitions for the SAM database. 
635     
636     Alternatively, provision() may call this, and then populate the database.
637     
638     :note: This will wipe the Sam Database!
639     
640     :note: This function always removes the local SAM LDB file. The erase 
641         parameter controls whether to erase the existing data, which 
642         may not be stored locally but in LDAP.
643
644     """
645     assert session_info is not None
646
647     old_partitions = None
648     new_partitions = None
649
650     # We use options=["modules:"] to stop the modules loading - we
651     # just want to wipe and re-initialise the database, not start it up
652
653     try:
654         samdb = Ldb(url=samdb_path, session_info=session_info, 
655                       lp=lp, options=["modules:"])
656         res = samdb.search(base="@PARTITION", scope=SCOPE_BASE, attrs=["partition"], expression="partition=*")
657         if len(res) == 1:
658             try:
659                 old_partitions = res[0]["partition"]
660             except KeyError:
661                 pass
662
663         if old_partitions is not None:
664             new_partitions = [];
665             for old_partition in old_partitions:
666                 new_partition = old_partition
667                 if old_partition.endswith(".ldb"):
668                     p = old_partition.split(":")[0]
669                     dn = ldb.Dn(schema.ldb, p)
670                     new_partition = dn.get_casefold()
671                 new_partitions.append(new_partition)
672
673         # Wipes the database
674         samdb.erase_except_schema_controlled()
675     except LdbError:
676         os.unlink(samdb_path)
677         samdb = Ldb(url=samdb_path, session_info=session_info, 
678                       lp=lp, options=["modules:"])
679          # Wipes the database
680         samdb.erase_except_schema_controlled()
681
682     #Add modules to the list to activate them by default
683     #beware often order is important
684     #
685     # Some Known ordering constraints:
686     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
687     # - objectclass must be before password_hash, because password_hash checks
688     #   that the objectclass is of type person (filled in by objectclass
689     #   module when expanding the objectclass list)
690     # - partition must be last
691     # - each partition has its own module list then
692     modules_list = ["resolve_oids",
693                     "rootdse",
694                     "lazy_commit",
695                     "acl",
696                     "paged_results",
697                     "ranged_results",
698                     "anr",
699                     "server_sort",
700                     "asq",
701                     "extended_dn_store",
702                     "extended_dn_in",
703                     "rdn_name",
704                     "objectclass",
705                     "descriptor",
706                     "samldb",
707                     "password_hash",
708                     "operational",
709                     "kludge_acl", 
710                     "instancetype"]
711     tdb_modules_list = [
712                     "subtree_rename",
713                     "subtree_delete",
714                     "linked_attributes",
715                     "extended_dn_out_ldb"]
716     modules_list2 = ["show_deleted",
717                      "schema_load",
718                      "new_partition",
719                      "partition"]
720
721     ldap_backend_line = "# No LDAP backend"
722     if provision_backend.type is not "ldb":
723         ldap_backend_line = "ldapBackend: %s" % provision_backend.ldapi_uri
724         
725         if provision_backend.ldap_backend_type == "fedora-ds":
726             backend_modules = ["nsuniqueid", "paged_searches"]
727             # We can handle linked attributes here, as we don't have directory-side subtree operations
728             tdb_modules_list = ["extended_dn_out_fds"]
729         elif ldap_backend.ldap_backend_type == "openldap":
730             backend_modules = ["entryuuid", "paged_searches"]
731             # OpenLDAP handles subtree renames, so we don't want to do any of these things
732             tdb_modules_list = ["extended_dn_out_openldap"]
733
734     elif serverrole == "domain controller":
735         tdb_modules_list.insert(0, "repl_meta_data")
736         backend_modules = []
737     else:
738         backend_modules = ["objectguid"]
739
740     if tdb_modules_list is None:
741         tdb_modules_list_as_string = ""
742     else:
743         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
744         
745     samdb.transaction_start()
746     try:
747         message("Setting up sam.ldb partitions and settings")
748         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
749                 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), 
750                 "SCHEMADN_MOD2": ",objectguid",
751                 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
752                 "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
753                 "SCHEMADN_MOD": "schema_data",
754                 "CONFIGDN_MOD": "naming_fsmo",
755                 "DOMAINDN_MOD": "pdc_fsmo",
756                 "MODULES_LIST": ",".join(modules_list),
757                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
758                 "MODULES_LIST2": ",".join(modules_list2),
759                 "BACKEND_MOD": ",".join(backend_modules),
760                 "LDAP_BACKEND_LINE": ldap_backend_line,
761         })
762
763         
764         if new_partitions is not None:
765             m = ldb.Message()
766             m.dn = ldb.Dn(samdb, "@PARTITION")
767             
768             m["partition"] = ldb.MessageElement(new_partitions, ldb.FLAG_MOD_ADD, "partition")
769             samdb.modify(m)
770
771         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
772
773         message("Setting up sam.ldb rootDSE")
774         setup_samdb_rootdse(samdb, setup_path, names)
775
776     except:
777         samdb.transaction_cancel()
778         raise
779
780     samdb.transaction_commit()
781
782         
783 def secretsdb_self_join(secretsdb, domain, 
784                         netbiosname, domainsid, machinepass, 
785                         realm=None, dnsdomain=None,
786                         keytab_path=None, 
787                         key_version_number=1,
788                         secure_channel_type=SEC_CHAN_WKSTA):
789     """Add domain join-specific bits to a secrets database.
790     
791     :param secretsdb: Ldb Handle to the secrets database
792     :param machinepass: Machine password
793     """
794     attrs=["whenChanged",
795            "secret",
796            "priorSecret",
797            "priorChanged",
798            "krb5Keytab",
799            "privateKeytab"]
800     
801
802     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
803     msg["secureChannelType"] = str(secure_channel_type)
804     msg["flatname"] = [domain]
805     msg["objectClass"] = ["top", "primaryDomain"]
806     if realm is not None:
807       if dnsdomain is None:
808         dnsdomain = realm.lower()
809       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
810       msg["realm"] = realm
811       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
812       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
813       msg["privateKeytab"] = ["secrets.keytab"];
814
815
816     msg["secret"] = [machinepass]
817     msg["samAccountName"] = ["%s$" % netbiosname]
818     msg["secureChannelType"] = [str(secure_channel_type)]
819     msg["objectSid"] = [ndr_pack(domainsid)]
820     
821     res = secretsdb.search(base="cn=Primary Domains", 
822                            attrs=attrs, 
823                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
824                            scope=SCOPE_ONELEVEL)
825     
826     for del_msg in res:
827       if del_msg.dn is not msg.dn:
828         secretsdb.delete(del_msg.dn)
829
830     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
831
832     if len(res) == 1:
833       msg["priorSecret"] = res[0]["secret"]
834       msg["priorWhenChanged"] = res[0]["whenChanged"]
835
836       if res["privateKeytab"] is not None:
837         msg["privateKeytab"] = res[0]["privateKeytab"]
838
839       if res["krb5Keytab"] is not None:
840         msg["krb5Keytab"] = res[0]["krb5Keytab"]
841
842       for el in msg:
843         el.set_flags(ldb.FLAG_MOD_REPLACE)
844         secretsdb.modify(msg)
845     else:
846       secretsdb.add(msg)
847
848
849 def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain, 
850                         dns_keytab_path, dnspass):
851     """Add DNS specific bits to a secrets database.
852     
853     :param secretsdb: Ldb Handle to the secrets database
854     :param setup_path: Setup path function
855     :param machinepass: Machine password
856     """
857     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
858             "REALM": realm,
859             "DNSDOMAIN": dnsdomain,
860             "DNS_KEYTAB": dns_keytab_path,
861             "DNSPASS_B64": b64encode(dnspass),
862             })
863
864
865 def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
866     """Setup the secrets database.
867
868     :param path: Path to the secrets database.
869     :param setup_path: Get the path to a setup file.
870     :param session_info: Session info.
871     :param credentials: Credentials
872     :param lp: Loadparm context
873     :return: LDB handle for the created secrets database
874     """
875     if os.path.exists(path):
876         os.unlink(path)
877     secrets_ldb = Ldb(path, session_info=session_info, 
878                       lp=lp)
879     secrets_ldb.erase()
880     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
881     secrets_ldb = Ldb(path, session_info=session_info, 
882                       lp=lp)
883     secrets_ldb.transaction_start()
884     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
885
886     if backend_credentials is not None and backend_credentials.authentication_requested():
887         if backend_credentials.get_bind_dn() is not None:
888             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
889                     "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
890                     "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
891                     })
892         else:
893             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
894                     "LDAPADMINUSER": backend_credentials.get_username(),
895                     "LDAPADMINREALM": backend_credentials.get_realm(),
896                     "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
897                     })
898
899     return secrets_ldb
900
901 def setup_privileges(path, setup_path, session_info, lp):
902     """Setup the privileges database.
903
904     :param path: Path to the privileges database.
905     :param setup_path: Get the path to a setup file.
906     :param session_info: Session info.
907     :param credentials: Credentials
908     :param lp: Loadparm context
909     :return: LDB handle for the created secrets database
910     """
911     if os.path.exists(path):
912         os.unlink(path)
913     privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
914     privilege_ldb.erase()
915     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
916
917
918 def setup_registry(path, setup_path, session_info, lp):
919     """Setup the registry.
920     
921     :param path: Path to the registry database
922     :param setup_path: Function that returns the path to a setup.
923     :param session_info: Session information
924     :param credentials: Credentials
925     :param lp: Loadparm context
926     """
927     reg = registry.Registry()
928     hive = registry.open_ldb(path, session_info=session_info, 
929                          lp_ctx=lp)
930     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
931     provision_reg = setup_path("provision.reg")
932     assert os.path.exists(provision_reg)
933     reg.diff_apply(provision_reg)
934
935
936 def setup_idmapdb(path, setup_path, session_info, lp):
937     """Setup the idmap database.
938
939     :param path: path to the idmap database
940     :param setup_path: Function that returns a path to a setup file
941     :param session_info: Session information
942     :param credentials: Credentials
943     :param lp: Loadparm context
944     """
945     if os.path.exists(path):
946         os.unlink(path)
947
948     idmap_ldb = IDmapDB(path, session_info=session_info,
949                         lp=lp)
950
951     idmap_ldb.erase()
952     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
953     return idmap_ldb
954
955
956 def setup_samdb_rootdse(samdb, setup_path, names):
957     """Setup the SamDB rootdse.
958
959     :param samdb: Sam Database handle
960     :param setup_path: Obtain setup path
961     """
962     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
963         "SCHEMADN": names.schemadn, 
964         "NETBIOSNAME": names.netbiosname,
965         "DNSDOMAIN": names.dnsdomain,
966         "REALM": names.realm,
967         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
968         "DOMAINDN": names.domaindn,
969         "ROOTDN": names.rootdn,
970         "CONFIGDN": names.configdn,
971         "SERVERDN": names.serverdn,
972         })
973         
974
975 def setup_self_join(samdb, names,
976                     machinepass, dnspass, 
977                     domainsid, invocationid, setup_path,
978                     policyguid, policyguid_dc, domainControllerFunctionality,
979                     ntdsguid):
980     """Join a host to its own domain."""
981     assert isinstance(invocationid, str)
982     if ntdsguid is not None:
983         ntdsguid_line = "objectGUID: %s\n"%ntdsguid
984     else:
985         ntdsguid_line = ""
986     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
987               "CONFIGDN": names.configdn, 
988               "SCHEMADN": names.schemadn,
989               "DOMAINDN": names.domaindn,
990               "SERVERDN": names.serverdn,
991               "INVOCATIONID": invocationid,
992               "NETBIOSNAME": names.netbiosname,
993               "DEFAULTSITE": names.sitename,
994               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
995               "MACHINEPASS_B64": b64encode(machinepass),
996               "DNSPASS_B64": b64encode(dnspass),
997               "REALM": names.realm,
998               "DOMAIN": names.domain,
999               "DNSDOMAIN": names.dnsdomain,
1000               "SAMBA_VERSION_STRING": version,
1001               "NTDSGUID": ntdsguid_line,
1002               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
1003
1004     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
1005               "POLICYGUID": policyguid,
1006               "POLICYGUID_DC": policyguid_dc,
1007               "DNSDOMAIN": names.dnsdomain,
1008               "DOMAINSID": str(domainsid),
1009               "DOMAINDN": names.domaindn})
1010     
1011     # add the NTDSGUID based SPNs
1012     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1013     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
1014                                      expression="", scope=SCOPE_BASE)
1015     assert isinstance(names.ntdsguid, str)
1016
1017     # Setup fSMORoleOwner entries to point at the newly created DC entry
1018     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
1019               "DOMAIN": names.domain,
1020               "DNSDOMAIN": names.dnsdomain,
1021               "DOMAINDN": names.domaindn,
1022               "CONFIGDN": names.configdn,
1023               "SCHEMADN": names.schemadn, 
1024               "DEFAULTSITE": names.sitename,
1025               "SERVERDN": names.serverdn,
1026               "NETBIOSNAME": names.netbiosname,
1027               "NTDSGUID": names.ntdsguid
1028               })
1029
1030
1031 def setup_samdb(path, setup_path, session_info, provision_backend, lp, 
1032                 names, message, 
1033                 domainsid, domainguid, policyguid, policyguid_dc,
1034                 fill, adminpass, krbtgtpass, 
1035                 machinepass, invocationid, dnspass, ntdsguid,
1036                 serverrole, dom_for_fun_level=None,
1037                 schema=None):
1038     """Setup a complete SAM Database.
1039     
1040     :note: This will wipe the main SAM database file!
1041     """
1042
1043     # ATTENTION: Do NOT change these default values without discussion with the
1044     # team and/or release manager. They have a big impact on the whole program!
1045     domainControllerFunctionality = DS_DC_FUNCTION_2008
1046
1047     if dom_for_fun_level is None:
1048         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
1049     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
1050         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
1051
1052     if dom_for_fun_level > domainControllerFunctionality:
1053         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!")
1054
1055     domainFunctionality = dom_for_fun_level
1056     forestFunctionality = dom_for_fun_level
1057
1058     # Also wipes the database
1059     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
1060                            provision_backend=provision_backend, session_info=session_info,
1061                            names=names, 
1062                            serverrole=serverrole, schema=schema)
1063
1064     if (schema == None):
1065         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1066                         sambadn=names.sambadn)
1067
1068     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
1069     samdb = Ldb(session_info=session_info, 
1070                 credentials=provision_backend.credentials, lp=lp)
1071
1072     message("Pre-loading the Samba 4 and AD schema")
1073
1074     # Load the schema from the one we computed earlier
1075     samdb.set_schema_from_ldb(schema.ldb)
1076
1077     # And now we can connect to the DB - the schema won't be loaded from the DB
1078     samdb.connect(path)
1079
1080     if fill == FILL_DRS:
1081         return samdb
1082         
1083     samdb.transaction_start()
1084     try:
1085         message("Erasing data from partitions")
1086         # Load the schema (again).  This time it will force a reindex,
1087         # and will therefore make the erase_partitions() below
1088         # computationally sane
1089         samdb.set_schema_from_ldb(schema.ldb)
1090         samdb.erase_partitions()
1091     
1092         # Set the domain functionality levels onto the database.
1093         # Various module (the password_hash module in particular) need
1094         # to know what level of AD we are emulating.
1095
1096         # These will be fixed into the database via the database
1097         # modifictions below, but we need them set from the start.
1098         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1099         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1100         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
1101
1102         samdb.set_domain_sid(str(domainsid))
1103         if serverrole == "domain controller":
1104             samdb.set_invocation_id(invocationid)
1105
1106         message("Adding DomainDN: %s" % names.domaindn)
1107
1108 #impersonate domain admin
1109         admin_session_info = admin_session(lp, str(domainsid))
1110         samdb.set_session_info(admin_session_info)
1111         if domainguid is not None:
1112             domainguid_line = "objectGUID: %s\n-" % domainguid
1113         else:
1114             domainguid_line = ""
1115         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1116                 "DOMAINDN": names.domaindn,
1117                 "DOMAINGUID": domainguid_line
1118                 })
1119
1120
1121         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1122             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1123             "DOMAINSID": str(domainsid),
1124             "SCHEMADN": names.schemadn, 
1125             "NETBIOSNAME": names.netbiosname,
1126             "DEFAULTSITE": names.sitename,
1127             "CONFIGDN": names.configdn,
1128             "SERVERDN": names.serverdn,
1129             "POLICYGUID": policyguid,
1130             "DOMAINDN": names.domaindn,
1131             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1132             "SAMBA_VERSION_STRING": version
1133             })
1134
1135         message("Adding configuration container")
1136         descr = get_config_descriptor(domainsid);
1137         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1138             "CONFIGDN": names.configdn, 
1139             "DESCRIPTOR": descr,
1140             })
1141         message("Modifying configuration container")
1142         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1143             "CONFIGDN": names.configdn, 
1144             "SCHEMADN": names.schemadn,
1145             })
1146
1147         # The LDIF here was created when the Schema object was constructed
1148         message("Setting up sam.ldb schema")
1149         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1150         samdb.modify_ldif(schema.schema_dn_modify)
1151         samdb.write_prefixes_from_schema()
1152         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1153         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1154                        {"SCHEMADN": names.schemadn})
1155
1156         message("Setting up sam.ldb configuration data")
1157         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1158             "CONFIGDN": names.configdn,
1159             "NETBIOSNAME": names.netbiosname,
1160             "DEFAULTSITE": names.sitename,
1161             "DNSDOMAIN": names.dnsdomain,
1162             "DOMAIN": names.domain,
1163             "SCHEMADN": names.schemadn,
1164             "DOMAINDN": names.domaindn,
1165             "SERVERDN": names.serverdn,
1166             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1167             })
1168
1169         message("Setting up display specifiers")
1170         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1171         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1172         check_all_substituted(display_specifiers_ldif)
1173         samdb.add_ldif(display_specifiers_ldif)
1174
1175         message("Adding users container")
1176         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1177                 "DOMAINDN": names.domaindn})
1178         message("Modifying users container")
1179         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1180                 "DOMAINDN": names.domaindn})
1181         message("Adding computers container")
1182         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1183                 "DOMAINDN": names.domaindn})
1184         message("Modifying computers container")
1185         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1186                 "DOMAINDN": names.domaindn})
1187         message("Setting up sam.ldb data")
1188         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1189             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1190             "DOMAINDN": names.domaindn,
1191             "NETBIOSNAME": names.netbiosname,
1192             "DEFAULTSITE": names.sitename,
1193             "CONFIGDN": names.configdn,
1194             "SERVERDN": names.serverdn,
1195             "POLICYGUID_DC": policyguid_dc
1196             })
1197
1198         if fill == FILL_FULL:
1199             message("Setting up sam.ldb users and groups")
1200             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1201                 "DOMAINDN": names.domaindn,
1202                 "DOMAINSID": str(domainsid),
1203                 "CONFIGDN": names.configdn,
1204                 "ADMINPASS_B64": b64encode(adminpass),
1205                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1206                 })
1207
1208             if serverrole == "domain controller":
1209                 message("Setting up self join")
1210                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1211                                 dnspass=dnspass,  
1212                                 machinepass=machinepass, 
1213                                 domainsid=domainsid, policyguid=policyguid,
1214                                 policyguid_dc=policyguid_dc,
1215                                 setup_path=setup_path,
1216                                 domainControllerFunctionality=domainControllerFunctionality,
1217                                 ntdsguid=ntdsguid)
1218
1219                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1220                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1221                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1222                 assert isinstance(names.ntdsguid, str)
1223
1224     except:
1225         samdb.transaction_cancel()
1226         raise
1227
1228     samdb.transaction_commit()
1229     return samdb
1230
1231
1232 FILL_FULL = "FULL"
1233 FILL_NT4SYNC = "NT4SYNC"
1234 FILL_DRS = "DRS"
1235
1236
1237 def provision(setup_dir, message, session_info, 
1238               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1239               realm=None, 
1240               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1241               serverdn=None,
1242               domain=None, hostname=None, hostip=None, hostip6=None, 
1243               domainsid=None, adminpass=None, ldapadminpass=None, 
1244               krbtgtpass=None, domainguid=None, 
1245               policyguid=None, policyguid_dc=None, invocationid=None,
1246               machinepass=None, ntdsguid=None,
1247               dnspass=None, root=None, nobody=None, users=None, 
1248               wheel=None, backup=None, aci=None, serverrole=None,
1249               dom_for_fun_level=None,
1250               ldap_backend_extra_port=None, backend_type=None,
1251               sitename=None,
1252               ol_mmr_urls=None, ol_olc=None, 
1253               setup_ds_path=None, slapd_path=None, nosync=False,
1254               ldap_dryrun_mode=False):
1255     """Provision samba4
1256     
1257     :note: caution, this wipes all existing data!
1258     """
1259
1260     def setup_path(file):
1261       return os.path.join(setup_dir, file)
1262
1263     if domainsid is None:
1264       domainsid = security.random_sid()
1265     else:
1266       domainsid = security.dom_sid(domainsid)
1267
1268     # create/adapt the group policy GUIDs
1269     if policyguid is None:
1270         policyguid = str(uuid.uuid4())
1271     policyguid = policyguid.upper()
1272     if policyguid_dc is None:
1273         policyguid_dc = str(uuid.uuid4())
1274     policyguid_dc = policyguid_dc.upper()
1275
1276     if adminpass is None:
1277         adminpass = glue.generate_random_str(12)
1278     if krbtgtpass is None:
1279         krbtgtpass = glue.generate_random_str(12)
1280     if machinepass is None:
1281         machinepass  = glue.generate_random_str(12)
1282     if dnspass is None:
1283         dnspass = glue.generate_random_str(12)
1284     if ldapadminpass is None:
1285         #Make a new, random password between Samba and it's LDAP server
1286         ldapadminpass=glue.generate_random_str(12)        
1287
1288     if backend_type is None:
1289         backend_type = "ldb"
1290
1291     sid_generator = "internal"
1292     if backend_type == "fedora-ds":
1293         sid_generator = "backend"
1294
1295     root_uid = findnss_uid([root or "root"])
1296     nobody_uid = findnss_uid([nobody or "nobody"])
1297     users_gid = findnss_gid([users or "users"])
1298     if wheel is None:
1299         wheel_gid = findnss_gid(["wheel", "adm"])
1300     else:
1301         wheel_gid = findnss_gid([wheel])
1302
1303     if targetdir is not None:
1304         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1305             os.makedirs(os.path.join(targetdir, "etc"))
1306         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1307     elif smbconf is None:
1308         smbconf = param.default_path()
1309
1310     # only install a new smb.conf if there isn't one there already
1311     if not os.path.exists(smbconf):
1312         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1313                      targetdir, sid_generator)
1314
1315     lp = param.LoadParm()
1316     lp.load(smbconf)
1317
1318     names = guess_names(lp=lp, hostname=hostname, domain=domain,
1319                         dnsdomain=realm, serverrole=serverrole,
1320                         domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1321                         serverdn=serverdn, sitename=sitename)
1322
1323     paths = provision_paths_from_lp(lp, names.dnsdomain)
1324
1325     if hostip is None:
1326         try:
1327             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1328         except socket.gaierror, (socket.EAI_NODATA, msg):
1329             hostip = None
1330
1331     if hostip6 is None:
1332         try:
1333             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1334         except socket.gaierror, (socket.EAI_NODATA, msg): 
1335             hostip6 = None
1336
1337     if serverrole is None:
1338         serverrole = lp.get("server role")
1339
1340     assert serverrole in ("domain controller", "member server", "standalone")
1341     if invocationid is None and serverrole == "domain controller":
1342         invocationid = str(uuid.uuid4())
1343
1344     if not os.path.exists(paths.private_dir):
1345         os.mkdir(paths.private_dir)
1346
1347     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1348     
1349     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1350                     sambadn=names.sambadn)
1351     
1352     provision_backend = ProvisionBackend(backend_type,
1353                                          paths=paths, setup_path=setup_path,
1354                                          lp=lp, credentials=credentials, 
1355                                          names=names,
1356                                          message=message, hostname=hostname,
1357                                          root=root, schema=schema,
1358                                          ldapadminpass=ldapadminpass,
1359                                          ldap_backend_extra_port=ldap_backend_extra_port,
1360                                          ol_mmr_urls=ol_mmr_urls, 
1361                                          slapd_path=slapd_path,
1362                                          setup_ds_path=setup_ds_path,
1363                                          ldap_dryrun_mode=ldap_dryrun_mode,
1364                                          domainsid=domainsid)
1365
1366     # only install a new shares config db if there is none
1367     if not os.path.exists(paths.shareconf):
1368         message("Setting up share.ldb")
1369         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1370                         lp=lp)
1371         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1372
1373      
1374     message("Setting up secrets.ldb")
1375     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1376                                   session_info=session_info, 
1377                                   backend_credentials=provision_backend.secrets_credentials, lp=lp)
1378
1379     message("Setting up the registry")
1380     setup_registry(paths.hklm, setup_path, session_info, 
1381                    lp=lp)
1382
1383     message("Setting up the privileges database")
1384     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1385
1386     message("Setting up idmap db")
1387     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1388                           lp=lp)
1389
1390     message("Setting up SAM db")
1391     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1392                         provision_backend, lp, names,
1393                         message, 
1394                         domainsid=domainsid, 
1395                         schema=schema, domainguid=domainguid,
1396                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1397                         fill=samdb_fill, 
1398                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1399                         invocationid=invocationid, 
1400                         machinepass=machinepass, dnspass=dnspass, 
1401                         ntdsguid=ntdsguid, serverrole=serverrole,
1402                         dom_for_fun_level=dom_for_fun_level)
1403
1404     if serverrole == "domain controller":
1405         if paths.netlogon is None:
1406             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1407             message("Please either remove %s or see the template at %s" % 
1408                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1409             assert(paths.netlogon is not None)
1410
1411         if paths.sysvol is None:
1412             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1413             message("Please either remove %s or see the template at %s" % 
1414                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1415             assert(paths.sysvol is not None)            
1416             
1417         # Set up group policies (domain policy and domain controller policy)
1418
1419         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1420                                    "{" + policyguid + "}")
1421         os.makedirs(policy_path, 0755)
1422         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1423                                    "[General]\r\nVersion=65543")
1424         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1425         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1426
1427         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1428                                    "{" + policyguid_dc + "}")
1429         os.makedirs(policy_path_dc, 0755)
1430         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1431                                    "[General]\r\nVersion=2")
1432         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1433         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1434
1435         if not os.path.isdir(paths.netlogon):
1436             os.makedirs(paths.netlogon, 0755)
1437
1438     if samdb_fill == FILL_FULL:
1439         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1440                             root_uid=root_uid, nobody_uid=nobody_uid,
1441                             users_gid=users_gid, wheel_gid=wheel_gid)
1442
1443         message("Setting up sam.ldb rootDSE marking as synchronized")
1444         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1445
1446         # Only make a zone file on the first DC, it should be replicated with DNS replication
1447         if serverrole == "domain controller":
1448             secretsdb_self_join(secrets_ldb, domain=domain,
1449                                 realm=names.realm,
1450                                 dnsdomain=names.dnsdomain,
1451                                 netbiosname=names.netbiosname,
1452                                 domainsid=domainsid, 
1453                                 machinepass=machinepass,
1454                                 secure_channel_type=SEC_CHAN_BDC)
1455
1456             secretsdb_setup_dns(secrets_ldb, setup_path, 
1457                                 realm=names.realm, dnsdomain=names.dnsdomain,
1458                                 dns_keytab_path=paths.dns_keytab,
1459                                 dnspass=dnspass)
1460
1461             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1462             assert isinstance(domainguid, str)
1463
1464             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1465                              hostip=hostip,
1466                              hostip6=hostip6, hostname=names.hostname,
1467                              realm=names.realm,
1468                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1469
1470             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1471                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1472
1473             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1474                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1475                               keytab_name=paths.dns_keytab)
1476             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1477             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1478
1479             create_krb5_conf(paths.krb5conf, setup_path,
1480                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1481                              realm=names.realm)
1482             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1483
1484     if provision_backend.post_setup is not None:
1485         provision_backend.post_setup()
1486
1487     if provision_backend.shutdown is not None:
1488         provision_backend.shutdown()
1489     
1490     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1491                                ldapi_url)
1492
1493     #Now commit the secrets.ldb to disk
1494     secrets_ldb.transaction_commit()
1495
1496     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1497
1498     message("Once the above files are installed, your Samba4 server will be ready to use")
1499     message("Server Role:           %s" % serverrole)
1500     message("Hostname:              %s" % names.hostname)
1501     message("NetBIOS Domain:        %s" % names.domain)
1502     message("DNS Domain:            %s" % names.dnsdomain)
1503     message("DOMAIN SID:            %s" % str(domainsid))
1504     if samdb_fill == FILL_FULL:
1505         message("Admin password:    %s" % adminpass)
1506     if provision_backend.type is not "ldb":
1507         if provision_backend.credentials.get_bind_dn() is not None:
1508             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1509         else:
1510             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1511
1512         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1513
1514         if provision_backend.slapd_command_escaped is not None:
1515             # now display slapd_command_file.txt to show how slapd must be started next time
1516             message("Use later the following commandline to start slapd, then Samba:")
1517             message(provision_backend.slapd_command_escaped)
1518             message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1519
1520
1521     result = ProvisionResult()
1522     result.domaindn = domaindn
1523     result.paths = paths
1524     result.lp = lp
1525     result.samdb = samdb
1526     return result
1527
1528
1529
1530 def provision_become_dc(setup_dir=None,
1531                         smbconf=None, targetdir=None, realm=None, 
1532                         rootdn=None, domaindn=None, schemadn=None,
1533                         configdn=None, serverdn=None,
1534                         domain=None, hostname=None, domainsid=None, 
1535                         adminpass=None, krbtgtpass=None, domainguid=None, 
1536                         policyguid=None, policyguid_dc=None, invocationid=None,
1537                         machinepass=None, 
1538                         dnspass=None, root=None, nobody=None, users=None, 
1539                         wheel=None, backup=None, serverrole=None, 
1540                         ldap_backend=None, ldap_backend_type=None,
1541                         sitename=None, debuglevel=1):
1542
1543     def message(text):
1544         """print a message if quiet is not set."""
1545         print text
1546
1547     glue.set_debug_level(debuglevel)
1548
1549     return provision(setup_dir, message, system_session(), None,
1550               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1551               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1552               configdn=configdn, serverdn=serverdn, domain=domain,
1553               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1554               machinepass=machinepass, serverrole="domain controller",
1555               sitename=sitename)
1556
1557
1558 def setup_db_config(setup_path, dbdir):
1559     """Setup a Berkeley database.
1560     
1561     :param setup_path: Setup path function.
1562     :param dbdir: Database directory."""
1563     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1564         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1565         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1566             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1567
1568     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1569                {"LDAPDBDIR": dbdir})
1570
1571 def ldap_backend_shutdown(self):
1572       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1573       if self.slapd.poll() is None:
1574         #Kill the slapd
1575         if hasattr(self.slapd, "terminate"):
1576           self.slapd.terminate()
1577         else:
1578           # Older python versions don't have .terminate()
1579           import signal
1580           os.kill(self.slapd.pid, signal.SIGTERM)
1581             
1582         #and now wait for it to die
1583         self.slapd.communicate()
1584
1585
1586 class ProvisionBackend(object):
1587     def __init__(self, backend_type, paths=None, setup_path=None, lp=None, credentials=None, 
1588                  names=None, message=None, 
1589                  hostname=None, root=None, 
1590                  schema=None, ldapadminpass=None,
1591                  ldap_backend_extra_port=None,
1592                  ol_mmr_urls=None, 
1593                  setup_ds_path=None, slapd_path=None, 
1594                  nosync=False, ldap_dryrun_mode=False,
1595                  domainsid=None):
1596         """Provision an LDAP backend for samba4
1597         
1598         This works for OpenLDAP and Fedora DS
1599         """
1600         self.paths = paths
1601         self.slapd_command = None
1602         self.slapd_command_escaped = None
1603
1604         self.type = backend_type
1605         
1606         # Set a default - the code for "existing" below replaces this
1607         self.ldap_backend_type = backend_type
1608
1609         self.post_setup = None
1610         self.shutdown = None
1611
1612         if self.type is "ldb":
1613             self.credentials = None
1614             self.secrets_credentials = None
1615             return
1616
1617         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1618         
1619         if self.type == "existing":
1620             #Check to see that this 'existing' LDAP backend in fact exists
1621             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1622             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1623                                                 expression="(objectClass=OpenLDAProotDSE)")
1624
1625             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1626             self.credentials = credentials
1627             # This caused them to be set into the long-term database later in the script.
1628             self.secrets_credentials = credentials
1629
1630             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1631             return
1632     
1633         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1634         # if another instance of slapd is already running 
1635         try:
1636             ldapi_db = Ldb(self.ldapi_uri)
1637             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1638                                                 expression="(objectClass=OpenLDAProotDSE)");
1639             try:
1640                 f = open(paths.slapdpid, "r")
1641                 p = f.read()
1642                 f.close()
1643                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1644             except:
1645                 pass
1646             
1647             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1648         
1649         except LdbError, e:
1650             pass
1651
1652         # Try to print helpful messages when the user has not specified the path to slapd
1653         if slapd_path is None:
1654             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1655         if not os.path.exists(slapd_path):
1656             message (slapd_path)
1657             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1658
1659
1660         if not os.path.isdir(paths.ldapdir):
1661             os.makedirs(paths.ldapdir, 0700)
1662
1663         # Put the LDIF of the schema into a database so we can search on
1664         # it to generate schema-dependent configurations in Fedora DS and
1665         # OpenLDAP
1666         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1667         try:
1668             os.unlink(schemadb_path)
1669         except OSError:
1670             pass
1671
1672         schema.write_to_tmp_ldb(schemadb_path);
1673
1674         self.credentials = Credentials()
1675         self.credentials.guess(lp)
1676         #Kerberos to an ldapi:// backend makes no sense
1677         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1678
1679         self.secrets_credentials = Credentials()
1680         self.secrets_credentials.guess(lp)
1681         #Kerberos to an ldapi:// backend makes no sense
1682         self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS)
1683
1684         self.shutdown = ldap_backend_shutdown
1685
1686         if self.type == "fedora-ds":
1687             provision_fds_backend(self, setup_path=setup_path,
1688                                   names=names, message=message, 
1689                                   hostname=hostname,
1690                                   ldapadminpass=ldapadminpass, root=root, 
1691                                   schema=schema,
1692                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1693                                   setup_ds_path=setup_ds_path,
1694                                   slapd_path=slapd_path,
1695                                   nosync=nosync,
1696                                   ldap_dryrun_mode=ldap_dryrun_mode,
1697                                   domainsid=domainsid)
1698             
1699         elif self.type == "openldap":
1700             provision_openldap_backend(self, setup_path=setup_path,
1701                                        names=names, message=message, 
1702                                        hostname=hostname,
1703                                        ldapadminpass=ldapadminpass, root=root, 
1704                                        schema=schema,
1705                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1706                                        ol_mmr_urls=ol_mmr_urls, 
1707                                        slapd_path=slapd_path,
1708                                        nosync=nosync,
1709                                        ldap_dryrun_mode=ldap_dryrun_mode)
1710         else:
1711             raise ProvisioningError("Unknown LDAP backend type selected")
1712
1713         self.credentials.set_password(ldapadminpass)
1714         self.secrets_credentials.set_username("samba-admin")
1715         self.secrets_credentials.set_password(ldapadminpass)
1716
1717         self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'"
1718         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1719                 "SLAPD_COMMAND" : slapd_command})
1720
1721         # Now start the slapd, so we can provision onto it.  We keep the
1722         # subprocess context around, to kill this off at the successful
1723         # end of the script
1724         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1725     
1726         while self.slapd.poll() is None:
1727             # Wait until the socket appears
1728             try:
1729                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1730                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1731                                                     expression="(objectClass=OpenLDAProotDSE)")
1732                 # If we have got here, then we must have a valid connection to the LDAP server!
1733                 return
1734             except LdbError, e:
1735                 time.sleep(1)
1736                 pass
1737         
1738         raise ProvisioningError("slapd died before we could make a connection to it")
1739
1740
1741 def provision_openldap_backend(result, setup_path=None, names=None,
1742                                message=None, 
1743                                hostname=None, ldapadminpass=None, root=None, 
1744                                schema=None, 
1745                                ldap_backend_extra_port=None,
1746                                ol_mmr_urls=None, 
1747                                slapd_path=None, nosync=False,
1748                                ldap_dryrun_mode=False):
1749
1750     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1751     nosync_config = ""
1752     if nosync:
1753         nosync_config = "dbnosync"
1754         
1755     lnkattr = schema.linked_attributes()
1756     refint_attributes = ""
1757     memberof_config = "# Generated from Samba4 schema\n"
1758     for att in  lnkattr.keys():
1759         if lnkattr[att] is not None:
1760             refint_attributes = refint_attributes + " " + att 
1761             
1762             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1763                                                  { "MEMBER_ATTR" : att ,
1764                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1765             
1766     refint_config = read_and_sub_file(setup_path("refint.conf"),
1767                                       { "LINK_ATTRS" : refint_attributes})
1768     
1769     attrs = ["linkID", "lDAPDisplayName"]
1770     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1771     index_config = ""
1772     for i in range (0, len(res)):
1773         index_attr = res[i]["lDAPDisplayName"][0]
1774         if index_attr == "objectGUID":
1775             index_attr = "entryUUID"
1776             
1777         index_config += "index " + index_attr + " eq\n"
1778
1779 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1780     mmr_on_config = ""
1781     mmr_replicator_acl = ""
1782     mmr_serverids_config = ""
1783     mmr_syncrepl_schema_config = "" 
1784     mmr_syncrepl_config_config = "" 
1785     mmr_syncrepl_user_config = "" 
1786        
1787     
1788     if ol_mmr_urls is not None:
1789         # For now, make these equal
1790         mmr_pass = ldapadminpass
1791         
1792         url_list=filter(None,ol_mmr_urls.split(' ')) 
1793         if (len(url_list) == 1):
1794             url_list=filter(None,ol_mmr_urls.split(',')) 
1795                      
1796             
1797             mmr_on_config = "MirrorMode On"
1798             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1799             serverid=0
1800             for url in url_list:
1801                 serverid=serverid+1
1802                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1803                                                           { "SERVERID" : str(serverid),
1804                                                             "LDAPSERVER" : url })
1805                 rid=serverid*10
1806                 rid=rid+1
1807                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1808                                                                 {  "RID" : str(rid),
1809                                                                    "MMRDN": names.schemadn,
1810                                                                    "LDAPSERVER" : url,
1811                                                                    "MMR_PASSWORD": mmr_pass})
1812                 
1813                 rid=rid+1
1814                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1815                                                                 {  "RID" : str(rid),
1816                                                                    "MMRDN": names.configdn,
1817                                                                    "LDAPSERVER" : url,
1818                                                                    "MMR_PASSWORD": mmr_pass})
1819                 
1820                 rid=rid+1
1821                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1822                                                               {  "RID" : str(rid),
1823                                                                  "MMRDN": names.domaindn,
1824                                                                  "LDAPSERVER" : url,
1825                                                                  "MMR_PASSWORD": mmr_pass })
1826     # OpenLDAP cn=config initialisation
1827     olc_syncrepl_config = ""
1828     olc_mmr_config = "" 
1829     # if mmr = yes, generate cn=config-replication directives
1830     # and olc_seed.lif for the other mmr-servers
1831     if ol_mmr_urls is not None:
1832         serverid=0
1833         olc_serverids_config = ""
1834         olc_syncrepl_seed_config = ""
1835         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1836         rid=1000
1837         for url in url_list:
1838             serverid=serverid+1
1839             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1840                                                       { "SERVERID" : str(serverid),
1841                                                         "LDAPSERVER" : url })
1842             
1843             rid=rid+1
1844             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1845                                                      {  "RID" : str(rid),
1846                                                         "LDAPSERVER" : url,
1847                                                         "MMR_PASSWORD": mmr_pass})
1848             
1849             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1850                                                           {  "RID" : str(rid),
1851                                                              "LDAPSERVER" : url})
1852                 
1853         setup_file(setup_path("olc_seed.ldif"), result.paths.olcseedldif,
1854                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1855                     "OLC_PW": ldapadminpass,
1856                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1857     # end olc
1858                 
1859     setup_file(setup_path("slapd.conf"), result.paths.slapdconf,
1860                {"DNSDOMAIN": names.dnsdomain,
1861                 "LDAPDIR": result.paths.ldapdir,
1862                 "DOMAINDN": names.domaindn,
1863                 "CONFIGDN": names.configdn,
1864                 "SCHEMADN": names.schemadn,
1865                 "MEMBEROF_CONFIG": memberof_config,
1866                 "MIRRORMODE": mmr_on_config,
1867                 "REPLICATOR_ACL": mmr_replicator_acl,
1868                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1869                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1870                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1871                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1872                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1873                 "OLC_MMR_CONFIG": olc_mmr_config,
1874                 "REFINT_CONFIG": refint_config,
1875                 "INDEX_CONFIG": index_config,
1876                 "NOSYNC": nosync_config})
1877         
1878     setup_db_config(setup_path, os.path.join(result.paths.ldapdir, "db", "user"))
1879     setup_db_config(setup_path, os.path.join(result.paths.ldapdir, "db", "config"))
1880     setup_db_config(setup_path, os.path.join(result.paths.ldapdir, "db", "schema"))
1881     
1882     if not os.path.exists(os.path.join(result.paths.ldapdir, "db", "samba",  "cn=samba")):
1883         os.makedirs(os.path.join(result.paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1884         
1885     setup_file(setup_path("cn=samba.ldif"), 
1886                os.path.join(result.paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1887                { "UUID": str(uuid.uuid4()), 
1888                  "LDAPTIME": timestring(int(time.time()))} )
1889     setup_file(setup_path("cn=samba-admin.ldif"), 
1890                os.path.join(result.paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1891                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1892                 "UUID": str(uuid.uuid4()), 
1893                 "LDAPTIME": timestring(int(time.time()))} )
1894     
1895     if ol_mmr_urls is not None:
1896         setup_file(setup_path("cn=replicator.ldif"),
1897                    os.path.join(result.paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1898                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1899                     "UUID": str(uuid.uuid4()),
1900                     "LDAPTIME": timestring(int(time.time()))} )
1901         
1902
1903     mapping = "schema-map-openldap-2.3"
1904     backend_schema = "backend-schema.schema"
1905
1906     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1907     assert backend_schema_data is not None
1908     open(os.path.join(result.paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1909
1910     # now we generate the needed strings to start slapd automatically,
1911     # first ldapi_uri...
1912     if ldap_backend_extra_port is not None:
1913         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1914         # specified there as part of it's clue as to it's own name,
1915         # and not to replicate to itself
1916         if ol_mmr_urls is None:
1917             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1918         else:
1919             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1920     else:
1921         server_port_string = ""
1922
1923     # Prepare the 'result' information - the commands to return in particular
1924     result.slapd_provision_command = [slapd_path]
1925
1926     result.slapd_provision_command.append("-F" + result.paths.olcdir)
1927
1928     result.slapd_provision_command.append("-h")
1929
1930     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1931     result.slapd_command = list(result.slapd_provision_command)
1932     
1933     result.slapd_provision_command.append(result.ldapi_uri)
1934     result.slapd_provision_command.append("-d0")
1935
1936     uris = result.ldapi_uri
1937     if server_port_string is not "":
1938         uris = uris + " " + server_port_string
1939
1940     result.slapd_command.append(uris)
1941
1942     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1943     result.credentials.set_username("samba-admin")
1944     
1945     # If we were just looking for crashes up to this point, it's a
1946     # good time to exit before we realise we don't have OpenLDAP on
1947     # this system
1948     if ldap_dryrun_mode:
1949         sys.exit(0)
1950
1951     # Finally, convert the configuration into cn=config style!
1952     if not os.path.isdir(result.paths.olcdir):
1953         os.makedirs(result.paths.olcdir, 0770)
1954
1955         retcode = subprocess.call([slapd_path, "-Ttest", "-f", result.paths.slapdconf, "-F", result.paths.olcdir], close_fds=True, shell=False)
1956
1957 #        We can't do this, as OpenLDAP is strange.  It gives an error
1958 #        output to the above, but does the conversion sucessfully...
1959 #
1960 #        if retcode != 0:
1961 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1962
1963         if not os.path.exists(os.path.join(result.paths.olcdir, "cn=config.ldif")):
1964             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1965
1966         # Don't confuse the admin by leaving the slapd.conf around
1967         os.remove(result.paths.slapdconf)        
1968
1969
1970 def provision_fds_backend(result, setup_path=None, names=None,
1971                           message=None, 
1972                           hostname=None, ldapadminpass=None, root=None, 
1973                           schema=None,
1974                           ldap_backend_extra_port=None,
1975                           setup_ds_path=None,
1976                           slapd_path=None,
1977                           nosync=False, 
1978                           ldap_dryrun_mode=False,
1979                           domainsid=None):
1980
1981     if ldap_backend_extra_port is not None:
1982         serverport = "ServerPort=%d" % ldap_backend_extra_port
1983     else:
1984         serverport = ""
1985         
1986     setup_file(setup_path("fedorads.inf"), result.paths.fedoradsinf, 
1987                {"ROOT": root,
1988                 "HOSTNAME": hostname,
1989                 "DNSDOMAIN": names.dnsdomain,
1990                 "LDAPDIR": result.paths.ldapdir,
1991                 "DOMAINDN": names.domaindn,
1992                 "LDAPMANAGERDN": names.ldapmanagerdn,
1993                 "LDAPMANAGERPASS": ldapadminpass, 
1994                 "SERVERPORT": serverport})
1995
1996     setup_file(setup_path("fedorads-partitions.ldif"), result.paths.fedoradspartitions, 
1997                {"CONFIGDN": names.configdn,
1998                 "SCHEMADN": names.schemadn,
1999                 "SAMBADN": names.sambadn,
2000                 })
2001
2002     setup_file(setup_path("fedorads-sasl.ldif"), result.paths.fedoradssasl, 
2003                {"SAMBADN": names.sambadn,
2004                 })
2005
2006     setup_file(setup_path("fedorads-dna.ldif"), result.paths.fedoradsdna, 
2007                {"DOMAINDN": names.domaindn,
2008                 "SAMBADN": names.sambadn,
2009                 "DOMAINSID": str(domainsid),
2010                 })
2011
2012     setup_file(setup_path("fedorads-pam.ldif"), result.paths.fedoradspam)
2013
2014     lnkattr = schema.linked_attributes()
2015
2016     refint_config = data = open(setup_path("fedorads-refint-delete.ldif"), 'r').read()
2017     memberof_config = ""
2018     index_config = ""
2019     argnum = 3
2020
2021     for attr in lnkattr.keys():
2022         if lnkattr[attr] is not None:
2023             refint_config += read_and_sub_file(setup_path("fedorads-refint-add.ldif"),
2024                                                  { "ARG_NUMBER" : str(argnum) ,
2025                                                    "LINK_ATTR" : attr })
2026             memberof_config += read_and_sub_file(setup_path("fedorads-linked-attributes.ldif"),
2027                                                  { "MEMBER_ATTR" : attr ,
2028                                                    "MEMBEROF_ATTR" : lnkattr[attr] })
2029             index_config += read_and_sub_file(setup_path("fedorads-index.ldif"),
2030                                                  { "ATTR" : attr })
2031             argnum += 1
2032
2033     open(result.paths.fedoradsrefint, 'w').write(refint_config)
2034     open(result.paths.fedoradslinkedattributes, 'w').write(memberof_config)
2035
2036     attrs = ["lDAPDisplayName"]
2037     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
2038
2039     for i in range (0, len(res)):
2040         attr = res[i]["lDAPDisplayName"][0]
2041
2042         if attr == "objectGUID":
2043             attr = "nsUniqueId"
2044
2045         index_config += read_and_sub_file(setup_path("fedorads-index.ldif"),
2046                                              { "ATTR" : attr })
2047
2048     open(result.paths.fedoradsindex, 'w').write(index_config)
2049
2050     setup_file(setup_path("fedorads-samba.ldif"), result.paths.fedoradssamba,
2051                 {"SAMBADN": names.sambadn, 
2052                  "LDAPADMINPASS": ldapadminpass
2053                 })
2054
2055     mapping = "schema-map-fedora-ds-1.0"
2056     backend_schema = "99_ad.ldif"
2057     
2058     # Build a schema file in Fedora DS format
2059     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
2060     assert backend_schema_data is not None
2061     open(os.path.join(result.paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
2062
2063     result.credentials.set_bind_dn(names.ldapmanagerdn)
2064
2065     # Destory the target directory, or else setup-ds.pl will complain
2066     fedora_ds_dir = os.path.join(result.paths.ldapdir, "slapd-samba4")
2067     shutil.rmtree(fedora_ds_dir, True)
2068
2069     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", result.paths.slapdpid];
2070     #In the 'provision' command line, stay in the foreground so we can easily kill it
2071     result.slapd_provision_command.append("-d0")
2072
2073     #the command for the final run is the normal script
2074     result.slapd_command = [os.path.join(result.paths.ldapdir, "slapd-samba4", "start-slapd")]
2075
2076     # If we were just looking for crashes up to this point, it's a
2077     # good time to exit before we realise we don't have Fedora DS on
2078     if ldap_dryrun_mode:
2079         sys.exit(0)
2080
2081     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
2082     if setup_ds_path is None:
2083         raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
2084     if not os.path.exists(setup_ds_path):
2085         message (setup_ds_path)
2086         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
2087
2088     # Run the Fedora DS setup utility
2089     retcode = subprocess.call([setup_ds_path, "--silent", "--file", result.paths.fedoradsinf], close_fds=True, shell=False)
2090     if retcode != 0:
2091         raise ProvisioningError("setup-ds failed")
2092
2093     # Load samba-admin
2094     retcode = subprocess.call([
2095         os.path.join(result.paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", result.paths.fedoradssamba],
2096         close_fds=True, shell=False)
2097     if retcode != 0:
2098         raise("ldib2db failed")
2099
2100     # Leave a hook to do the 'post initilisation' setup
2101     def fds_post_setup(self):
2102         ldapi_db = Ldb(self.ldapi_uri, credentials=self.credentials)
2103
2104         # delete default SASL mappings
2105         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
2106     
2107         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
2108         for i in range (0, len(res)):
2109             dn = str(res[i]["dn"])
2110             ldapi_db.delete(dn)
2111             
2112             aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
2113         
2114             m = ldb.Message()
2115             m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
2116
2117             m.dn = ldb.Dn(1, names.domaindn)
2118             ldapi_db.modify(m)
2119             
2120             m.dn = ldb.Dn(1, names.configdn)
2121             ldapi_db.modify(m)
2122             
2123             m.dn = ldb.Dn(1, names.schemadn)
2124             ldapi_db.modify(m)
2125             
2126     result.post_setup = fds_post_setup
2127     
2128
2129 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
2130     """Create a PHP LDAP admin configuration file.
2131
2132     :param path: Path to write the configuration to.
2133     :param setup_path: Function to generate setup paths.
2134     """
2135     setup_file(setup_path("phpldapadmin-config.php"), path, 
2136             {"S4_LDAPI_URI": ldapi_uri})
2137
2138
2139 def create_zone_file(path, setup_path, dnsdomain, 
2140                      hostip, hostip6, hostname, realm, domainguid,
2141                      ntdsguid):
2142     """Write out a DNS zone file, from the info in the current database.
2143
2144     :param path: Path of the new zone file.
2145     :param setup_path: Setup path function.
2146     :param dnsdomain: DNS Domain name
2147     :param domaindn: DN of the Domain
2148     :param hostip: Local IPv4 IP
2149     :param hostip6: Local IPv6 IP
2150     :param hostname: Local hostname
2151     :param realm: Realm name
2152     :param domainguid: GUID of the domain.
2153     :param ntdsguid: GUID of the hosts nTDSDSA record.
2154     """
2155     assert isinstance(domainguid, str)
2156
2157     if hostip6 is not None:
2158         hostip6_base_line = "            IN AAAA    " + hostip6
2159         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
2160     else:
2161         hostip6_base_line = ""
2162         hostip6_host_line = ""
2163
2164     if hostip is not None:
2165         hostip_base_line = "            IN A    " + hostip
2166         hostip_host_line = hostname + "        IN A    " + hostip
2167     else:
2168         hostip_base_line = ""
2169         hostip_host_line = ""
2170
2171     setup_file(setup_path("provision.zone"), path, {
2172             "HOSTNAME": hostname,
2173             "DNSDOMAIN": dnsdomain,
2174             "REALM": realm,
2175             "HOSTIP_BASE_LINE": hostip_base_line,
2176             "HOSTIP_HOST_LINE": hostip_host_line,
2177             "DOMAINGUID": domainguid,
2178             "DATESTRING": time.strftime("%Y%m%d%H"),
2179             "DEFAULTSITE": DEFAULTSITE,
2180             "NTDSGUID": ntdsguid,
2181             "HOSTIP6_BASE_LINE": hostip6_base_line,
2182             "HOSTIP6_HOST_LINE": hostip6_host_line,
2183         })
2184
2185
2186 def create_named_conf(path, setup_path, realm, dnsdomain,
2187                       private_dir):
2188     """Write out a file containing zone statements suitable for inclusion in a
2189     named.conf file (including GSS-TSIG configuration).
2190     
2191     :param path: Path of the new named.conf file.
2192     :param setup_path: Setup path function.
2193     :param realm: Realm name
2194     :param dnsdomain: DNS Domain name
2195     :param private_dir: Path to private directory
2196     :param keytab_name: File name of DNS keytab file
2197     """
2198
2199     setup_file(setup_path("named.conf"), path, {
2200             "DNSDOMAIN": dnsdomain,
2201             "REALM": realm,
2202             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
2203             "PRIVATE_DIR": private_dir
2204             })
2205
2206 def create_named_txt(path, setup_path, realm, dnsdomain,
2207                       private_dir, keytab_name):
2208     """Write out a file containing zone statements suitable for inclusion in a
2209     named.conf file (including GSS-TSIG configuration).
2210     
2211     :param path: Path of the new named.conf file.
2212     :param setup_path: Setup path function.
2213     :param realm: Realm name
2214     :param dnsdomain: DNS Domain name
2215     :param private_dir: Path to private directory
2216     :param keytab_name: File name of DNS keytab file
2217     """
2218
2219     setup_file(setup_path("named.txt"), path, {
2220             "DNSDOMAIN": dnsdomain,
2221             "REALM": realm,
2222             "DNS_KEYTAB": keytab_name,
2223             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
2224             "PRIVATE_DIR": private_dir
2225         })
2226
2227 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
2228     """Write out a file containing zone statements suitable for inclusion in a
2229     named.conf file (including GSS-TSIG configuration).
2230     
2231     :param path: Path of the new named.conf file.
2232     :param setup_path: Setup path function.
2233     :param dnsdomain: DNS Domain name
2234     :param hostname: Local hostname
2235     :param realm: Realm name
2236     """
2237
2238     setup_file(setup_path("krb5.conf"), path, {
2239             "DNSDOMAIN": dnsdomain,
2240             "HOSTNAME": hostname,
2241             "REALM": realm,
2242         })
2243
2244