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