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