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