s4:provision - Moved provision_xxx_backend() into backend-specific provision() method.
[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 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     elif backend_type == "ldb" or backend_type == "existing":
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     else:
1279         raise ProvisioningError("Unknown LDAP backend type selected")
1280
1281     provision_backend.provision()
1282     provision_backend.start()
1283
1284     # only install a new shares config db if there is none
1285     if not os.path.exists(paths.shareconf):
1286         message("Setting up share.ldb")
1287         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1288                         lp=lp)
1289         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1290
1291      
1292     message("Setting up secrets.ldb")
1293     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1294                                   session_info=session_info, 
1295                                   backend_credentials=provision_backend.secrets_credentials, lp=lp)
1296
1297     message("Setting up the registry")
1298     setup_registry(paths.hklm, setup_path, session_info, 
1299                    lp=lp)
1300
1301     message("Setting up the privileges database")
1302     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1303
1304     message("Setting up idmap db")
1305     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1306                           lp=lp)
1307
1308     message("Setting up SAM db")
1309     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1310                         provision_backend, lp, names,
1311                         message, 
1312                         domainsid=domainsid, 
1313                         schema=schema, domainguid=domainguid,
1314                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1315                         fill=samdb_fill, 
1316                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1317                         invocationid=invocationid, 
1318                         machinepass=machinepass, dnspass=dnspass, 
1319                         ntdsguid=ntdsguid, serverrole=serverrole,
1320                         dom_for_fun_level=dom_for_fun_level)
1321
1322     if serverrole == "domain controller":
1323         if paths.netlogon is None:
1324             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1325             message("Please either remove %s or see the template at %s" % 
1326                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1327             assert(paths.netlogon is not None)
1328
1329         if paths.sysvol is None:
1330             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1331             message("Please either remove %s or see the template at %s" % 
1332                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1333             assert(paths.sysvol is not None)            
1334             
1335         # Set up group policies (domain policy and domain controller policy)
1336
1337         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1338                                    "{" + policyguid + "}")
1339         os.makedirs(policy_path, 0755)
1340         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1341                                    "[General]\r\nVersion=65543")
1342         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1343         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1344
1345         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1346                                    "{" + policyguid_dc + "}")
1347         os.makedirs(policy_path_dc, 0755)
1348         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1349                                    "[General]\r\nVersion=2")
1350         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1351         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1352
1353         if not os.path.isdir(paths.netlogon):
1354             os.makedirs(paths.netlogon, 0755)
1355
1356     if samdb_fill == FILL_FULL:
1357         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1358                             root_uid=root_uid, nobody_uid=nobody_uid,
1359                             users_gid=users_gid, wheel_gid=wheel_gid)
1360
1361         message("Setting up sam.ldb rootDSE marking as synchronized")
1362         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1363
1364         # Only make a zone file on the first DC, it should be replicated with DNS replication
1365         if serverrole == "domain controller":
1366             secretsdb_self_join(secrets_ldb, domain=domain,
1367                                 realm=names.realm,
1368                                 dnsdomain=names.dnsdomain,
1369                                 netbiosname=names.netbiosname,
1370                                 domainsid=domainsid, 
1371                                 machinepass=machinepass,
1372                                 secure_channel_type=SEC_CHAN_BDC)
1373
1374             secretsdb_setup_dns(secrets_ldb, setup_path, 
1375                                 realm=names.realm, dnsdomain=names.dnsdomain,
1376                                 dns_keytab_path=paths.dns_keytab,
1377                                 dnspass=dnspass)
1378
1379             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1380             assert isinstance(domainguid, str)
1381
1382             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1383                              hostip=hostip,
1384                              hostip6=hostip6, hostname=names.hostname,
1385                              realm=names.realm,
1386                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1387
1388             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1389                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1390
1391             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1392                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1393                               keytab_name=paths.dns_keytab)
1394             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1395             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1396
1397             create_krb5_conf(paths.krb5conf, setup_path,
1398                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1399                              realm=names.realm)
1400             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1401
1402     provision_backend.post_setup()
1403     provision_backend.shutdown()
1404     
1405     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1406                                ldapi_url)
1407
1408     #Now commit the secrets.ldb to disk
1409     secrets_ldb.transaction_commit()
1410
1411     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1412
1413     message("Once the above files are installed, your Samba4 server will be ready to use")
1414     message("Server Role:           %s" % serverrole)
1415     message("Hostname:              %s" % names.hostname)
1416     message("NetBIOS Domain:        %s" % names.domain)
1417     message("DNS Domain:            %s" % names.dnsdomain)
1418     message("DOMAIN SID:            %s" % str(domainsid))
1419     if samdb_fill == FILL_FULL:
1420         message("Admin password:    %s" % adminpass)
1421     if provision_backend.type is not "ldb":
1422         if provision_backend.credentials.get_bind_dn() is not None:
1423             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1424         else:
1425             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1426
1427         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1428
1429         if provision_backend.slapd_command_escaped is not None:
1430             # now display slapd_command_file.txt to show how slapd must be started next time
1431             message("Use later the following commandline to start slapd, then Samba:")
1432             message(provision_backend.slapd_command_escaped)
1433             message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1434
1435
1436     result = ProvisionResult()
1437     result.domaindn = domaindn
1438     result.paths = paths
1439     result.lp = lp
1440     result.samdb = samdb
1441     return result
1442
1443
1444
1445 def provision_become_dc(setup_dir=None,
1446                         smbconf=None, targetdir=None, realm=None, 
1447                         rootdn=None, domaindn=None, schemadn=None,
1448                         configdn=None, serverdn=None,
1449                         domain=None, hostname=None, domainsid=None, 
1450                         adminpass=None, krbtgtpass=None, domainguid=None, 
1451                         policyguid=None, policyguid_dc=None, invocationid=None,
1452                         machinepass=None, 
1453                         dnspass=None, root=None, nobody=None, users=None, 
1454                         wheel=None, backup=None, serverrole=None, 
1455                         ldap_backend=None, ldap_backend_type=None,
1456                         sitename=None, debuglevel=1):
1457
1458     def message(text):
1459         """print a message if quiet is not set."""
1460         print text
1461
1462     glue.set_debug_level(debuglevel)
1463
1464     return provision(setup_dir, message, system_session(), None,
1465               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1466               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1467               configdn=configdn, serverdn=serverdn, domain=domain,
1468               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1469               machinepass=machinepass, serverrole="domain controller",
1470               sitename=sitename)
1471
1472
1473 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1474     """Create a PHP LDAP admin configuration file.
1475
1476     :param path: Path to write the configuration to.
1477     :param setup_path: Function to generate setup paths.
1478     """
1479     setup_file(setup_path("phpldapadmin-config.php"), path, 
1480             {"S4_LDAPI_URI": ldapi_uri})
1481
1482
1483 def create_zone_file(path, setup_path, dnsdomain, 
1484                      hostip, hostip6, hostname, realm, domainguid,
1485                      ntdsguid):
1486     """Write out a DNS zone file, from the info in the current database.
1487
1488     :param path: Path of the new zone file.
1489     :param setup_path: Setup path function.
1490     :param dnsdomain: DNS Domain name
1491     :param domaindn: DN of the Domain
1492     :param hostip: Local IPv4 IP
1493     :param hostip6: Local IPv6 IP
1494     :param hostname: Local hostname
1495     :param realm: Realm name
1496     :param domainguid: GUID of the domain.
1497     :param ntdsguid: GUID of the hosts nTDSDSA record.
1498     """
1499     assert isinstance(domainguid, str)
1500
1501     if hostip6 is not None:
1502         hostip6_base_line = "            IN AAAA    " + hostip6
1503         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1504     else:
1505         hostip6_base_line = ""
1506         hostip6_host_line = ""
1507
1508     if hostip is not None:
1509         hostip_base_line = "            IN A    " + hostip
1510         hostip_host_line = hostname + "        IN A    " + hostip
1511     else:
1512         hostip_base_line = ""
1513         hostip_host_line = ""
1514
1515     setup_file(setup_path("provision.zone"), path, {
1516             "HOSTNAME": hostname,
1517             "DNSDOMAIN": dnsdomain,
1518             "REALM": realm,
1519             "HOSTIP_BASE_LINE": hostip_base_line,
1520             "HOSTIP_HOST_LINE": hostip_host_line,
1521             "DOMAINGUID": domainguid,
1522             "DATESTRING": time.strftime("%Y%m%d%H"),
1523             "DEFAULTSITE": DEFAULTSITE,
1524             "NTDSGUID": ntdsguid,
1525             "HOSTIP6_BASE_LINE": hostip6_base_line,
1526             "HOSTIP6_HOST_LINE": hostip6_host_line,
1527         })
1528
1529
1530 def create_named_conf(path, setup_path, realm, dnsdomain,
1531                       private_dir):
1532     """Write out a file containing zone statements suitable for inclusion in a
1533     named.conf file (including GSS-TSIG configuration).
1534     
1535     :param path: Path of the new named.conf file.
1536     :param setup_path: Setup path function.
1537     :param realm: Realm name
1538     :param dnsdomain: DNS Domain name
1539     :param private_dir: Path to private directory
1540     :param keytab_name: File name of DNS keytab file
1541     """
1542
1543     setup_file(setup_path("named.conf"), path, {
1544             "DNSDOMAIN": dnsdomain,
1545             "REALM": realm,
1546             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1547             "PRIVATE_DIR": private_dir
1548             })
1549
1550 def create_named_txt(path, setup_path, realm, dnsdomain,
1551                       private_dir, keytab_name):
1552     """Write out a file containing zone statements suitable for inclusion in a
1553     named.conf file (including GSS-TSIG configuration).
1554     
1555     :param path: Path of the new named.conf file.
1556     :param setup_path: Setup path function.
1557     :param realm: Realm name
1558     :param dnsdomain: DNS Domain name
1559     :param private_dir: Path to private directory
1560     :param keytab_name: File name of DNS keytab file
1561     """
1562
1563     setup_file(setup_path("named.txt"), path, {
1564             "DNSDOMAIN": dnsdomain,
1565             "REALM": realm,
1566             "DNS_KEYTAB": keytab_name,
1567             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1568             "PRIVATE_DIR": private_dir
1569         })
1570
1571 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1572     """Write out a file containing zone statements suitable for inclusion in a
1573     named.conf file (including GSS-TSIG configuration).
1574     
1575     :param path: Path of the new named.conf file.
1576     :param setup_path: Setup path function.
1577     :param dnsdomain: DNS Domain name
1578     :param hostname: Local hostname
1579     :param realm: Realm name
1580     """
1581
1582     setup_file(setup_path("krb5.conf"), path, {
1583             "DNSDOMAIN": dnsdomain,
1584             "HOSTNAME": hostname,
1585             "REALM": realm,
1586         })
1587
1588