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