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