s4:dsdb Add 'lazy_commit' module to swallow the 'lazy commit' OID
[ira/wip.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #   
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #   
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 """Functions for setting up a Samba configuration."""
27
28 from base64 import b64encode
29 import os
30 import sys
31 import pwd
32 import grp
33 import time
34 import uuid, glue
35 import socket
36 import param
37 import registry
38 import samba
39 import subprocess
40 import ldb
41
42 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     tdb_modules_list = [
636                     "subtree_rename",
637                     "subtree_delete",
638                     "linked_attributes",
639                     "extended_dn_out_ldb"]
640     modules_list2 = ["show_deleted",
641                     "partition"]
642  
643     domaindn_ldb = "users.ldb"
644     configdn_ldb = "configuration.ldb"
645     schemadn_ldb = "schema.ldb"
646     if ldap_backend is not None:
647         domaindn_ldb = ldap_backend.ldapi_uri
648         configdn_ldb = ldap_backend.ldapi_uri
649         schemadn_ldb = ldap_backend.ldapi_uri
650         
651         if ldap_backend.ldap_backend_type == "fedora-ds":
652             backend_modules = ["nsuniqueid", "paged_searches"]
653             # We can handle linked attributes here, as we don't have directory-side subtree operations
654             tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
655         elif ldap_backend.ldap_backend_type == "openldap":
656             backend_modules = ["entryuuid", "paged_searches"]
657             # OpenLDAP handles subtree renames, so we don't want to do any of these things
658             tdb_modules_list = ["extended_dn_out_dereference"]
659
660     elif serverrole == "domain controller":
661         tdb_modules_list.insert(0, "repl_meta_data")
662         backend_modules = []
663     else:
664         backend_modules = ["objectguid"]
665
666     if tdb_modules_list is None:
667         tdb_modules_list_as_string = ""
668     else:
669         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
670         
671     samdb.transaction_start()
672     try:
673         message("Setting up sam.ldb partitions and settings")
674         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
675                 "SCHEMADN": names.schemadn, 
676                 "SCHEMADN_LDB": schemadn_ldb,
677                 "SCHEMADN_MOD2": ",objectguid",
678                 "CONFIGDN": names.configdn,
679                 "CONFIGDN_LDB": configdn_ldb,
680                 "DOMAINDN": names.domaindn,
681                 "DOMAINDN_LDB": domaindn_ldb,
682                 "SCHEMADN_MOD": "schema_fsmo,instancetype",
683                 "CONFIGDN_MOD": "naming_fsmo,instancetype",
684                 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
685                 "MODULES_LIST": ",".join(modules_list),
686                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
687                 "MODULES_LIST2": ",".join(modules_list2),
688                 "BACKEND_MOD": ",".join(backend_modules),
689         })
690
691         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
692
693         message("Setting up sam.ldb rootDSE")
694         setup_samdb_rootdse(samdb, setup_path, names)
695
696     except:
697         samdb.transaction_cancel()
698         raise
699
700     samdb.transaction_commit()
701     
702 def secretsdb_self_join(secretsdb, domain, 
703                         netbiosname, domainsid, machinepass, 
704                         realm=None, dnsdomain=None,
705                         keytab_path=None, 
706                         key_version_number=1,
707                         secure_channel_type=SEC_CHAN_WKSTA):
708     """Add domain join-specific bits to a secrets database.
709     
710     :param secretsdb: Ldb Handle to the secrets database
711     :param machinepass: Machine password
712     """
713     attrs=["whenChanged",
714            "secret",
715            "priorSecret",
716            "priorChanged",
717            "krb5Keytab",
718            "privateKeytab"]
719     
720
721     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
722     msg["secureChannelType"] = str(secure_channel_type)
723     msg["flatname"] = [domain]
724     msg["objectClass"] = ["top", "primaryDomain"]
725     if realm is not None:
726       if dnsdomain is None:
727         dnsdomain = realm.lower()
728       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
729       msg["realm"] = realm
730       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
731       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
732       msg["privateKeytab"] = ["secrets.keytab"];
733
734
735     msg["secret"] = [machinepass]
736     msg["samAccountName"] = ["%s$" % netbiosname]
737     msg["secureChannelType"] = [str(secure_channel_type)]
738     msg["objectSid"] = [ndr_pack(domainsid)]
739     
740     res = secretsdb.search(base="cn=Primary Domains", 
741                            attrs=attrs, 
742                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
743                            scope=SCOPE_ONELEVEL)
744     
745     for del_msg in res:
746       if del_msg.dn is not msg.dn:
747         secretsdb.delete(del_msg.dn)
748
749     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
750
751     if len(res) == 1:
752       msg["priorSecret"] = res[0]["secret"]
753       msg["priorWhenChanged"] = res[0]["whenChanged"]
754
755       if res["privateKeytab"] is not None:
756         msg["privateKeytab"] = res[0]["privateKeytab"]
757
758       if res["krb5Keytab"] is not None:
759         msg["krb5Keytab"] = res[0]["krb5Keytab"]
760
761       for el in msg:
762         el.set_flags(ldb.FLAG_MOD_REPLACE)
763         secretsdb.modify(msg)
764     else:
765       secretsdb.add(msg)
766
767
768 def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain, 
769                         dns_keytab_path, dnspass):
770     """Add DNS specific bits to a secrets database.
771     
772     :param secretsdb: Ldb Handle to the secrets database
773     :param setup_path: Setup path function
774     :param machinepass: Machine password
775     """
776     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
777             "REALM": realm,
778             "DNSDOMAIN": dnsdomain,
779             "DNS_KEYTAB": dns_keytab_path,
780             "DNSPASS_B64": b64encode(dnspass),
781             })
782
783
784 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
785     """Setup the secrets database.
786
787     :param path: Path to the secrets database.
788     :param setup_path: Get the path to a setup file.
789     :param session_info: Session info.
790     :param credentials: Credentials
791     :param lp: Loadparm context
792     :return: LDB handle for the created secrets database
793     """
794     if os.path.exists(path):
795         os.unlink(path)
796     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
797                       lp=lp)
798     secrets_ldb.erase()
799     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
800     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
801                       lp=lp)
802     secrets_ldb.transaction_start()
803     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
804
805     if credentials is not None and credentials.authentication_requested():
806         if credentials.get_bind_dn() is not None:
807             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
808                     "LDAPMANAGERDN": credentials.get_bind_dn(),
809                     "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
810                     })
811         else:
812             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
813                     "LDAPADMINUSER": credentials.get_username(),
814                     "LDAPADMINREALM": credentials.get_realm(),
815                     "LDAPADMINPASS_B64": b64encode(credentials.get_password())
816                     })
817
818     return secrets_ldb
819
820 def setup_registry(path, setup_path, session_info, lp):
821     """Setup the registry.
822     
823     :param path: Path to the registry database
824     :param setup_path: Function that returns the path to a setup.
825     :param session_info: Session information
826     :param credentials: Credentials
827     :param lp: Loadparm context
828     """
829     reg = registry.Registry()
830     hive = registry.open_ldb(path, session_info=session_info, 
831                          lp_ctx=lp)
832     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
833     provision_reg = setup_path("provision.reg")
834     assert os.path.exists(provision_reg)
835     reg.diff_apply(provision_reg)
836
837
838 def setup_idmapdb(path, setup_path, session_info, lp):
839     """Setup the idmap database.
840
841     :param path: path to the idmap database
842     :param setup_path: Function that returns a path to a setup file
843     :param session_info: Session information
844     :param credentials: Credentials
845     :param lp: Loadparm context
846     """
847     if os.path.exists(path):
848         os.unlink(path)
849
850     idmap_ldb = IDmapDB(path, session_info=session_info,
851                         lp=lp)
852
853     idmap_ldb.erase()
854     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
855     return idmap_ldb
856
857
858 def setup_samdb_rootdse(samdb, setup_path, names):
859     """Setup the SamDB rootdse.
860
861     :param samdb: Sam Database handle
862     :param setup_path: Obtain setup path
863     """
864     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
865         "SCHEMADN": names.schemadn, 
866         "NETBIOSNAME": names.netbiosname,
867         "DNSDOMAIN": names.dnsdomain,
868         "REALM": names.realm,
869         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
870         "DOMAINDN": names.domaindn,
871         "ROOTDN": names.rootdn,
872         "CONFIGDN": names.configdn,
873         "SERVERDN": names.serverdn,
874         })
875         
876
877 def setup_self_join(samdb, names,
878                     machinepass, dnspass, 
879                     domainsid, invocationid, setup_path,
880                     policyguid, policyguid_dc, domainControllerFunctionality,ntdsguid):
881     """Join a host to its own domain."""
882     assert isinstance(invocationid, str)
883     if ntdsguid is not None:
884         ntdsguid_mod = "objectGUID: %s\n"%ntdsguid
885     else:
886         ntdsguid_mod = ""
887     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
888               "CONFIGDN": names.configdn, 
889               "SCHEMADN": names.schemadn,
890               "DOMAINDN": names.domaindn,
891               "SERVERDN": names.serverdn,
892               "INVOCATIONID": invocationid,
893               "NETBIOSNAME": names.netbiosname,
894               "DEFAULTSITE": names.sitename,
895               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
896               "MACHINEPASS_B64": b64encode(machinepass),
897               "DNSPASS_B64": b64encode(dnspass),
898               "REALM": names.realm,
899               "DOMAIN": names.domain,
900               "DNSDOMAIN": names.dnsdomain,
901               "SAMBA_VERSION_STRING": version,
902               "NTDSGUID": ntdsguid_mod,
903               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
904
905     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
906               "POLICYGUID": policyguid,
907               "POLICYGUID_DC": policyguid_dc,
908               "DNSDOMAIN": names.dnsdomain,
909               "DOMAINSID": str(domainsid),
910               "DOMAINDN": names.domaindn})
911     
912     # add the NTDSGUID based SPNs
913     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
914     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
915                                      expression="", scope=SCOPE_BASE)
916     assert isinstance(names.ntdsguid, str)
917
918     # Setup fSMORoleOwner entries to point at the newly created DC entry
919     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
920               "DOMAIN": names.domain,
921               "DNSDOMAIN": names.dnsdomain,
922               "DOMAINDN": names.domaindn,
923               "CONFIGDN": names.configdn,
924               "SCHEMADN": names.schemadn, 
925               "DEFAULTSITE": names.sitename,
926               "SERVERDN": names.serverdn,
927               "NETBIOSNAME": names.netbiosname,
928               "NTDSGUID": names.ntdsguid
929               })
930
931
932 def setup_samdb(path, setup_path, session_info, credentials, lp, 
933                 names, message, 
934                 domainsid, domainguid, policyguid, policyguid_dc,
935                 fill, adminpass, krbtgtpass, 
936                 machinepass, invocationid, dnspass, ntdsguid,
937                 serverrole, dom_for_fun_level=None,
938                 schema=None, ldap_backend=None):
939     """Setup a complete SAM Database.
940     
941     :note: This will wipe the main SAM database file!
942     """
943
944     # ATTENTION: Do NOT change these default values without discussion with the
945     # team and/or release manager. They have a big impact on the whole program!
946     domainControllerFunctionality = DS_DC_FUNCTION_2008
947
948     if dom_for_fun_level is None:
949         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
950     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
951         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
952
953     if dom_for_fun_level > domainControllerFunctionality:
954         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!")
955
956     domainFunctionality = dom_for_fun_level
957     forestFunctionality = dom_for_fun_level
958
959     # Also wipes the database
960     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
961                            credentials=credentials, session_info=session_info,
962                            names=names, ldap_backend=ldap_backend,
963                            serverrole=serverrole)
964
965     if (schema == None):
966         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
967             sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
968
969     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
970     samdb = Ldb(session_info=session_info, 
971                 credentials=credentials, lp=lp)
972
973     message("Pre-loading the Samba 4 and AD schema")
974
975     # Load the schema from the one we computed earlier
976     samdb.set_schema_from_ldb(schema.ldb)
977
978     # And now we can connect to the DB - the schema won't be loaded from the DB
979     samdb.connect(path)
980
981     # Load @OPTIONS
982     samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
983
984     if fill == FILL_DRS:
985         return samdb
986
987     samdb.transaction_start()
988     try:
989         message("Erasing data from partitions")
990         # Load the schema (again).  This time it will force a reindex,
991         # and will therefore make the erase_partitions() below
992         # computationally sane
993         samdb.set_schema_from_ldb(schema.ldb)
994         samdb.erase_partitions()
995     
996         # Set the domain functionality levels onto the database.
997         # Various module (the password_hash module in particular) need
998         # to know what level of AD we are emulating.
999
1000         # These will be fixed into the database via the database
1001         # modifictions below, but we need them set from the start.
1002         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1003         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1004         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
1005
1006         samdb.set_domain_sid(str(domainsid))
1007         if serverrole == "domain controller":
1008             samdb.set_invocation_id(invocationid)
1009
1010         message("Adding DomainDN: %s" % names.domaindn)
1011         if serverrole == "domain controller":
1012             domain_oc = "domainDNS"
1013         else:
1014             domain_oc = "samba4LocalDomain"
1015
1016 #impersonate domain admin
1017         admin_session_info = admin_session(lp, str(domainsid))
1018         samdb.set_session_info(admin_session_info)
1019         if domainguid is not None:
1020             domainguid_mod = "objectGUID: %s\n-" % domainguid
1021         else:
1022             domainguid_mod = ""
1023         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1024                 "DOMAINDN": names.domaindn,
1025                 "DOMAIN_OC": domain_oc,
1026                 "DOMAINGUID": domainguid_mod
1027                 })
1028
1029
1030         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1031             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1032             "DOMAINSID": str(domainsid),
1033             "SCHEMADN": names.schemadn, 
1034             "NETBIOSNAME": names.netbiosname,
1035             "DEFAULTSITE": names.sitename,
1036             "CONFIGDN": names.configdn,
1037             "SERVERDN": names.serverdn,
1038             "POLICYGUID": policyguid,
1039             "DOMAINDN": names.domaindn,
1040             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1041             "SAMBA_VERSION_STRING": version
1042             })
1043
1044         message("Adding configuration container")
1045         descr = get_config_descriptor(domainsid);
1046         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1047             "CONFIGDN": names.configdn, 
1048             "DESCRIPTOR": descr,
1049             })
1050         message("Modifying configuration container")
1051         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1052             "CONFIGDN": names.configdn, 
1053             "SCHEMADN": names.schemadn,
1054             })
1055
1056         # The LDIF here was created when the Schema object was constructed
1057         message("Setting up sam.ldb schema")
1058         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1059         samdb.modify_ldif(schema.schema_dn_modify)
1060         samdb.write_prefixes_from_schema()
1061         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1062         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1063                        {"SCHEMADN": names.schemadn})
1064
1065         message("Setting up sam.ldb configuration data")
1066         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1067             "CONFIGDN": names.configdn,
1068             "NETBIOSNAME": names.netbiosname,
1069             "DEFAULTSITE": names.sitename,
1070             "DNSDOMAIN": names.dnsdomain,
1071             "DOMAIN": names.domain,
1072             "SCHEMADN": names.schemadn,
1073             "DOMAINDN": names.domaindn,
1074             "SERVERDN": names.serverdn,
1075             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1076             })
1077
1078         message("Setting up display specifiers")
1079         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1080         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1081         check_all_substituted(display_specifiers_ldif)
1082         samdb.add_ldif(display_specifiers_ldif)
1083
1084         message("Adding users container")
1085         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1086                 "DOMAINDN": names.domaindn})
1087         message("Modifying users container")
1088         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1089                 "DOMAINDN": names.domaindn})
1090         message("Adding computers container")
1091         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1092                 "DOMAINDN": names.domaindn})
1093         message("Modifying computers container")
1094         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1095                 "DOMAINDN": names.domaindn})
1096         message("Setting up sam.ldb data")
1097         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1098             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1099             "DOMAINDN": names.domaindn,
1100             "NETBIOSNAME": names.netbiosname,
1101             "DEFAULTSITE": names.sitename,
1102             "CONFIGDN": names.configdn,
1103             "SERVERDN": names.serverdn,
1104             "POLICYGUID_DC": policyguid_dc
1105             })
1106
1107         if fill == FILL_FULL:
1108             message("Setting up sam.ldb users and groups")
1109             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1110                 "DOMAINDN": names.domaindn,
1111                 "DOMAINSID": str(domainsid),
1112                 "CONFIGDN": names.configdn,
1113                 "ADMINPASS_B64": b64encode(adminpass),
1114                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1115                 })
1116
1117             if serverrole == "domain controller":
1118                 message("Setting up self join")
1119                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1120                                 dnspass=dnspass,  
1121                                 machinepass=machinepass, 
1122                                 domainsid=domainsid, policyguid=policyguid,
1123                                 policyguid_dc=policyguid_dc,
1124                                 setup_path=setup_path,
1125                                 domainControllerFunctionality=domainControllerFunctionality,ntdsguid=ntdsguid)
1126
1127                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1128                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1129                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1130                 assert isinstance(names.ntdsguid, str)
1131
1132     except:
1133         samdb.transaction_cancel()
1134         raise
1135
1136     samdb.transaction_commit()
1137     return samdb
1138
1139
1140 FILL_FULL = "FULL"
1141 FILL_NT4SYNC = "NT4SYNC"
1142 FILL_DRS = "DRS"
1143
1144
1145 def provision(setup_dir, message, session_info, 
1146               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1147               realm=None, 
1148               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1149               serverdn=None,
1150               domain=None, hostname=None, hostip=None, hostip6=None, 
1151               domainsid=None, adminpass=None, ldapadminpass=None, 
1152               krbtgtpass=None, domainguid=None, 
1153               policyguid=None, policyguid_dc=None, invocationid=None,
1154               machinepass=None,ntdsguid=None,
1155               dnspass=None, root=None, nobody=None, users=None, 
1156               wheel=None, backup=None, aci=None, serverrole=None,
1157               dom_for_fun_level=None,
1158               ldap_backend_extra_port=None, ldap_backend_type=None,
1159               sitename=None,
1160               ol_mmr_urls=None, ol_olc=None, 
1161               setup_ds_path=None, slapd_path=None, nosync=False,
1162               ldap_dryrun_mode=False):
1163     """Provision samba4
1164     
1165     :note: caution, this wipes all existing data!
1166     """
1167
1168     def setup_path(file):
1169       return os.path.join(setup_dir, file)
1170
1171     if domainsid is None:
1172       domainsid = security.random_sid()
1173     else:
1174       domainsid = security.dom_sid(domainsid)
1175
1176     # create/adapt the group policy GUIDs
1177     if policyguid is None:
1178         policyguid = str(uuid.uuid4())
1179     policyguid = policyguid.upper()
1180     if policyguid_dc is None:
1181         policyguid_dc = str(uuid.uuid4())
1182     policyguid_dc = policyguid_dc.upper()
1183
1184     if adminpass is None:
1185         adminpass = glue.generate_random_str(12)
1186     if krbtgtpass is None:
1187         krbtgtpass = glue.generate_random_str(12)
1188     if machinepass is None:
1189         machinepass  = glue.generate_random_str(12)
1190     if dnspass is None:
1191         dnspass = glue.generate_random_str(12)
1192     if ldapadminpass is None:
1193         #Make a new, random password between Samba and it's LDAP server
1194         ldapadminpass=glue.generate_random_str(12)        
1195
1196
1197     root_uid = findnss_uid([root or "root"])
1198     nobody_uid = findnss_uid([nobody or "nobody"])
1199     users_gid = findnss_gid([users or "users"])
1200     if wheel is None:
1201         wheel_gid = findnss_gid(["wheel", "adm"])
1202     else:
1203         wheel_gid = findnss_gid([wheel])
1204
1205     if targetdir is not None:
1206         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1207             os.makedirs(os.path.join(targetdir, "etc"))
1208         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1209     elif smbconf is None:
1210         smbconf = param.default_path()
1211
1212     # only install a new smb.conf if there isn't one there already
1213     if not os.path.exists(smbconf):
1214         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1215                      targetdir)
1216
1217     lp = param.LoadParm()
1218     lp.load(smbconf)
1219
1220     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1221                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1222                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1223                         serverdn=serverdn)
1224
1225     paths = provision_paths_from_lp(lp, names.dnsdomain)
1226
1227     if hostip is None:
1228         try:
1229             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1230         except socket.gaierror, (socket.EAI_NODATA, msg):
1231             hostip = None
1232
1233     if hostip6 is None:
1234         try:
1235             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1236         except socket.gaierror, (socket.EAI_NODATA, msg): 
1237             hostip6 = None
1238
1239     if serverrole is None:
1240         serverrole = lp.get("server role")
1241
1242     assert serverrole in ("domain controller", "member server", "standalone")
1243     if invocationid is None and serverrole == "domain controller":
1244         invocationid = str(uuid.uuid4())
1245
1246     if not os.path.exists(paths.private_dir):
1247         os.mkdir(paths.private_dir)
1248
1249     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1250     
1251     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1252         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1253     
1254     secrets_credentials = credentials
1255     provision_backend = None
1256     if ldap_backend_type:
1257         # We only support an LDAP backend over ldapi://
1258
1259         provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1260                                              lp=lp, credentials=credentials, 
1261                                              names=names,
1262                                              message=message, hostname=hostname,
1263                                              root=root, schema=schema,
1264                                              ldap_backend_type=ldap_backend_type,
1265                                              ldapadminpass=ldapadminpass,
1266                                              ldap_backend_extra_port=ldap_backend_extra_port,
1267                                              ol_mmr_urls=ol_mmr_urls, 
1268                                              slapd_path=slapd_path,
1269                                              setup_ds_path=setup_ds_path,
1270                                              ldap_dryrun_mode=ldap_dryrun_mode)
1271
1272         # Now use the backend credentials to access the databases
1273         credentials = provision_backend.credentials
1274         secrets_credentials = provision_backend.adminCredentials
1275         ldapi_url = provision_backend.ldapi_uri
1276
1277     # only install a new shares config db if there is none
1278     if not os.path.exists(paths.shareconf):
1279         message("Setting up share.ldb")
1280         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1281                         credentials=credentials, lp=lp)
1282         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1283
1284      
1285     message("Setting up secrets.ldb")
1286     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1287                                   session_info=session_info, 
1288                                   credentials=secrets_credentials, lp=lp)
1289
1290     message("Setting up the registry")
1291     setup_registry(paths.hklm, setup_path, session_info, 
1292                    lp=lp)
1293
1294     message("Setting up idmap db")
1295     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1296                           lp=lp)
1297
1298     message("Setting up SAM db")
1299     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1300                         credentials=credentials, lp=lp, names=names,
1301                         message=message, 
1302                         domainsid=domainsid, 
1303                         schema=schema, domainguid=domainguid,
1304                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1305                         fill=samdb_fill, 
1306                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1307                         invocationid=invocationid, 
1308                         machinepass=machinepass, dnspass=dnspass, 
1309                         ntdsguid=ntdsguid,
1310                         serverrole=serverrole,
1311                         dom_for_fun_level=dom_for_fun_level,
1312                         ldap_backend=provision_backend)
1313
1314     if serverrole == "domain controller":
1315         if paths.netlogon is None:
1316             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1317             message("Please either remove %s or see the template at %s" % 
1318                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1319             assert(paths.netlogon is not None)
1320
1321         if paths.sysvol is None:
1322             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1323             message("Please either remove %s or see the template at %s" % 
1324                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1325             assert(paths.sysvol is not None)            
1326             
1327         # Set up group policies (domain policy and domain controller policy)
1328
1329         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1330                                    "{" + policyguid + "}")
1331         os.makedirs(policy_path, 0755)
1332         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1333                                    "[General]\r\nVersion=65543")
1334         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1335         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1336
1337         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1338                                    "{" + policyguid_dc + "}")
1339         os.makedirs(policy_path_dc, 0755)
1340         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1341                                    "[General]\r\nVersion=2")
1342         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1343         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1344
1345         if not os.path.isdir(paths.netlogon):
1346             os.makedirs(paths.netlogon, 0755)
1347
1348     if samdb_fill == FILL_FULL:
1349         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1350                             root_uid=root_uid, nobody_uid=nobody_uid,
1351                             users_gid=users_gid, wheel_gid=wheel_gid)
1352
1353         message("Setting up sam.ldb rootDSE marking as synchronized")
1354         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1355
1356         # Only make a zone file on the first DC, it should be replicated with DNS replication
1357         if serverrole == "domain controller":
1358             secretsdb_self_join(secrets_ldb, domain=domain,
1359                                 realm=names.realm,
1360                                 dnsdomain=names.dnsdomain,
1361                                 netbiosname=names.netbiosname,
1362                                 domainsid=domainsid, 
1363                                 machinepass=machinepass,
1364                                 secure_channel_type=SEC_CHAN_BDC)
1365
1366             secretsdb_setup_dns(secrets_ldb, setup_path, 
1367                                 realm=names.realm, dnsdomain=names.dnsdomain,
1368                                 dns_keytab_path=paths.dns_keytab,
1369                                 dnspass=dnspass)
1370
1371             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1372             assert isinstance(domainguid, str)
1373
1374             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1375                              domaindn=names.domaindn, hostip=hostip,
1376                              hostip6=hostip6, hostname=names.hostname,
1377                              dnspass=dnspass, realm=names.realm,
1378                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1379
1380             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1381                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1382
1383             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1384                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1385                               keytab_name=paths.dns_keytab)
1386             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1387             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1388
1389             create_krb5_conf(paths.krb5conf, setup_path,
1390                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1391                              realm=names.realm)
1392             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1393
1394     #Now commit the secrets.ldb to disk
1395     secrets_ldb.transaction_commit()
1396
1397     if provision_backend is not None: 
1398       if ldap_backend_type == "fedora-ds":
1399         ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1400
1401         # delete default SASL mappings
1402         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1403
1404         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1405         for i in range (0, len(res)):
1406           dn = str(res[i]["dn"])
1407           ldapi_db.delete(dn)
1408
1409           aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1410
1411           m = ldb.Message()
1412           m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1413         
1414           m.dn = ldb.Dn(1, names.domaindn)
1415           ldapi_db.modify(m)
1416
1417           m.dn = ldb.Dn(1, names.configdn)
1418           ldapi_db.modify(m)
1419
1420           m.dn = ldb.Dn(1, names.schemadn)
1421           ldapi_db.modify(m)
1422
1423       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1424       if provision_backend.slapd.poll() is None:
1425         #Kill the slapd
1426         if hasattr(provision_backend.slapd, "terminate"):
1427           provision_backend.slapd.terminate()
1428         else:
1429           # Older python versions don't have .terminate()
1430           import signal
1431           os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1432             
1433         #and now wait for it to die
1434         provision_backend.slapd.communicate()
1435             
1436     # now display slapd_command_file.txt to show how slapd must be started next time
1437         message("Use later the following commandline to start slapd, then Samba:")
1438         slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1439         message(slapd_command)
1440         message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1441
1442         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1443                 "SLAPD_COMMAND" : slapd_command})
1444
1445     
1446     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1447                                ldapi_url)
1448
1449     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1450
1451     message("Once the above files are installed, your Samba4 server will be ready to use")
1452     message("Server Role:           %s" % serverrole)
1453     message("Hostname:              %s" % names.hostname)
1454     message("NetBIOS Domain:        %s" % names.domain)
1455     message("DNS Domain:            %s" % names.dnsdomain)
1456     message("DOMAIN SID:            %s" % str(domainsid))
1457     if samdb_fill == FILL_FULL:
1458         message("Admin password:    %s" % adminpass)
1459     if provision_backend:
1460         if provision_backend.credentials.get_bind_dn() is not None:
1461             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1462         else:
1463             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1464
1465         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1466   
1467     result = ProvisionResult()
1468     result.domaindn = domaindn
1469     result.paths = paths
1470     result.lp = lp
1471     result.samdb = samdb
1472     return result
1473
1474
1475
1476 def provision_become_dc(setup_dir=None,
1477                         smbconf=None, targetdir=None, realm=None, 
1478                         rootdn=None, domaindn=None, schemadn=None,
1479                         configdn=None, serverdn=None,
1480                         domain=None, hostname=None, domainsid=None, 
1481                         adminpass=None, krbtgtpass=None, domainguid=None, 
1482                         policyguid=None, policyguid_dc=None, invocationid=None,
1483                         machinepass=None, 
1484                         dnspass=None, root=None, nobody=None, users=None, 
1485                         wheel=None, backup=None, serverrole=None, 
1486                         ldap_backend=None, ldap_backend_type=None,
1487                         sitename=None, debuglevel=1):
1488
1489     def message(text):
1490         """print a message if quiet is not set."""
1491         print text
1492
1493     glue.set_debug_level(debuglevel)
1494
1495     return provision(setup_dir, message, system_session(), None,
1496               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1497               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1498               configdn=configdn, serverdn=serverdn, domain=domain,
1499               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1500               machinepass=machinepass, serverrole="domain controller",
1501               sitename=sitename)
1502
1503
1504 def setup_db_config(setup_path, dbdir):
1505     """Setup a Berkeley database.
1506     
1507     :param setup_path: Setup path function.
1508     :param dbdir: Database directory."""
1509     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1510         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1511         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1512             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1513
1514     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1515                {"LDAPDBDIR": dbdir})
1516     
1517 class ProvisionBackend(object):
1518     def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, 
1519                  names=None, message=None, 
1520                  hostname=None, root=None, 
1521                  schema=None, ldapadminpass=None,
1522                  ldap_backend_type=None, ldap_backend_extra_port=None,
1523                  ol_mmr_urls=None, 
1524                  setup_ds_path=None, slapd_path=None, 
1525                  nosync=False, ldap_dryrun_mode=False):
1526         """Provision an LDAP backend for samba4
1527         
1528         This works for OpenLDAP and Fedora DS
1529         """
1530
1531         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1532         
1533         if not os.path.isdir(paths.ldapdir):
1534             os.makedirs(paths.ldapdir, 0700)
1535             
1536         if ldap_backend_type == "existing":
1537             #Check to see that this 'existing' LDAP backend in fact exists
1538             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1539             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1540                                                 expression="(objectClass=OpenLDAProotDSE)")
1541
1542             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1543             # This caused them to be set into the long-term database later in the script.
1544             self.credentials = credentials
1545             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1546             return
1547     
1548         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1549         # if another instance of slapd is already running 
1550         try:
1551             ldapi_db = Ldb(self.ldapi_uri)
1552             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1553                                                 expression="(objectClass=OpenLDAProotDSE)");
1554             try:
1555                 f = open(paths.slapdpid, "r")
1556                 p = f.read()
1557                 f.close()
1558                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1559             except:
1560                 pass
1561             
1562             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1563         
1564         except LdbError, e:
1565             pass
1566
1567         # Try to print helpful messages when the user has not specified the path to slapd
1568         if slapd_path is None:
1569             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1570         if not os.path.exists(slapd_path):
1571             message (slapd_path)
1572             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1573
1574         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1575         try:
1576             os.unlink(schemadb_path)
1577         except OSError:
1578             pass
1579
1580
1581         # Put the LDIF of the schema into a database so we can search on
1582         # it to generate schema-dependent configurations in Fedora DS and
1583         # OpenLDAP
1584         os.path.join(paths.ldapdir, "schema-tmp.ldb")
1585         schema.ldb.connect(schemadb_path)
1586         schema.ldb.transaction_start()
1587     
1588         # These bits of LDIF are supplied when the Schema object is created
1589         schema.ldb.add_ldif(schema.schema_dn_add)
1590         schema.ldb.modify_ldif(schema.schema_dn_modify)
1591         schema.ldb.add_ldif(schema.schema_data)
1592         schema.ldb.transaction_commit()
1593
1594         self.credentials = Credentials()
1595         self.credentials.guess(lp)
1596         #Kerberos to an ldapi:// backend makes no sense
1597         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1598
1599         self.adminCredentials = Credentials()
1600         self.adminCredentials.guess(lp)
1601         #Kerberos to an ldapi:// backend makes no sense
1602         self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1603
1604         self.ldap_backend_type = ldap_backend_type
1605
1606         if ldap_backend_type == "fedora-ds":
1607             provision_fds_backend(self, paths=paths, setup_path=setup_path,
1608                                   names=names, message=message, 
1609                                   hostname=hostname,
1610                                   ldapadminpass=ldapadminpass, root=root, 
1611                                   schema=schema,
1612                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1613                                   setup_ds_path=setup_ds_path,
1614                                   slapd_path=slapd_path,
1615                                   nosync=nosync,
1616                                   ldap_dryrun_mode=ldap_dryrun_mode)
1617             
1618         elif ldap_backend_type == "openldap":
1619             provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1620                                        names=names, message=message, 
1621                                        hostname=hostname,
1622                                        ldapadminpass=ldapadminpass, root=root, 
1623                                        schema=schema,
1624                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1625                                        ol_mmr_urls=ol_mmr_urls, 
1626                                        slapd_path=slapd_path,
1627                                        nosync=nosync,
1628                                        ldap_dryrun_mode=ldap_dryrun_mode)
1629         else:
1630             raise ProvisioningError("Unknown LDAP backend type selected")
1631
1632         self.credentials.set_password(ldapadminpass)
1633         self.adminCredentials.set_username("samba-admin")
1634         self.adminCredentials.set_password(ldapadminpass)
1635
1636         # Now start the slapd, so we can provision onto it.  We keep the
1637         # subprocess context around, to kill this off at the successful
1638         # end of the script
1639         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1640     
1641         while self.slapd.poll() is None:
1642             # Wait until the socket appears
1643             try:
1644                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1645                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1646                                                     expression="(objectClass=OpenLDAProotDSE)")
1647                 # If we have got here, then we must have a valid connection to the LDAP server!
1648                 return
1649             except LdbError, e:
1650                 time.sleep(1)
1651                 pass
1652         
1653         raise ProvisioningError("slapd died before we could make a connection to it")
1654
1655
1656 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1657                                message=None, 
1658                                hostname=None, ldapadminpass=None, root=None, 
1659                                schema=None, 
1660                                ldap_backend_extra_port=None,
1661                                ol_mmr_urls=None, 
1662                                slapd_path=None, nosync=False,
1663                                ldap_dryrun_mode=False):
1664
1665     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1666     nosync_config = ""
1667     if nosync:
1668         nosync_config = "dbnosync"
1669         
1670     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1671     refint_attributes = ""
1672     memberof_config = "# Generated from Samba4 schema\n"
1673     for att in  lnkattr.keys():
1674         if lnkattr[att] is not None:
1675             refint_attributes = refint_attributes + " " + att 
1676             
1677             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1678                                                  { "MEMBER_ATTR" : att ,
1679                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1680             
1681     refint_config = read_and_sub_file(setup_path("refint.conf"),
1682                                       { "LINK_ATTRS" : refint_attributes})
1683     
1684     attrs = ["linkID", "lDAPDisplayName"]
1685     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1686     index_config = ""
1687     for i in range (0, len(res)):
1688         index_attr = res[i]["lDAPDisplayName"][0]
1689         if index_attr == "objectGUID":
1690             index_attr = "entryUUID"
1691             
1692         index_config += "index " + index_attr + " eq\n"
1693
1694 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1695     mmr_on_config = ""
1696     mmr_replicator_acl = ""
1697     mmr_serverids_config = ""
1698     mmr_syncrepl_schema_config = "" 
1699     mmr_syncrepl_config_config = "" 
1700     mmr_syncrepl_user_config = "" 
1701        
1702     
1703     if ol_mmr_urls is not None:
1704         # For now, make these equal
1705         mmr_pass = ldapadminpass
1706         
1707         url_list=filter(None,ol_mmr_urls.split(' ')) 
1708         if (len(url_list) == 1):
1709             url_list=filter(None,ol_mmr_urls.split(',')) 
1710                      
1711             
1712             mmr_on_config = "MirrorMode On"
1713             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1714             serverid=0
1715             for url in url_list:
1716                 serverid=serverid+1
1717                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1718                                                           { "SERVERID" : str(serverid),
1719                                                             "LDAPSERVER" : url })
1720                 rid=serverid*10
1721                 rid=rid+1
1722                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1723                                                                 {  "RID" : str(rid),
1724                                                                    "MMRDN": names.schemadn,
1725                                                                    "LDAPSERVER" : url,
1726                                                                    "MMR_PASSWORD": mmr_pass})
1727                 
1728                 rid=rid+1
1729                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1730                                                                 {  "RID" : str(rid),
1731                                                                    "MMRDN": names.configdn,
1732                                                                    "LDAPSERVER" : url,
1733                                                                    "MMR_PASSWORD": mmr_pass})
1734                 
1735                 rid=rid+1
1736                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1737                                                               {  "RID" : str(rid),
1738                                                                  "MMRDN": names.domaindn,
1739                                                                  "LDAPSERVER" : url,
1740                                                                  "MMR_PASSWORD": mmr_pass })
1741     # OpenLDAP cn=config initialisation
1742     olc_syncrepl_config = ""
1743     olc_mmr_config = "" 
1744     # if mmr = yes, generate cn=config-replication directives
1745     # and olc_seed.lif for the other mmr-servers
1746     if ol_mmr_urls is not None:
1747         serverid=0
1748         olc_serverids_config = ""
1749         olc_syncrepl_seed_config = ""
1750         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1751         rid=1000
1752         for url in url_list:
1753             serverid=serverid+1
1754             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1755                                                       { "SERVERID" : str(serverid),
1756                                                         "LDAPSERVER" : url })
1757             
1758             rid=rid+1
1759             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1760                                                      {  "RID" : str(rid),
1761                                                         "LDAPSERVER" : url,
1762                                                         "MMR_PASSWORD": mmr_pass})
1763             
1764             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1765                                                           {  "RID" : str(rid),
1766                                                              "LDAPSERVER" : url})
1767                 
1768         setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1769                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1770                     "OLC_PW": ldapadminpass,
1771                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1772     # end olc
1773                 
1774     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1775                {"DNSDOMAIN": names.dnsdomain,
1776                 "LDAPDIR": paths.ldapdir,
1777                 "DOMAINDN": names.domaindn,
1778                 "CONFIGDN": names.configdn,
1779                 "SCHEMADN": names.schemadn,
1780                 "MEMBEROF_CONFIG": memberof_config,
1781                 "MIRRORMODE": mmr_on_config,
1782                 "REPLICATOR_ACL": mmr_replicator_acl,
1783                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1784                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1785                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1786                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1787                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1788                 "OLC_MMR_CONFIG": olc_mmr_config,
1789                 "REFINT_CONFIG": refint_config,
1790                 "INDEX_CONFIG": index_config,
1791                 "NOSYNC": nosync_config})
1792         
1793     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1794     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1795     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1796     
1797     if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1798         os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1799         
1800     setup_file(setup_path("cn=samba.ldif"), 
1801                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1802                { "UUID": str(uuid.uuid4()), 
1803                  "LDAPTIME": timestring(int(time.time()))} )
1804     setup_file(setup_path("cn=samba-admin.ldif"), 
1805                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1806                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1807                 "UUID": str(uuid.uuid4()), 
1808                 "LDAPTIME": timestring(int(time.time()))} )
1809     
1810     if ol_mmr_urls is not None:
1811         setup_file(setup_path("cn=replicator.ldif"),
1812                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1813                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1814                     "UUID": str(uuid.uuid4()),
1815                     "LDAPTIME": timestring(int(time.time()))} )
1816         
1817
1818     mapping = "schema-map-openldap-2.3"
1819     backend_schema = "backend-schema.schema"
1820
1821     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1822     assert backend_schema_data is not None
1823     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1824
1825     # now we generate the needed strings to start slapd automatically,
1826     # first ldapi_uri...
1827     if ldap_backend_extra_port is not None:
1828         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1829         # specified there as part of it's clue as to it's own name,
1830         # and not to replicate to itself
1831         if ol_mmr_urls is None:
1832             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1833         else:
1834             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1835     else:
1836         server_port_string = ""
1837
1838     # Prepare the 'result' information - the commands to return in particular
1839     result.slapd_provision_command = [slapd_path]
1840
1841     result.slapd_provision_command.append("-F" + paths.olcdir)
1842
1843     result.slapd_provision_command.append("-h")
1844
1845     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1846     result.slapd_command = list(result.slapd_provision_command)
1847     
1848     result.slapd_provision_command.append(result.ldapi_uri)
1849     result.slapd_provision_command.append("-d0")
1850
1851     uris = result.ldapi_uri
1852     if server_port_string is not "":
1853         uris = uris + " " + server_port_string
1854
1855     result.slapd_command.append(uris)
1856
1857     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1858     result.credentials.set_username("samba-admin")
1859     
1860     # If we were just looking for crashes up to this point, it's a
1861     # good time to exit before we realise we don't have OpenLDAP on
1862     # this system
1863     if ldap_dryrun_mode:
1864         sys.exit(0)
1865
1866     # Finally, convert the configuration into cn=config style!
1867     if not os.path.isdir(paths.olcdir):
1868         os.makedirs(paths.olcdir, 0770)
1869
1870         retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1871
1872 #        We can't do this, as OpenLDAP is strange.  It gives an error
1873 #        output to the above, but does the conversion sucessfully...
1874 #
1875 #        if retcode != 0:
1876 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1877
1878         if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1879             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1880
1881         # Don't confuse the admin by leaving the slapd.conf around
1882         os.remove(paths.slapdconf)        
1883           
1884
1885 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1886                           message=None, 
1887                           hostname=None, ldapadminpass=None, root=None, 
1888                           schema=None,
1889                           ldap_backend_extra_port=None,
1890                           setup_ds_path=None,
1891                           slapd_path=None,
1892                           nosync=False, 
1893                           ldap_dryrun_mode=False):
1894
1895     if ldap_backend_extra_port is not None:
1896         serverport = "ServerPort=%d" % ldap_backend_extra_port
1897     else:
1898         serverport = ""
1899         
1900     setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1901                {"ROOT": root,
1902                 "HOSTNAME": hostname,
1903                 "DNSDOMAIN": names.dnsdomain,
1904                 "LDAPDIR": paths.ldapdir,
1905                 "DOMAINDN": names.domaindn,
1906                 "LDAPMANAGERDN": names.ldapmanagerdn,
1907                 "LDAPMANAGERPASS": ldapadminpass, 
1908                 "SERVERPORT": serverport})
1909
1910     setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1911                {"CONFIGDN": names.configdn,
1912                 "SCHEMADN": names.schemadn,
1913                 "SAMBADN": names.sambadn,
1914                 })
1915
1916     setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, 
1917                {"SAMBADN": names.sambadn,
1918                 })
1919
1920     setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1921                 {"SAMBADN": names.sambadn, 
1922                  "LDAPADMINPASS": ldapadminpass
1923                 })
1924
1925     mapping = "schema-map-fedora-ds-1.0"
1926     backend_schema = "99_ad.ldif"
1927     
1928     # Build a schema file in Fedora DS format
1929     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1930     assert backend_schema_data is not None
1931     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1932
1933     result.credentials.set_bind_dn(names.ldapmanagerdn)
1934
1935     # Destory the target directory, or else setup-ds.pl will complain
1936     fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1937     shutil.rmtree(fedora_ds_dir, True)
1938
1939     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1940     #In the 'provision' command line, stay in the foreground so we can easily kill it
1941     result.slapd_provision_command.append("-d0")
1942
1943     #the command for the final run is the normal script
1944     result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1945
1946     # If we were just looking for crashes up to this point, it's a
1947     # good time to exit before we realise we don't have Fedora DS on
1948     if ldap_dryrun_mode:
1949         sys.exit(0)
1950
1951     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1952     if setup_ds_path is None:
1953         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\"!")
1954     if not os.path.exists(setup_ds_path):
1955         message (setup_ds_path)
1956         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1957
1958     # Run the Fedora DS setup utility
1959     retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1960     if retcode != 0:
1961         raise ProvisioningError("setup-ds failed")
1962
1963     # Load samba-admin
1964     retcode = subprocess.call([
1965         os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1966         close_fds=True, shell=False)
1967     if retcode != 0:
1968         raise("ldib2db failed")
1969
1970 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1971     """Create a PHP LDAP admin configuration file.
1972
1973     :param path: Path to write the configuration to.
1974     :param setup_path: Function to generate setup paths.
1975     """
1976     setup_file(setup_path("phpldapadmin-config.php"), path, 
1977             {"S4_LDAPI_URI": ldapi_uri})
1978
1979
1980 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1981                      hostip, hostip6, hostname, dnspass, realm, domainguid,
1982                      ntdsguid):
1983     """Write out a DNS zone file, from the info in the current database.
1984
1985     :param path: Path of the new zone file.
1986     :param setup_path: Setup path function.
1987     :param dnsdomain: DNS Domain name
1988     :param domaindn: DN of the Domain
1989     :param hostip: Local IPv4 IP
1990     :param hostip6: Local IPv6 IP
1991     :param hostname: Local hostname
1992     :param dnspass: Password for DNS
1993     :param realm: Realm name
1994     :param domainguid: GUID of the domain.
1995     :param ntdsguid: GUID of the hosts nTDSDSA record.
1996     """
1997     assert isinstance(domainguid, str)
1998
1999     if hostip6 is not None:
2000         hostip6_base_line = "            IN AAAA    " + hostip6
2001         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
2002     else:
2003         hostip6_base_line = ""
2004         hostip6_host_line = ""
2005
2006     if hostip is not None:
2007         hostip_base_line = "            IN A    " + hostip
2008         hostip_host_line = hostname + "        IN A    " + hostip
2009     else:
2010         hostip_base_line = ""
2011         hostip_host_line = ""
2012
2013     setup_file(setup_path("provision.zone"), path, {
2014             "DNSPASS_B64": b64encode(dnspass),
2015             "HOSTNAME": hostname,
2016             "DNSDOMAIN": dnsdomain,
2017             "REALM": realm,
2018             "HOSTIP_BASE_LINE": hostip_base_line,
2019             "HOSTIP_HOST_LINE": hostip_host_line,
2020             "DOMAINGUID": domainguid,
2021             "DATESTRING": time.strftime("%Y%m%d%H"),
2022             "DEFAULTSITE": DEFAULTSITE,
2023             "NTDSGUID": ntdsguid,
2024             "HOSTIP6_BASE_LINE": hostip6_base_line,
2025             "HOSTIP6_HOST_LINE": hostip6_host_line,
2026         })
2027
2028
2029 def create_named_conf(path, setup_path, realm, dnsdomain,
2030                       private_dir):
2031     """Write out a file containing zone statements suitable for inclusion in a
2032     named.conf file (including GSS-TSIG configuration).
2033     
2034     :param path: Path of the new named.conf file.
2035     :param setup_path: Setup path function.
2036     :param realm: Realm name
2037     :param dnsdomain: DNS Domain name
2038     :param private_dir: Path to private directory
2039     :param keytab_name: File name of DNS keytab file
2040     """
2041
2042     setup_file(setup_path("named.conf"), path, {
2043             "DNSDOMAIN": dnsdomain,
2044             "REALM": realm,
2045             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
2046             "PRIVATE_DIR": private_dir
2047             })
2048
2049 def create_named_txt(path, setup_path, realm, dnsdomain,
2050                       private_dir, keytab_name):
2051     """Write out a file containing zone statements suitable for inclusion in a
2052     named.conf file (including GSS-TSIG configuration).
2053     
2054     :param path: Path of the new named.conf file.
2055     :param setup_path: Setup path function.
2056     :param realm: Realm name
2057     :param dnsdomain: DNS Domain name
2058     :param private_dir: Path to private directory
2059     :param keytab_name: File name of DNS keytab file
2060     """
2061
2062     setup_file(setup_path("named.txt"), path, {
2063             "DNSDOMAIN": dnsdomain,
2064             "REALM": realm,
2065             "DNS_KEYTAB": keytab_name,
2066             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
2067             "PRIVATE_DIR": private_dir
2068         })
2069
2070 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
2071     """Write out a file containing zone statements suitable for inclusion in a
2072     named.conf file (including GSS-TSIG configuration).
2073     
2074     :param path: Path of the new named.conf file.
2075     :param setup_path: Setup path function.
2076     :param dnsdomain: DNS Domain name
2077     :param hostname: Local hostname
2078     :param realm: Realm name
2079     """
2080
2081     setup_file(setup_path("krb5.conf"), path, {
2082             "DNSDOMAIN": dnsdomain,
2083             "HOSTNAME": hostname,
2084             "REALM": realm,
2085         })
2086
2087