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