Merge branch 'master' of git://git.samba.org/samba
[sfrench/samba-autobuild/.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #   
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #   
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 """Functions for setting up a Samba configuration."""
27
28 from base64 import b64encode
29 import os
30 import sys
31 import pwd
32 import grp
33 import time
34 import uuid, glue
35 import socket
36 import param
37 import registry
38 import samba
39 import subprocess
40 import ldb
41
42 import shutil
43 from credentials import Credentials, DONT_USE_KERBEROS
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name
46 from samba import check_all_substituted
47 from samba import DS_DOMAIN_FUNCTION_2000, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008, DS_DC_FUNCTION_2008_R2
48 from samba.samdb import SamDB
49 from samba.idmap import IDmapDB
50 from samba.dcerpc import security
51 from samba.ndr import ndr_pack
52 import urllib
53 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
54 from ms_schema import read_ms_schema
55 from ms_display_specifiers import read_ms_ldif
56 from signal import SIGTERM
57 from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
58
59 __docformat__ = "restructuredText"
60
61 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):
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     """
298     assert isinstance(ldif_path, str)
299
300     data = read_and_sub_file(ldif_path, subst_vars)
301     ldb.add_ldif(data)
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                     "paged_results",
618                     "ranged_results",
619                     "anr",
620                     "server_sort",
621                     "asq",
622                     "extended_dn_store",
623                     "extended_dn_in",
624                     "rdn_name",
625                     "objectclass",
626                     "descriptor",
627                     "samldb",
628                     "password_hash",
629                     "operational",
630                     "kludge_acl"]
631     tdb_modules_list = [
632                     "subtree_rename",
633                     "subtree_delete",
634                     "linked_attributes",
635                     "extended_dn_out_ldb"]
636     modules_list2 = ["show_deleted",
637                     "partition"]
638  
639     domaindn_ldb = "users.ldb"
640     configdn_ldb = "configuration.ldb"
641     schemadn_ldb = "schema.ldb"
642     if ldap_backend is not None:
643         domaindn_ldb = ldap_backend.ldapi_uri
644         configdn_ldb = ldap_backend.ldapi_uri
645         schemadn_ldb = ldap_backend.ldapi_uri
646         
647         if ldap_backend.ldap_backend_type == "fedora-ds":
648             backend_modules = ["nsuniqueid", "paged_searches"]
649             # We can handle linked attributes here, as we don't have directory-side subtree operations
650             tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
651         elif ldap_backend.ldap_backend_type == "openldap":
652             backend_modules = ["entryuuid", "paged_searches"]
653             # OpenLDAP handles subtree renames, so we don't want to do any of these things
654             tdb_modules_list = ["extended_dn_out_dereference"]
655
656     elif serverrole == "domain controller":
657         tdb_modules_list.insert(0, "repl_meta_data")
658         backend_modules = []
659     else:
660         backend_modules = ["objectguid"]
661
662     if tdb_modules_list is None:
663         tdb_modules_list_as_string = ""
664     else:
665         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
666         
667     samdb.transaction_start()
668     try:
669         message("Setting up sam.ldb partitions and settings")
670         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
671                 "SCHEMADN": names.schemadn, 
672                 "SCHEMADN_LDB": schemadn_ldb,
673                 "SCHEMADN_MOD2": ",objectguid",
674                 "CONFIGDN": names.configdn,
675                 "CONFIGDN_LDB": configdn_ldb,
676                 "DOMAINDN": names.domaindn,
677                 "DOMAINDN_LDB": domaindn_ldb,
678                 "SCHEMADN_MOD": "schema_fsmo,instancetype",
679                 "CONFIGDN_MOD": "naming_fsmo,instancetype",
680                 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
681                 "MODULES_LIST": ",".join(modules_list),
682                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
683                 "MODULES_LIST2": ",".join(modules_list2),
684                 "BACKEND_MOD": ",".join(backend_modules),
685         })
686
687         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
688
689         message("Setting up sam.ldb rootDSE")
690         setup_samdb_rootdse(samdb, setup_path, names)
691
692     except:
693         samdb.transaction_cancel()
694         raise
695
696     samdb.transaction_commit()
697     
698 def secretsdb_self_join(secretsdb, domain, 
699                         netbiosname, domainsid, machinepass, 
700                         realm=None, dnsdomain=None,
701                         keytab_path=None, 
702                         key_version_number=1,
703                         secure_channel_type=SEC_CHAN_WKSTA):
704     """Add domain join-specific bits to a secrets database.
705     
706     :param secretsdb: Ldb Handle to the secrets database
707     :param machinepass: Machine password
708     """
709     attrs=["whenChanged",
710            "secret",
711            "priorSecret",
712            "priorChanged",
713            "krb5Keytab",
714            "privateKeytab"]
715     
716
717     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
718     msg["secureChannelType"] = str(secure_channel_type)
719     msg["flatname"] = [domain]
720     msg["objectClass"] = ["top", "primaryDomain"]
721     if realm is not None:
722       if dnsdomain is None:
723         dnsdomain = realm.lower()
724       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
725       msg["realm"] = realm
726       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
727       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
728       msg["privateKeytab"] = ["secrets.keytab"];
729
730
731     msg["secret"] = [machinepass]
732     msg["samAccountName"] = ["%s$" % netbiosname]
733     msg["secureChannelType"] = [str(secure_channel_type)]
734     msg["objectSid"] = [ndr_pack(domainsid)]
735     
736     res = secretsdb.search(base="cn=Primary Domains", 
737                            attrs=attrs, 
738                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
739                            scope=SCOPE_ONELEVEL)
740     
741     for del_msg in res:
742       if del_msg.dn is not msg.dn:
743         secretsdb.delete(del_msg.dn)
744
745     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
746
747     if len(res) == 1:
748       msg["priorSecret"] = res[0]["secret"]
749       msg["priorWhenChanged"] = res[0]["whenChanged"]
750
751       if res["privateKeytab"] is not None:
752         msg["privateKeytab"] = res[0]["privateKeytab"]
753
754       if res["krb5Keytab"] is not None:
755         msg["krb5Keytab"] = res[0]["krb5Keytab"]
756
757       for el in msg:
758         el.set_flags(ldb.FLAG_MOD_REPLACE)
759         secretsdb.modify(msg)
760     else:
761       secretsdb.add(msg)
762
763
764 def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain, 
765                         dns_keytab_path, dnspass):
766     """Add DNS specific bits to a secrets database.
767     
768     :param secretsdb: Ldb Handle to the secrets database
769     :param setup_path: Setup path function
770     :param machinepass: Machine password
771     """
772     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
773             "REALM": realm,
774             "DNSDOMAIN": dnsdomain,
775             "DNS_KEYTAB": dns_keytab_path,
776             "DNSPASS_B64": b64encode(dnspass),
777             })
778
779
780 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
781     """Setup the secrets database.
782
783     :param path: Path to the secrets database.
784     :param setup_path: Get the path to a setup file.
785     :param session_info: Session info.
786     :param credentials: Credentials
787     :param lp: Loadparm context
788     :return: LDB handle for the created secrets database
789     """
790     if os.path.exists(path):
791         os.unlink(path)
792     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
793                       lp=lp)
794     secrets_ldb.erase()
795     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
796     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
797                       lp=lp)
798     secrets_ldb.transaction_start()
799     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
800
801     if credentials is not None and credentials.authentication_requested():
802         if credentials.get_bind_dn() is not None:
803             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
804                     "LDAPMANAGERDN": credentials.get_bind_dn(),
805                     "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
806                     })
807         else:
808             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
809                     "LDAPADMINUSER": credentials.get_username(),
810                     "LDAPADMINREALM": credentials.get_realm(),
811                     "LDAPADMINPASS_B64": b64encode(credentials.get_password())
812                     })
813
814     return secrets_ldb
815
816 def setup_registry(path, setup_path, session_info, lp):
817     """Setup the registry.
818     
819     :param path: Path to the registry database
820     :param setup_path: Function that returns the path to a setup.
821     :param session_info: Session information
822     :param credentials: Credentials
823     :param lp: Loadparm context
824     """
825     reg = registry.Registry()
826     hive = registry.open_ldb(path, session_info=session_info, 
827                          lp_ctx=lp)
828     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
829     provision_reg = setup_path("provision.reg")
830     assert os.path.exists(provision_reg)
831     reg.diff_apply(provision_reg)
832
833
834 def setup_idmapdb(path, setup_path, session_info, lp):
835     """Setup the idmap database.
836
837     :param path: path to the idmap database
838     :param setup_path: Function that returns a path to a setup file
839     :param session_info: Session information
840     :param credentials: Credentials
841     :param lp: Loadparm context
842     """
843     if os.path.exists(path):
844         os.unlink(path)
845
846     idmap_ldb = IDmapDB(path, session_info=session_info,
847                         lp=lp)
848
849     idmap_ldb.erase()
850     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
851     return idmap_ldb
852
853
854 def setup_samdb_rootdse(samdb, setup_path, names):
855     """Setup the SamDB rootdse.
856
857     :param samdb: Sam Database handle
858     :param setup_path: Obtain setup path
859     """
860     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
861         "SCHEMADN": names.schemadn, 
862         "NETBIOSNAME": names.netbiosname,
863         "DNSDOMAIN": names.dnsdomain,
864         "REALM": names.realm,
865         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
866         "DOMAINDN": names.domaindn,
867         "ROOTDN": names.rootdn,
868         "CONFIGDN": names.configdn,
869         "SERVERDN": names.serverdn,
870         })
871         
872
873 def setup_self_join(samdb, names,
874                     machinepass, dnspass, 
875                     domainsid, invocationid, setup_path,
876                     policyguid, policyguid_dc, domainControllerFunctionality):
877     """Join a host to its own domain."""
878     assert isinstance(invocationid, str)
879     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
880               "CONFIGDN": names.configdn, 
881               "SCHEMADN": names.schemadn,
882               "DOMAINDN": names.domaindn,
883               "SERVERDN": names.serverdn,
884               "INVOCATIONID": invocationid,
885               "NETBIOSNAME": names.netbiosname,
886               "DEFAULTSITE": names.sitename,
887               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
888               "MACHINEPASS_B64": b64encode(machinepass),
889               "DNSPASS_B64": b64encode(dnspass),
890               "REALM": names.realm,
891               "DOMAIN": names.domain,
892               "DNSDOMAIN": names.dnsdomain,
893               "SAMBA_VERSION_STRING": version,
894               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
895
896     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
897               "POLICYGUID": policyguid,
898               "POLICYGUID_DC": policyguid_dc,
899               "DNSDOMAIN": names.dnsdomain,
900               "DOMAINSID": str(domainsid),
901               "DOMAINDN": names.domaindn})
902     
903     # add the NTDSGUID based SPNs
904     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
905     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
906                                      expression="", scope=SCOPE_BASE)
907     assert isinstance(names.ntdsguid, str)
908
909     # Setup fSMORoleOwner entries to point at the newly created DC entry
910     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
911               "DOMAIN": names.domain,
912               "DNSDOMAIN": names.dnsdomain,
913               "DOMAINDN": names.domaindn,
914               "CONFIGDN": names.configdn,
915               "SCHEMADN": names.schemadn, 
916               "DEFAULTSITE": names.sitename,
917               "SERVERDN": names.serverdn,
918               "NETBIOSNAME": names.netbiosname,
919               "NTDSGUID": names.ntdsguid
920               })
921
922
923 def setup_samdb(path, setup_path, session_info, credentials, lp, 
924                 names, message, 
925                 domainsid, domainguid, policyguid, policyguid_dc,
926                 fill, adminpass, krbtgtpass, 
927                 machinepass, invocationid, dnspass,
928                 serverrole, schema=None, ldap_backend=None):
929     """Setup a complete SAM Database.
930     
931     :note: This will wipe the main SAM database file!
932     """
933
934     # Do NOT change these default values without discussion with the team and reslease manager.  
935     domainFunctionality = DS_DOMAIN_FUNCTION_2008
936     forestFunctionality = DS_DOMAIN_FUNCTION_2008
937     domainControllerFunctionality = DS_DC_FUNCTION_2008
938
939     # Also wipes the database
940     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
941                            credentials=credentials, session_info=session_info,
942                            names=names, 
943                            ldap_backend=ldap_backend, serverrole=serverrole)
944
945     if (schema == None):
946         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
947             sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
948
949     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
950     samdb = Ldb(session_info=session_info, 
951                 credentials=credentials, lp=lp)
952
953     message("Pre-loading the Samba 4 and AD schema")
954
955     # Load the schema from the one we computed earlier
956     samdb.set_schema_from_ldb(schema.ldb)
957
958     # And now we can connect to the DB - the schema won't be loaded from the DB
959     samdb.connect(path)
960
961     # Load @OPTIONS
962     samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
963
964     if fill == FILL_DRS:
965         return samdb
966
967     samdb.transaction_start()
968     try:
969         message("Erasing data from partitions")
970         # Load the schema (again).  This time it will force a reindex,
971         # and will therefore make the erase_partitions() below
972         # computationally sane
973         samdb.set_schema_from_ldb(schema.ldb)
974         samdb.erase_partitions()
975     
976         # Set the domain functionality levels onto the database.
977         # Various module (the password_hash module in particular) need
978         # to know what level of AD we are emulating.
979
980         # These will be fixed into the database via the database
981         # modifictions below, but we need them set from the start.
982         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
983         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
984         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
985
986         samdb.set_domain_sid(str(domainsid))
987         if serverrole == "domain controller":
988             samdb.set_invocation_id(invocationid)
989
990         message("Adding DomainDN: %s" % names.domaindn)
991         if serverrole == "domain controller":
992             domain_oc = "domainDNS"
993         else:
994             domain_oc = "samba4LocalDomain"
995
996 #impersonate domain admin
997         admin_session_info = admin_session(lp, str(domainsid))
998         samdb.set_session_info(admin_session_info)
999
1000         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1001                 "DOMAINDN": names.domaindn,
1002                 "DOMAIN_OC": domain_oc
1003                 })
1004
1005         message("Modifying DomainDN: " + names.domaindn + "")
1006         if domainguid is not None:
1007             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
1008         else:
1009             domainguid_mod = ""
1010
1011         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1012             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1013             "DOMAINSID": str(domainsid),
1014             "SCHEMADN": names.schemadn, 
1015             "NETBIOSNAME": names.netbiosname,
1016             "DEFAULTSITE": names.sitename,
1017             "CONFIGDN": names.configdn,
1018             "SERVERDN": names.serverdn,
1019             "POLICYGUID": policyguid,
1020             "DOMAINDN": names.domaindn,
1021             "DOMAINGUID_MOD": domainguid_mod,
1022             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1023             "SAMBA_VERSION_STRING": version
1024             })
1025
1026         message("Adding configuration container")
1027         descr = get_config_descriptor(domainsid);
1028         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1029             "CONFIGDN": names.configdn, 
1030             "DESCRIPTOR": descr,
1031             })
1032         message("Modifying configuration container")
1033         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1034             "CONFIGDN": names.configdn, 
1035             "SCHEMADN": names.schemadn,
1036             })
1037
1038         # The LDIF here was created when the Schema object was constructed
1039         message("Setting up sam.ldb schema")
1040         samdb.add_ldif(schema.schema_dn_add)
1041         samdb.modify_ldif(schema.schema_dn_modify)
1042         samdb.write_prefixes_from_schema()
1043         samdb.add_ldif(schema.schema_data)
1044         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1045                        {"SCHEMADN": names.schemadn})
1046
1047         message("Setting up sam.ldb configuration data")
1048         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1049             "CONFIGDN": names.configdn,
1050             "NETBIOSNAME": names.netbiosname,
1051             "DEFAULTSITE": names.sitename,
1052             "DNSDOMAIN": names.dnsdomain,
1053             "DOMAIN": names.domain,
1054             "SCHEMADN": names.schemadn,
1055             "DOMAINDN": names.domaindn,
1056             "SERVERDN": names.serverdn,
1057             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1058             })
1059
1060         message("Setting up display specifiers")
1061         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1062         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1063         check_all_substituted(display_specifiers_ldif)
1064         samdb.add_ldif(display_specifiers_ldif)
1065
1066         message("Adding users container")
1067         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1068                 "DOMAINDN": names.domaindn})
1069         message("Modifying users container")
1070         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1071                 "DOMAINDN": names.domaindn})
1072         message("Adding computers container")
1073         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1074                 "DOMAINDN": names.domaindn})
1075         message("Modifying computers container")
1076         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1077                 "DOMAINDN": names.domaindn})
1078         message("Setting up sam.ldb data")
1079         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1080             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1081             "DOMAINDN": names.domaindn,
1082             "NETBIOSNAME": names.netbiosname,
1083             "DEFAULTSITE": names.sitename,
1084             "CONFIGDN": names.configdn,
1085             "SERVERDN": names.serverdn,
1086             "POLICYGUID_DC": policyguid_dc
1087             })
1088
1089         if fill == FILL_FULL:
1090             message("Setting up sam.ldb users and groups")
1091             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1092                 "DOMAINDN": names.domaindn,
1093                 "DOMAINSID": str(domainsid),
1094                 "CONFIGDN": names.configdn,
1095                 "ADMINPASS_B64": b64encode(adminpass),
1096                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1097                 })
1098
1099             if serverrole == "domain controller":
1100                 message("Setting up self join")
1101                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1102                                 dnspass=dnspass,  
1103                                 machinepass=machinepass, 
1104                                 domainsid=domainsid, policyguid=policyguid,
1105                                 policyguid_dc=policyguid_dc,
1106                                 setup_path=setup_path,
1107                                 domainControllerFunctionality=domainControllerFunctionality)
1108
1109                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1110                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1111                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1112                 assert isinstance(names.ntdsguid, str)
1113
1114     except:
1115         samdb.transaction_cancel()
1116         raise
1117
1118     samdb.transaction_commit()
1119     return samdb
1120
1121
1122 FILL_FULL = "FULL"
1123 FILL_NT4SYNC = "NT4SYNC"
1124 FILL_DRS = "DRS"
1125
1126
1127 def provision(setup_dir, message, session_info, 
1128               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1129               realm=None, 
1130               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1131               serverdn=None,
1132               domain=None, hostname=None, hostip=None, hostip6=None, 
1133               domainsid=None, adminpass=None, ldapadminpass=None, 
1134               krbtgtpass=None, domainguid=None, 
1135               policyguid=None, policyguid_dc=None, invocationid=None,
1136               machinepass=None, 
1137               dnspass=None, root=None, nobody=None, users=None, 
1138               wheel=None, backup=None, aci=None, serverrole=None, 
1139               ldap_backend_extra_port=None, ldap_backend_type=None,
1140               sitename=None,
1141               ol_mmr_urls=None, ol_olc=None, 
1142               setup_ds_path=None, slapd_path=None, nosync=False,
1143               ldap_dryrun_mode=False):
1144     """Provision samba4
1145     
1146     :note: caution, this wipes all existing data!
1147     """
1148
1149     def setup_path(file):
1150       return os.path.join(setup_dir, file)
1151
1152     if domainsid is None:
1153       domainsid = security.random_sid()
1154     else:
1155       domainsid = security.dom_sid(domainsid)
1156
1157
1158     # create/adapt the group policy GUIDs
1159     if policyguid is None:
1160         policyguid = str(uuid.uuid4())
1161     policyguid = policyguid.upper()
1162     if policyguid_dc is None:
1163         policyguid_dc = str(uuid.uuid4())
1164     policyguid_dc = policyguid_dc.upper()
1165
1166     if adminpass is None:
1167         adminpass = glue.generate_random_str(12)
1168     if krbtgtpass is None:
1169         krbtgtpass = glue.generate_random_str(12)
1170     if machinepass is None:
1171         machinepass  = glue.generate_random_str(12)
1172     if dnspass is None:
1173         dnspass = glue.generate_random_str(12)
1174     if ldapadminpass is None:
1175         #Make a new, random password between Samba and it's LDAP server
1176         ldapadminpass=glue.generate_random_str(12)        
1177
1178
1179     root_uid = findnss_uid([root or "root"])
1180     nobody_uid = findnss_uid([nobody or "nobody"])
1181     users_gid = findnss_gid([users or "users"])
1182     if wheel is None:
1183         wheel_gid = findnss_gid(["wheel", "adm"])
1184     else:
1185         wheel_gid = findnss_gid([wheel])
1186
1187     if targetdir is not None:
1188         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1189             os.makedirs(os.path.join(targetdir, "etc"))
1190         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1191     elif smbconf is None:
1192         smbconf = param.default_path()
1193
1194     # only install a new smb.conf if there isn't one there already
1195     if not os.path.exists(smbconf):
1196         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1197                      targetdir)
1198
1199     lp = param.LoadParm()
1200     lp.load(smbconf)
1201
1202     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1203                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1204                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1205                         serverdn=serverdn)
1206
1207     paths = provision_paths_from_lp(lp, names.dnsdomain)
1208
1209     if hostip is None:
1210         try:
1211             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1212         except socket.gaierror, (socket.EAI_NODATA, msg):
1213             hostip = None
1214
1215     if hostip6 is None:
1216         try:
1217             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1218         except socket.gaierror, (socket.EAI_NODATA, msg): 
1219             hostip6 = None
1220
1221     if serverrole is None:
1222         serverrole = lp.get("server role")
1223
1224     assert serverrole in ("domain controller", "member server", "standalone")
1225     if invocationid is None and serverrole == "domain controller":
1226         invocationid = str(uuid.uuid4())
1227
1228     if not os.path.exists(paths.private_dir):
1229         os.mkdir(paths.private_dir)
1230
1231     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1232     
1233     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1234         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1235     
1236     secrets_credentials = credentials
1237     provision_backend = None
1238     if ldap_backend_type:
1239         # We only support an LDAP backend over ldapi://
1240
1241         provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1242                                              lp=lp, credentials=credentials, 
1243                                              names=names,
1244                                              message=message, hostname=hostname,
1245                                              root=root, schema=schema,
1246                                              ldap_backend_type=ldap_backend_type,
1247                                              ldapadminpass=ldapadminpass,
1248                                              ldap_backend_extra_port=ldap_backend_extra_port,
1249                                              ol_mmr_urls=ol_mmr_urls, 
1250                                              slapd_path=slapd_path,
1251                                              setup_ds_path=setup_ds_path,
1252                                              ldap_dryrun_mode=ldap_dryrun_mode)
1253
1254         # Now use the backend credentials to access the databases
1255         credentials = provision_backend.credentials
1256         secrets_credentials = provision_backend.adminCredentials
1257         ldapi_url = provision_backend.ldapi_uri
1258
1259     # only install a new shares config db if there is none
1260     if not os.path.exists(paths.shareconf):
1261         message("Setting up share.ldb")
1262         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1263                         credentials=credentials, lp=lp)
1264         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1265
1266      
1267     message("Setting up secrets.ldb")
1268     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1269                                   session_info=session_info, 
1270                                   credentials=secrets_credentials, lp=lp)
1271
1272     message("Setting up the registry")
1273     setup_registry(paths.hklm, setup_path, session_info, 
1274                    lp=lp)
1275
1276     message("Setting up idmap db")
1277     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1278                           lp=lp)
1279
1280     message("Setting up SAM db")
1281     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1282                         credentials=credentials, lp=lp, names=names,
1283                         message=message, 
1284                         domainsid=domainsid, 
1285                         schema=schema, domainguid=domainguid,
1286                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1287                         fill=samdb_fill, 
1288                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1289                         invocationid=invocationid, 
1290                         machinepass=machinepass, dnspass=dnspass,
1291                         serverrole=serverrole, ldap_backend=provision_backend)
1292
1293     if serverrole == "domain controller":
1294         if paths.netlogon is None:
1295             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1296             message("Please either remove %s or see the template at %s" % 
1297                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1298             assert(paths.netlogon is not None)
1299
1300         if paths.sysvol is None:
1301             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1302             message("Please either remove %s or see the template at %s" % 
1303                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1304             assert(paths.sysvol is not None)            
1305             
1306         # Set up group policies (domain policy and domain controller policy)
1307
1308         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1309                                    "{" + policyguid + "}")
1310         os.makedirs(policy_path, 0755)
1311         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1312                                    "[General]\r\nVersion=65543")
1313         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1314         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1315
1316         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1317                                    "{" + policyguid_dc + "}")
1318         os.makedirs(policy_path_dc, 0755)
1319         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1320                                    "[General]\r\nVersion=2")
1321         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1322         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1323
1324         if not os.path.isdir(paths.netlogon):
1325             os.makedirs(paths.netlogon, 0755)
1326
1327     if samdb_fill == FILL_FULL:
1328         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1329                             root_uid=root_uid, nobody_uid=nobody_uid,
1330                             users_gid=users_gid, wheel_gid=wheel_gid)
1331
1332         message("Setting up sam.ldb rootDSE marking as synchronized")
1333         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1334
1335         # Only make a zone file on the first DC, it should be replicated with DNS replication
1336         if serverrole == "domain controller":
1337             secretsdb_self_join(secrets_ldb, domain=domain,
1338                                 realm=names.realm,
1339                                 dnsdomain=names.dnsdomain,
1340                                 netbiosname=names.netbiosname,
1341                                 domainsid=domainsid, 
1342                                 machinepass=machinepass,
1343                                 secure_channel_type=SEC_CHAN_BDC)
1344
1345             secretsdb_setup_dns(secrets_ldb, setup_path, 
1346                                 realm=names.realm, dnsdomain=names.dnsdomain,
1347                                 dns_keytab_path=paths.dns_keytab,
1348                                 dnspass=dnspass)
1349
1350             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1351             assert isinstance(domainguid, str)
1352
1353             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1354                              domaindn=names.domaindn, hostip=hostip,
1355                              hostip6=hostip6, hostname=names.hostname,
1356                              dnspass=dnspass, realm=names.realm,
1357                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1358
1359             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1360                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1361
1362             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1363                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1364                               keytab_name=paths.dns_keytab)
1365             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1366             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1367
1368             create_krb5_conf(paths.krb5conf, setup_path,
1369                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1370                              realm=names.realm)
1371             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1372
1373     #Now commit the secrets.ldb to disk
1374     secrets_ldb.transaction_commit()
1375
1376     if provision_backend is not None: 
1377       if ldap_backend_type == "fedora-ds":
1378         ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1379
1380         # delete default SASL mappings
1381         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1382
1383         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1384         for i in range (0, len(res)):
1385           dn = str(res[i]["dn"])
1386           ldapi_db.delete(dn)
1387
1388           aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1389
1390           m = ldb.Message()
1391           m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1392         
1393           m.dn = ldb.Dn(1, names.domaindn)
1394           ldapi_db.modify(m)
1395
1396           m.dn = ldb.Dn(1, names.configdn)
1397           ldapi_db.modify(m)
1398
1399           m.dn = ldb.Dn(1, names.schemadn)
1400           ldapi_db.modify(m)
1401
1402       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1403       if provision_backend.slapd.poll() is None:
1404         #Kill the slapd
1405         if hasattr(provision_backend.slapd, "terminate"):
1406           provision_backend.slapd.terminate()
1407         else:
1408           # Older python versions don't have .terminate()
1409           import signal
1410           os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1411             
1412         #and now wait for it to die
1413         provision_backend.slapd.communicate()
1414             
1415     # now display slapd_command_file.txt to show how slapd must be started next time
1416         message("Use later the following commandline to start slapd, then Samba:")
1417         slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1418         message(slapd_command)
1419         message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1420
1421         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1422                 "SLAPD_COMMAND" : slapd_command})
1423
1424     
1425     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1426                                ldapi_url)
1427
1428     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1429
1430     message("Once the above files are installed, your Samba4 server will be ready to use")
1431     message("Server Role:           %s" % serverrole)
1432     message("Hostname:              %s" % names.hostname)
1433     message("NetBIOS Domain:        %s" % names.domain)
1434     message("DNS Domain:            %s" % names.dnsdomain)
1435     message("DOMAIN SID:            %s" % str(domainsid))
1436     if samdb_fill == FILL_FULL:
1437         message("Admin password:    %s" % adminpass)
1438     if provision_backend:
1439         if provision_backend.credentials.get_bind_dn() is not None:
1440             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1441         else:
1442             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1443
1444         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1445   
1446     result = ProvisionResult()
1447     result.domaindn = domaindn
1448     result.paths = paths
1449     result.lp = lp
1450     result.samdb = samdb
1451     return result
1452
1453
1454
1455 def provision_become_dc(setup_dir=None,
1456                         smbconf=None, targetdir=None, realm=None, 
1457                         rootdn=None, domaindn=None, schemadn=None,
1458                         configdn=None, serverdn=None,
1459                         domain=None, hostname=None, domainsid=None, 
1460                         adminpass=None, krbtgtpass=None, domainguid=None, 
1461                         policyguid=None, policyguid_dc=None, invocationid=None,
1462                         machinepass=None, 
1463                         dnspass=None, root=None, nobody=None, users=None, 
1464                         wheel=None, backup=None, serverrole=None, 
1465                         ldap_backend=None, ldap_backend_type=None,
1466                         sitename=None, debuglevel=1):
1467
1468     def message(text):
1469         """print a message if quiet is not set."""
1470         print text
1471
1472     glue.set_debug_level(debuglevel)
1473
1474     return provision(setup_dir, message, system_session(), None,
1475               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1476               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1477               configdn=configdn, serverdn=serverdn, domain=domain,
1478               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1479               machinepass=machinepass, serverrole="domain controller",
1480               sitename=sitename)
1481
1482
1483 def setup_db_config(setup_path, dbdir):
1484     """Setup a Berkeley database.
1485     
1486     :param setup_path: Setup path function.
1487     :param dbdir: Database directory."""
1488     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1489         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1490         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1491             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1492
1493     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1494                {"LDAPDBDIR": dbdir})
1495     
1496 class ProvisionBackend(object):
1497     def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, 
1498                  names=None, message=None, 
1499                  hostname=None, root=None, 
1500                  schema=None, ldapadminpass=None,
1501                  ldap_backend_type=None, ldap_backend_extra_port=None,
1502                  ol_mmr_urls=None, 
1503                  setup_ds_path=None, slapd_path=None, 
1504                  nosync=False, ldap_dryrun_mode=False):
1505         """Provision an LDAP backend for samba4
1506         
1507         This works for OpenLDAP and Fedora DS
1508         """
1509
1510         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1511         
1512         if not os.path.isdir(paths.ldapdir):
1513             os.makedirs(paths.ldapdir, 0700)
1514             
1515         if ldap_backend_type == "existing":
1516             #Check to see that this 'existing' LDAP backend in fact exists
1517             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1518             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1519                                                 expression="(objectClass=OpenLDAProotDSE)")
1520
1521             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1522             # This caused them to be set into the long-term database later in the script.
1523             self.credentials = credentials
1524             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1525             return
1526     
1527         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1528         # if another instance of slapd is already running 
1529         try:
1530             ldapi_db = Ldb(self.ldapi_uri)
1531             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1532                                                 expression="(objectClass=OpenLDAProotDSE)");
1533             try:
1534                 f = open(paths.slapdpid, "r")
1535                 p = f.read()
1536                 f.close()
1537                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1538             except:
1539                 pass
1540             
1541             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1542         
1543         except LdbError, e:
1544             pass
1545
1546         # Try to print helpful messages when the user has not specified the path to slapd
1547         if slapd_path is None:
1548             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1549         if not os.path.exists(slapd_path):
1550             message (slapd_path)
1551             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1552
1553         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1554         try:
1555             os.unlink(schemadb_path)
1556         except OSError:
1557             pass
1558
1559
1560         # Put the LDIF of the schema into a database so we can search on
1561         # it to generate schema-dependent configurations in Fedora DS and
1562         # OpenLDAP
1563         os.path.join(paths.ldapdir, "schema-tmp.ldb")
1564         schema.ldb.connect(schemadb_path)
1565         schema.ldb.transaction_start()
1566     
1567         # These bits of LDIF are supplied when the Schema object is created
1568         schema.ldb.add_ldif(schema.schema_dn_add)
1569         schema.ldb.modify_ldif(schema.schema_dn_modify)
1570         schema.ldb.add_ldif(schema.schema_data)
1571         schema.ldb.transaction_commit()
1572
1573         self.credentials = Credentials()
1574         self.credentials.guess(lp)
1575         #Kerberos to an ldapi:// backend makes no sense
1576         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1577
1578         self.adminCredentials = Credentials()
1579         self.adminCredentials.guess(lp)
1580         #Kerberos to an ldapi:// backend makes no sense
1581         self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1582
1583         self.ldap_backend_type = ldap_backend_type
1584
1585         if ldap_backend_type == "fedora-ds":
1586             provision_fds_backend(self, paths=paths, setup_path=setup_path,
1587                                   names=names, message=message, 
1588                                   hostname=hostname,
1589                                   ldapadminpass=ldapadminpass, root=root, 
1590                                   schema=schema,
1591                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1592                                   setup_ds_path=setup_ds_path,
1593                                   slapd_path=slapd_path,
1594                                   nosync=nosync,
1595                                   ldap_dryrun_mode=ldap_dryrun_mode)
1596             
1597         elif ldap_backend_type == "openldap":
1598             provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1599                                        names=names, message=message, 
1600                                        hostname=hostname,
1601                                        ldapadminpass=ldapadminpass, root=root, 
1602                                        schema=schema,
1603                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1604                                        ol_mmr_urls=ol_mmr_urls, 
1605                                        slapd_path=slapd_path,
1606                                        nosync=nosync,
1607                                        ldap_dryrun_mode=ldap_dryrun_mode)
1608         else:
1609             raise ProvisioningError("Unknown LDAP backend type selected")
1610
1611         self.credentials.set_password(ldapadminpass)
1612         self.adminCredentials.set_username("samba-admin")
1613         self.adminCredentials.set_password(ldapadminpass)
1614
1615         # Now start the slapd, so we can provision onto it.  We keep the
1616         # subprocess context around, to kill this off at the successful
1617         # end of the script
1618         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1619     
1620         while self.slapd.poll() is None:
1621             # Wait until the socket appears
1622             try:
1623                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1624                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1625                                                     expression="(objectClass=OpenLDAProotDSE)")
1626                 # If we have got here, then we must have a valid connection to the LDAP server!
1627                 return
1628             except LdbError, e:
1629                 time.sleep(1)
1630                 pass
1631         
1632         raise ProvisioningError("slapd died before we could make a connection to it")
1633
1634
1635 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1636                                message=None, 
1637                                hostname=None, ldapadminpass=None, root=None, 
1638                                schema=None, 
1639                                ldap_backend_extra_port=None,
1640                                ol_mmr_urls=None, 
1641                                slapd_path=None, nosync=False,
1642                                ldap_dryrun_mode=False):
1643
1644     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1645     nosync_config = ""
1646     if nosync:
1647         nosync_config = "dbnosync"
1648         
1649     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1650     refint_attributes = ""
1651     memberof_config = "# Generated from Samba4 schema\n"
1652     for att in  lnkattr.keys():
1653         if lnkattr[att] is not None:
1654             refint_attributes = refint_attributes + " " + att 
1655             
1656             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1657                                                  { "MEMBER_ATTR" : att ,
1658                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1659             
1660     refint_config = read_and_sub_file(setup_path("refint.conf"),
1661                                       { "LINK_ATTRS" : refint_attributes})
1662     
1663     attrs = ["linkID", "lDAPDisplayName"]
1664     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1665     index_config = ""
1666     for i in range (0, len(res)):
1667         index_attr = res[i]["lDAPDisplayName"][0]
1668         if index_attr == "objectGUID":
1669             index_attr = "entryUUID"
1670             
1671         index_config += "index " + index_attr + " eq\n"
1672
1673 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1674     mmr_on_config = ""
1675     mmr_replicator_acl = ""
1676     mmr_serverids_config = ""
1677     mmr_syncrepl_schema_config = "" 
1678     mmr_syncrepl_config_config = "" 
1679     mmr_syncrepl_user_config = "" 
1680        
1681     
1682     if ol_mmr_urls is not None:
1683         # For now, make these equal
1684         mmr_pass = ldapadminpass
1685         
1686         url_list=filter(None,ol_mmr_urls.split(' ')) 
1687         if (len(url_list) == 1):
1688             url_list=filter(None,ol_mmr_urls.split(',')) 
1689                      
1690             
1691             mmr_on_config = "MirrorMode On"
1692             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1693             serverid=0
1694             for url in url_list:
1695                 serverid=serverid+1
1696                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1697                                                           { "SERVERID" : str(serverid),
1698                                                             "LDAPSERVER" : url })
1699                 rid=serverid*10
1700                 rid=rid+1
1701                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1702                                                                 {  "RID" : str(rid),
1703                                                                    "MMRDN": names.schemadn,
1704                                                                    "LDAPSERVER" : url,
1705                                                                    "MMR_PASSWORD": mmr_pass})
1706                 
1707                 rid=rid+1
1708                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1709                                                                 {  "RID" : str(rid),
1710                                                                    "MMRDN": names.configdn,
1711                                                                    "LDAPSERVER" : url,
1712                                                                    "MMR_PASSWORD": mmr_pass})
1713                 
1714                 rid=rid+1
1715                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1716                                                               {  "RID" : str(rid),
1717                                                                  "MMRDN": names.domaindn,
1718                                                                  "LDAPSERVER" : url,
1719                                                                  "MMR_PASSWORD": mmr_pass })
1720     # OpenLDAP cn=config initialisation
1721     olc_syncrepl_config = ""
1722     olc_mmr_config = "" 
1723     # if mmr = yes, generate cn=config-replication directives
1724     # and olc_seed.lif for the other mmr-servers
1725     if ol_mmr_urls is not None:
1726         serverid=0
1727         olc_serverids_config = ""
1728         olc_syncrepl_seed_config = ""
1729         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1730         rid=1000
1731         for url in url_list:
1732             serverid=serverid+1
1733             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1734                                                       { "SERVERID" : str(serverid),
1735                                                         "LDAPSERVER" : url })
1736             
1737             rid=rid+1
1738             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1739                                                      {  "RID" : str(rid),
1740                                                         "LDAPSERVER" : url,
1741                                                         "MMR_PASSWORD": mmr_pass})
1742             
1743             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1744                                                           {  "RID" : str(rid),
1745                                                              "LDAPSERVER" : url})
1746                 
1747         setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1748                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1749                     "OLC_PW": ldapadminpass,
1750                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1751     # end olc
1752                 
1753     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1754                {"DNSDOMAIN": names.dnsdomain,
1755                 "LDAPDIR": paths.ldapdir,
1756                 "DOMAINDN": names.domaindn,
1757                 "CONFIGDN": names.configdn,
1758                 "SCHEMADN": names.schemadn,
1759                 "MEMBEROF_CONFIG": memberof_config,
1760                 "MIRRORMODE": mmr_on_config,
1761                 "REPLICATOR_ACL": mmr_replicator_acl,
1762                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1763                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1764                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1765                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1766                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1767                 "OLC_MMR_CONFIG": olc_mmr_config,
1768                 "REFINT_CONFIG": refint_config,
1769                 "INDEX_CONFIG": index_config,
1770                 "NOSYNC": nosync_config})
1771         
1772     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1773     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1774     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1775     
1776     if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1777         os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1778         
1779     setup_file(setup_path("cn=samba.ldif"), 
1780                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1781                { "UUID": str(uuid.uuid4()), 
1782                  "LDAPTIME": timestring(int(time.time()))} )
1783     setup_file(setup_path("cn=samba-admin.ldif"), 
1784                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1785                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1786                 "UUID": str(uuid.uuid4()), 
1787                 "LDAPTIME": timestring(int(time.time()))} )
1788     
1789     if ol_mmr_urls is not None:
1790         setup_file(setup_path("cn=replicator.ldif"),
1791                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1792                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1793                     "UUID": str(uuid.uuid4()),
1794                     "LDAPTIME": timestring(int(time.time()))} )
1795         
1796
1797     mapping = "schema-map-openldap-2.3"
1798     backend_schema = "backend-schema.schema"
1799
1800     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1801     assert backend_schema_data is not None
1802     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1803
1804     # now we generate the needed strings to start slapd automatically,
1805     # first ldapi_uri...
1806     if ldap_backend_extra_port is not None:
1807         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1808         # specified there as part of it's clue as to it's own name,
1809         # and not to replicate to itself
1810         if ol_mmr_urls is None:
1811             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1812         else:
1813             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1814     else:
1815         server_port_string = ""
1816
1817     # Prepare the 'result' information - the commands to return in particular
1818     result.slapd_provision_command = [slapd_path]
1819
1820     result.slapd_provision_command.append("-F" + paths.olcdir)
1821
1822     result.slapd_provision_command.append("-h")
1823
1824     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1825     result.slapd_command = list(result.slapd_provision_command)
1826     
1827     result.slapd_provision_command.append(result.ldapi_uri)
1828     result.slapd_provision_command.append("-d0")
1829
1830     uris = result.ldapi_uri
1831     if server_port_string is not "":
1832         uris = uris + " " + server_port_string
1833
1834     result.slapd_command.append(uris)
1835
1836     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1837     result.credentials.set_username("samba-admin")
1838     
1839     # If we were just looking for crashes up to this point, it's a
1840     # good time to exit before we realise we don't have OpenLDAP on
1841     # this system
1842     if ldap_dryrun_mode:
1843         sys.exit(0)
1844
1845     # Finally, convert the configuration into cn=config style!
1846     if not os.path.isdir(paths.olcdir):
1847         os.makedirs(paths.olcdir, 0770)
1848
1849         retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1850
1851 #        We can't do this, as OpenLDAP is strange.  It gives an error
1852 #        output to the above, but does the conversion sucessfully...
1853 #
1854 #        if retcode != 0:
1855 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1856
1857         if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1858             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1859
1860         # Don't confuse the admin by leaving the slapd.conf around
1861         os.remove(paths.slapdconf)        
1862           
1863
1864 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1865                           message=None, 
1866                           hostname=None, ldapadminpass=None, root=None, 
1867                           schema=None,
1868                           ldap_backend_extra_port=None,
1869                           setup_ds_path=None,
1870                           slapd_path=None,
1871                           nosync=False, 
1872                           ldap_dryrun_mode=False):
1873
1874     if ldap_backend_extra_port is not None:
1875         serverport = "ServerPort=%d" % ldap_backend_extra_port
1876     else:
1877         serverport = ""
1878         
1879     setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1880                {"ROOT": root,
1881                 "HOSTNAME": hostname,
1882                 "DNSDOMAIN": names.dnsdomain,
1883                 "LDAPDIR": paths.ldapdir,
1884                 "DOMAINDN": names.domaindn,
1885                 "LDAPMANAGERDN": names.ldapmanagerdn,
1886                 "LDAPMANAGERPASS": ldapadminpass, 
1887                 "SERVERPORT": serverport})
1888
1889     setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1890                {"CONFIGDN": names.configdn,
1891                 "SCHEMADN": names.schemadn,
1892                 "SAMBADN": names.sambadn,
1893                 })
1894
1895     setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, 
1896                {"SAMBADN": names.sambadn,
1897                 })
1898
1899     setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1900                 {"SAMBADN": names.sambadn, 
1901                  "LDAPADMINPASS": ldapadminpass
1902                 })
1903
1904     mapping = "schema-map-fedora-ds-1.0"
1905     backend_schema = "99_ad.ldif"
1906     
1907     # Build a schema file in Fedora DS format
1908     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1909     assert backend_schema_data is not None
1910     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1911
1912     result.credentials.set_bind_dn(names.ldapmanagerdn)
1913
1914     # Destory the target directory, or else setup-ds.pl will complain
1915     fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1916     shutil.rmtree(fedora_ds_dir, True)
1917
1918     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1919     #In the 'provision' command line, stay in the foreground so we can easily kill it
1920     result.slapd_provision_command.append("-d0")
1921
1922     #the command for the final run is the normal script
1923     result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1924
1925     # If we were just looking for crashes up to this point, it's a
1926     # good time to exit before we realise we don't have Fedora DS on
1927     if ldap_dryrun_mode:
1928         sys.exit(0)
1929
1930     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1931     if setup_ds_path is None:
1932         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\"!")
1933     if not os.path.exists(setup_ds_path):
1934         message (setup_ds_path)
1935         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1936
1937     # Run the Fedora DS setup utility
1938     retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1939     if retcode != 0:
1940         raise ProvisioningError("setup-ds failed")
1941
1942     # Load samba-admin
1943     retcode = subprocess.call([
1944         os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1945         close_fds=True, shell=False)
1946     if retcode != 0:
1947         raise("ldib2db failed")
1948
1949 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1950     """Create a PHP LDAP admin configuration file.
1951
1952     :param path: Path to write the configuration to.
1953     :param setup_path: Function to generate setup paths.
1954     """
1955     setup_file(setup_path("phpldapadmin-config.php"), path, 
1956             {"S4_LDAPI_URI": ldapi_uri})
1957
1958
1959 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1960                      hostip, hostip6, hostname, dnspass, realm, domainguid,
1961                      ntdsguid):
1962     """Write out a DNS zone file, from the info in the current database.
1963
1964     :param path: Path of the new zone file.
1965     :param setup_path: Setup path function.
1966     :param dnsdomain: DNS Domain name
1967     :param domaindn: DN of the Domain
1968     :param hostip: Local IPv4 IP
1969     :param hostip6: Local IPv6 IP
1970     :param hostname: Local hostname
1971     :param dnspass: Password for DNS
1972     :param realm: Realm name
1973     :param domainguid: GUID of the domain.
1974     :param ntdsguid: GUID of the hosts nTDSDSA record.
1975     """
1976     assert isinstance(domainguid, str)
1977
1978     if hostip6 is not None:
1979         hostip6_base_line = "            IN AAAA    " + hostip6
1980         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1981     else:
1982         hostip6_base_line = ""
1983         hostip6_host_line = ""
1984
1985     if hostip is not None:
1986         hostip_base_line = "            IN A    " + hostip
1987         hostip_host_line = hostname + "        IN A    " + hostip
1988     else:
1989         hostip_base_line = ""
1990         hostip_host_line = ""
1991
1992     setup_file(setup_path("provision.zone"), path, {
1993             "DNSPASS_B64": b64encode(dnspass),
1994             "HOSTNAME": hostname,
1995             "DNSDOMAIN": dnsdomain,
1996             "REALM": realm,
1997             "HOSTIP_BASE_LINE": hostip_base_line,
1998             "HOSTIP_HOST_LINE": hostip_host_line,
1999             "DOMAINGUID": domainguid,
2000             "DATESTRING": time.strftime("%Y%m%d%H"),
2001             "DEFAULTSITE": DEFAULTSITE,
2002             "NTDSGUID": ntdsguid,
2003             "HOSTIP6_BASE_LINE": hostip6_base_line,
2004             "HOSTIP6_HOST_LINE": hostip6_host_line,
2005         })
2006
2007
2008 def create_named_conf(path, setup_path, realm, dnsdomain,
2009                       private_dir):
2010     """Write out a file containing zone statements suitable for inclusion in a
2011     named.conf file (including GSS-TSIG configuration).
2012     
2013     :param path: Path of the new named.conf file.
2014     :param setup_path: Setup path function.
2015     :param realm: Realm name
2016     :param dnsdomain: DNS Domain name
2017     :param private_dir: Path to private directory
2018     :param keytab_name: File name of DNS keytab file
2019     """
2020
2021     setup_file(setup_path("named.conf"), path, {
2022             "DNSDOMAIN": dnsdomain,
2023             "REALM": realm,
2024             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
2025             "PRIVATE_DIR": private_dir
2026             })
2027
2028 def create_named_txt(path, setup_path, realm, dnsdomain,
2029                       private_dir, keytab_name):
2030     """Write out a file containing zone statements suitable for inclusion in a
2031     named.conf file (including GSS-TSIG configuration).
2032     
2033     :param path: Path of the new named.conf file.
2034     :param setup_path: Setup path function.
2035     :param realm: Realm name
2036     :param dnsdomain: DNS Domain name
2037     :param private_dir: Path to private directory
2038     :param keytab_name: File name of DNS keytab file
2039     """
2040
2041     setup_file(setup_path("named.txt"), path, {
2042             "DNSDOMAIN": dnsdomain,
2043             "REALM": realm,
2044             "DNS_KEYTAB": keytab_name,
2045             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
2046             "PRIVATE_DIR": private_dir
2047         })
2048
2049 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
2050     """Write out a file containing zone statements suitable for inclusion in a
2051     named.conf file (including GSS-TSIG configuration).
2052     
2053     :param path: Path of the new named.conf file.
2054     :param setup_path: Setup path function.
2055     :param dnsdomain: DNS Domain name
2056     :param hostname: Local hostname
2057     :param realm: Realm name
2058     """
2059
2060     setup_file(setup_path("krb5.conf"), path, {
2061             "DNSDOMAIN": dnsdomain,
2062             "HOSTNAME": hostname,
2063             "REALM": realm,
2064         })
2065
2066