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