s4:provision - Lets the user choose between the supported forest/domain function...
[samba.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #   
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #   
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 """Functions for setting up a Samba configuration."""
27
28 from base64 import b64encode
29 import os
30 import sys
31 import pwd
32 import grp
33 import time
34 import uuid, glue
35 import socket
36 import param
37 import registry
38 import samba
39 import subprocess
40 import ldb
41
42 import shutil
43 from credentials import Credentials, DONT_USE_KERBEROS
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name
46 from samba import check_all_substituted
47 from samba import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008
48 from samba.samdb import SamDB
49 from samba.idmap import IDmapDB
50 from samba.dcerpc import security
51 from samba.ndr import ndr_pack
52 import urllib
53 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
54 from ms_schema import read_ms_schema
55 from ms_display_specifiers import read_ms_ldif
56 from signal import SIGTERM
57 from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
58
59 __docformat__ = "restructuredText"
60
61 def find_setup_dir():
62     """Find the setup directory used by provision."""
63     dirname = os.path.dirname(__file__)
64     if "/site-packages/" in dirname:
65         prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
66         for suffix in ["share/setup", "share/samba/setup", "setup"]:
67             ret = os.path.join(prefix, suffix)
68             if os.path.isdir(ret):
69                 return ret
70     # In source tree
71     ret = os.path.join(dirname, "../../../setup")
72     if os.path.isdir(ret):
73         return ret
74     raise Exception("Unable to find setup directory.")
75
76 def get_schema_descriptor(domain_sid):
77     sddl = "O:SAG:SAD:(A;CI;RPLCLORC;;;AU)(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \
78            "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
79            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
80            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
81            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
82            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
83            "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \
84            "(AU;CISA;WP;;;WD)(AU;SA;CR;;;BA)" \
85            "(AU;SA;CR;;;DU)(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \
86            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
87     sec = security.descriptor.from_sddl(sddl, domain_sid)
88     return b64encode(ndr_pack(sec))
89
90 def get_config_descriptor(domain_sid):
91     sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
92            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
93            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
94            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
95            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
96            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
97            "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
98            "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
99            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
100            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
101            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
102            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
103            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;S-1-5-21-3191434175-1265308384-3577286990-498)" \
104            "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
105            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
106     sec = security.descriptor.from_sddl(sddl, domain_sid)
107     return b64encode(ndr_pack(sec))
108
109
110 DEFAULTSITE = "Default-First-Site-Name"
111
112 # Exception classes
113
114 class ProvisioningError(Exception):
115     """A generic provision error."""
116
117 class InvalidNetbiosName(Exception):
118     """A specified name was not a valid NetBIOS name."""
119     def __init__(self, name):
120         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
121
122
123 class ProvisionPaths(object):
124     def __init__(self):
125         self.shareconf = None
126         self.hklm = None
127         self.hkcu = None
128         self.hkcr = None
129         self.hku = None
130         self.hkpd = None
131         self.hkpt = None
132         self.samdb = None
133         self.idmapdb = None
134         self.secrets = None
135         self.keytab = None
136         self.dns_keytab = None
137         self.dns = None
138         self.winsdb = None
139         self.private_dir = None
140         self.ldapdir = None
141         self.slapdconf = None
142         self.modulesconf = None
143         self.memberofconf = None
144         self.fedoradsinf = None
145         self.fedoradspartitions = None
146         self.fedoradssasl = None
147         self.olmmron = None
148         self.olmmrserveridsconf = None
149         self.olmmrsyncreplconf = None
150         self.olcdir = None
151         self.olslapd = None
152         self.olcseedldif = None
153
154
155 class ProvisionNames(object):
156     def __init__(self):
157         self.rootdn = None
158         self.domaindn = None
159         self.configdn = None
160         self.schemadn = None
161         self.sambadn = None
162         self.ldapmanagerdn = None
163         self.dnsdomain = None
164         self.realm = None
165         self.netbiosname = None
166         self.domain = None
167         self.hostname = None
168         self.sitename = None
169         self.smbconf = None
170     
171
172 class ProvisionResult(object):
173     def __init__(self):
174         self.paths = None
175         self.domaindn = None
176         self.lp = None
177         self.samdb = None
178         
179 class Schema(object):
180     def __init__(self, setup_path, domain_sid, schemadn=None,
181                  serverdn=None, sambadn=None, ldap_backend_type=None):
182         """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
183         
184         :param samdb: Load a schema into a SamDB.
185         :param setup_path: Setup path function.
186         :param schemadn: DN of the schema
187         :param serverdn: DN of the server
188         
189         Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
190         """
191         
192         self.ldb = Ldb()
193         self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
194                                           setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
195         self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
196         self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
197         check_all_substituted(self.schema_data)
198
199         self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
200                                                   {"SCHEMADN": schemadn,
201                                                    "SERVERDN": serverdn,
202                                                    })
203
204         descr = get_schema_descriptor(domain_sid)
205         self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
206                                                {"SCHEMADN": schemadn,
207                                                 "DESCRIPTOR": descr
208                                                 })
209
210         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
211         prefixmap = b64encode(prefixmap)
212
213         # We don't actually add this ldif, just parse it
214         prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
215         self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
216
217
218 # Return a hash with the forward attribute as a key and the back as the value 
219 def get_linked_attributes(schemadn,schemaldb):
220     attrs = ["linkID", "lDAPDisplayName"]
221     res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
222     attributes = {}
223     for i in range (0, len(res)):
224         expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
225         target = schemaldb.searchone(basedn=schemadn, 
226                                      expression=expression, 
227                                      attribute="lDAPDisplayName", 
228                                      scope=SCOPE_SUBTREE)
229         if target is not None:
230             attributes[str(res[i]["lDAPDisplayName"])]=str(target)
231             
232     return attributes
233
234 def get_dnsyntax_attributes(schemadn,schemaldb):
235     attrs = ["linkID", "lDAPDisplayName"]
236     res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
237     attributes = []
238     for i in range (0, len(res)):
239         attributes.append(str(res[i]["lDAPDisplayName"]))
240         
241     return attributes
242     
243     
244 def check_install(lp, session_info, credentials):
245     """Check whether the current install seems ok.
246     
247     :param lp: Loadparm context
248     :param session_info: Session information
249     :param credentials: Credentials
250     """
251     if lp.get("realm") == "":
252         raise Exception("Realm empty")
253     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
254             credentials=credentials, lp=lp)
255     if len(ldb.search("(cn=Administrator)")) != 1:
256         raise ProvisioningError("No administrator account found")
257
258
259 def findnss(nssfn, names):
260     """Find a user or group from a list of possibilities.
261     
262     :param nssfn: NSS Function to try (should raise KeyError if not found)
263     :param names: Names to check.
264     :return: Value return by first names list.
265     """
266     for name in names:
267         try:
268             return nssfn(name)
269         except KeyError:
270             pass
271     raise KeyError("Unable to find user/group %r" % names)
272
273
274 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
275 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
276
277
278 def read_and_sub_file(file, subst_vars):
279     """Read a file and sub in variables found in it
280     
281     :param file: File to be read (typically from setup directory)
282      param subst_vars: Optional variables to subsitute in the file.
283     """
284     data = open(file, 'r').read()
285     if subst_vars is not None:
286         data = substitute_var(data, subst_vars)
287     check_all_substituted(data)
288     return data
289
290
291 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
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, dom_for_fun_level=None,
930                 schema=None, ldap_backend=None):
931     """Setup a complete SAM Database.
932     
933     :note: This will wipe the main SAM database file!
934     """
935
936     # ATTENTION: Do NOT change these default values without discussion with the
937     # team and/or release manager. They have a big impact on the whole program!
938     domainControllerFunctionality = DS_DC_FUNCTION_2008
939
940     if dom_for_fun_level is None:
941         dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
942     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
943         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
944
945     if dom_for_fun_level > domainControllerFunctionality:
946         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008). This won't work!")
947
948     domainFunctionality = dom_for_fun_level
949     forestFunctionality = dom_for_fun_level
950
951     # Also wipes the database
952     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
953                            credentials=credentials, session_info=session_info,
954                            names=names, ldap_backend=ldap_backend,
955                            serverrole=serverrole)
956
957     if (schema == None):
958         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
959             sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
960
961     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
962     samdb = Ldb(session_info=session_info, 
963                 credentials=credentials, lp=lp)
964
965     message("Pre-loading the Samba 4 and AD schema")
966
967     # Load the schema from the one we computed earlier
968     samdb.set_schema_from_ldb(schema.ldb)
969
970     # And now we can connect to the DB - the schema won't be loaded from the DB
971     samdb.connect(path)
972
973     # Load @OPTIONS
974     samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
975
976     if fill == FILL_DRS:
977         return samdb
978
979     samdb.transaction_start()
980     try:
981         message("Erasing data from partitions")
982         # Load the schema (again).  This time it will force a reindex,
983         # and will therefore make the erase_partitions() below
984         # computationally sane
985         samdb.set_schema_from_ldb(schema.ldb)
986         samdb.erase_partitions()
987     
988         # Set the domain functionality levels onto the database.
989         # Various module (the password_hash module in particular) need
990         # to know what level of AD we are emulating.
991
992         # These will be fixed into the database via the database
993         # modifictions below, but we need them set from the start.
994         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
995         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
996         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
997
998         samdb.set_domain_sid(str(domainsid))
999         if serverrole == "domain controller":
1000             samdb.set_invocation_id(invocationid)
1001
1002         message("Adding DomainDN: %s" % names.domaindn)
1003         if serverrole == "domain controller":
1004             domain_oc = "domainDNS"
1005         else:
1006             domain_oc = "samba4LocalDomain"
1007
1008 #impersonate domain admin
1009         admin_session_info = admin_session(lp, str(domainsid))
1010         samdb.set_session_info(admin_session_info)
1011
1012         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1013                 "DOMAINDN": names.domaindn,
1014                 "DOMAIN_OC": domain_oc
1015                 })
1016
1017         message("Modifying DomainDN: " + names.domaindn + "")
1018         if domainguid is not None:
1019             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
1020         else:
1021             domainguid_mod = ""
1022
1023         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1024             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1025             "DOMAINSID": str(domainsid),
1026             "SCHEMADN": names.schemadn, 
1027             "NETBIOSNAME": names.netbiosname,
1028             "DEFAULTSITE": names.sitename,
1029             "CONFIGDN": names.configdn,
1030             "SERVERDN": names.serverdn,
1031             "POLICYGUID": policyguid,
1032             "DOMAINDN": names.domaindn,
1033             "DOMAINGUID_MOD": domainguid_mod,
1034             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1035             "SAMBA_VERSION_STRING": version
1036             })
1037
1038         message("Adding configuration container")
1039         descr = get_config_descriptor(domainsid);
1040         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1041             "CONFIGDN": names.configdn, 
1042             "DESCRIPTOR": descr,
1043             })
1044         message("Modifying configuration container")
1045         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1046             "CONFIGDN": names.configdn, 
1047             "SCHEMADN": names.schemadn,
1048             })
1049
1050         # The LDIF here was created when the Schema object was constructed
1051         message("Setting up sam.ldb schema")
1052         samdb.add_ldif(schema.schema_dn_add)
1053         samdb.modify_ldif(schema.schema_dn_modify)
1054         samdb.write_prefixes_from_schema()
1055         samdb.add_ldif(schema.schema_data)
1056         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1057                        {"SCHEMADN": names.schemadn})
1058
1059         message("Setting up sam.ldb configuration data")
1060         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1061             "CONFIGDN": names.configdn,
1062             "NETBIOSNAME": names.netbiosname,
1063             "DEFAULTSITE": names.sitename,
1064             "DNSDOMAIN": names.dnsdomain,
1065             "DOMAIN": names.domain,
1066             "SCHEMADN": names.schemadn,
1067             "DOMAINDN": names.domaindn,
1068             "SERVERDN": names.serverdn,
1069             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1070             })
1071
1072         message("Setting up display specifiers")
1073         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1074         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1075         check_all_substituted(display_specifiers_ldif)
1076         samdb.add_ldif(display_specifiers_ldif)
1077
1078         message("Adding users container")
1079         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1080                 "DOMAINDN": names.domaindn})
1081         message("Modifying users container")
1082         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1083                 "DOMAINDN": names.domaindn})
1084         message("Adding computers container")
1085         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1086                 "DOMAINDN": names.domaindn})
1087         message("Modifying computers container")
1088         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1089                 "DOMAINDN": names.domaindn})
1090         message("Setting up sam.ldb data")
1091         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1092             "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
1093             "DOMAINDN": names.domaindn,
1094             "NETBIOSNAME": names.netbiosname,
1095             "DEFAULTSITE": names.sitename,
1096             "CONFIGDN": names.configdn,
1097             "SERVERDN": names.serverdn,
1098             "POLICYGUID_DC": policyguid_dc
1099             })
1100
1101         if fill == FILL_FULL:
1102             message("Setting up sam.ldb users and groups")
1103             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1104                 "DOMAINDN": names.domaindn,
1105                 "DOMAINSID": str(domainsid),
1106                 "CONFIGDN": names.configdn,
1107                 "ADMINPASS_B64": b64encode(adminpass),
1108                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1109                 })
1110
1111             if serverrole == "domain controller":
1112                 message("Setting up self join")
1113                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1114                                 dnspass=dnspass,  
1115                                 machinepass=machinepass, 
1116                                 domainsid=domainsid, policyguid=policyguid,
1117                                 policyguid_dc=policyguid_dc,
1118                                 setup_path=setup_path,
1119                                 domainControllerFunctionality=domainControllerFunctionality)
1120
1121                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1122                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1123                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1124                 assert isinstance(names.ntdsguid, str)
1125
1126     except:
1127         samdb.transaction_cancel()
1128         raise
1129
1130     samdb.transaction_commit()
1131     return samdb
1132
1133
1134 FILL_FULL = "FULL"
1135 FILL_NT4SYNC = "NT4SYNC"
1136 FILL_DRS = "DRS"
1137
1138
1139 def provision(setup_dir, message, session_info, 
1140               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1141               realm=None, 
1142               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1143               serverdn=None,
1144               domain=None, hostname=None, hostip=None, hostip6=None, 
1145               domainsid=None, adminpass=None, ldapadminpass=None, 
1146               krbtgtpass=None, domainguid=None, 
1147               policyguid=None, policyguid_dc=None, invocationid=None,
1148               machinepass=None, 
1149               dnspass=None, root=None, nobody=None, users=None, 
1150               wheel=None, backup=None, aci=None, serverrole=None,
1151               dom_for_fun_level=None,
1152               ldap_backend_extra_port=None, ldap_backend_type=None,
1153               sitename=None,
1154               ol_mmr_urls=None, ol_olc=None, 
1155               setup_ds_path=None, slapd_path=None, nosync=False,
1156               ldap_dryrun_mode=False):
1157     """Provision samba4
1158     
1159     :note: caution, this wipes all existing data!
1160     """
1161
1162     def setup_path(file):
1163       return os.path.join(setup_dir, file)
1164
1165     if domainsid is None:
1166       domainsid = security.random_sid()
1167     else:
1168       domainsid = security.dom_sid(domainsid)
1169
1170     # create/adapt the group policy GUIDs
1171     if policyguid is None:
1172         policyguid = str(uuid.uuid4())
1173     policyguid = policyguid.upper()
1174     if policyguid_dc is None:
1175         policyguid_dc = str(uuid.uuid4())
1176     policyguid_dc = policyguid_dc.upper()
1177
1178     if adminpass is None:
1179         adminpass = glue.generate_random_str(12)
1180     if krbtgtpass is None:
1181         krbtgtpass = glue.generate_random_str(12)
1182     if machinepass is None:
1183         machinepass  = glue.generate_random_str(12)
1184     if dnspass is None:
1185         dnspass = glue.generate_random_str(12)
1186     if ldapadminpass is None:
1187         #Make a new, random password between Samba and it's LDAP server
1188         ldapadminpass=glue.generate_random_str(12)        
1189
1190
1191     root_uid = findnss_uid([root or "root"])
1192     nobody_uid = findnss_uid([nobody or "nobody"])
1193     users_gid = findnss_gid([users or "users"])
1194     if wheel is None:
1195         wheel_gid = findnss_gid(["wheel", "adm"])
1196     else:
1197         wheel_gid = findnss_gid([wheel])
1198
1199     if targetdir is not None:
1200         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1201             os.makedirs(os.path.join(targetdir, "etc"))
1202         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1203     elif smbconf is None:
1204         smbconf = param.default_path()
1205
1206     # only install a new smb.conf if there isn't one there already
1207     if not os.path.exists(smbconf):
1208         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1209                      targetdir)
1210
1211     lp = param.LoadParm()
1212     lp.load(smbconf)
1213
1214     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1215                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1216                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1217                         serverdn=serverdn)
1218
1219     paths = provision_paths_from_lp(lp, names.dnsdomain)
1220
1221     if hostip is None:
1222         try:
1223             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1224         except socket.gaierror, (socket.EAI_NODATA, msg):
1225             hostip = None
1226
1227     if hostip6 is None:
1228         try:
1229             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1230         except socket.gaierror, (socket.EAI_NODATA, msg): 
1231             hostip6 = None
1232
1233     if serverrole is None:
1234         serverrole = lp.get("server role")
1235
1236     assert serverrole in ("domain controller", "member server", "standalone")
1237     if invocationid is None and serverrole == "domain controller":
1238         invocationid = str(uuid.uuid4())
1239
1240     if not os.path.exists(paths.private_dir):
1241         os.mkdir(paths.private_dir)
1242
1243     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1244     
1245     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1246         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1247     
1248     secrets_credentials = credentials
1249     provision_backend = None
1250     if ldap_backend_type:
1251         # We only support an LDAP backend over ldapi://
1252
1253         provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1254                                              lp=lp, credentials=credentials, 
1255                                              names=names,
1256                                              message=message, hostname=hostname,
1257                                              root=root, schema=schema,
1258                                              ldap_backend_type=ldap_backend_type,
1259                                              ldapadminpass=ldapadminpass,
1260                                              ldap_backend_extra_port=ldap_backend_extra_port,
1261                                              ol_mmr_urls=ol_mmr_urls, 
1262                                              slapd_path=slapd_path,
1263                                              setup_ds_path=setup_ds_path,
1264                                              ldap_dryrun_mode=ldap_dryrun_mode)
1265
1266         # Now use the backend credentials to access the databases
1267         credentials = provision_backend.credentials
1268         secrets_credentials = provision_backend.adminCredentials
1269         ldapi_url = provision_backend.ldapi_uri
1270
1271     # only install a new shares config db if there is none
1272     if not os.path.exists(paths.shareconf):
1273         message("Setting up share.ldb")
1274         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1275                         credentials=credentials, lp=lp)
1276         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1277
1278      
1279     message("Setting up secrets.ldb")
1280     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1281                                   session_info=session_info, 
1282                                   credentials=secrets_credentials, lp=lp)
1283
1284     message("Setting up the registry")
1285     setup_registry(paths.hklm, setup_path, session_info, 
1286                    lp=lp)
1287
1288     message("Setting up idmap db")
1289     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1290                           lp=lp)
1291
1292     message("Setting up SAM db")
1293     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1294                         credentials=credentials, lp=lp, names=names,
1295                         message=message, 
1296                         domainsid=domainsid, 
1297                         schema=schema, domainguid=domainguid,
1298                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1299                         fill=samdb_fill, 
1300                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1301                         invocationid=invocationid, 
1302                         machinepass=machinepass, dnspass=dnspass,
1303                         serverrole=serverrole,
1304                         dom_for_fun_level=dom_for_fun_level,
1305                         ldap_backend=provision_backend)
1306
1307     if serverrole == "domain controller":
1308         if paths.netlogon is None:
1309             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1310             message("Please either remove %s or see the template at %s" % 
1311                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1312             assert(paths.netlogon is not None)
1313
1314         if paths.sysvol is None:
1315             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1316             message("Please either remove %s or see the template at %s" % 
1317                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1318             assert(paths.sysvol is not None)            
1319             
1320         # Set up group policies (domain policy and domain controller policy)
1321
1322         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1323                                    "{" + policyguid + "}")
1324         os.makedirs(policy_path, 0755)
1325         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1326                                    "[General]\r\nVersion=65543")
1327         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1328         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1329
1330         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1331                                    "{" + policyguid_dc + "}")
1332         os.makedirs(policy_path_dc, 0755)
1333         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1334                                    "[General]\r\nVersion=2")
1335         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1336         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1337
1338         if not os.path.isdir(paths.netlogon):
1339             os.makedirs(paths.netlogon, 0755)
1340
1341     if samdb_fill == FILL_FULL:
1342         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1343                             root_uid=root_uid, nobody_uid=nobody_uid,
1344                             users_gid=users_gid, wheel_gid=wheel_gid)
1345
1346         message("Setting up sam.ldb rootDSE marking as synchronized")
1347         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1348
1349         # Only make a zone file on the first DC, it should be replicated with DNS replication
1350         if serverrole == "domain controller":
1351             secretsdb_self_join(secrets_ldb, domain=domain,
1352                                 realm=names.realm,
1353                                 dnsdomain=names.dnsdomain,
1354                                 netbiosname=names.netbiosname,
1355                                 domainsid=domainsid, 
1356                                 machinepass=machinepass,
1357                                 secure_channel_type=SEC_CHAN_BDC)
1358
1359             secretsdb_setup_dns(secrets_ldb, setup_path, 
1360                                 realm=names.realm, dnsdomain=names.dnsdomain,
1361                                 dns_keytab_path=paths.dns_keytab,
1362                                 dnspass=dnspass)
1363
1364             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1365             assert isinstance(domainguid, str)
1366
1367             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1368                              domaindn=names.domaindn, hostip=hostip,
1369                              hostip6=hostip6, hostname=names.hostname,
1370                              dnspass=dnspass, realm=names.realm,
1371                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1372
1373             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1374                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1375
1376             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1377                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1378                               keytab_name=paths.dns_keytab)
1379             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1380             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1381
1382             create_krb5_conf(paths.krb5conf, setup_path,
1383                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1384                              realm=names.realm)
1385             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1386
1387     #Now commit the secrets.ldb to disk
1388     secrets_ldb.transaction_commit()
1389
1390     if provision_backend is not None: 
1391       if ldap_backend_type == "fedora-ds":
1392         ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1393
1394         # delete default SASL mappings
1395         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1396
1397         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1398         for i in range (0, len(res)):
1399           dn = str(res[i]["dn"])
1400           ldapi_db.delete(dn)
1401
1402           aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1403
1404           m = ldb.Message()
1405           m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1406         
1407           m.dn = ldb.Dn(1, names.domaindn)
1408           ldapi_db.modify(m)
1409
1410           m.dn = ldb.Dn(1, names.configdn)
1411           ldapi_db.modify(m)
1412
1413           m.dn = ldb.Dn(1, names.schemadn)
1414           ldapi_db.modify(m)
1415
1416       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1417       if provision_backend.slapd.poll() is None:
1418         #Kill the slapd
1419         if hasattr(provision_backend.slapd, "terminate"):
1420           provision_backend.slapd.terminate()
1421         else:
1422           # Older python versions don't have .terminate()
1423           import signal
1424           os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1425             
1426         #and now wait for it to die
1427         provision_backend.slapd.communicate()
1428             
1429     # now display slapd_command_file.txt to show how slapd must be started next time
1430         message("Use later the following commandline to start slapd, then Samba:")
1431         slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1432         message(slapd_command)
1433         message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1434
1435         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1436                 "SLAPD_COMMAND" : slapd_command})
1437
1438     
1439     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1440                                ldapi_url)
1441
1442     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1443
1444     message("Once the above files are installed, your Samba4 server will be ready to use")
1445     message("Server Role:           %s" % serverrole)
1446     message("Hostname:              %s" % names.hostname)
1447     message("NetBIOS Domain:        %s" % names.domain)
1448     message("DNS Domain:            %s" % names.dnsdomain)
1449     message("DOMAIN SID:            %s" % str(domainsid))
1450     if samdb_fill == FILL_FULL:
1451         message("Admin password:    %s" % adminpass)
1452     if provision_backend:
1453         if provision_backend.credentials.get_bind_dn() is not None:
1454             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1455         else:
1456             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1457
1458         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1459   
1460     result = ProvisionResult()
1461     result.domaindn = domaindn
1462     result.paths = paths
1463     result.lp = lp
1464     result.samdb = samdb
1465     return result
1466
1467
1468
1469 def provision_become_dc(setup_dir=None,
1470                         smbconf=None, targetdir=None, realm=None, 
1471                         rootdn=None, domaindn=None, schemadn=None,
1472                         configdn=None, serverdn=None,
1473                         domain=None, hostname=None, domainsid=None, 
1474                         adminpass=None, krbtgtpass=None, domainguid=None, 
1475                         policyguid=None, policyguid_dc=None, invocationid=None,
1476                         machinepass=None, 
1477                         dnspass=None, root=None, nobody=None, users=None, 
1478                         wheel=None, backup=None, serverrole=None, 
1479                         ldap_backend=None, ldap_backend_type=None,
1480                         sitename=None, debuglevel=1):
1481
1482     def message(text):
1483         """print a message if quiet is not set."""
1484         print text
1485
1486     glue.set_debug_level(debuglevel)
1487
1488     return provision(setup_dir, message, system_session(), None,
1489               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1490               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1491               configdn=configdn, serverdn=serverdn, domain=domain,
1492               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1493               machinepass=machinepass, serverrole="domain controller",
1494               sitename=sitename)
1495
1496
1497 def setup_db_config(setup_path, dbdir):
1498     """Setup a Berkeley database.
1499     
1500     :param setup_path: Setup path function.
1501     :param dbdir: Database directory."""
1502     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1503         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1504         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1505             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1506
1507     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1508                {"LDAPDBDIR": dbdir})
1509     
1510 class ProvisionBackend(object):
1511     def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, 
1512                  names=None, message=None, 
1513                  hostname=None, root=None, 
1514                  schema=None, ldapadminpass=None,
1515                  ldap_backend_type=None, ldap_backend_extra_port=None,
1516                  ol_mmr_urls=None, 
1517                  setup_ds_path=None, slapd_path=None, 
1518                  nosync=False, ldap_dryrun_mode=False):
1519         """Provision an LDAP backend for samba4
1520         
1521         This works for OpenLDAP and Fedora DS
1522         """
1523
1524         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1525         
1526         if not os.path.isdir(paths.ldapdir):
1527             os.makedirs(paths.ldapdir, 0700)
1528             
1529         if ldap_backend_type == "existing":
1530             #Check to see that this 'existing' LDAP backend in fact exists
1531             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1532             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1533                                                 expression="(objectClass=OpenLDAProotDSE)")
1534
1535             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1536             # This caused them to be set into the long-term database later in the script.
1537             self.credentials = credentials
1538             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1539             return
1540     
1541         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1542         # if another instance of slapd is already running 
1543         try:
1544             ldapi_db = Ldb(self.ldapi_uri)
1545             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1546                                                 expression="(objectClass=OpenLDAProotDSE)");
1547             try:
1548                 f = open(paths.slapdpid, "r")
1549                 p = f.read()
1550                 f.close()
1551                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1552             except:
1553                 pass
1554             
1555             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1556         
1557         except LdbError, e:
1558             pass
1559
1560         # Try to print helpful messages when the user has not specified the path to slapd
1561         if slapd_path is None:
1562             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1563         if not os.path.exists(slapd_path):
1564             message (slapd_path)
1565             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1566
1567         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1568         try:
1569             os.unlink(schemadb_path)
1570         except OSError:
1571             pass
1572
1573
1574         # Put the LDIF of the schema into a database so we can search on
1575         # it to generate schema-dependent configurations in Fedora DS and
1576         # OpenLDAP
1577         os.path.join(paths.ldapdir, "schema-tmp.ldb")
1578         schema.ldb.connect(schemadb_path)
1579         schema.ldb.transaction_start()
1580     
1581         # These bits of LDIF are supplied when the Schema object is created
1582         schema.ldb.add_ldif(schema.schema_dn_add)
1583         schema.ldb.modify_ldif(schema.schema_dn_modify)
1584         schema.ldb.add_ldif(schema.schema_data)
1585         schema.ldb.transaction_commit()
1586
1587         self.credentials = Credentials()
1588         self.credentials.guess(lp)
1589         #Kerberos to an ldapi:// backend makes no sense
1590         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1591
1592         self.adminCredentials = Credentials()
1593         self.adminCredentials.guess(lp)
1594         #Kerberos to an ldapi:// backend makes no sense
1595         self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1596
1597         self.ldap_backend_type = ldap_backend_type
1598
1599         if ldap_backend_type == "fedora-ds":
1600             provision_fds_backend(self, paths=paths, setup_path=setup_path,
1601                                   names=names, message=message, 
1602                                   hostname=hostname,
1603                                   ldapadminpass=ldapadminpass, root=root, 
1604                                   schema=schema,
1605                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1606                                   setup_ds_path=setup_ds_path,
1607                                   slapd_path=slapd_path,
1608                                   nosync=nosync,
1609                                   ldap_dryrun_mode=ldap_dryrun_mode)
1610             
1611         elif ldap_backend_type == "openldap":
1612             provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1613                                        names=names, message=message, 
1614                                        hostname=hostname,
1615                                        ldapadminpass=ldapadminpass, root=root, 
1616                                        schema=schema,
1617                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1618                                        ol_mmr_urls=ol_mmr_urls, 
1619                                        slapd_path=slapd_path,
1620                                        nosync=nosync,
1621                                        ldap_dryrun_mode=ldap_dryrun_mode)
1622         else:
1623             raise ProvisioningError("Unknown LDAP backend type selected")
1624
1625         self.credentials.set_password(ldapadminpass)
1626         self.adminCredentials.set_username("samba-admin")
1627         self.adminCredentials.set_password(ldapadminpass)
1628
1629         # Now start the slapd, so we can provision onto it.  We keep the
1630         # subprocess context around, to kill this off at the successful
1631         # end of the script
1632         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1633     
1634         while self.slapd.poll() is None:
1635             # Wait until the socket appears
1636             try:
1637                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1638                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1639                                                     expression="(objectClass=OpenLDAProotDSE)")
1640                 # If we have got here, then we must have a valid connection to the LDAP server!
1641                 return
1642             except LdbError, e:
1643                 time.sleep(1)
1644                 pass
1645         
1646         raise ProvisioningError("slapd died before we could make a connection to it")
1647
1648
1649 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1650                                message=None, 
1651                                hostname=None, ldapadminpass=None, root=None, 
1652                                schema=None, 
1653                                ldap_backend_extra_port=None,
1654                                ol_mmr_urls=None, 
1655                                slapd_path=None, nosync=False,
1656                                ldap_dryrun_mode=False):
1657
1658     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1659     nosync_config = ""
1660     if nosync:
1661         nosync_config = "dbnosync"
1662         
1663     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1664     refint_attributes = ""
1665     memberof_config = "# Generated from Samba4 schema\n"
1666     for att in  lnkattr.keys():
1667         if lnkattr[att] is not None:
1668             refint_attributes = refint_attributes + " " + att 
1669             
1670             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1671                                                  { "MEMBER_ATTR" : att ,
1672                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1673             
1674     refint_config = read_and_sub_file(setup_path("refint.conf"),
1675                                       { "LINK_ATTRS" : refint_attributes})
1676     
1677     attrs = ["linkID", "lDAPDisplayName"]
1678     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1679     index_config = ""
1680     for i in range (0, len(res)):
1681         index_attr = res[i]["lDAPDisplayName"][0]
1682         if index_attr == "objectGUID":
1683             index_attr = "entryUUID"
1684             
1685         index_config += "index " + index_attr + " eq\n"
1686
1687 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1688     mmr_on_config = ""
1689     mmr_replicator_acl = ""
1690     mmr_serverids_config = ""
1691     mmr_syncrepl_schema_config = "" 
1692     mmr_syncrepl_config_config = "" 
1693     mmr_syncrepl_user_config = "" 
1694        
1695     
1696     if ol_mmr_urls is not None:
1697         # For now, make these equal
1698         mmr_pass = ldapadminpass
1699         
1700         url_list=filter(None,ol_mmr_urls.split(' ')) 
1701         if (len(url_list) == 1):
1702             url_list=filter(None,ol_mmr_urls.split(',')) 
1703                      
1704             
1705             mmr_on_config = "MirrorMode On"
1706             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1707             serverid=0
1708             for url in url_list:
1709                 serverid=serverid+1
1710                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1711                                                           { "SERVERID" : str(serverid),
1712                                                             "LDAPSERVER" : url })
1713                 rid=serverid*10
1714                 rid=rid+1
1715                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1716                                                                 {  "RID" : str(rid),
1717                                                                    "MMRDN": names.schemadn,
1718                                                                    "LDAPSERVER" : url,
1719                                                                    "MMR_PASSWORD": mmr_pass})
1720                 
1721                 rid=rid+1
1722                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1723                                                                 {  "RID" : str(rid),
1724                                                                    "MMRDN": names.configdn,
1725                                                                    "LDAPSERVER" : url,
1726                                                                    "MMR_PASSWORD": mmr_pass})
1727                 
1728                 rid=rid+1
1729                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1730                                                               {  "RID" : str(rid),
1731                                                                  "MMRDN": names.domaindn,
1732                                                                  "LDAPSERVER" : url,
1733                                                                  "MMR_PASSWORD": mmr_pass })
1734     # OpenLDAP cn=config initialisation
1735     olc_syncrepl_config = ""
1736     olc_mmr_config = "" 
1737     # if mmr = yes, generate cn=config-replication directives
1738     # and olc_seed.lif for the other mmr-servers
1739     if ol_mmr_urls is not None:
1740         serverid=0
1741         olc_serverids_config = ""
1742         olc_syncrepl_seed_config = ""
1743         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1744         rid=1000
1745         for url in url_list:
1746             serverid=serverid+1
1747             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1748                                                       { "SERVERID" : str(serverid),
1749                                                         "LDAPSERVER" : url })
1750             
1751             rid=rid+1
1752             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1753                                                      {  "RID" : str(rid),
1754                                                         "LDAPSERVER" : url,
1755                                                         "MMR_PASSWORD": mmr_pass})
1756             
1757             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1758                                                           {  "RID" : str(rid),
1759                                                              "LDAPSERVER" : url})
1760                 
1761         setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1762                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1763                     "OLC_PW": ldapadminpass,
1764                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1765     # end olc
1766                 
1767     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1768                {"DNSDOMAIN": names.dnsdomain,
1769                 "LDAPDIR": paths.ldapdir,
1770                 "DOMAINDN": names.domaindn,
1771                 "CONFIGDN": names.configdn,
1772                 "SCHEMADN": names.schemadn,
1773                 "MEMBEROF_CONFIG": memberof_config,
1774                 "MIRRORMODE": mmr_on_config,
1775                 "REPLICATOR_ACL": mmr_replicator_acl,
1776                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1777                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1778                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1779                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1780                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1781                 "OLC_MMR_CONFIG": olc_mmr_config,
1782                 "REFINT_CONFIG": refint_config,
1783                 "INDEX_CONFIG": index_config,
1784                 "NOSYNC": nosync_config})
1785         
1786     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1787     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1788     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1789     
1790     if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1791         os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1792         
1793     setup_file(setup_path("cn=samba.ldif"), 
1794                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1795                { "UUID": str(uuid.uuid4()), 
1796                  "LDAPTIME": timestring(int(time.time()))} )
1797     setup_file(setup_path("cn=samba-admin.ldif"), 
1798                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1799                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1800                 "UUID": str(uuid.uuid4()), 
1801                 "LDAPTIME": timestring(int(time.time()))} )
1802     
1803     if ol_mmr_urls is not None:
1804         setup_file(setup_path("cn=replicator.ldif"),
1805                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1806                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1807                     "UUID": str(uuid.uuid4()),
1808                     "LDAPTIME": timestring(int(time.time()))} )
1809         
1810
1811     mapping = "schema-map-openldap-2.3"
1812     backend_schema = "backend-schema.schema"
1813
1814     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1815     assert backend_schema_data is not None
1816     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1817
1818     # now we generate the needed strings to start slapd automatically,
1819     # first ldapi_uri...
1820     if ldap_backend_extra_port is not None:
1821         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1822         # specified there as part of it's clue as to it's own name,
1823         # and not to replicate to itself
1824         if ol_mmr_urls is None:
1825             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1826         else:
1827             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1828     else:
1829         server_port_string = ""
1830
1831     # Prepare the 'result' information - the commands to return in particular
1832     result.slapd_provision_command = [slapd_path]
1833
1834     result.slapd_provision_command.append("-F" + paths.olcdir)
1835
1836     result.slapd_provision_command.append("-h")
1837
1838     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1839     result.slapd_command = list(result.slapd_provision_command)
1840     
1841     result.slapd_provision_command.append(result.ldapi_uri)
1842     result.slapd_provision_command.append("-d0")
1843
1844     uris = result.ldapi_uri
1845     if server_port_string is not "":
1846         uris = uris + " " + server_port_string
1847
1848     result.slapd_command.append(uris)
1849
1850     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1851     result.credentials.set_username("samba-admin")
1852     
1853     # If we were just looking for crashes up to this point, it's a
1854     # good time to exit before we realise we don't have OpenLDAP on
1855     # this system
1856     if ldap_dryrun_mode:
1857         sys.exit(0)
1858
1859     # Finally, convert the configuration into cn=config style!
1860     if not os.path.isdir(paths.olcdir):
1861         os.makedirs(paths.olcdir, 0770)
1862
1863         retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1864
1865 #        We can't do this, as OpenLDAP is strange.  It gives an error
1866 #        output to the above, but does the conversion sucessfully...
1867 #
1868 #        if retcode != 0:
1869 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1870
1871         if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1872             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1873
1874         # Don't confuse the admin by leaving the slapd.conf around
1875         os.remove(paths.slapdconf)        
1876           
1877
1878 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1879                           message=None, 
1880                           hostname=None, ldapadminpass=None, root=None, 
1881                           schema=None,
1882                           ldap_backend_extra_port=None,
1883                           setup_ds_path=None,
1884                           slapd_path=None,
1885                           nosync=False, 
1886                           ldap_dryrun_mode=False):
1887
1888     if ldap_backend_extra_port is not None:
1889         serverport = "ServerPort=%d" % ldap_backend_extra_port
1890     else:
1891         serverport = ""
1892         
1893     setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1894                {"ROOT": root,
1895                 "HOSTNAME": hostname,
1896                 "DNSDOMAIN": names.dnsdomain,
1897                 "LDAPDIR": paths.ldapdir,
1898                 "DOMAINDN": names.domaindn,
1899                 "LDAPMANAGERDN": names.ldapmanagerdn,
1900                 "LDAPMANAGERPASS": ldapadminpass, 
1901                 "SERVERPORT": serverport})
1902
1903     setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1904                {"CONFIGDN": names.configdn,
1905                 "SCHEMADN": names.schemadn,
1906                 "SAMBADN": names.sambadn,
1907                 })
1908
1909     setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, 
1910                {"SAMBADN": names.sambadn,
1911                 })
1912
1913     setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1914                 {"SAMBADN": names.sambadn, 
1915                  "LDAPADMINPASS": ldapadminpass
1916                 })
1917
1918     mapping = "schema-map-fedora-ds-1.0"
1919     backend_schema = "99_ad.ldif"
1920     
1921     # Build a schema file in Fedora DS format
1922     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1923     assert backend_schema_data is not None
1924     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1925
1926     result.credentials.set_bind_dn(names.ldapmanagerdn)
1927
1928     # Destory the target directory, or else setup-ds.pl will complain
1929     fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1930     shutil.rmtree(fedora_ds_dir, True)
1931
1932     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1933     #In the 'provision' command line, stay in the foreground so we can easily kill it
1934     result.slapd_provision_command.append("-d0")
1935
1936     #the command for the final run is the normal script
1937     result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1938
1939     # If we were just looking for crashes up to this point, it's a
1940     # good time to exit before we realise we don't have Fedora DS on
1941     if ldap_dryrun_mode:
1942         sys.exit(0)
1943
1944     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1945     if setup_ds_path is None:
1946         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\"!")
1947     if not os.path.exists(setup_ds_path):
1948         message (setup_ds_path)
1949         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1950
1951     # Run the Fedora DS setup utility
1952     retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1953     if retcode != 0:
1954         raise ProvisioningError("setup-ds failed")
1955
1956     # Load samba-admin
1957     retcode = subprocess.call([
1958         os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1959         close_fds=True, shell=False)
1960     if retcode != 0:
1961         raise("ldib2db failed")
1962
1963 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1964     """Create a PHP LDAP admin configuration file.
1965
1966     :param path: Path to write the configuration to.
1967     :param setup_path: Function to generate setup paths.
1968     """
1969     setup_file(setup_path("phpldapadmin-config.php"), path, 
1970             {"S4_LDAPI_URI": ldapi_uri})
1971
1972
1973 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1974                      hostip, hostip6, hostname, dnspass, realm, domainguid,
1975                      ntdsguid):
1976     """Write out a DNS zone file, from the info in the current database.
1977
1978     :param path: Path of the new zone file.
1979     :param setup_path: Setup path function.
1980     :param dnsdomain: DNS Domain name
1981     :param domaindn: DN of the Domain
1982     :param hostip: Local IPv4 IP
1983     :param hostip6: Local IPv6 IP
1984     :param hostname: Local hostname
1985     :param dnspass: Password for DNS
1986     :param realm: Realm name
1987     :param domainguid: GUID of the domain.
1988     :param ntdsguid: GUID of the hosts nTDSDSA record.
1989     """
1990     assert isinstance(domainguid, str)
1991
1992     if hostip6 is not None:
1993         hostip6_base_line = "            IN AAAA    " + hostip6
1994         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1995     else:
1996         hostip6_base_line = ""
1997         hostip6_host_line = ""
1998
1999     if hostip is not None:
2000         hostip_base_line = "            IN A    " + hostip
2001         hostip_host_line = hostname + "        IN A    " + hostip
2002     else:
2003         hostip_base_line = ""
2004         hostip_host_line = ""
2005
2006     setup_file(setup_path("provision.zone"), path, {
2007             "DNSPASS_B64": b64encode(dnspass),
2008             "HOSTNAME": hostname,
2009             "DNSDOMAIN": dnsdomain,
2010             "REALM": realm,
2011             "HOSTIP_BASE_LINE": hostip_base_line,
2012             "HOSTIP_HOST_LINE": hostip_host_line,
2013             "DOMAINGUID": domainguid,
2014             "DATESTRING": time.strftime("%Y%m%d%H"),
2015             "DEFAULTSITE": DEFAULTSITE,
2016             "NTDSGUID": ntdsguid,
2017             "HOSTIP6_BASE_LINE": hostip6_base_line,
2018             "HOSTIP6_HOST_LINE": hostip6_host_line,
2019         })
2020
2021
2022 def create_named_conf(path, setup_path, realm, dnsdomain,
2023                       private_dir):
2024     """Write out a file containing zone statements suitable for inclusion in a
2025     named.conf file (including GSS-TSIG configuration).
2026     
2027     :param path: Path of the new named.conf file.
2028     :param setup_path: Setup path function.
2029     :param realm: Realm name
2030     :param dnsdomain: DNS Domain name
2031     :param private_dir: Path to private directory
2032     :param keytab_name: File name of DNS keytab file
2033     """
2034
2035     setup_file(setup_path("named.conf"), path, {
2036             "DNSDOMAIN": dnsdomain,
2037             "REALM": realm,
2038             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
2039             "PRIVATE_DIR": private_dir
2040             })
2041
2042 def create_named_txt(path, setup_path, realm, dnsdomain,
2043                       private_dir, keytab_name):
2044     """Write out a file containing zone statements suitable for inclusion in a
2045     named.conf file (including GSS-TSIG configuration).
2046     
2047     :param path: Path of the new named.conf file.
2048     :param setup_path: Setup path function.
2049     :param realm: Realm name
2050     :param dnsdomain: DNS Domain name
2051     :param private_dir: Path to private directory
2052     :param keytab_name: File name of DNS keytab file
2053     """
2054
2055     setup_file(setup_path("named.txt"), path, {
2056             "DNSDOMAIN": dnsdomain,
2057             "REALM": realm,
2058             "DNS_KEYTAB": keytab_name,
2059             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
2060             "PRIVATE_DIR": private_dir
2061         })
2062
2063 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
2064     """Write out a file containing zone statements suitable for inclusion in a
2065     named.conf file (including GSS-TSIG configuration).
2066     
2067     :param path: Path of the new named.conf file.
2068     :param setup_path: Setup path function.
2069     :param dnsdomain: DNS Domain name
2070     :param hostname: Local hostname
2071     :param realm: Realm name
2072     """
2073
2074     setup_file(setup_path("krb5.conf"), path, {
2075             "DNSDOMAIN": dnsdomain,
2076             "HOSTNAME": hostname,
2077             "REALM": realm,
2078         })
2079
2080