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