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