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