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