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