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