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