s4:dsdb rework instanceType module - put instanceType in provision
[samba.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #   
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #   
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 """Functions for setting up a Samba configuration."""
27
28 from base64 import b64encode
29 import os
30 import sys
31 import pwd
32 import grp
33 import time
34 import uuid, glue
35 import socket
36 import param
37 import registry
38 import samba
39 import subprocess
40 import ldb
41
42 import shutil
43 from credentials import Credentials, DONT_USE_KERBEROS
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name
46 from samba import check_all_substituted
47 from samba import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008
48 from samba.samdb import SamDB
49 from samba.idmap import IDmapDB
50 from samba.dcerpc import security
51 from samba.ndr import ndr_pack
52 import urllib
53 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
54 from ms_schema import read_ms_schema
55 from ms_display_specifiers import read_ms_ldif
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 def get_schema_descriptor(domain_sid):
77     sddl = "O:SAG:SAD:(A;CI;RPLCLORC;;;AU)(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \
78            "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
79            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
80            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
81            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
82            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
83            "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \
84            "(AU;CISA;WP;;;WD)(AU;SA;CR;;;BA)" \
85            "(AU;SA;CR;;;DU)(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \
86            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
87     sec = security.descriptor.from_sddl(sddl, domain_sid)
88     return b64encode(ndr_pack(sec))
89
90 def get_config_descriptor(domain_sid):
91     sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
92            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
93            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
94            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
95            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
96            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
97            "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
98            "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
99            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
100            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
101            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
102            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
103            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;S-1-5-21-3191434175-1265308384-3577286990-498)" \
104            "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
105            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
106     sec = security.descriptor.from_sddl(sddl, domain_sid)
107     return b64encode(ndr_pack(sec))
108
109
110 DEFAULTSITE = "Default-First-Site-Name"
111
112 # Exception classes
113
114 class ProvisioningError(Exception):
115     """A generic provision error."""
116
117 class InvalidNetbiosName(Exception):
118     """A specified name was not a valid NetBIOS name."""
119     def __init__(self, name):
120         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
121
122
123 class ProvisionPaths(object):
124     def __init__(self):
125         self.shareconf = None
126         self.hklm = None
127         self.hkcu = None
128         self.hkcr = None
129         self.hku = None
130         self.hkpd = None
131         self.hkpt = None
132         self.samdb = None
133         self.idmapdb = None
134         self.secrets = None
135         self.keytab = None
136         self.dns_keytab = None
137         self.dns = None
138         self.winsdb = None
139         self.private_dir = None
140         self.ldapdir = None
141         self.slapdconf = None
142         self.modulesconf = None
143         self.memberofconf = None
144         self.fedoradsinf = None
145         self.fedoradspartitions = None
146         self.fedoradssasl = None
147         self.olmmron = None
148         self.olmmrserveridsconf = None
149         self.olmmrsyncreplconf = None
150         self.olcdir = None
151         self.olslapd = None
152         self.olcseedldif = None
153
154
155 class ProvisionNames(object):
156     def __init__(self):
157         self.rootdn = None
158         self.domaindn = None
159         self.configdn = None
160         self.schemadn = None
161         self.sambadn = None
162         self.ldapmanagerdn = None
163         self.dnsdomain = None
164         self.realm = None
165         self.netbiosname = None
166         self.domain = None
167         self.hostname = None
168         self.sitename = None
169         self.smbconf = None
170     
171
172 class ProvisionResult(object):
173     def __init__(self):
174         self.paths = None
175         self.domaindn = None
176         self.lp = None
177         self.samdb = None
178         
179 class Schema(object):
180     def __init__(self, setup_path, domain_sid, schemadn=None,
181                  serverdn=None, sambadn=None, ldap_backend_type=None):
182         """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
183         
184         :param samdb: Load a schema into a SamDB.
185         :param setup_path: Setup path function.
186         :param schemadn: DN of the schema
187         :param serverdn: DN of the server
188         
189         Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
190         """
191         
192         self.ldb = Ldb()
193         self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
194                                           setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
195         self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
196         self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
197         check_all_substituted(self.schema_data)
198
199         self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
200                                                   {"SCHEMADN": schemadn,
201                                                    "SERVERDN": serverdn,
202                                                    })
203
204         descr = get_schema_descriptor(domain_sid)
205         self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
206                                                {"SCHEMADN": schemadn,
207                                                 "DESCRIPTOR": descr
208                                                 })
209
210         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
211         prefixmap = b64encode(prefixmap)
212
213         
214
215         # We don't actually add this ldif, just parse it
216         prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
217         self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
218
219
220 # Return a hash with the forward attribute as a key and the back as the value 
221 def get_linked_attributes(schemadn,schemaldb):
222     attrs = ["linkID", "lDAPDisplayName"]
223     res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
224     attributes = {}
225     for i in range (0, len(res)):
226         expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
227         target = schemaldb.searchone(basedn=schemadn, 
228                                      expression=expression, 
229                                      attribute="lDAPDisplayName", 
230                                      scope=SCOPE_SUBTREE)
231         if target is not None:
232             attributes[str(res[i]["lDAPDisplayName"])]=str(target)
233             
234     return attributes
235
236 def get_dnsyntax_attributes(schemadn,schemaldb):
237     attrs = ["linkID", "lDAPDisplayName"]
238     res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
239     attributes = []
240     for i in range (0, len(res)):
241         attributes.append(str(res[i]["lDAPDisplayName"]))
242         
243     return attributes
244     
245     
246 def check_install(lp, session_info, credentials):
247     """Check whether the current install seems ok.
248     
249     :param lp: Loadparm context
250     :param session_info: Session information
251     :param credentials: Credentials
252     """
253     if lp.get("realm") == "":
254         raise Exception("Realm empty")
255     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
256             credentials=credentials, lp=lp)
257     if len(ldb.search("(cn=Administrator)")) != 1:
258         raise ProvisioningError("No administrator account found")
259
260
261 def findnss(nssfn, names):
262     """Find a user or group from a list of possibilities.
263     
264     :param nssfn: NSS Function to try (should raise KeyError if not found)
265     :param names: Names to check.
266     :return: Value return by first names list.
267     """
268     for name in names:
269         try:
270             return nssfn(name)
271         except KeyError:
272             pass
273     raise KeyError("Unable to find user/group %r" % names)
274
275
276 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
277 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
278
279
280 def read_and_sub_file(file, subst_vars):
281     """Read a file and sub in variables found in it
282     
283     :param file: File to be read (typically from setup directory)
284      param subst_vars: Optional variables to subsitute in the file.
285     """
286     data = open(file, 'r').read()
287     if subst_vars is not None:
288         data = substitute_var(data, subst_vars)
289     check_all_substituted(data)
290     return data
291
292
293 def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
294     """Setup a ldb in the private dir.
295     
296     :param ldb: LDB file to import data into
297     :param ldif_path: Path of the LDIF file to load
298     :param subst_vars: Optional variables to subsitute in LDIF.
299     :param nocontrols: Optional list of controls, can be None for no controls
300     """
301     assert isinstance(ldif_path, str)
302     data = read_and_sub_file(ldif_path, subst_vars)
303     ldb.add_ldif(data,controls)
304
305
306 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
307     """Modify a ldb in the private dir.
308     
309     :param ldb: LDB object.
310     :param ldif_path: LDIF file path.
311     :param subst_vars: Optional dictionary with substitution variables.
312     """
313     data = read_and_sub_file(ldif_path, subst_vars)
314
315     ldb.modify_ldif(data)
316
317
318 def setup_ldb(ldb, ldif_path, subst_vars):
319     """Import a LDIF a file into a LDB handle, optionally substituting variables.
320
321     :note: Either all LDIF data will be added or none (using transactions).
322
323     :param ldb: LDB file to import into.
324     :param ldif_path: Path to the LDIF file.
325     :param subst_vars: Dictionary with substitution variables.
326     """
327     assert ldb is not None
328     ldb.transaction_start()
329     try:
330         setup_add_ldif(ldb, ldif_path, subst_vars)
331     except:
332         ldb.transaction_cancel()
333         raise
334     ldb.transaction_commit()
335
336
337 def setup_file(template, fname, subst_vars):
338     """Setup a file in the private dir.
339
340     :param template: Path of the template file.
341     :param fname: Path of the file to create.
342     :param subst_vars: Substitution variables.
343     """
344     f = fname
345
346     if os.path.exists(f):
347         os.unlink(f)
348
349     data = read_and_sub_file(template, subst_vars)
350     open(f, 'w').write(data)
351
352
353 def provision_paths_from_lp(lp, dnsdomain):
354     """Set the default paths for provisioning.
355
356     :param lp: Loadparm context.
357     :param dnsdomain: DNS Domain name
358     """
359     paths = ProvisionPaths()
360     paths.private_dir = lp.get("private dir")
361     paths.dns_keytab = "dns.keytab"
362
363     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
364     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
365     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
366     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
367     paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
368     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
369     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
370     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
371     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
372     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
373     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
374                                             "phpldapadmin-config.php")
375     paths.ldapdir = os.path.join(paths.private_dir, 
376                                  "ldap")
377     paths.slapdconf = os.path.join(paths.ldapdir, 
378                                    "slapd.conf")
379     paths.slapdpid = os.path.join(paths.ldapdir, 
380                                    "slapd.pid")
381     paths.modulesconf = os.path.join(paths.ldapdir, 
382                                      "modules.conf")
383     paths.memberofconf = os.path.join(paths.ldapdir, 
384                                       "memberof.conf")
385     paths.fedoradsinf = os.path.join(paths.ldapdir, 
386                                      "fedorads.inf")
387     paths.fedoradspartitions = os.path.join(paths.ldapdir, 
388                                             "fedorads-partitions.ldif")
389     paths.fedoradssasl = os.path.join(paths.ldapdir, 
390                                       "fedorads-sasl.ldif")
391     paths.fedoradssamba = os.path.join(paths.ldapdir, 
392                                         "fedorads-samba.ldif")
393     paths.olmmrserveridsconf = os.path.join(paths.ldapdir, 
394                                             "mmr_serverids.conf")
395     paths.olmmrsyncreplconf = os.path.join(paths.ldapdir, 
396                                            "mmr_syncrepl.conf")
397     paths.olcdir = os.path.join(paths.ldapdir, 
398                                  "slapd.d")
399     paths.olcseedldif = os.path.join(paths.ldapdir, 
400                                  "olc_seed.ldif")
401     paths.hklm = "hklm.ldb"
402     paths.hkcr = "hkcr.ldb"
403     paths.hkcu = "hkcu.ldb"
404     paths.hku = "hku.ldb"
405     paths.hkpd = "hkpd.ldb"
406     paths.hkpt = "hkpt.ldb"
407
408     paths.sysvol = lp.get("path", "sysvol")
409
410     paths.netlogon = lp.get("path", "netlogon")
411
412     paths.smbconf = lp.configfile
413
414     return paths
415
416
417 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
418                 serverrole=None, rootdn=None, domaindn=None, configdn=None,
419                 schemadn=None, serverdn=None, sitename=None, sambadn=None):
420     """Guess configuration settings to use."""
421
422     if hostname is None:
423         hostname = socket.gethostname().split(".")[0].lower()
424
425     netbiosname = hostname.upper()
426     if not valid_netbios_name(netbiosname):
427         raise InvalidNetbiosName(netbiosname)
428
429     hostname = hostname.lower()
430
431     if dnsdomain is None:
432         dnsdomain = lp.get("realm")
433
434     if serverrole is None:
435         serverrole = lp.get("server role")
436
437     assert dnsdomain is not None
438     realm = dnsdomain.upper()
439
440     if lp.get("realm").upper() != realm:
441         raise Exception("realm '%s' in %s must match chosen realm '%s'" %
442                         (lp.get("realm"), lp.configfile, realm))
443     
444     dnsdomain = dnsdomain.lower()
445
446     if serverrole == "domain controller":
447         if domain is None:
448             domain = lp.get("workgroup")
449         if domaindn is None:
450             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
451         if lp.get("workgroup").upper() != domain.upper():
452             raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
453                         lp.get("workgroup"), domain)
454     else:
455         domain = netbiosname
456         if domaindn is None:
457             domaindn = "CN=" + netbiosname
458         
459     assert domain is not None
460     domain = domain.upper()
461     if not valid_netbios_name(domain):
462         raise InvalidNetbiosName(domain)
463         
464     if netbiosname.upper() == realm.upper():
465         raise Exception("realm %s must not be equal to netbios domain name %s", realm, netbiosname)
466         
467     if hostname.upper() == realm.upper():
468         raise Exception("realm %s must not be equal to hostname %s", realm, hostname)
469         
470     if domain.upper() == realm.upper():
471         raise Exception("realm %s must not be equal to domain name %s", realm, domain)
472
473     if rootdn is None:
474        rootdn = domaindn
475        
476     if configdn is None:
477         configdn = "CN=Configuration," + rootdn
478     if schemadn is None:
479         schemadn = "CN=Schema," + configdn
480     if sambadn is None:
481         sambadn = "CN=Samba"
482
483     if sitename is None:
484         sitename=DEFAULTSITE
485
486     names = ProvisionNames()
487     names.rootdn = rootdn
488     names.domaindn = domaindn
489     names.configdn = configdn
490     names.schemadn = schemadn
491     names.sambadn = sambadn
492     names.ldapmanagerdn = "CN=Manager," + rootdn
493     names.dnsdomain = dnsdomain
494     names.domain = domain
495     names.realm = realm
496     names.netbiosname = netbiosname
497     names.hostname = hostname
498     names.sitename = sitename
499     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
500  
501     return names
502     
503
504 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
505                  targetdir):
506     """Create a new smb.conf file based on a couple of basic settings.
507     """
508     assert smbconf is not None
509     if hostname is None:
510         hostname = socket.gethostname().split(".")[0].lower()
511
512     if serverrole is None:
513         serverrole = "standalone"
514
515     assert serverrole in ("domain controller", "member server", "standalone")
516     if serverrole == "domain controller":
517         smbconfsuffix = "dc"
518     elif serverrole == "member server":
519         smbconfsuffix = "member"
520     elif serverrole == "standalone":
521         smbconfsuffix = "standalone"
522
523     assert domain is not None
524     assert realm is not None
525
526     default_lp = param.LoadParm()
527     #Load non-existant file
528     if os.path.exists(smbconf):
529         default_lp.load(smbconf)
530     
531     if targetdir is not None:
532         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
533         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
534
535         default_lp.set("lock dir", os.path.abspath(targetdir))
536     else:
537         privatedir_line = ""
538         lockdir_line = ""
539
540     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
541     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
542
543     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
544                smbconf, {
545             "HOSTNAME": hostname,
546             "DOMAIN": domain,
547             "REALM": realm,
548             "SERVERROLE": serverrole,
549             "NETLOGONPATH": netlogon,
550             "SYSVOLPATH": sysvol,
551             "PRIVATEDIR_LINE": privatedir_line,
552             "LOCKDIR_LINE": lockdir_line
553             })
554
555
556 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
557                         users_gid, wheel_gid):
558     """setup reasonable name mappings for sam names to unix names.
559
560     :param samdb: SamDB object.
561     :param idmap: IDmap db object.
562     :param sid: The domain sid.
563     :param domaindn: The domain DN.
564     :param root_uid: uid of the UNIX root user.
565     :param nobody_uid: uid of the UNIX nobody user.
566     :param users_gid: gid of the UNIX users group.
567     :param wheel_gid: gid of the UNIX wheel group."""
568
569     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
570     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
571     
572     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
573     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
574
575 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
576                            credentials, names,
577                            serverrole, ldap_backend=None, 
578                            erase=False):
579     """Setup the partitions for the SAM database. 
580     
581     Alternatively, provision() may call this, and then populate the database.
582     
583     :note: This will wipe the Sam Database!
584     
585     :note: This function always removes the local SAM LDB file. The erase 
586         parameter controls whether to erase the existing data, which 
587         may not be stored locally but in LDAP.
588     """
589     assert session_info is not None
590
591     # We use options=["modules:"] to stop the modules loading - we
592     # just want to wipe and re-initialise the database, not start it up
593
594     try:
595         samdb = Ldb(url=samdb_path, session_info=session_info, 
596                       credentials=credentials, lp=lp, options=["modules:"])
597         # Wipes the database
598         samdb.erase_except_schema_controlled()
599     except LdbError:
600         os.unlink(samdb_path)
601         samdb = Ldb(url=samdb_path, session_info=session_info, 
602                       credentials=credentials, lp=lp, options=["modules:"])
603          # Wipes the database
604         samdb.erase_except_schema_controlled()
605         
606
607     #Add modules to the list to activate them by default
608     #beware often order is important
609     #
610     # Some Known ordering constraints:
611     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
612     # - objectclass must be before password_hash, because password_hash checks
613     #   that the objectclass is of type person (filled in by objectclass
614     #   module when expanding the objectclass list)
615     # - partition must be last
616     # - each partition has its own module list then
617     modules_list = ["resolve_oids",
618                     "rootdse",
619                     "lazy_commit",
620                     "acl",
621                     "paged_results",
622                     "ranged_results",
623                     "anr",
624                     "server_sort",
625                     "asq",
626                     "extended_dn_store",
627                     "extended_dn_in",
628                     "rdn_name",
629                     "objectclass",
630                     "descriptor",
631                     "samldb",
632                     "password_hash",
633                     "operational",
634                     "kludge_acl", 
635                     "instancetype"]
636     tdb_modules_list = [
637                     "subtree_rename",
638                     "subtree_delete",
639                     "linked_attributes",
640                     "extended_dn_out_ldb"]
641     modules_list2 = ["show_deleted",
642                     "partition"]
643  
644     domaindn_ldb = "users.ldb"
645     configdn_ldb = "configuration.ldb"
646     schemadn_ldb = "schema.ldb"
647     if ldap_backend is not None:
648         domaindn_ldb = ldap_backend.ldapi_uri
649         configdn_ldb = ldap_backend.ldapi_uri
650         schemadn_ldb = ldap_backend.ldapi_uri
651         
652         if ldap_backend.ldap_backend_type == "fedora-ds":
653             backend_modules = ["nsuniqueid", "paged_searches"]
654             # We can handle linked attributes here, as we don't have directory-side subtree operations
655             tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
656         elif ldap_backend.ldap_backend_type == "openldap":
657             backend_modules = ["entryuuid", "paged_searches"]
658             # OpenLDAP handles subtree renames, so we don't want to do any of these things
659             tdb_modules_list = ["extended_dn_out_dereference"]
660
661     elif serverrole == "domain controller":
662         tdb_modules_list.insert(0, "repl_meta_data")
663         backend_modules = []
664     else:
665         backend_modules = ["objectguid"]
666
667     if tdb_modules_list is None:
668         tdb_modules_list_as_string = ""
669     else:
670         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
671         
672     samdb.transaction_start()
673     try:
674         message("Setting up sam.ldb partitions and settings")
675         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
676                 "SCHEMADN": names.schemadn, 
677                 "SCHEMADN_LDB": schemadn_ldb,
678                 "SCHEMADN_MOD2": ",objectguid",
679                 "CONFIGDN": names.configdn,
680                 "CONFIGDN_LDB": configdn_ldb,
681                 "DOMAINDN": names.domaindn,
682                 "DOMAINDN_LDB": domaindn_ldb,
683                 "SCHEMADN_MOD": "schema_fsmo",
684                 "CONFIGDN_MOD": "naming_fsmo",
685                 "DOMAINDN_MOD": "pdc_fsmo",
686                 "MODULES_LIST": ",".join(modules_list),
687                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
688                 "MODULES_LIST2": ",".join(modules_list2),
689                 "BACKEND_MOD": ",".join(backend_modules),
690         })
691
692         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
693
694         message("Setting up sam.ldb rootDSE")
695         setup_samdb_rootdse(samdb, setup_path, names)
696
697     except:
698         samdb.transaction_cancel()
699         raise
700
701     samdb.transaction_commit()
702     
703 def secretsdb_self_join(secretsdb, domain, 
704                         netbiosname, domainsid, machinepass, 
705                         realm=None, dnsdomain=None,
706                         keytab_path=None, 
707                         key_version_number=1,
708                         secure_channel_type=SEC_CHAN_WKSTA):
709     """Add domain join-specific bits to a secrets database.
710     
711     :param secretsdb: Ldb Handle to the secrets database
712     :param machinepass: Machine password
713     """
714     attrs=["whenChanged",
715            "secret",
716            "priorSecret",
717            "priorChanged",
718            "krb5Keytab",
719            "privateKeytab"]
720     
721
722     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
723     msg["secureChannelType"] = str(secure_channel_type)
724     msg["flatname"] = [domain]
725     msg["objectClass"] = ["top", "primaryDomain"]
726     if realm is not None:
727       if dnsdomain is None:
728         dnsdomain = realm.lower()
729       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
730       msg["realm"] = realm
731       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
732       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
733       msg["privateKeytab"] = ["secrets.keytab"];
734
735
736     msg["secret"] = [machinepass]
737     msg["samAccountName"] = ["%s$" % netbiosname]
738     msg["secureChannelType"] = [str(secure_channel_type)]
739     msg["objectSid"] = [ndr_pack(domainsid)]
740     
741     res = secretsdb.search(base="cn=Primary Domains", 
742                            attrs=attrs, 
743                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
744                            scope=SCOPE_ONELEVEL)
745     
746     for del_msg in res:
747       if del_msg.dn is not msg.dn:
748         secretsdb.delete(del_msg.dn)
749
750     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
751
752     if len(res) == 1:
753       msg["priorSecret"] = res[0]["secret"]
754       msg["priorWhenChanged"] = res[0]["whenChanged"]
755
756       if res["privateKeytab"] is not None:
757         msg["privateKeytab"] = res[0]["privateKeytab"]
758
759       if res["krb5Keytab"] is not None:
760         msg["krb5Keytab"] = res[0]["krb5Keytab"]
761
762       for el in msg:
763         el.set_flags(ldb.FLAG_MOD_REPLACE)
764         secretsdb.modify(msg)
765     else:
766       secretsdb.add(msg)
767
768
769 def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain, 
770                         dns_keytab_path, dnspass):
771     """Add DNS specific bits to a secrets database.
772     
773     :param secretsdb: Ldb Handle to the secrets database
774     :param setup_path: Setup path function
775     :param machinepass: Machine password
776     """
777     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
778             "REALM": realm,
779             "DNSDOMAIN": dnsdomain,
780             "DNS_KEYTAB": dns_keytab_path,
781             "DNSPASS_B64": b64encode(dnspass),
782             })
783
784
785 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
786     """Setup the secrets database.
787
788     :param path: Path to the secrets database.
789     :param setup_path: Get the path to a setup file.
790     :param session_info: Session info.
791     :param credentials: Credentials
792     :param lp: Loadparm context
793     :return: LDB handle for the created secrets database
794     """
795     if os.path.exists(path):
796         os.unlink(path)
797     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
798                       lp=lp)
799     secrets_ldb.erase()
800     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
801     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
802                       lp=lp)
803     secrets_ldb.transaction_start()
804     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
805
806     if credentials is not None and credentials.authentication_requested():
807         if credentials.get_bind_dn() is not None:
808             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
809                     "LDAPMANAGERDN": credentials.get_bind_dn(),
810                     "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
811                     })
812         else:
813             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
814                     "LDAPADMINUSER": credentials.get_username(),
815                     "LDAPADMINREALM": credentials.get_realm(),
816                     "LDAPADMINPASS_B64": b64encode(credentials.get_password())
817                     })
818
819     return secrets_ldb
820
821 def setup_registry(path, setup_path, session_info, lp):
822     """Setup the registry.
823     
824     :param path: Path to the registry database
825     :param setup_path: Function that returns the path to a setup.
826     :param session_info: Session information
827     :param credentials: Credentials
828     :param lp: Loadparm context
829     """
830     reg = registry.Registry()
831     hive = registry.open_ldb(path, session_info=session_info, 
832                          lp_ctx=lp)
833     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
834     provision_reg = setup_path("provision.reg")
835     assert os.path.exists(provision_reg)
836     reg.diff_apply(provision_reg)
837
838
839 def setup_idmapdb(path, setup_path, session_info, lp):
840     """Setup the idmap database.
841
842     :param path: path to the idmap database
843     :param setup_path: Function that returns a path to a setup file
844     :param session_info: Session information
845     :param credentials: Credentials
846     :param lp: Loadparm context
847     """
848     if os.path.exists(path):
849         os.unlink(path)
850
851     idmap_ldb = IDmapDB(path, session_info=session_info,
852                         lp=lp)
853
854     idmap_ldb.erase()
855     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
856     return idmap_ldb
857
858
859 def setup_samdb_rootdse(samdb, setup_path, names):
860     """Setup the SamDB rootdse.
861
862     :param samdb: Sam Database handle
863     :param setup_path: Obtain setup path
864     """
865     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
866         "SCHEMADN": names.schemadn, 
867         "NETBIOSNAME": names.netbiosname,
868         "DNSDOMAIN": names.dnsdomain,
869         "REALM": names.realm,
870         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
871         "DOMAINDN": names.domaindn,
872         "ROOTDN": names.rootdn,
873         "CONFIGDN": names.configdn,
874         "SERVERDN": names.serverdn,
875         })
876         
877
878 def setup_self_join(samdb, names,
879                     machinepass, dnspass, 
880                     domainsid, invocationid, setup_path,
881                     policyguid, policyguid_dc, domainControllerFunctionality,ntdsguid):
882     """Join a host to its own domain."""
883     assert isinstance(invocationid, str)
884     if ntdsguid is not None:
885         ntdsguid_mod = "objectGUID: %s\n"%ntdsguid
886     else:
887         ntdsguid_mod = ""
888     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
889               "CONFIGDN": names.configdn, 
890               "SCHEMADN": names.schemadn,
891               "DOMAINDN": names.domaindn,
892               "SERVERDN": names.serverdn,
893               "INVOCATIONID": invocationid,
894               "NETBIOSNAME": names.netbiosname,
895               "DEFAULTSITE": names.sitename,
896               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
897               "MACHINEPASS_B64": b64encode(machinepass),
898               "DNSPASS_B64": b64encode(dnspass),
899               "REALM": names.realm,
900               "DOMAIN": names.domain,
901               "DNSDOMAIN": names.dnsdomain,
902               "SAMBA_VERSION_STRING": version,
903               "NTDSGUID": ntdsguid_mod,
904               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
905
906     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
907               "POLICYGUID": policyguid,
908               "POLICYGUID_DC": policyguid_dc,
909               "DNSDOMAIN": names.dnsdomain,
910               "DOMAINSID": str(domainsid),
911               "DOMAINDN": names.domaindn})
912     
913     # add the NTDSGUID based SPNs
914     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
915     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
916                                      expression="", scope=SCOPE_BASE)
917     assert isinstance(names.ntdsguid, str)
918
919     # Setup fSMORoleOwner entries to point at the newly created DC entry
920     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
921               "DOMAIN": names.domain,
922               "DNSDOMAIN": names.dnsdomain,
923               "DOMAINDN": names.domaindn,
924               "CONFIGDN": names.configdn,
925               "SCHEMADN": names.schemadn, 
926               "DEFAULTSITE": names.sitename,
927               "SERVERDN": names.serverdn,
928               "NETBIOSNAME": names.netbiosname,
929               "NTDSGUID": names.ntdsguid
930               })
931
932
933 def setup_samdb(path, setup_path, session_info, credentials, lp, 
934                 names, message, 
935                 domainsid, domainguid, policyguid, policyguid_dc,
936                 fill, adminpass, krbtgtpass, 
937                 machinepass, invocationid, dnspass, ntdsguid,
938                 serverrole, dom_for_fun_level=None,
939                 schema=None, ldap_backend=None):
940     """Setup a complete SAM Database.
941     
942     :note: This will wipe the main SAM database file!
943     """
944
945     # ATTENTION: Do NOT change these default values without discussion with the
946     # team and/or release manager. They have a big impact on the whole program!
947     domainControllerFunctionality = DS_DC_FUNCTION_2008
948
949     if dom_for_fun_level is None:
950         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
951     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
952         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
953
954     if dom_for_fun_level > domainControllerFunctionality:
955         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!")
956
957     domainFunctionality = dom_for_fun_level
958     forestFunctionality = dom_for_fun_level
959
960     # Also wipes the database
961     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
962                            credentials=credentials, session_info=session_info,
963                            names=names, ldap_backend=ldap_backend,
964                            serverrole=serverrole)
965
966     if (schema == None):
967         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
968             sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
969
970     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
971     samdb = Ldb(session_info=session_info, 
972                 credentials=credentials, lp=lp)
973
974     message("Pre-loading the Samba 4 and AD schema")
975
976     # Load the schema from the one we computed earlier
977     samdb.set_schema_from_ldb(schema.ldb)
978
979     # And now we can connect to the DB - the schema won't be loaded from the DB
980     samdb.connect(path)
981
982     # Load @OPTIONS
983     samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
984
985     if fill == FILL_DRS:
986         return samdb
987
988     samdb.transaction_start()
989     try:
990         message("Erasing data from partitions")
991         # Load the schema (again).  This time it will force a reindex,
992         # and will therefore make the erase_partitions() below
993         # computationally sane
994         samdb.set_schema_from_ldb(schema.ldb)
995         samdb.erase_partitions()
996     
997         # Set the domain functionality levels onto the database.
998         # Various module (the password_hash module in particular) need
999         # to know what level of AD we are emulating.
1000
1001         # These will be fixed into the database via the database
1002         # modifictions below, but we need them set from the start.
1003         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1004         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1005         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
1006
1007         samdb.set_domain_sid(str(domainsid))
1008         if serverrole == "domain controller":
1009             samdb.set_invocation_id(invocationid)
1010
1011         message("Adding DomainDN: %s" % names.domaindn)
1012         if serverrole == "domain controller":
1013             domain_oc = "domainDNS"
1014         else:
1015             domain_oc = "samba4LocalDomain"
1016
1017 #impersonate domain admin
1018         admin_session_info = admin_session(lp, str(domainsid))
1019         samdb.set_session_info(admin_session_info)
1020         if domainguid is not None:
1021             domainguid_mod = "objectGUID: %s\n-" % domainguid
1022         else:
1023             domainguid_mod = ""
1024         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1025                 "DOMAINDN": names.domaindn,
1026                 "DOMAIN_OC": domain_oc,
1027                 "DOMAINGUID": domainguid_mod
1028                 })
1029
1030
1031         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1032             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1033             "DOMAINSID": str(domainsid),
1034             "SCHEMADN": names.schemadn, 
1035             "NETBIOSNAME": names.netbiosname,
1036             "DEFAULTSITE": names.sitename,
1037             "CONFIGDN": names.configdn,
1038             "SERVERDN": names.serverdn,
1039             "POLICYGUID": policyguid,
1040             "DOMAINDN": names.domaindn,
1041             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1042             "SAMBA_VERSION_STRING": version
1043             })
1044
1045         message("Adding configuration container")
1046         descr = get_config_descriptor(domainsid);
1047         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1048             "CONFIGDN": names.configdn, 
1049             "DESCRIPTOR": descr,
1050             })
1051         message("Modifying configuration container")
1052         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1053             "CONFIGDN": names.configdn, 
1054             "SCHEMADN": names.schemadn,
1055             })
1056
1057         # The LDIF here was created when the Schema object was constructed
1058         message("Setting up sam.ldb schema")
1059         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1060         samdb.modify_ldif(schema.schema_dn_modify)
1061         samdb.write_prefixes_from_schema()
1062         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1063         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1064                        {"SCHEMADN": names.schemadn})
1065
1066         message("Setting up sam.ldb configuration data")
1067         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1068             "CONFIGDN": names.configdn,
1069             "NETBIOSNAME": names.netbiosname,
1070             "DEFAULTSITE": names.sitename,
1071             "DNSDOMAIN": names.dnsdomain,
1072             "DOMAIN": names.domain,
1073             "SCHEMADN": names.schemadn,
1074             "DOMAINDN": names.domaindn,
1075             "SERVERDN": names.serverdn,
1076             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1077             })
1078
1079         message("Setting up display specifiers")
1080         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1081         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1082         check_all_substituted(display_specifiers_ldif)
1083         samdb.add_ldif(display_specifiers_ldif)
1084
1085         message("Adding users container")
1086         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1087                 "DOMAINDN": names.domaindn})
1088         message("Modifying users container")
1089         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1090                 "DOMAINDN": names.domaindn})
1091         message("Adding computers container")
1092         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1093                 "DOMAINDN": names.domaindn})
1094         message("Modifying computers container")
1095         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1096                 "DOMAINDN": names.domaindn})
1097         message("Setting up sam.ldb data")
1098         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1099             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1100             "DOMAINDN": names.domaindn,
1101             "NETBIOSNAME": names.netbiosname,
1102             "DEFAULTSITE": names.sitename,
1103             "CONFIGDN": names.configdn,
1104             "SERVERDN": names.serverdn,
1105             "POLICYGUID_DC": policyguid_dc
1106             })
1107
1108         if fill == FILL_FULL:
1109             message("Setting up sam.ldb users and groups")
1110             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1111                 "DOMAINDN": names.domaindn,
1112                 "DOMAINSID": str(domainsid),
1113                 "CONFIGDN": names.configdn,
1114                 "ADMINPASS_B64": b64encode(adminpass),
1115                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1116                 })
1117
1118             if serverrole == "domain controller":
1119                 message("Setting up self join")
1120                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1121                                 dnspass=dnspass,  
1122                                 machinepass=machinepass, 
1123                                 domainsid=domainsid, policyguid=policyguid,
1124                                 policyguid_dc=policyguid_dc,
1125                                 setup_path=setup_path,
1126                                 domainControllerFunctionality=domainControllerFunctionality,ntdsguid=ntdsguid)
1127
1128                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1129                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1130                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1131                 assert isinstance(names.ntdsguid, str)
1132
1133     except:
1134         samdb.transaction_cancel()
1135         raise
1136
1137     samdb.transaction_commit()
1138     return samdb
1139
1140
1141 FILL_FULL = "FULL"
1142 FILL_NT4SYNC = "NT4SYNC"
1143 FILL_DRS = "DRS"
1144
1145
1146 def provision(setup_dir, message, session_info, 
1147               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1148               realm=None, 
1149               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1150               serverdn=None,
1151               domain=None, hostname=None, hostip=None, hostip6=None, 
1152               domainsid=None, adminpass=None, ldapadminpass=None, 
1153               krbtgtpass=None, domainguid=None, 
1154               policyguid=None, policyguid_dc=None, invocationid=None,
1155               machinepass=None,ntdsguid=None,
1156               dnspass=None, root=None, nobody=None, users=None, 
1157               wheel=None, backup=None, aci=None, serverrole=None,
1158               dom_for_fun_level=None,
1159               ldap_backend_extra_port=None, ldap_backend_type=None,
1160               sitename=None,
1161               ol_mmr_urls=None, ol_olc=None, 
1162               setup_ds_path=None, slapd_path=None, nosync=False,
1163               ldap_dryrun_mode=False):
1164     """Provision samba4
1165     
1166     :note: caution, this wipes all existing data!
1167     """
1168
1169     def setup_path(file):
1170       return os.path.join(setup_dir, file)
1171
1172     if domainsid is None:
1173       domainsid = security.random_sid()
1174     else:
1175       domainsid = security.dom_sid(domainsid)
1176
1177     # create/adapt the group policy GUIDs
1178     if policyguid is None:
1179         policyguid = str(uuid.uuid4())
1180     policyguid = policyguid.upper()
1181     if policyguid_dc is None:
1182         policyguid_dc = str(uuid.uuid4())
1183     policyguid_dc = policyguid_dc.upper()
1184
1185     if adminpass is None:
1186         adminpass = glue.generate_random_str(12)
1187     if krbtgtpass is None:
1188         krbtgtpass = glue.generate_random_str(12)
1189     if machinepass is None:
1190         machinepass  = glue.generate_random_str(12)
1191     if dnspass is None:
1192         dnspass = glue.generate_random_str(12)
1193     if ldapadminpass is None:
1194         #Make a new, random password between Samba and it's LDAP server
1195         ldapadminpass=glue.generate_random_str(12)        
1196
1197
1198     root_uid = findnss_uid([root or "root"])
1199     nobody_uid = findnss_uid([nobody or "nobody"])
1200     users_gid = findnss_gid([users or "users"])
1201     if wheel is None:
1202         wheel_gid = findnss_gid(["wheel", "adm"])
1203     else:
1204         wheel_gid = findnss_gid([wheel])
1205
1206     if targetdir is not None:
1207         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1208             os.makedirs(os.path.join(targetdir, "etc"))
1209         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1210     elif smbconf is None:
1211         smbconf = param.default_path()
1212
1213     # only install a new smb.conf if there isn't one there already
1214     if not os.path.exists(smbconf):
1215         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1216                      targetdir)
1217
1218     lp = param.LoadParm()
1219     lp.load(smbconf)
1220
1221     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1222                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1223                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1224                         serverdn=serverdn)
1225
1226     paths = provision_paths_from_lp(lp, names.dnsdomain)
1227
1228     if hostip is None:
1229         try:
1230             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1231         except socket.gaierror, (socket.EAI_NODATA, msg):
1232             hostip = None
1233
1234     if hostip6 is None:
1235         try:
1236             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1237         except socket.gaierror, (socket.EAI_NODATA, msg): 
1238             hostip6 = None
1239
1240     if serverrole is None:
1241         serverrole = lp.get("server role")
1242
1243     assert serverrole in ("domain controller", "member server", "standalone")
1244     if invocationid is None and serverrole == "domain controller":
1245         invocationid = str(uuid.uuid4())
1246
1247     if not os.path.exists(paths.private_dir):
1248         os.mkdir(paths.private_dir)
1249
1250     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1251     
1252     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1253         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1254     
1255     secrets_credentials = credentials
1256     provision_backend = None
1257     if ldap_backend_type:
1258         # We only support an LDAP backend over ldapi://
1259
1260         provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1261                                              lp=lp, credentials=credentials, 
1262                                              names=names,
1263                                              message=message, hostname=hostname,
1264                                              root=root, schema=schema,
1265                                              ldap_backend_type=ldap_backend_type,
1266                                              ldapadminpass=ldapadminpass,
1267                                              ldap_backend_extra_port=ldap_backend_extra_port,
1268                                              ol_mmr_urls=ol_mmr_urls, 
1269                                              slapd_path=slapd_path,
1270                                              setup_ds_path=setup_ds_path,
1271                                              ldap_dryrun_mode=ldap_dryrun_mode)
1272
1273         # Now use the backend credentials to access the databases
1274         credentials = provision_backend.credentials
1275         secrets_credentials = provision_backend.adminCredentials
1276         ldapi_url = provision_backend.ldapi_uri
1277
1278     # only install a new shares config db if there is none
1279     if not os.path.exists(paths.shareconf):
1280         message("Setting up share.ldb")
1281         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1282                         credentials=credentials, lp=lp)
1283         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1284
1285      
1286     message("Setting up secrets.ldb")
1287     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1288                                   session_info=session_info, 
1289                                   credentials=secrets_credentials, lp=lp)
1290
1291     message("Setting up the registry")
1292     setup_registry(paths.hklm, setup_path, session_info, 
1293                    lp=lp)
1294
1295     message("Setting up idmap db")
1296     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1297                           lp=lp)
1298
1299     message("Setting up SAM db")
1300     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1301                         credentials=credentials, lp=lp, names=names,
1302                         message=message, 
1303                         domainsid=domainsid, 
1304                         schema=schema, domainguid=domainguid,
1305                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1306                         fill=samdb_fill, 
1307                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1308                         invocationid=invocationid, 
1309                         machinepass=machinepass, dnspass=dnspass, 
1310                         ntdsguid=ntdsguid,
1311                         serverrole=serverrole,
1312                         dom_for_fun_level=dom_for_fun_level,
1313                         ldap_backend=provision_backend)
1314
1315     if serverrole == "domain controller":
1316         if paths.netlogon is None:
1317             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1318             message("Please either remove %s or see the template at %s" % 
1319                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1320             assert(paths.netlogon is not None)
1321
1322         if paths.sysvol is None:
1323             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1324             message("Please either remove %s or see the template at %s" % 
1325                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1326             assert(paths.sysvol is not None)            
1327             
1328         # Set up group policies (domain policy and domain controller policy)
1329
1330         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1331                                    "{" + policyguid + "}")
1332         os.makedirs(policy_path, 0755)
1333         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1334                                    "[General]\r\nVersion=65543")
1335         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1336         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1337
1338         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1339                                    "{" + policyguid_dc + "}")
1340         os.makedirs(policy_path_dc, 0755)
1341         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1342                                    "[General]\r\nVersion=2")
1343         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1344         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1345
1346         if not os.path.isdir(paths.netlogon):
1347             os.makedirs(paths.netlogon, 0755)
1348
1349     if samdb_fill == FILL_FULL:
1350         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1351                             root_uid=root_uid, nobody_uid=nobody_uid,
1352                             users_gid=users_gid, wheel_gid=wheel_gid)
1353
1354         message("Setting up sam.ldb rootDSE marking as synchronized")
1355         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1356
1357         # Only make a zone file on the first DC, it should be replicated with DNS replication
1358         if serverrole == "domain controller":
1359             secretsdb_self_join(secrets_ldb, domain=domain,
1360                                 realm=names.realm,
1361                                 dnsdomain=names.dnsdomain,
1362                                 netbiosname=names.netbiosname,
1363                                 domainsid=domainsid, 
1364                                 machinepass=machinepass,
1365                                 secure_channel_type=SEC_CHAN_BDC)
1366
1367             secretsdb_setup_dns(secrets_ldb, setup_path, 
1368                                 realm=names.realm, dnsdomain=names.dnsdomain,
1369                                 dns_keytab_path=paths.dns_keytab,
1370                                 dnspass=dnspass)
1371
1372             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1373             assert isinstance(domainguid, str)
1374
1375             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1376                              domaindn=names.domaindn, hostip=hostip,
1377                              hostip6=hostip6, hostname=names.hostname,
1378                              dnspass=dnspass, realm=names.realm,
1379                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1380
1381             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1382                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1383
1384             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1385                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1386                               keytab_name=paths.dns_keytab)
1387             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1388             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1389
1390             create_krb5_conf(paths.krb5conf, setup_path,
1391                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1392                              realm=names.realm)
1393             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1394
1395     #Now commit the secrets.ldb to disk
1396     secrets_ldb.transaction_commit()
1397
1398     if provision_backend is not None: 
1399       if ldap_backend_type == "fedora-ds":
1400         ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1401
1402         # delete default SASL mappings
1403         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1404
1405         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1406         for i in range (0, len(res)):
1407           dn = str(res[i]["dn"])
1408           ldapi_db.delete(dn)
1409
1410           aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1411
1412           m = ldb.Message()
1413           m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1414         
1415           m.dn = ldb.Dn(1, names.domaindn)
1416           ldapi_db.modify(m)
1417
1418           m.dn = ldb.Dn(1, names.configdn)
1419           ldapi_db.modify(m)
1420
1421           m.dn = ldb.Dn(1, names.schemadn)
1422           ldapi_db.modify(m)
1423
1424       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1425       if provision_backend.slapd.poll() is None:
1426         #Kill the slapd
1427         if hasattr(provision_backend.slapd, "terminate"):
1428           provision_backend.slapd.terminate()
1429         else:
1430           # Older python versions don't have .terminate()
1431           import signal
1432           os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1433             
1434         #and now wait for it to die
1435         provision_backend.slapd.communicate()
1436             
1437     # now display slapd_command_file.txt to show how slapd must be started next time
1438         message("Use later the following commandline to start slapd, then Samba:")
1439         slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1440         message(slapd_command)
1441         message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1442
1443         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1444                 "SLAPD_COMMAND" : slapd_command})
1445
1446     
1447     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1448                                ldapi_url)
1449
1450     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1451
1452     message("Once the above files are installed, your Samba4 server will be ready to use")
1453     message("Server Role:           %s" % serverrole)
1454     message("Hostname:              %s" % names.hostname)
1455     message("NetBIOS Domain:        %s" % names.domain)
1456     message("DNS Domain:            %s" % names.dnsdomain)
1457     message("DOMAIN SID:            %s" % str(domainsid))
1458     if samdb_fill == FILL_FULL:
1459         message("Admin password:    %s" % adminpass)
1460     if provision_backend:
1461         if provision_backend.credentials.get_bind_dn() is not None:
1462             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1463         else:
1464             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1465
1466         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1467   
1468     result = ProvisionResult()
1469     result.domaindn = domaindn
1470     result.paths = paths
1471     result.lp = lp
1472     result.samdb = samdb
1473     return result
1474
1475
1476
1477 def provision_become_dc(setup_dir=None,
1478                         smbconf=None, targetdir=None, realm=None, 
1479                         rootdn=None, domaindn=None, schemadn=None,
1480                         configdn=None, serverdn=None,
1481                         domain=None, hostname=None, domainsid=None, 
1482                         adminpass=None, krbtgtpass=None, domainguid=None, 
1483                         policyguid=None, policyguid_dc=None, invocationid=None,
1484                         machinepass=None, 
1485                         dnspass=None, root=None, nobody=None, users=None, 
1486                         wheel=None, backup=None, serverrole=None, 
1487                         ldap_backend=None, ldap_backend_type=None,
1488                         sitename=None, debuglevel=1):
1489
1490     def message(text):
1491         """print a message if quiet is not set."""
1492         print text
1493
1494     glue.set_debug_level(debuglevel)
1495
1496     return provision(setup_dir, message, system_session(), None,
1497               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1498               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1499               configdn=configdn, serverdn=serverdn, domain=domain,
1500               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1501               machinepass=machinepass, serverrole="domain controller",
1502               sitename=sitename)
1503
1504
1505 def setup_db_config(setup_path, dbdir):
1506     """Setup a Berkeley database.
1507     
1508     :param setup_path: Setup path function.
1509     :param dbdir: Database directory."""
1510     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1511         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1512         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1513             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1514
1515     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1516                {"LDAPDBDIR": dbdir})
1517     
1518 class ProvisionBackend(object):
1519     def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, 
1520                  names=None, message=None, 
1521                  hostname=None, root=None, 
1522                  schema=None, ldapadminpass=None,
1523                  ldap_backend_type=None, ldap_backend_extra_port=None,
1524                  ol_mmr_urls=None, 
1525                  setup_ds_path=None, slapd_path=None, 
1526                  nosync=False, ldap_dryrun_mode=False):
1527         """Provision an LDAP backend for samba4
1528         
1529         This works for OpenLDAP and Fedora DS
1530         """
1531
1532         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1533         
1534         if not os.path.isdir(paths.ldapdir):
1535             os.makedirs(paths.ldapdir, 0700)
1536             
1537         if ldap_backend_type == "existing":
1538             #Check to see that this 'existing' LDAP backend in fact exists
1539             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1540             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1541                                                 expression="(objectClass=OpenLDAProotDSE)")
1542
1543             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1544             # This caused them to be set into the long-term database later in the script.
1545             self.credentials = credentials
1546             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1547             return
1548     
1549         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1550         # if another instance of slapd is already running 
1551         try:
1552             ldapi_db = Ldb(self.ldapi_uri)
1553             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1554                                                 expression="(objectClass=OpenLDAProotDSE)");
1555             try:
1556                 f = open(paths.slapdpid, "r")
1557                 p = f.read()
1558                 f.close()
1559                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1560             except:
1561                 pass
1562             
1563             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1564         
1565         except LdbError, e:
1566             pass
1567
1568         # Try to print helpful messages when the user has not specified the path to slapd
1569         if slapd_path is None:
1570             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1571         if not os.path.exists(slapd_path):
1572             message (slapd_path)
1573             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1574
1575         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1576         try:
1577             os.unlink(schemadb_path)
1578         except OSError:
1579             pass
1580
1581
1582         # Put the LDIF of the schema into a database so we can search on
1583         # it to generate schema-dependent configurations in Fedora DS and
1584         # OpenLDAP
1585         os.path.join(paths.ldapdir, "schema-tmp.ldb")
1586         schema.ldb.connect(schemadb_path)
1587         schema.ldb.transaction_start()
1588     
1589         # These bits of LDIF are supplied when the Schema object is created
1590         schema.ldb.add_ldif(schema.schema_dn_add)
1591         schema.ldb.modify_ldif(schema.schema_dn_modify)
1592         schema.ldb.add_ldif(schema.schema_data)
1593         schema.ldb.transaction_commit()
1594
1595         self.credentials = Credentials()
1596         self.credentials.guess(lp)
1597         #Kerberos to an ldapi:// backend makes no sense
1598         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1599
1600         self.adminCredentials = Credentials()
1601         self.adminCredentials.guess(lp)
1602         #Kerberos to an ldapi:// backend makes no sense
1603         self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1604
1605         self.ldap_backend_type = ldap_backend_type
1606
1607         if ldap_backend_type == "fedora-ds":
1608             provision_fds_backend(self, paths=paths, setup_path=setup_path,
1609                                   names=names, message=message, 
1610                                   hostname=hostname,
1611                                   ldapadminpass=ldapadminpass, root=root, 
1612                                   schema=schema,
1613                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1614                                   setup_ds_path=setup_ds_path,
1615                                   slapd_path=slapd_path,
1616                                   nosync=nosync,
1617                                   ldap_dryrun_mode=ldap_dryrun_mode)
1618             
1619         elif ldap_backend_type == "openldap":
1620             provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1621                                        names=names, message=message, 
1622                                        hostname=hostname,
1623                                        ldapadminpass=ldapadminpass, root=root, 
1624                                        schema=schema,
1625                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1626                                        ol_mmr_urls=ol_mmr_urls, 
1627                                        slapd_path=slapd_path,
1628                                        nosync=nosync,
1629                                        ldap_dryrun_mode=ldap_dryrun_mode)
1630         else:
1631             raise ProvisioningError("Unknown LDAP backend type selected")
1632
1633         self.credentials.set_password(ldapadminpass)
1634         self.adminCredentials.set_username("samba-admin")
1635         self.adminCredentials.set_password(ldapadminpass)
1636
1637         # Now start the slapd, so we can provision onto it.  We keep the
1638         # subprocess context around, to kill this off at the successful
1639         # end of the script
1640         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1641     
1642         while self.slapd.poll() is None:
1643             # Wait until the socket appears
1644             try:
1645                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1646                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1647                                                     expression="(objectClass=OpenLDAProotDSE)")
1648                 # If we have got here, then we must have a valid connection to the LDAP server!
1649                 return
1650             except LdbError, e:
1651                 time.sleep(1)
1652                 pass
1653         
1654         raise ProvisioningError("slapd died before we could make a connection to it")
1655
1656
1657 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1658                                message=None, 
1659                                hostname=None, ldapadminpass=None, root=None, 
1660                                schema=None, 
1661                                ldap_backend_extra_port=None,
1662                                ol_mmr_urls=None, 
1663                                slapd_path=None, nosync=False,
1664                                ldap_dryrun_mode=False):
1665
1666     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1667     nosync_config = ""
1668     if nosync:
1669         nosync_config = "dbnosync"
1670         
1671     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1672     refint_attributes = ""
1673     memberof_config = "# Generated from Samba4 schema\n"
1674     for att in  lnkattr.keys():
1675         if lnkattr[att] is not None:
1676             refint_attributes = refint_attributes + " " + att 
1677             
1678             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1679                                                  { "MEMBER_ATTR" : att ,
1680                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1681             
1682     refint_config = read_and_sub_file(setup_path("refint.conf"),
1683                                       { "LINK_ATTRS" : refint_attributes})
1684     
1685     attrs = ["linkID", "lDAPDisplayName"]
1686     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1687     index_config = ""
1688     for i in range (0, len(res)):
1689         index_attr = res[i]["lDAPDisplayName"][0]
1690         if index_attr == "objectGUID":
1691             index_attr = "entryUUID"
1692             
1693         index_config += "index " + index_attr + " eq\n"
1694
1695 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1696     mmr_on_config = ""
1697     mmr_replicator_acl = ""
1698     mmr_serverids_config = ""
1699     mmr_syncrepl_schema_config = "" 
1700     mmr_syncrepl_config_config = "" 
1701     mmr_syncrepl_user_config = "" 
1702        
1703     
1704     if ol_mmr_urls is not None:
1705         # For now, make these equal
1706         mmr_pass = ldapadminpass
1707         
1708         url_list=filter(None,ol_mmr_urls.split(' ')) 
1709         if (len(url_list) == 1):
1710             url_list=filter(None,ol_mmr_urls.split(',')) 
1711                      
1712             
1713             mmr_on_config = "MirrorMode On"
1714             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1715             serverid=0
1716             for url in url_list:
1717                 serverid=serverid+1
1718                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1719                                                           { "SERVERID" : str(serverid),
1720                                                             "LDAPSERVER" : url })
1721                 rid=serverid*10
1722                 rid=rid+1
1723                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1724                                                                 {  "RID" : str(rid),
1725                                                                    "MMRDN": names.schemadn,
1726                                                                    "LDAPSERVER" : url,
1727                                                                    "MMR_PASSWORD": mmr_pass})
1728                 
1729                 rid=rid+1
1730                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1731                                                                 {  "RID" : str(rid),
1732                                                                    "MMRDN": names.configdn,
1733                                                                    "LDAPSERVER" : url,
1734                                                                    "MMR_PASSWORD": mmr_pass})
1735                 
1736                 rid=rid+1
1737                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1738                                                               {  "RID" : str(rid),
1739                                                                  "MMRDN": names.domaindn,
1740                                                                  "LDAPSERVER" : url,
1741                                                                  "MMR_PASSWORD": mmr_pass })
1742     # OpenLDAP cn=config initialisation
1743     olc_syncrepl_config = ""
1744     olc_mmr_config = "" 
1745     # if mmr = yes, generate cn=config-replication directives
1746     # and olc_seed.lif for the other mmr-servers
1747     if ol_mmr_urls is not None:
1748         serverid=0
1749         olc_serverids_config = ""
1750         olc_syncrepl_seed_config = ""
1751         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1752         rid=1000
1753         for url in url_list:
1754             serverid=serverid+1
1755             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1756                                                       { "SERVERID" : str(serverid),
1757                                                         "LDAPSERVER" : url })
1758             
1759             rid=rid+1
1760             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1761                                                      {  "RID" : str(rid),
1762                                                         "LDAPSERVER" : url,
1763                                                         "MMR_PASSWORD": mmr_pass})
1764             
1765             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1766                                                           {  "RID" : str(rid),
1767                                                              "LDAPSERVER" : url})
1768                 
1769         setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1770                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1771                     "OLC_PW": ldapadminpass,
1772                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1773     # end olc
1774                 
1775     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1776                {"DNSDOMAIN": names.dnsdomain,
1777                 "LDAPDIR": paths.ldapdir,
1778                 "DOMAINDN": names.domaindn,
1779                 "CONFIGDN": names.configdn,
1780                 "SCHEMADN": names.schemadn,
1781                 "MEMBEROF_CONFIG": memberof_config,
1782                 "MIRRORMODE": mmr_on_config,
1783                 "REPLICATOR_ACL": mmr_replicator_acl,
1784                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1785                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1786                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1787                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1788                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1789                 "OLC_MMR_CONFIG": olc_mmr_config,
1790                 "REFINT_CONFIG": refint_config,
1791                 "INDEX_CONFIG": index_config,
1792                 "NOSYNC": nosync_config})
1793         
1794     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1795     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1796     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1797     
1798     if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1799         os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1800         
1801     setup_file(setup_path("cn=samba.ldif"), 
1802                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1803                { "UUID": str(uuid.uuid4()), 
1804                  "LDAPTIME": timestring(int(time.time()))} )
1805     setup_file(setup_path("cn=samba-admin.ldif"), 
1806                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1807                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1808                 "UUID": str(uuid.uuid4()), 
1809                 "LDAPTIME": timestring(int(time.time()))} )
1810     
1811     if ol_mmr_urls is not None:
1812         setup_file(setup_path("cn=replicator.ldif"),
1813                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1814                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1815                     "UUID": str(uuid.uuid4()),
1816                     "LDAPTIME": timestring(int(time.time()))} )
1817         
1818
1819     mapping = "schema-map-openldap-2.3"
1820     backend_schema = "backend-schema.schema"
1821
1822     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1823     assert backend_schema_data is not None
1824     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1825
1826     # now we generate the needed strings to start slapd automatically,
1827     # first ldapi_uri...
1828     if ldap_backend_extra_port is not None:
1829         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1830         # specified there as part of it's clue as to it's own name,
1831         # and not to replicate to itself
1832         if ol_mmr_urls is None:
1833             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1834         else:
1835             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1836     else:
1837         server_port_string = ""
1838
1839     # Prepare the 'result' information - the commands to return in particular
1840     result.slapd_provision_command = [slapd_path]
1841
1842     result.slapd_provision_command.append("-F" + paths.olcdir)
1843
1844     result.slapd_provision_command.append("-h")
1845
1846     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1847     result.slapd_command = list(result.slapd_provision_command)
1848     
1849     result.slapd_provision_command.append(result.ldapi_uri)
1850     result.slapd_provision_command.append("-d0")
1851
1852     uris = result.ldapi_uri
1853     if server_port_string is not "":
1854         uris = uris + " " + server_port_string
1855
1856     result.slapd_command.append(uris)
1857
1858     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1859     result.credentials.set_username("samba-admin")
1860     
1861     # If we were just looking for crashes up to this point, it's a
1862     # good time to exit before we realise we don't have OpenLDAP on
1863     # this system
1864     if ldap_dryrun_mode:
1865         sys.exit(0)
1866
1867     # Finally, convert the configuration into cn=config style!
1868     if not os.path.isdir(paths.olcdir):
1869         os.makedirs(paths.olcdir, 0770)
1870
1871         retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1872
1873 #        We can't do this, as OpenLDAP is strange.  It gives an error
1874 #        output to the above, but does the conversion sucessfully...
1875 #
1876 #        if retcode != 0:
1877 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1878
1879         if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1880             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1881
1882         # Don't confuse the admin by leaving the slapd.conf around
1883         os.remove(paths.slapdconf)        
1884           
1885
1886 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1887                           message=None, 
1888                           hostname=None, ldapadminpass=None, root=None, 
1889                           schema=None,
1890                           ldap_backend_extra_port=None,
1891                           setup_ds_path=None,
1892                           slapd_path=None,
1893                           nosync=False, 
1894                           ldap_dryrun_mode=False):
1895
1896     if ldap_backend_extra_port is not None:
1897         serverport = "ServerPort=%d" % ldap_backend_extra_port
1898     else:
1899         serverport = ""
1900         
1901     setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1902                {"ROOT": root,
1903                 "HOSTNAME": hostname,
1904                 "DNSDOMAIN": names.dnsdomain,
1905                 "LDAPDIR": paths.ldapdir,
1906                 "DOMAINDN": names.domaindn,
1907                 "LDAPMANAGERDN": names.ldapmanagerdn,
1908                 "LDAPMANAGERPASS": ldapadminpass, 
1909                 "SERVERPORT": serverport})
1910
1911     setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1912                {"CONFIGDN": names.configdn,
1913                 "SCHEMADN": names.schemadn,
1914                 "SAMBADN": names.sambadn,
1915                 })
1916
1917     setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, 
1918                {"SAMBADN": names.sambadn,
1919                 })
1920
1921     setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1922                 {"SAMBADN": names.sambadn, 
1923                  "LDAPADMINPASS": ldapadminpass
1924                 })
1925
1926     mapping = "schema-map-fedora-ds-1.0"
1927     backend_schema = "99_ad.ldif"
1928     
1929     # Build a schema file in Fedora DS format
1930     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1931     assert backend_schema_data is not None
1932     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1933
1934     result.credentials.set_bind_dn(names.ldapmanagerdn)
1935
1936     # Destory the target directory, or else setup-ds.pl will complain
1937     fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1938     shutil.rmtree(fedora_ds_dir, True)
1939
1940     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1941     #In the 'provision' command line, stay in the foreground so we can easily kill it
1942     result.slapd_provision_command.append("-d0")
1943
1944     #the command for the final run is the normal script
1945     result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1946
1947     # If we were just looking for crashes up to this point, it's a
1948     # good time to exit before we realise we don't have Fedora DS on
1949     if ldap_dryrun_mode:
1950         sys.exit(0)
1951
1952     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1953     if setup_ds_path is None:
1954         raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1955     if not os.path.exists(setup_ds_path):
1956         message (setup_ds_path)
1957         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1958
1959     # Run the Fedora DS setup utility
1960     retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1961     if retcode != 0:
1962         raise ProvisioningError("setup-ds failed")
1963
1964     # Load samba-admin
1965     retcode = subprocess.call([
1966         os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1967         close_fds=True, shell=False)
1968     if retcode != 0:
1969         raise("ldib2db failed")
1970
1971 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1972     """Create a PHP LDAP admin configuration file.
1973
1974     :param path: Path to write the configuration to.
1975     :param setup_path: Function to generate setup paths.
1976     """
1977     setup_file(setup_path("phpldapadmin-config.php"), path, 
1978             {"S4_LDAPI_URI": ldapi_uri})
1979
1980
1981 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1982                      hostip, hostip6, hostname, dnspass, realm, domainguid,
1983                      ntdsguid):
1984     """Write out a DNS zone file, from the info in the current database.
1985
1986     :param path: Path of the new zone file.
1987     :param setup_path: Setup path function.
1988     :param dnsdomain: DNS Domain name
1989     :param domaindn: DN of the Domain
1990     :param hostip: Local IPv4 IP
1991     :param hostip6: Local IPv6 IP
1992     :param hostname: Local hostname
1993     :param dnspass: Password for DNS
1994     :param realm: Realm name
1995     :param domainguid: GUID of the domain.
1996     :param ntdsguid: GUID of the hosts nTDSDSA record.
1997     """
1998     assert isinstance(domainguid, str)
1999
2000     if hostip6 is not None:
2001         hostip6_base_line = "            IN AAAA    " + hostip6
2002         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
2003     else:
2004         hostip6_base_line = ""
2005         hostip6_host_line = ""
2006
2007     if hostip is not None:
2008         hostip_base_line = "            IN A    " + hostip
2009         hostip_host_line = hostname + "        IN A    " + hostip
2010     else:
2011         hostip_base_line = ""
2012         hostip_host_line = ""
2013
2014     setup_file(setup_path("provision.zone"), path, {
2015             "DNSPASS_B64": b64encode(dnspass),
2016             "HOSTNAME": hostname,
2017             "DNSDOMAIN": dnsdomain,
2018             "REALM": realm,
2019             "HOSTIP_BASE_LINE": hostip_base_line,
2020             "HOSTIP_HOST_LINE": hostip_host_line,
2021             "DOMAINGUID": domainguid,
2022             "DATESTRING": time.strftime("%Y%m%d%H"),
2023             "DEFAULTSITE": DEFAULTSITE,
2024             "NTDSGUID": ntdsguid,
2025             "HOSTIP6_BASE_LINE": hostip6_base_line,
2026             "HOSTIP6_HOST_LINE": hostip6_host_line,
2027         })
2028
2029
2030 def create_named_conf(path, setup_path, realm, dnsdomain,
2031                       private_dir):
2032     """Write out a file containing zone statements suitable for inclusion in a
2033     named.conf file (including GSS-TSIG configuration).
2034     
2035     :param path: Path of the new named.conf file.
2036     :param setup_path: Setup path function.
2037     :param realm: Realm name
2038     :param dnsdomain: DNS Domain name
2039     :param private_dir: Path to private directory
2040     :param keytab_name: File name of DNS keytab file
2041     """
2042
2043     setup_file(setup_path("named.conf"), path, {
2044             "DNSDOMAIN": dnsdomain,
2045             "REALM": realm,
2046             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
2047             "PRIVATE_DIR": private_dir
2048             })
2049
2050 def create_named_txt(path, setup_path, realm, dnsdomain,
2051                       private_dir, keytab_name):
2052     """Write out a file containing zone statements suitable for inclusion in a
2053     named.conf file (including GSS-TSIG configuration).
2054     
2055     :param path: Path of the new named.conf file.
2056     :param setup_path: Setup path function.
2057     :param realm: Realm name
2058     :param dnsdomain: DNS Domain name
2059     :param private_dir: Path to private directory
2060     :param keytab_name: File name of DNS keytab file
2061     """
2062
2063     setup_file(setup_path("named.txt"), path, {
2064             "DNSDOMAIN": dnsdomain,
2065             "REALM": realm,
2066             "DNS_KEYTAB": keytab_name,
2067             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
2068             "PRIVATE_DIR": private_dir
2069         })
2070
2071 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
2072     """Write out a file containing zone statements suitable for inclusion in a
2073     named.conf file (including GSS-TSIG configuration).
2074     
2075     :param path: Path of the new named.conf file.
2076     :param setup_path: Setup path function.
2077     :param dnsdomain: DNS Domain name
2078     :param hostname: Local hostname
2079     :param realm: Realm name
2080     """
2081
2082     setup_file(setup_path("krb5.conf"), path, {
2083             "DNSDOMAIN": dnsdomain,
2084             "HOSTNAME": hostname,
2085             "REALM": realm,
2086         })
2087
2088