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