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