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