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