s4/provision: add the nTDSDSA GUID based DNS entries and SPNs
[sfrench/samba-autobuild/.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     # add the NTDSGUID based SPNs
796     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
797     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
798                                      expression="", scope=SCOPE_BASE)
799     assert isinstance(names.ntdsguid, str)
800
801     # Setup fSMORoleOwner entries to point at the newly created DC entry
802     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
803               "DOMAIN": names.domain,
804               "DOMAINDN": names.domaindn,
805               "CONFIGDN": names.configdn,
806               "SCHEMADN": names.schemadn, 
807               "DEFAULTSITE": names.sitename,
808               "SERVERDN": names.serverdn,
809               "NETBIOSNAME": names.netbiosname,
810               "NTDSGUID": names.ntdsguid
811               })
812
813
814 def setup_samdb(path, setup_path, session_info, credentials, lp, 
815                 names, message, 
816                 domainsid, domainguid, policyguid, 
817                 fill, adminpass, krbtgtpass, 
818                 machinepass, invocationid, dnspass,
819                 serverrole, schema=None, ldap_backend=None):
820     """Setup a complete SAM Database.
821     
822     :note: This will wipe the main SAM database file!
823     """
824
825     domainFunctionality = DS_BEHAVIOR_WIN2008
826     forestFunctionality = DS_BEHAVIOR_WIN2008
827     domainControllerFunctionality = DS_BEHAVIOR_WIN2008
828
829     # Also wipes the database
830     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
831                            credentials=credentials, session_info=session_info,
832                            names=names, 
833                            ldap_backend=ldap_backend, serverrole=serverrole)
834
835     if (schema == None):
836         schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
837             sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
838
839     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
840     samdb = Ldb(session_info=session_info, 
841                 credentials=credentials, lp=lp)
842
843     message("Pre-loading the Samba 4 and AD schema")
844
845     # Load the schema from the one we computed earlier
846     samdb.set_schema_from_ldb(schema.ldb)
847
848     # And now we can connect to the DB - the schema won't be loaded from the DB
849     samdb.connect(path)
850
851     # Load @OPTIONS
852     samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
853
854     if fill == FILL_DRS:
855         return samdb
856
857     samdb.transaction_start()
858     try:
859         message("Erasing data from partitions")
860         # Load the schema (again).  This time it will force a reindex,
861         # and will therefore make the erase_partitions() below
862         # computationally sane
863         samdb.set_schema_from_ldb(schema.ldb)
864         samdb.erase_partitions()
865     
866         # Set the domain functionality levels onto the database.
867         # Various module (the password_hash module in particular) need
868         # to know what level of AD we are emulating.
869
870         # These will be fixed into the database via the database
871         # modifictions below, but we need them set from the start.
872         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
873         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
874         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
875
876         samdb.set_domain_sid(str(domainsid))
877         if serverrole == "domain controller":
878             samdb.set_invocation_id(invocationid)
879
880         message("Adding DomainDN: %s" % names.domaindn)
881         if serverrole == "domain controller":
882             domain_oc = "domainDNS"
883         else:
884             domain_oc = "samba4LocalDomain"
885
886 #impersonate domain admin
887         admin_session_info = admin_session(lp, str(domainsid))
888         samdb.set_session_info(admin_session_info)
889
890         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
891                 "DOMAINDN": names.domaindn,
892                 "DOMAIN_OC": domain_oc
893                 })
894
895         message("Modifying DomainDN: " + names.domaindn + "")
896         if domainguid is not None:
897             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
898         else:
899             domainguid_mod = ""
900
901         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
902             "LDAPTIME": timestring(int(time.time())),
903             "DOMAINSID": str(domainsid),
904             "SCHEMADN": names.schemadn, 
905             "NETBIOSNAME": names.netbiosname,
906             "DEFAULTSITE": names.sitename,
907             "CONFIGDN": names.configdn,
908             "SERVERDN": names.serverdn,
909             "POLICYGUID": policyguid,
910             "DOMAINDN": names.domaindn,
911             "DOMAINGUID_MOD": domainguid_mod,
912             "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
913             })
914
915         message("Adding configuration container")
916         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
917             "CONFIGDN": names.configdn, 
918             })
919         message("Modifying configuration container")
920         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
921             "CONFIGDN": names.configdn, 
922             "SCHEMADN": names.schemadn,
923             })
924
925         # The LDIF here was created when the Schema object was constructed
926         message("Setting up sam.ldb schema")
927         samdb.add_ldif(schema.schema_dn_add)
928         samdb.modify_ldif(schema.schema_dn_modify)
929         samdb.write_prefixes_from_schema()
930         samdb.add_ldif(schema.schema_data)
931         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
932                        {"SCHEMADN": names.schemadn})
933
934         message("Setting up sam.ldb configuration data")
935         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
936             "CONFIGDN": names.configdn,
937             "NETBIOSNAME": names.netbiosname,
938             "DEFAULTSITE": names.sitename,
939             "DNSDOMAIN": names.dnsdomain,
940             "DOMAIN": names.domain,
941             "SCHEMADN": names.schemadn,
942             "DOMAINDN": names.domaindn,
943             "SERVERDN": names.serverdn,
944             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
945             })
946
947         message("Setting up display specifiers")
948         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
949         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
950         check_all_substituted(display_specifiers_ldif)
951         samdb.add_ldif(display_specifiers_ldif)
952
953         message("Adding users container")
954         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
955                 "DOMAINDN": names.domaindn})
956         message("Modifying users container")
957         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
958                 "DOMAINDN": names.domaindn})
959         message("Adding computers container")
960         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
961                 "DOMAINDN": names.domaindn})
962         message("Modifying computers container")
963         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
964                 "DOMAINDN": names.domaindn})
965         message("Setting up sam.ldb data")
966         setup_add_ldif(samdb, setup_path("provision.ldif"), {
967             "DOMAINDN": names.domaindn,
968             "NETBIOSNAME": names.netbiosname,
969             "DEFAULTSITE": names.sitename,
970             "CONFIGDN": names.configdn,
971             "SERVERDN": names.serverdn
972             })
973
974         if fill == FILL_FULL:
975             message("Setting up sam.ldb users and groups")
976             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
977                 "DOMAINDN": names.domaindn,
978                 "DOMAINSID": str(domainsid),
979                 "CONFIGDN": names.configdn,
980                 "ADMINPASS_B64": b64encode(adminpass),
981                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
982                 })
983
984             if serverrole == "domain controller":
985                 message("Setting up self join")
986                 setup_self_join(samdb, names=names, invocationid=invocationid, 
987                                 dnspass=dnspass,  
988                                 machinepass=machinepass, 
989                                 domainsid=domainsid, policyguid=policyguid,
990                                 setup_path=setup_path,
991                                 domainControllerFunctionality=domainControllerFunctionality)
992                 # add the NTDSGUID based SPNs
993                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
994                 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
995                                                  expression="", scope=SCOPE_BASE)
996                 assert isinstance(names.ntdsguid, str)
997
998     except:
999         samdb.transaction_cancel()
1000         raise
1001
1002     samdb.transaction_commit()
1003     return samdb
1004
1005
1006 FILL_FULL = "FULL"
1007 FILL_NT4SYNC = "NT4SYNC"
1008 FILL_DRS = "DRS"
1009
1010
1011 def provision(setup_dir, message, session_info, 
1012               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1013               realm=None, 
1014               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1015               serverdn=None,
1016               domain=None, hostname=None, hostip=None, hostip6=None, 
1017               domainsid=None, adminpass=None, ldapadminpass=None, 
1018               krbtgtpass=None, domainguid=None, 
1019               policyguid=None, invocationid=None, machinepass=None, 
1020               dnspass=None, root=None, nobody=None, users=None, 
1021               wheel=None, backup=None, aci=None, serverrole=None, 
1022               ldap_backend_extra_port=None, ldap_backend_type=None,
1023               sitename=None,
1024               ol_mmr_urls=None, ol_olc=None, 
1025               setup_ds_path=None, slapd_path=None, nosync=False,
1026               ldap_dryrun_mode=False):
1027     """Provision samba4
1028     
1029     :note: caution, this wipes all existing data!
1030     """
1031
1032     def setup_path(file):
1033         return os.path.join(setup_dir, file)
1034
1035     if domainsid is None:
1036         domainsid = security.random_sid()
1037
1038     if policyguid is None:
1039         policyguid = str(uuid.uuid4())
1040     if adminpass is None:
1041         adminpass = glue.generate_random_str(12)
1042     if krbtgtpass is None:
1043         krbtgtpass = glue.generate_random_str(12)
1044     if machinepass is None:
1045         machinepass  = glue.generate_random_str(12)
1046     if dnspass is None:
1047         dnspass = glue.generate_random_str(12)
1048     if ldapadminpass is None:
1049         #Make a new, random password between Samba and it's LDAP server
1050         ldapadminpass=glue.generate_random_str(12)        
1051
1052
1053     root_uid = findnss_uid([root or "root"])
1054     nobody_uid = findnss_uid([nobody or "nobody"])
1055     users_gid = findnss_gid([users or "users"])
1056     if wheel is None:
1057         wheel_gid = findnss_gid(["wheel", "adm"])
1058     else:
1059         wheel_gid = findnss_gid([wheel])
1060
1061     if targetdir is not None:
1062         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1063             os.makedirs(os.path.join(targetdir, "etc"))
1064         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1065     elif smbconf is None:
1066         smbconf = param.default_path()
1067
1068     # only install a new smb.conf if there isn't one there already
1069     if not os.path.exists(smbconf):
1070         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1071                      targetdir)
1072
1073     lp = param.LoadParm()
1074     lp.load(smbconf)
1075
1076     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1077                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1078                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1079                         serverdn=serverdn)
1080
1081     paths = provision_paths_from_lp(lp, names.dnsdomain)
1082
1083     if hostip is None:
1084         try:
1085             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1086         except socket.gaierror, (socket.EAI_NODATA, msg):
1087             hostip = None
1088
1089     if hostip6 is None:
1090         try:
1091             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1092         except socket.gaierror, (socket.EAI_NODATA, msg): 
1093             hostip6 = None
1094
1095     if serverrole is None:
1096         serverrole = lp.get("server role")
1097
1098     assert serverrole in ("domain controller", "member server", "standalone")
1099     if invocationid is None and serverrole == "domain controller":
1100         invocationid = str(uuid.uuid4())
1101
1102     if not os.path.exists(paths.private_dir):
1103         os.mkdir(paths.private_dir)
1104
1105     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1106     
1107     schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1108         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1109     
1110     secrets_credentials = credentials
1111     provision_backend = None
1112     if ldap_backend_type:
1113         # We only support an LDAP backend over ldapi://
1114
1115         provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1116                                              lp=lp, credentials=credentials, 
1117                                              names=names,
1118                                              message=message, hostname=hostname,
1119                                              root=root, schema=schema,
1120                                              ldap_backend_type=ldap_backend_type,
1121                                              ldapadminpass=ldapadminpass,
1122                                              ldap_backend_extra_port=ldap_backend_extra_port,
1123                                              ol_mmr_urls=ol_mmr_urls, 
1124                                              slapd_path=slapd_path,
1125                                              setup_ds_path=setup_ds_path,
1126                                              ldap_dryrun_mode=ldap_dryrun_mode)
1127
1128         # Now use the backend credentials to access the databases
1129         credentials = provision_backend.credentials
1130         secrets_credentials = provision_backend.adminCredentials
1131         ldapi_url = provision_backend.ldapi_uri
1132
1133     # only install a new shares config db if there is none
1134     if not os.path.exists(paths.shareconf):
1135         message("Setting up share.ldb")
1136         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1137                         credentials=credentials, lp=lp)
1138         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1139
1140      
1141     message("Setting up secrets.ldb")
1142     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1143                                   session_info=session_info, 
1144                                   credentials=secrets_credentials, lp=lp)
1145
1146     message("Setting up the registry")
1147     setup_registry(paths.hklm, setup_path, session_info, 
1148                    lp=lp)
1149
1150     message("Setting up idmap db")
1151     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1152                           lp=lp)
1153
1154     message("Setting up SAM db")
1155     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1156                         credentials=credentials, lp=lp, names=names,
1157                         message=message, 
1158                         domainsid=domainsid, 
1159                         schema=schema, domainguid=domainguid, policyguid=policyguid, 
1160                         fill=samdb_fill, 
1161                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1162                         invocationid=invocationid, 
1163                         machinepass=machinepass, dnspass=dnspass,
1164                         serverrole=serverrole, ldap_backend=provision_backend)
1165
1166     if serverrole == "domain controller":
1167         if paths.netlogon is None:
1168             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1169             message("Please either remove %s or see the template at %s" % 
1170                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1171             assert(paths.netlogon is not None)
1172
1173         if paths.sysvol is None:
1174             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1175             message("Please either remove %s or see the template at %s" % 
1176                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1177             assert(paths.sysvol is not None)            
1178             
1179         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1180                                    "{" + policyguid + "}")
1181         os.makedirs(policy_path, 0755)
1182         open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1183         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1184         os.makedirs(os.path.join(policy_path, "User"), 0755)
1185         if not os.path.isdir(paths.netlogon):
1186             os.makedirs(paths.netlogon, 0755)
1187
1188     if samdb_fill == FILL_FULL:
1189         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1190                             root_uid=root_uid, nobody_uid=nobody_uid,
1191                             users_gid=users_gid, wheel_gid=wheel_gid)
1192
1193         message("Setting up sam.ldb rootDSE marking as synchronized")
1194         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1195
1196         # Only make a zone file on the first DC, it should be replicated with DNS replication
1197         if serverrole == "domain controller":
1198             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1199                               credentials=credentials, lp=lp)
1200             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1201                                 realm=names.realm,
1202                                 netbiosname=names.netbiosname,
1203                                 domainsid=domainsid, 
1204                                 keytab_path=paths.keytab, samdb_url=paths.samdb,
1205                                 dns_keytab_path=paths.dns_keytab,
1206                                 dnspass=dnspass, machinepass=machinepass,
1207                                 dnsdomain=names.dnsdomain)
1208
1209             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1210             assert isinstance(domainguid, str)
1211
1212             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1213                              domaindn=names.domaindn, hostip=hostip,
1214                              hostip6=hostip6, hostname=names.hostname,
1215                              dnspass=dnspass, realm=names.realm,
1216                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1217
1218             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1219                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1220
1221             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1222                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1223                               keytab_name=paths.dns_keytab)
1224             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1225             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1226
1227             create_krb5_conf(paths.krb5conf, setup_path,
1228                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1229                              realm=names.realm)
1230             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1231
1232
1233     if provision_backend is not None: 
1234       if ldap_backend_type == "fedora-ds":
1235         ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1236
1237         # delete default SASL mappings
1238         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1239
1240         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1241         for i in range (0, len(res)):
1242           dn = str(res[i]["dn"])
1243           ldapi_db.delete(dn)
1244
1245           aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1246
1247           m = ldb.Message()
1248           m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1249         
1250           m.dn = ldb.Dn(1, names.domaindn)
1251           ldapi_db.modify(m)
1252
1253           m.dn = ldb.Dn(1, names.configdn)
1254           ldapi_db.modify(m)
1255
1256           m.dn = ldb.Dn(1, names.schemadn)
1257           ldapi_db.modify(m)
1258
1259       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1260       if provision_backend.slapd.poll() is None:
1261         #Kill the slapd
1262         if hasattr(provision_backend.slapd, "terminate"):
1263           provision_backend.slapd.terminate()
1264         else:
1265           # Older python versions don't have .terminate()
1266           import signal
1267           os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1268             
1269         #and now wait for it to die
1270         provision_backend.slapd.communicate()
1271             
1272     # now display slapd_command_file.txt to show how slapd must be started next time
1273         message("Use later the following commandline to start slapd, then Samba:")
1274         slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1275         message(slapd_command)
1276         message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1277
1278         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1279                 "SLAPD_COMMAND" : slapd_command})
1280
1281     
1282     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1283                                ldapi_url)
1284
1285     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1286
1287     message("Once the above files are installed, your Samba4 server will be ready to use")
1288     message("Server Role:           %s" % serverrole)
1289     message("Hostname:              %s" % names.hostname)
1290     message("NetBIOS Domain:        %s" % names.domain)
1291     message("DNS Domain:            %s" % names.dnsdomain)
1292     message("DOMAIN SID:            %s" % str(domainsid))
1293     if samdb_fill == FILL_FULL:
1294         message("Admin password:    %s" % adminpass)
1295     if provision_backend:
1296         if provision_backend.credentials.get_bind_dn() is not None:
1297             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1298         else:
1299             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1300
1301         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1302   
1303     result = ProvisionResult()
1304     result.domaindn = domaindn
1305     result.paths = paths
1306     result.lp = lp
1307     result.samdb = samdb
1308     return result
1309
1310
1311
1312 def provision_become_dc(setup_dir=None,
1313                         smbconf=None, targetdir=None, realm=None, 
1314                         rootdn=None, domaindn=None, schemadn=None,
1315                         configdn=None, serverdn=None,
1316                         domain=None, hostname=None, domainsid=None, 
1317                         adminpass=None, krbtgtpass=None, domainguid=None, 
1318                         policyguid=None, invocationid=None, machinepass=None, 
1319                         dnspass=None, root=None, nobody=None, users=None, 
1320                         wheel=None, backup=None, serverrole=None, 
1321                         ldap_backend=None, ldap_backend_type=None,
1322                         sitename=None, debuglevel=1):
1323
1324     def message(text):
1325         """print a message if quiet is not set."""
1326         print text
1327
1328     glue.set_debug_level(debuglevel)
1329
1330     return provision(setup_dir, message, system_session(), None,
1331               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1332               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1333               configdn=configdn, serverdn=serverdn, domain=domain,
1334               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1335               machinepass=machinepass, serverrole="domain controller",
1336               sitename=sitename)
1337
1338
1339 def setup_db_config(setup_path, dbdir):
1340     """Setup a Berkeley database.
1341     
1342     :param setup_path: Setup path function.
1343     :param dbdir: Database directory."""
1344     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1345         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1346         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1347             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1348
1349     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1350                {"LDAPDBDIR": dbdir})
1351     
1352 class ProvisionBackend(object):
1353     def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, 
1354                  names=None, message=None, 
1355                  hostname=None, root=None, 
1356                  schema=None, ldapadminpass=None,
1357                  ldap_backend_type=None, ldap_backend_extra_port=None,
1358                  ol_mmr_urls=None, 
1359                  setup_ds_path=None, slapd_path=None, 
1360                  nosync=False, ldap_dryrun_mode=False):
1361         """Provision an LDAP backend for samba4
1362         
1363         This works for OpenLDAP and Fedora DS
1364         """
1365
1366         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1367         
1368         if not os.path.isdir(paths.ldapdir):
1369             os.makedirs(paths.ldapdir, 0700)
1370             
1371         if ldap_backend_type == "existing":
1372             #Check to see that this 'existing' LDAP backend in fact exists
1373             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1374             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1375                                                 expression="(objectClass=OpenLDAProotDSE)")
1376
1377             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1378             # This caused them to be set into the long-term database later in the script.
1379             self.credentials = credentials
1380             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1381             return
1382     
1383         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1384         # if another instance of slapd is already running 
1385         try:
1386             ldapi_db = Ldb(self.ldapi_uri)
1387             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1388                                                 expression="(objectClass=OpenLDAProotDSE)");
1389             try:
1390                 f = open(paths.slapdpid, "r")
1391                 p = f.read()
1392                 f.close()
1393                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1394             except:
1395                 pass
1396             
1397             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1398         
1399         except LdbError, e:
1400             pass
1401
1402         # Try to print helpful messages when the user has not specified the path to slapd
1403         if slapd_path is None:
1404             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1405         if not os.path.exists(slapd_path):
1406             message (slapd_path)
1407             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1408
1409         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1410         try:
1411             os.unlink(schemadb_path)
1412         except OSError:
1413             pass
1414
1415
1416         # Put the LDIF of the schema into a database so we can search on
1417         # it to generate schema-dependent configurations in Fedora DS and
1418         # OpenLDAP
1419         os.path.join(paths.ldapdir, "schema-tmp.ldb")
1420         schema.ldb.connect(schemadb_path)
1421         schema.ldb.transaction_start()
1422     
1423         # These bits of LDIF are supplied when the Schema object is created
1424         schema.ldb.add_ldif(schema.schema_dn_add)
1425         schema.ldb.modify_ldif(schema.schema_dn_modify)
1426         schema.ldb.add_ldif(schema.schema_data)
1427         schema.ldb.transaction_commit()
1428
1429         self.credentials = Credentials()
1430         self.credentials.guess(lp)
1431         #Kerberos to an ldapi:// backend makes no sense
1432         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1433
1434         self.adminCredentials = Credentials()
1435         self.adminCredentials.guess(lp)
1436         #Kerberos to an ldapi:// backend makes no sense
1437         self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1438
1439         self.ldap_backend_type = ldap_backend_type
1440
1441         if ldap_backend_type == "fedora-ds":
1442             provision_fds_backend(self, paths=paths, setup_path=setup_path,
1443                                   names=names, message=message, 
1444                                   hostname=hostname,
1445                                   ldapadminpass=ldapadminpass, root=root, 
1446                                   schema=schema,
1447                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1448                                   setup_ds_path=setup_ds_path,
1449                                   slapd_path=slapd_path,
1450                                   nosync=nosync,
1451                                   ldap_dryrun_mode=ldap_dryrun_mode)
1452             
1453         elif ldap_backend_type == "openldap":
1454             provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1455                                        names=names, message=message, 
1456                                        hostname=hostname,
1457                                        ldapadminpass=ldapadminpass, root=root, 
1458                                        schema=schema,
1459                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1460                                        ol_mmr_urls=ol_mmr_urls, 
1461                                        slapd_path=slapd_path,
1462                                        nosync=nosync,
1463                                        ldap_dryrun_mode=ldap_dryrun_mode)
1464         else:
1465             raise ProvisioningError("Unknown LDAP backend type selected")
1466
1467         self.credentials.set_password(ldapadminpass)
1468         self.adminCredentials.set_username("samba-admin")
1469         self.adminCredentials.set_password(ldapadminpass)
1470
1471         # Now start the slapd, so we can provision onto it.  We keep the
1472         # subprocess context around, to kill this off at the successful
1473         # end of the script
1474         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1475     
1476         while self.slapd.poll() is None:
1477             # Wait until the socket appears
1478             try:
1479                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1480                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1481                                                     expression="(objectClass=OpenLDAProotDSE)")
1482                 # If we have got here, then we must have a valid connection to the LDAP server!
1483                 return
1484             except LdbError, e:
1485                 time.sleep(1)
1486                 pass
1487         
1488         raise ProvisioningError("slapd died before we could make a connection to it")
1489
1490
1491 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1492                                message=None, 
1493                                hostname=None, ldapadminpass=None, root=None, 
1494                                schema=None, 
1495                                ldap_backend_extra_port=None,
1496                                ol_mmr_urls=None, 
1497                                slapd_path=None, nosync=False,
1498                                ldap_dryrun_mode=False):
1499
1500     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1501     nosync_config = ""
1502     if nosync:
1503         nosync_config = "dbnosync"
1504         
1505     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1506     refint_attributes = ""
1507     memberof_config = "# Generated from Samba4 schema\n"
1508     for att in  lnkattr.keys():
1509         if lnkattr[att] is not None:
1510             refint_attributes = refint_attributes + " " + att 
1511             
1512             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1513                                                  { "MEMBER_ATTR" : att ,
1514                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1515             
1516     refint_config = read_and_sub_file(setup_path("refint.conf"),
1517                                       { "LINK_ATTRS" : refint_attributes})
1518     
1519     attrs = ["linkID", "lDAPDisplayName"]
1520     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1521     index_config = ""
1522     for i in range (0, len(res)):
1523         index_attr = res[i]["lDAPDisplayName"][0]
1524         if index_attr == "objectGUID":
1525             index_attr = "entryUUID"
1526             
1527         index_config += "index " + index_attr + " eq\n"
1528
1529 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1530     mmr_on_config = ""
1531     mmr_replicator_acl = ""
1532     mmr_serverids_config = ""
1533     mmr_syncrepl_schema_config = "" 
1534     mmr_syncrepl_config_config = "" 
1535     mmr_syncrepl_user_config = "" 
1536        
1537     
1538     if ol_mmr_urls is not None:
1539         # For now, make these equal
1540         mmr_pass = ldapadminpass
1541         
1542         url_list=filter(None,ol_mmr_urls.split(' ')) 
1543         if (len(url_list) == 1):
1544             url_list=filter(None,ol_mmr_urls.split(',')) 
1545                      
1546             
1547             mmr_on_config = "MirrorMode On"
1548             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1549             serverid=0
1550             for url in url_list:
1551                 serverid=serverid+1
1552                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1553                                                           { "SERVERID" : str(serverid),
1554                                                             "LDAPSERVER" : url })
1555                 rid=serverid*10
1556                 rid=rid+1
1557                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1558                                                                 {  "RID" : str(rid),
1559                                                                    "MMRDN": names.schemadn,
1560                                                                    "LDAPSERVER" : url,
1561                                                                    "MMR_PASSWORD": mmr_pass})
1562                 
1563                 rid=rid+1
1564                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1565                                                                 {  "RID" : str(rid),
1566                                                                    "MMRDN": names.configdn,
1567                                                                    "LDAPSERVER" : url,
1568                                                                    "MMR_PASSWORD": mmr_pass})
1569                 
1570                 rid=rid+1
1571                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1572                                                               {  "RID" : str(rid),
1573                                                                  "MMRDN": names.domaindn,
1574                                                                  "LDAPSERVER" : url,
1575                                                                  "MMR_PASSWORD": mmr_pass })
1576     # OpenLDAP cn=config initialisation
1577     olc_syncrepl_config = ""
1578     olc_mmr_config = "" 
1579     # if mmr = yes, generate cn=config-replication directives
1580     # and olc_seed.lif for the other mmr-servers
1581     if ol_mmr_urls is not None:
1582         serverid=0
1583         olc_serverids_config = ""
1584         olc_syncrepl_seed_config = ""
1585         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1586         rid=1000
1587         for url in url_list:
1588             serverid=serverid+1
1589             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1590                                                       { "SERVERID" : str(serverid),
1591                                                         "LDAPSERVER" : url })
1592             
1593             rid=rid+1
1594             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1595                                                      {  "RID" : str(rid),
1596                                                         "LDAPSERVER" : url,
1597                                                         "MMR_PASSWORD": mmr_pass})
1598             
1599             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1600                                                           {  "RID" : str(rid),
1601                                                              "LDAPSERVER" : url})
1602                 
1603         setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1604                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1605                     "OLC_PW": ldapadminpass,
1606                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1607     # end olc
1608                 
1609     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1610                {"DNSDOMAIN": names.dnsdomain,
1611                 "LDAPDIR": paths.ldapdir,
1612                 "DOMAINDN": names.domaindn,
1613                 "CONFIGDN": names.configdn,
1614                 "SCHEMADN": names.schemadn,
1615                 "MEMBEROF_CONFIG": memberof_config,
1616                 "MIRRORMODE": mmr_on_config,
1617                 "REPLICATOR_ACL": mmr_replicator_acl,
1618                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1619                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1620                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1621                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1622                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1623                 "OLC_MMR_CONFIG": olc_mmr_config,
1624                 "REFINT_CONFIG": refint_config,
1625                 "INDEX_CONFIG": index_config,
1626                 "NOSYNC": nosync_config})
1627         
1628     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1629     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1630     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1631     
1632     if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1633         os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1634         
1635     setup_file(setup_path("cn=samba.ldif"), 
1636                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1637                { "UUID": str(uuid.uuid4()), 
1638                  "LDAPTIME": timestring(int(time.time()))} )
1639     setup_file(setup_path("cn=samba-admin.ldif"), 
1640                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1641                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1642                 "UUID": str(uuid.uuid4()), 
1643                 "LDAPTIME": timestring(int(time.time()))} )
1644     
1645     if ol_mmr_urls is not None:
1646         setup_file(setup_path("cn=replicator.ldif"),
1647                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1648                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1649                     "UUID": str(uuid.uuid4()),
1650                     "LDAPTIME": timestring(int(time.time()))} )
1651         
1652
1653     mapping = "schema-map-openldap-2.3"
1654     backend_schema = "backend-schema.schema"
1655
1656     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1657     assert backend_schema_data is not None
1658     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1659
1660     # now we generate the needed strings to start slapd automatically,
1661     # first ldapi_uri...
1662     if ldap_backend_extra_port is not None:
1663         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1664         # specified there as part of it's clue as to it's own name,
1665         # and not to replicate to itself
1666         if ol_mmr_urls is None:
1667             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1668         else:
1669             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1670     else:
1671         server_port_string = ""
1672
1673     # Prepare the 'result' information - the commands to return in particular
1674     result.slapd_provision_command = [slapd_path]
1675
1676     result.slapd_provision_command.append("-F" + paths.olcdir)
1677
1678     result.slapd_provision_command.append("-h")
1679
1680     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1681     result.slapd_command = list(result.slapd_provision_command)
1682     
1683     result.slapd_provision_command.append(result.ldapi_uri)
1684     result.slapd_provision_command.append("-d0")
1685
1686     uris = result.ldapi_uri
1687     if server_port_string is not "":
1688         uris = uris + " " + server_port_string
1689
1690     result.slapd_command.append(uris)
1691
1692     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1693     result.credentials.set_username("samba-admin")
1694     
1695     # If we were just looking for crashes up to this point, it's a
1696     # good time to exit before we realise we don't have OpenLDAP on
1697     # this system
1698     if ldap_dryrun_mode:
1699         sys.exit(0)
1700
1701     # Finally, convert the configuration into cn=config style!
1702     if not os.path.isdir(paths.olcdir):
1703         os.makedirs(paths.olcdir, 0770)
1704
1705         retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1706
1707 #        We can't do this, as OpenLDAP is strange.  It gives an error
1708 #        output to the above, but does the conversion sucessfully...
1709 #
1710 #        if retcode != 0:
1711 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1712
1713         if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1714             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1715
1716         # Don't confuse the admin by leaving the slapd.conf around
1717         os.remove(paths.slapdconf)        
1718           
1719
1720 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1721                           message=None, 
1722                           hostname=None, ldapadminpass=None, root=None, 
1723                           schema=None,
1724                           ldap_backend_extra_port=None,
1725                           setup_ds_path=None,
1726                           slapd_path=None,
1727                           nosync=False, 
1728                           ldap_dryrun_mode=False):
1729
1730     if ldap_backend_extra_port is not None:
1731         serverport = "ServerPort=%d" % ldap_backend_extra_port
1732     else:
1733         serverport = ""
1734         
1735     setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1736                {"ROOT": root,
1737                 "HOSTNAME": hostname,
1738                 "DNSDOMAIN": names.dnsdomain,
1739                 "LDAPDIR": paths.ldapdir,
1740                 "DOMAINDN": names.domaindn,
1741                 "LDAPMANAGERDN": names.ldapmanagerdn,
1742                 "LDAPMANAGERPASS": ldapadminpass, 
1743                 "SERVERPORT": serverport})
1744
1745     setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1746                {"CONFIGDN": names.configdn,
1747                 "SCHEMADN": names.schemadn,
1748                 "SAMBADN": names.sambadn,
1749                 })
1750
1751     setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, 
1752                {"SAMBADN": names.sambadn,
1753                 })
1754
1755     setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1756                 {"SAMBADN": names.sambadn, 
1757                  "LDAPADMINPASS": ldapadminpass
1758                 })
1759
1760     mapping = "schema-map-fedora-ds-1.0"
1761     backend_schema = "99_ad.ldif"
1762     
1763     # Build a schema file in Fedora DS format
1764     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1765     assert backend_schema_data is not None
1766     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1767
1768     result.credentials.set_bind_dn(names.ldapmanagerdn)
1769
1770     # Destory the target directory, or else setup-ds.pl will complain
1771     fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1772     shutil.rmtree(fedora_ds_dir, True)
1773
1774     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1775     #In the 'provision' command line, stay in the foreground so we can easily kill it
1776     result.slapd_provision_command.append("-d0")
1777
1778     #the command for the final run is the normal script
1779     result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1780
1781     # If we were just looking for crashes up to this point, it's a
1782     # good time to exit before we realise we don't have Fedora DS on
1783     if ldap_dryrun_mode:
1784         sys.exit(0)
1785
1786     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1787     if setup_ds_path is None:
1788         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\"!")
1789     if not os.path.exists(setup_ds_path):
1790         message (setup_ds_path)
1791         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1792
1793     # Run the Fedora DS setup utility
1794     retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1795     if retcode != 0:
1796         raise ProvisioningError("setup-ds failed")
1797
1798     # Load samba-admin
1799     retcode = subprocess.call([
1800         os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1801         close_fds=True, shell=False)
1802     if retcode != 0:
1803         raise("ldib2db failed")
1804
1805 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1806     """Create a PHP LDAP admin configuration file.
1807
1808     :param path: Path to write the configuration to.
1809     :param setup_path: Function to generate setup paths.
1810     """
1811     setup_file(setup_path("phpldapadmin-config.php"), path, 
1812             {"S4_LDAPI_URI": ldapi_uri})
1813
1814
1815 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1816                      hostip, hostip6, hostname, dnspass, realm, domainguid,
1817                      ntdsguid):
1818     """Write out a DNS zone file, from the info in the current database.
1819
1820     :param path: Path of the new zone file.
1821     :param setup_path: Setup path function.
1822     :param dnsdomain: DNS Domain name
1823     :param domaindn: DN of the Domain
1824     :param hostip: Local IPv4 IP
1825     :param hostip6: Local IPv6 IP
1826     :param hostname: Local hostname
1827     :param dnspass: Password for DNS
1828     :param realm: Realm name
1829     :param domainguid: GUID of the domain.
1830     :param ntdsguid: GUID of the hosts nTDSDSA record.
1831     """
1832     assert isinstance(domainguid, str)
1833
1834     if hostip6 is not None:
1835         hostip6_base_line = "            IN AAAA    " + hostip6
1836         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1837     else:
1838         hostip6_base_line = ""
1839         hostip6_host_line = ""
1840
1841     if hostip is not None:
1842         hostip_base_line = "            IN A    " + hostip
1843         hostip_host_line = hostname + "        IN A    " + hostip
1844     else:
1845         hostip_base_line = ""
1846         hostip_host_line = ""
1847
1848     setup_file(setup_path("provision.zone"), path, {
1849             "DNSPASS_B64": b64encode(dnspass),
1850             "HOSTNAME": hostname,
1851             "DNSDOMAIN": dnsdomain,
1852             "REALM": realm,
1853             "HOSTIP_BASE_LINE": hostip_base_line,
1854             "HOSTIP_HOST_LINE": hostip_host_line,
1855             "DOMAINGUID": domainguid,
1856             "DATESTRING": time.strftime("%Y%m%d%H"),
1857             "DEFAULTSITE": DEFAULTSITE,
1858             "NTDSGUID": ntdsguid,
1859             "HOSTIP6_BASE_LINE": hostip6_base_line,
1860             "HOSTIP6_HOST_LINE": hostip6_host_line,
1861         })
1862
1863
1864 def create_named_conf(path, setup_path, realm, dnsdomain,
1865                       private_dir):
1866     """Write out a file containing zone statements suitable for inclusion in a
1867     named.conf file (including GSS-TSIG configuration).
1868     
1869     :param path: Path of the new named.conf file.
1870     :param setup_path: Setup path function.
1871     :param realm: Realm name
1872     :param dnsdomain: DNS Domain name
1873     :param private_dir: Path to private directory
1874     :param keytab_name: File name of DNS keytab file
1875     """
1876
1877     setup_file(setup_path("named.conf"), path, {
1878             "DNSDOMAIN": dnsdomain,
1879             "REALM": realm,
1880             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1881             "PRIVATE_DIR": private_dir
1882             })
1883
1884 def create_named_txt(path, setup_path, realm, dnsdomain,
1885                       private_dir, keytab_name):
1886     """Write out a file containing zone statements suitable for inclusion in a
1887     named.conf file (including GSS-TSIG configuration).
1888     
1889     :param path: Path of the new named.conf file.
1890     :param setup_path: Setup path function.
1891     :param realm: Realm name
1892     :param dnsdomain: DNS Domain name
1893     :param private_dir: Path to private directory
1894     :param keytab_name: File name of DNS keytab file
1895     """
1896
1897     setup_file(setup_path("named.txt"), path, {
1898             "DNSDOMAIN": dnsdomain,
1899             "REALM": realm,
1900             "DNS_KEYTAB": keytab_name,
1901             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1902             "PRIVATE_DIR": private_dir
1903         })
1904
1905 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1906     """Write out a file containing zone statements suitable for inclusion in a
1907     named.conf file (including GSS-TSIG configuration).
1908     
1909     :param path: Path of the new named.conf file.
1910     :param setup_path: Setup path function.
1911     :param dnsdomain: DNS Domain name
1912     :param hostname: Local hostname
1913     :param realm: Realm name
1914     """
1915
1916     setup_file(setup_path("krb5.conf"), path, {
1917             "DNSDOMAIN": dnsdomain,
1918             "HOSTNAME": hostname,
1919             "REALM": realm,
1920         })
1921
1922