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