s4:provision Set @OPTIONS in the provision_init.ldif
[abartlet/samba.git/.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     if fill == FILL_DRS:
1008         return samdb
1009
1010     samdb.transaction_start()
1011     try:
1012         message("Erasing data from partitions")
1013         # Load the schema (again).  This time it will force a reindex,
1014         # and will therefore make the erase_partitions() below
1015         # computationally sane
1016         samdb.set_schema_from_ldb(schema.ldb)
1017         samdb.erase_partitions()
1018     
1019         # Set the domain functionality levels onto the database.
1020         # Various module (the password_hash module in particular) need
1021         # to know what level of AD we are emulating.
1022
1023         # These will be fixed into the database via the database
1024         # modifictions below, but we need them set from the start.
1025         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
1026         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
1027         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
1028
1029         samdb.set_domain_sid(str(domainsid))
1030         if serverrole == "domain controller":
1031             samdb.set_invocation_id(invocationid)
1032
1033         message("Adding DomainDN: %s" % names.domaindn)
1034
1035 #impersonate domain admin
1036         admin_session_info = admin_session(lp, str(domainsid))
1037         samdb.set_session_info(admin_session_info)
1038         if domainguid is not None:
1039             domainguid_line = "objectGUID: %s\n-" % domainguid
1040         else:
1041             domainguid_line = ""
1042         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1043                 "DOMAINDN": names.domaindn,
1044                 "DOMAINGUID": domainguid_line
1045                 })
1046
1047
1048         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1049             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1050             "DOMAINSID": str(domainsid),
1051             "SCHEMADN": names.schemadn, 
1052             "NETBIOSNAME": names.netbiosname,
1053             "DEFAULTSITE": names.sitename,
1054             "CONFIGDN": names.configdn,
1055             "SERVERDN": names.serverdn,
1056             "POLICYGUID": policyguid,
1057             "DOMAINDN": names.domaindn,
1058             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1059             "SAMBA_VERSION_STRING": version
1060             })
1061
1062         message("Adding configuration container")
1063         descr = get_config_descriptor(domainsid);
1064         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1065             "CONFIGDN": names.configdn, 
1066             "DESCRIPTOR": descr,
1067             })
1068         message("Modifying configuration container")
1069         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1070             "CONFIGDN": names.configdn, 
1071             "SCHEMADN": names.schemadn,
1072             })
1073
1074         # The LDIF here was created when the Schema object was constructed
1075         message("Setting up sam.ldb schema")
1076         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1077         samdb.modify_ldif(schema.schema_dn_modify)
1078         samdb.write_prefixes_from_schema()
1079         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1080         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1081                        {"SCHEMADN": names.schemadn})
1082
1083         message("Setting up sam.ldb configuration data")
1084         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1085             "CONFIGDN": names.configdn,
1086             "NETBIOSNAME": names.netbiosname,
1087             "DEFAULTSITE": names.sitename,
1088             "DNSDOMAIN": names.dnsdomain,
1089             "DOMAIN": names.domain,
1090             "SCHEMADN": names.schemadn,
1091             "DOMAINDN": names.domaindn,
1092             "SERVERDN": names.serverdn,
1093             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1094             })
1095
1096         message("Setting up display specifiers")
1097         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1098         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1099         check_all_substituted(display_specifiers_ldif)
1100         samdb.add_ldif(display_specifiers_ldif)
1101
1102         message("Adding users container")
1103         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1104                 "DOMAINDN": names.domaindn})
1105         message("Modifying users container")
1106         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1107                 "DOMAINDN": names.domaindn})
1108         message("Adding computers container")
1109         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1110                 "DOMAINDN": names.domaindn})
1111         message("Modifying computers container")
1112         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1113                 "DOMAINDN": names.domaindn})
1114         message("Setting up sam.ldb data")
1115         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1116             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1117             "DOMAINDN": names.domaindn,
1118             "NETBIOSNAME": names.netbiosname,
1119             "DEFAULTSITE": names.sitename,
1120             "CONFIGDN": names.configdn,
1121             "SERVERDN": names.serverdn,
1122             "POLICYGUID_DC": policyguid_dc
1123             })
1124
1125         if fill == FILL_FULL:
1126             message("Setting up sam.ldb users and groups")
1127             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1128                 "DOMAINDN": names.domaindn,
1129                 "DOMAINSID": str(domainsid),
1130                 "CONFIGDN": names.configdn,
1131                 "ADMINPASS_B64": b64encode(adminpass),
1132                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1133                 })
1134
1135             if serverrole == "domain controller":
1136                 message("Setting up self join")
1137                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1138                                 dnspass=dnspass,  
1139                                 machinepass=machinepass, 
1140                                 domainsid=domainsid, policyguid=policyguid,
1141                                 policyguid_dc=policyguid_dc,
1142                                 setup_path=setup_path,
1143                                 domainControllerFunctionality=domainControllerFunctionality,
1144                                 ntdsguid=ntdsguid)
1145
1146                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1147                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1148                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1149                 assert isinstance(names.ntdsguid, str)
1150
1151     except:
1152         samdb.transaction_cancel()
1153         raise
1154
1155     samdb.transaction_commit()
1156     return samdb
1157
1158
1159 FILL_FULL = "FULL"
1160 FILL_NT4SYNC = "NT4SYNC"
1161 FILL_DRS = "DRS"
1162
1163
1164 def provision(setup_dir, message, session_info, 
1165               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1166               realm=None, 
1167               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1168               serverdn=None,
1169               domain=None, hostname=None, hostip=None, hostip6=None, 
1170               domainsid=None, adminpass=None, ldapadminpass=None, 
1171               krbtgtpass=None, domainguid=None, 
1172               policyguid=None, policyguid_dc=None, invocationid=None,
1173               machinepass=None, ntdsguid=None,
1174               dnspass=None, root=None, nobody=None, users=None, 
1175               wheel=None, backup=None, aci=None, serverrole=None,
1176               dom_for_fun_level=None,
1177               ldap_backend_extra_port=None, ldap_backend_type=None,
1178               sitename=None,
1179               ol_mmr_urls=None, ol_olc=None, 
1180               setup_ds_path=None, slapd_path=None, nosync=False,
1181               ldap_dryrun_mode=False):
1182     """Provision samba4
1183     
1184     :note: caution, this wipes all existing data!
1185     """
1186
1187     def setup_path(file):
1188       return os.path.join(setup_dir, file)
1189
1190     if domainsid is None:
1191       domainsid = security.random_sid()
1192     else:
1193       domainsid = security.dom_sid(domainsid)
1194
1195     # create/adapt the group policy GUIDs
1196     if policyguid is None:
1197         policyguid = str(uuid.uuid4())
1198     policyguid = policyguid.upper()
1199     if policyguid_dc is None:
1200         policyguid_dc = str(uuid.uuid4())
1201     policyguid_dc = policyguid_dc.upper()
1202
1203     if adminpass is None:
1204         adminpass = glue.generate_random_str(12)
1205     if krbtgtpass is None:
1206         krbtgtpass = glue.generate_random_str(12)
1207     if machinepass is None:
1208         machinepass  = glue.generate_random_str(12)
1209     if dnspass is None:
1210         dnspass = glue.generate_random_str(12)
1211     if ldapadminpass is None:
1212         #Make a new, random password between Samba and it's LDAP server
1213         ldapadminpass=glue.generate_random_str(12)        
1214
1215
1216     root_uid = findnss_uid([root or "root"])
1217     nobody_uid = findnss_uid([nobody or "nobody"])
1218     users_gid = findnss_gid([users or "users"])
1219     if wheel is None:
1220         wheel_gid = findnss_gid(["wheel", "adm"])
1221     else:
1222         wheel_gid = findnss_gid([wheel])
1223
1224     if targetdir is not None:
1225         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1226             os.makedirs(os.path.join(targetdir, "etc"))
1227         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1228     elif smbconf is None:
1229         smbconf = param.default_path()
1230
1231     # only install a new smb.conf if there isn't one there already
1232     if not os.path.exists(smbconf):
1233         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1234                      targetdir)
1235
1236     lp = param.LoadParm()
1237     lp.load(smbconf)
1238
1239     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1240                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1241                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1242                         serverdn=serverdn)
1243
1244     paths = provision_paths_from_lp(lp, names.dnsdomain)
1245
1246     if hostip is None:
1247         try:
1248             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1249         except socket.gaierror, (socket.EAI_NODATA, msg):
1250             hostip = None
1251
1252     if hostip6 is None:
1253         try:
1254             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1255         except socket.gaierror, (socket.EAI_NODATA, msg): 
1256             hostip6 = None
1257
1258     if serverrole is None:
1259         serverrole = lp.get("server role")
1260
1261     assert serverrole in ("domain controller", "member server", "standalone")
1262     if invocationid is None and serverrole == "domain controller":
1263         invocationid = str(uuid.uuid4())
1264
1265     if not os.path.exists(paths.private_dir):
1266         os.mkdir(paths.private_dir)
1267
1268     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1269     
1270     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1271         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1272     
1273     secrets_credentials = credentials
1274     provision_backend = None
1275     if ldap_backend_type:
1276         # We only support an LDAP backend over ldapi://
1277
1278         provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1279                                              lp=lp, credentials=credentials, 
1280                                              names=names,
1281                                              message=message, hostname=hostname,
1282                                              root=root, schema=schema,
1283                                              ldap_backend_type=ldap_backend_type,
1284                                              ldapadminpass=ldapadminpass,
1285                                              ldap_backend_extra_port=ldap_backend_extra_port,
1286                                              ol_mmr_urls=ol_mmr_urls, 
1287                                              slapd_path=slapd_path,
1288                                              setup_ds_path=setup_ds_path,
1289                                              ldap_dryrun_mode=ldap_dryrun_mode)
1290
1291         # Now use the backend credentials to access the databases
1292         credentials = provision_backend.credentials
1293         secrets_credentials = provision_backend.adminCredentials
1294         ldapi_url = provision_backend.ldapi_uri
1295
1296     # only install a new shares config db if there is none
1297     if not os.path.exists(paths.shareconf):
1298         message("Setting up share.ldb")
1299         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1300                         credentials=credentials, lp=lp)
1301         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1302
1303      
1304     message("Setting up secrets.ldb")
1305     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1306                                   session_info=session_info, 
1307                                   credentials=secrets_credentials, lp=lp)
1308
1309     message("Setting up the registry")
1310     setup_registry(paths.hklm, setup_path, session_info, 
1311                    lp=lp)
1312
1313     message("Setting up the privileges database")
1314     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1315
1316     message("Setting up idmap db")
1317     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1318                           lp=lp)
1319
1320     message("Setting up SAM db")
1321     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1322                         credentials=credentials, lp=lp, names=names,
1323                         message=message, 
1324                         domainsid=domainsid, 
1325                         schema=schema, domainguid=domainguid,
1326                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1327                         fill=samdb_fill, 
1328                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1329                         invocationid=invocationid, 
1330                         machinepass=machinepass, dnspass=dnspass, 
1331                         ntdsguid=ntdsguid, serverrole=serverrole,
1332                         dom_for_fun_level=dom_for_fun_level,
1333                         ldap_backend=provision_backend)
1334
1335     if serverrole == "domain controller":
1336         if paths.netlogon is None:
1337             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1338             message("Please either remove %s or see the template at %s" % 
1339                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1340             assert(paths.netlogon is not None)
1341
1342         if paths.sysvol is None:
1343             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1344             message("Please either remove %s or see the template at %s" % 
1345                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1346             assert(paths.sysvol is not None)            
1347             
1348         # Set up group policies (domain policy and domain controller policy)
1349
1350         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1351                                    "{" + policyguid + "}")
1352         os.makedirs(policy_path, 0755)
1353         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1354                                    "[General]\r\nVersion=65543")
1355         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1356         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1357
1358         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1359                                    "{" + policyguid_dc + "}")
1360         os.makedirs(policy_path_dc, 0755)
1361         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1362                                    "[General]\r\nVersion=2")
1363         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1364         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1365
1366         if not os.path.isdir(paths.netlogon):
1367             os.makedirs(paths.netlogon, 0755)
1368
1369     if samdb_fill == FILL_FULL:
1370         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1371                             root_uid=root_uid, nobody_uid=nobody_uid,
1372                             users_gid=users_gid, wheel_gid=wheel_gid)
1373
1374         message("Setting up sam.ldb rootDSE marking as synchronized")
1375         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1376
1377         # Only make a zone file on the first DC, it should be replicated with DNS replication
1378         if serverrole == "domain controller":
1379             secretsdb_self_join(secrets_ldb, domain=domain,
1380                                 realm=names.realm,
1381                                 dnsdomain=names.dnsdomain,
1382                                 netbiosname=names.netbiosname,
1383                                 domainsid=domainsid, 
1384                                 machinepass=machinepass,
1385                                 secure_channel_type=SEC_CHAN_BDC)
1386
1387             secretsdb_setup_dns(secrets_ldb, setup_path, 
1388                                 realm=names.realm, dnsdomain=names.dnsdomain,
1389                                 dns_keytab_path=paths.dns_keytab,
1390                                 dnspass=dnspass)
1391
1392             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1393             assert isinstance(domainguid, str)
1394
1395             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1396                              hostip=hostip,
1397                              hostip6=hostip6, hostname=names.hostname,
1398                              realm=names.realm,
1399                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1400
1401             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1402                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1403
1404             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1405                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1406                               keytab_name=paths.dns_keytab)
1407             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1408             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1409
1410             create_krb5_conf(paths.krb5conf, setup_path,
1411                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1412                              realm=names.realm)
1413             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1414
1415     #Now commit the secrets.ldb to disk
1416     secrets_ldb.transaction_commit()
1417
1418     if provision_backend is not None: 
1419       if ldap_backend_type == "fedora-ds":
1420         ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1421
1422         # delete default SASL mappings
1423         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1424
1425         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1426         for i in range (0, len(res)):
1427           dn = str(res[i]["dn"])
1428           ldapi_db.delete(dn)
1429
1430           aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1431
1432           m = ldb.Message()
1433           m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1434         
1435           m.dn = ldb.Dn(1, names.domaindn)
1436           ldapi_db.modify(m)
1437
1438           m.dn = ldb.Dn(1, names.configdn)
1439           ldapi_db.modify(m)
1440
1441           m.dn = ldb.Dn(1, names.schemadn)
1442           ldapi_db.modify(m)
1443
1444       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1445       if provision_backend.slapd.poll() is None:
1446         #Kill the slapd
1447         if hasattr(provision_backend.slapd, "terminate"):
1448           provision_backend.slapd.terminate()
1449         else:
1450           # Older python versions don't have .terminate()
1451           import signal
1452           os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1453             
1454         #and now wait for it to die
1455         provision_backend.slapd.communicate()
1456             
1457     # now display slapd_command_file.txt to show how slapd must be started next time
1458         message("Use later the following commandline to start slapd, then Samba:")
1459         slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1460         message(slapd_command)
1461         message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1462
1463         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1464                 "SLAPD_COMMAND" : slapd_command})
1465
1466     
1467     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1468                                ldapi_url)
1469
1470     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1471
1472     message("Once the above files are installed, your Samba4 server will be ready to use")
1473     message("Server Role:           %s" % serverrole)
1474     message("Hostname:              %s" % names.hostname)
1475     message("NetBIOS Domain:        %s" % names.domain)
1476     message("DNS Domain:            %s" % names.dnsdomain)
1477     message("DOMAIN SID:            %s" % str(domainsid))
1478     if samdb_fill == FILL_FULL:
1479         message("Admin password:    %s" % adminpass)
1480     if provision_backend:
1481         if provision_backend.credentials.get_bind_dn() is not None:
1482             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1483         else:
1484             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1485
1486         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1487   
1488     result = ProvisionResult()
1489     result.domaindn = domaindn
1490     result.paths = paths
1491     result.lp = lp
1492     result.samdb = samdb
1493     return result
1494
1495
1496
1497 def provision_become_dc(setup_dir=None,
1498                         smbconf=None, targetdir=None, realm=None, 
1499                         rootdn=None, domaindn=None, schemadn=None,
1500                         configdn=None, serverdn=None,
1501                         domain=None, hostname=None, domainsid=None, 
1502                         adminpass=None, krbtgtpass=None, domainguid=None, 
1503                         policyguid=None, policyguid_dc=None, invocationid=None,
1504                         machinepass=None, 
1505                         dnspass=None, root=None, nobody=None, users=None, 
1506                         wheel=None, backup=None, serverrole=None, 
1507                         ldap_backend=None, ldap_backend_type=None,
1508                         sitename=None, debuglevel=1):
1509
1510     def message(text):
1511         """print a message if quiet is not set."""
1512         print text
1513
1514     glue.set_debug_level(debuglevel)
1515
1516     return provision(setup_dir, message, system_session(), None,
1517               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1518               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1519               configdn=configdn, serverdn=serverdn, domain=domain,
1520               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1521               machinepass=machinepass, serverrole="domain controller",
1522               sitename=sitename)
1523
1524
1525 def setup_db_config(setup_path, dbdir):
1526     """Setup a Berkeley database.
1527     
1528     :param setup_path: Setup path function.
1529     :param dbdir: Database directory."""
1530     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1531         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1532         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1533             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1534
1535     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1536                {"LDAPDBDIR": dbdir})
1537     
1538 class ProvisionBackend(object):
1539     def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, 
1540                  names=None, message=None, 
1541                  hostname=None, root=None, 
1542                  schema=None, ldapadminpass=None,
1543                  ldap_backend_type=None, ldap_backend_extra_port=None,
1544                  ol_mmr_urls=None, 
1545                  setup_ds_path=None, slapd_path=None, 
1546                  nosync=False, ldap_dryrun_mode=False):
1547         """Provision an LDAP backend for samba4
1548         
1549         This works for OpenLDAP and Fedora DS
1550         """
1551
1552         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1553         
1554         if not os.path.isdir(paths.ldapdir):
1555             os.makedirs(paths.ldapdir, 0700)
1556             
1557         if ldap_backend_type == "existing":
1558             #Check to see that this 'existing' LDAP backend in fact exists
1559             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1560             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1561                                                 expression="(objectClass=OpenLDAProotDSE)")
1562
1563             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1564             # This caused them to be set into the long-term database later in the script.
1565             self.credentials = credentials
1566             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1567             return
1568     
1569         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1570         # if another instance of slapd is already running 
1571         try:
1572             ldapi_db = Ldb(self.ldapi_uri)
1573             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1574                                                 expression="(objectClass=OpenLDAProotDSE)");
1575             try:
1576                 f = open(paths.slapdpid, "r")
1577                 p = f.read()
1578                 f.close()
1579                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1580             except:
1581                 pass
1582             
1583             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1584         
1585         except LdbError, e:
1586             pass
1587
1588         # Try to print helpful messages when the user has not specified the path to slapd
1589         if slapd_path is None:
1590             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1591         if not os.path.exists(slapd_path):
1592             message (slapd_path)
1593             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1594
1595         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1596         try:
1597             os.unlink(schemadb_path)
1598         except OSError:
1599             pass
1600
1601
1602         # Put the LDIF of the schema into a database so we can search on
1603         # it to generate schema-dependent configurations in Fedora DS and
1604         # OpenLDAP
1605         os.path.join(paths.ldapdir, "schema-tmp.ldb")
1606         schema.ldb.connect(schemadb_path)
1607         schema.ldb.transaction_start()
1608     
1609         # These bits of LDIF are supplied when the Schema object is created
1610         schema.ldb.add_ldif(schema.schema_dn_add)
1611         schema.ldb.modify_ldif(schema.schema_dn_modify)
1612         schema.ldb.add_ldif(schema.schema_data)
1613         schema.ldb.transaction_commit()
1614
1615         self.credentials = Credentials()
1616         self.credentials.guess(lp)
1617         #Kerberos to an ldapi:// backend makes no sense
1618         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1619
1620         self.adminCredentials = Credentials()
1621         self.adminCredentials.guess(lp)
1622         #Kerberos to an ldapi:// backend makes no sense
1623         self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1624
1625         self.ldap_backend_type = ldap_backend_type
1626
1627         if ldap_backend_type == "fedora-ds":
1628             provision_fds_backend(self, paths=paths, setup_path=setup_path,
1629                                   names=names, message=message, 
1630                                   hostname=hostname,
1631                                   ldapadminpass=ldapadminpass, root=root, 
1632                                   schema=schema,
1633                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1634                                   setup_ds_path=setup_ds_path,
1635                                   slapd_path=slapd_path,
1636                                   nosync=nosync,
1637                                   ldap_dryrun_mode=ldap_dryrun_mode)
1638             
1639         elif ldap_backend_type == "openldap":
1640             provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1641                                        names=names, message=message, 
1642                                        hostname=hostname,
1643                                        ldapadminpass=ldapadminpass, root=root, 
1644                                        schema=schema,
1645                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1646                                        ol_mmr_urls=ol_mmr_urls, 
1647                                        slapd_path=slapd_path,
1648                                        nosync=nosync,
1649                                        ldap_dryrun_mode=ldap_dryrun_mode)
1650         else:
1651             raise ProvisioningError("Unknown LDAP backend type selected")
1652
1653         self.credentials.set_password(ldapadminpass)
1654         self.adminCredentials.set_username("samba-admin")
1655         self.adminCredentials.set_password(ldapadminpass)
1656
1657         # Now start the slapd, so we can provision onto it.  We keep the
1658         # subprocess context around, to kill this off at the successful
1659         # end of the script
1660         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1661     
1662         while self.slapd.poll() is None:
1663             # Wait until the socket appears
1664             try:
1665                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1666                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1667                                                     expression="(objectClass=OpenLDAProotDSE)")
1668                 # If we have got here, then we must have a valid connection to the LDAP server!
1669                 return
1670             except LdbError, e:
1671                 time.sleep(1)
1672                 pass
1673         
1674         raise ProvisioningError("slapd died before we could make a connection to it")
1675
1676
1677 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1678                                message=None, 
1679                                hostname=None, ldapadminpass=None, root=None, 
1680                                schema=None, 
1681                                ldap_backend_extra_port=None,
1682                                ol_mmr_urls=None, 
1683                                slapd_path=None, nosync=False,
1684                                ldap_dryrun_mode=False):
1685
1686     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1687     nosync_config = ""
1688     if nosync:
1689         nosync_config = "dbnosync"
1690         
1691     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1692     refint_attributes = ""
1693     memberof_config = "# Generated from Samba4 schema\n"
1694     for att in  lnkattr.keys():
1695         if lnkattr[att] is not None:
1696             refint_attributes = refint_attributes + " " + att 
1697             
1698             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1699                                                  { "MEMBER_ATTR" : att ,
1700                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1701             
1702     refint_config = read_and_sub_file(setup_path("refint.conf"),
1703                                       { "LINK_ATTRS" : refint_attributes})
1704     
1705     attrs = ["linkID", "lDAPDisplayName"]
1706     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1707     index_config = ""
1708     for i in range (0, len(res)):
1709         index_attr = res[i]["lDAPDisplayName"][0]
1710         if index_attr == "objectGUID":
1711             index_attr = "entryUUID"
1712             
1713         index_config += "index " + index_attr + " eq\n"
1714
1715 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1716     mmr_on_config = ""
1717     mmr_replicator_acl = ""
1718     mmr_serverids_config = ""
1719     mmr_syncrepl_schema_config = "" 
1720     mmr_syncrepl_config_config = "" 
1721     mmr_syncrepl_user_config = "" 
1722        
1723     
1724     if ol_mmr_urls is not None:
1725         # For now, make these equal
1726         mmr_pass = ldapadminpass
1727         
1728         url_list=filter(None,ol_mmr_urls.split(' ')) 
1729         if (len(url_list) == 1):
1730             url_list=filter(None,ol_mmr_urls.split(',')) 
1731                      
1732             
1733             mmr_on_config = "MirrorMode On"
1734             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1735             serverid=0
1736             for url in url_list:
1737                 serverid=serverid+1
1738                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1739                                                           { "SERVERID" : str(serverid),
1740                                                             "LDAPSERVER" : url })
1741                 rid=serverid*10
1742                 rid=rid+1
1743                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1744                                                                 {  "RID" : str(rid),
1745                                                                    "MMRDN": names.schemadn,
1746                                                                    "LDAPSERVER" : url,
1747                                                                    "MMR_PASSWORD": mmr_pass})
1748                 
1749                 rid=rid+1
1750                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1751                                                                 {  "RID" : str(rid),
1752                                                                    "MMRDN": names.configdn,
1753                                                                    "LDAPSERVER" : url,
1754                                                                    "MMR_PASSWORD": mmr_pass})
1755                 
1756                 rid=rid+1
1757                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1758                                                               {  "RID" : str(rid),
1759                                                                  "MMRDN": names.domaindn,
1760                                                                  "LDAPSERVER" : url,
1761                                                                  "MMR_PASSWORD": mmr_pass })
1762     # OpenLDAP cn=config initialisation
1763     olc_syncrepl_config = ""
1764     olc_mmr_config = "" 
1765     # if mmr = yes, generate cn=config-replication directives
1766     # and olc_seed.lif for the other mmr-servers
1767     if ol_mmr_urls is not None:
1768         serverid=0
1769         olc_serverids_config = ""
1770         olc_syncrepl_seed_config = ""
1771         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1772         rid=1000
1773         for url in url_list:
1774             serverid=serverid+1
1775             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1776                                                       { "SERVERID" : str(serverid),
1777                                                         "LDAPSERVER" : url })
1778             
1779             rid=rid+1
1780             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1781                                                      {  "RID" : str(rid),
1782                                                         "LDAPSERVER" : url,
1783                                                         "MMR_PASSWORD": mmr_pass})
1784             
1785             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1786                                                           {  "RID" : str(rid),
1787                                                              "LDAPSERVER" : url})
1788                 
1789         setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1790                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1791                     "OLC_PW": ldapadminpass,
1792                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1793     # end olc
1794                 
1795     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1796                {"DNSDOMAIN": names.dnsdomain,
1797                 "LDAPDIR": paths.ldapdir,
1798                 "DOMAINDN": names.domaindn,
1799                 "CONFIGDN": names.configdn,
1800                 "SCHEMADN": names.schemadn,
1801                 "MEMBEROF_CONFIG": memberof_config,
1802                 "MIRRORMODE": mmr_on_config,
1803                 "REPLICATOR_ACL": mmr_replicator_acl,
1804                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1805                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1806                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1807                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1808                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1809                 "OLC_MMR_CONFIG": olc_mmr_config,
1810                 "REFINT_CONFIG": refint_config,
1811                 "INDEX_CONFIG": index_config,
1812                 "NOSYNC": nosync_config})
1813         
1814     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1815     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1816     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1817     
1818     if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1819         os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1820         
1821     setup_file(setup_path("cn=samba.ldif"), 
1822                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1823                { "UUID": str(uuid.uuid4()), 
1824                  "LDAPTIME": timestring(int(time.time()))} )
1825     setup_file(setup_path("cn=samba-admin.ldif"), 
1826                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1827                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1828                 "UUID": str(uuid.uuid4()), 
1829                 "LDAPTIME": timestring(int(time.time()))} )
1830     
1831     if ol_mmr_urls is not None:
1832         setup_file(setup_path("cn=replicator.ldif"),
1833                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1834                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1835                     "UUID": str(uuid.uuid4()),
1836                     "LDAPTIME": timestring(int(time.time()))} )
1837         
1838
1839     mapping = "schema-map-openldap-2.3"
1840     backend_schema = "backend-schema.schema"
1841
1842     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1843     assert backend_schema_data is not None
1844     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1845
1846     # now we generate the needed strings to start slapd automatically,
1847     # first ldapi_uri...
1848     if ldap_backend_extra_port is not None:
1849         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1850         # specified there as part of it's clue as to it's own name,
1851         # and not to replicate to itself
1852         if ol_mmr_urls is None:
1853             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1854         else:
1855             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1856     else:
1857         server_port_string = ""
1858
1859     # Prepare the 'result' information - the commands to return in particular
1860     result.slapd_provision_command = [slapd_path]
1861
1862     result.slapd_provision_command.append("-F" + paths.olcdir)
1863
1864     result.slapd_provision_command.append("-h")
1865
1866     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1867     result.slapd_command = list(result.slapd_provision_command)
1868     
1869     result.slapd_provision_command.append(result.ldapi_uri)
1870     result.slapd_provision_command.append("-d0")
1871
1872     uris = result.ldapi_uri
1873     if server_port_string is not "":
1874         uris = uris + " " + server_port_string
1875
1876     result.slapd_command.append(uris)
1877
1878     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1879     result.credentials.set_username("samba-admin")
1880     
1881     # If we were just looking for crashes up to this point, it's a
1882     # good time to exit before we realise we don't have OpenLDAP on
1883     # this system
1884     if ldap_dryrun_mode:
1885         sys.exit(0)
1886
1887     # Finally, convert the configuration into cn=config style!
1888     if not os.path.isdir(paths.olcdir):
1889         os.makedirs(paths.olcdir, 0770)
1890
1891         retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1892
1893 #        We can't do this, as OpenLDAP is strange.  It gives an error
1894 #        output to the above, but does the conversion sucessfully...
1895 #
1896 #        if retcode != 0:
1897 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1898
1899         if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1900             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1901
1902         # Don't confuse the admin by leaving the slapd.conf around
1903         os.remove(paths.slapdconf)        
1904           
1905
1906 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1907                           message=None, 
1908                           hostname=None, ldapadminpass=None, root=None, 
1909                           schema=None,
1910                           ldap_backend_extra_port=None,
1911                           setup_ds_path=None,
1912                           slapd_path=None,
1913                           nosync=False, 
1914                           ldap_dryrun_mode=False):
1915
1916     if ldap_backend_extra_port is not None:
1917         serverport = "ServerPort=%d" % ldap_backend_extra_port
1918     else:
1919         serverport = ""
1920         
1921     setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1922                {"ROOT": root,
1923                 "HOSTNAME": hostname,
1924                 "DNSDOMAIN": names.dnsdomain,
1925                 "LDAPDIR": paths.ldapdir,
1926                 "DOMAINDN": names.domaindn,
1927                 "LDAPMANAGERDN": names.ldapmanagerdn,
1928                 "LDAPMANAGERPASS": ldapadminpass, 
1929                 "SERVERPORT": serverport})
1930
1931     setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1932                {"CONFIGDN": names.configdn,
1933                 "SCHEMADN": names.schemadn,
1934                 "SAMBADN": names.sambadn,
1935                 })
1936
1937     setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, 
1938                {"SAMBADN": names.sambadn,
1939                 })
1940
1941     setup_file(setup_path("fedorads-pam.ldif"), paths.fedoradspam)
1942
1943     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1944
1945     refint_config = data = open(setup_path("fedorads-refint-delete.ldif"), 'r').read()
1946     memberof_config = ""
1947     index_config = ""
1948     argnum = 3
1949
1950     for attr in lnkattr.keys():
1951         if lnkattr[attr] is not None:
1952             refint_config += read_and_sub_file(setup_path("fedorads-refint-add.ldif"),
1953                                                  { "ARG_NUMBER" : str(argnum) ,
1954                                                    "LINK_ATTR" : attr })
1955             memberof_config += read_and_sub_file(setup_path("fedorads-linked-attributes.ldif"),
1956                                                  { "MEMBER_ATTR" : attr ,
1957                                                    "MEMBEROF_ATTR" : lnkattr[attr] })
1958             index_config += read_and_sub_file(setup_path("fedorads-index.ldif"),
1959                                                  { "ATTR" : attr })
1960             argnum += 1
1961
1962     open(paths.fedoradsrefint, 'w').write(refint_config)
1963     open(paths.fedoradslinkedattributes, 'w').write(memberof_config)
1964
1965     attrs = ["lDAPDisplayName"]
1966     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1967
1968     for i in range (0, len(res)):
1969         attr = res[i]["lDAPDisplayName"][0]
1970
1971         if attr == "objectGUID":
1972             attr = "nsUniqueId"
1973
1974         index_config += read_and_sub_file(setup_path("fedorads-index.ldif"),
1975                                              { "ATTR" : attr })
1976
1977     open(paths.fedoradsindex, 'w').write(index_config)
1978
1979     setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1980                 {"SAMBADN": names.sambadn, 
1981                  "LDAPADMINPASS": ldapadminpass
1982                 })
1983
1984     mapping = "schema-map-fedora-ds-1.0"
1985     backend_schema = "99_ad.ldif"
1986     
1987     # Build a schema file in Fedora DS format
1988     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1989     assert backend_schema_data is not None
1990     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1991
1992     result.credentials.set_bind_dn(names.ldapmanagerdn)
1993
1994     # Destory the target directory, or else setup-ds.pl will complain
1995     fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1996     shutil.rmtree(fedora_ds_dir, True)
1997
1998     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1999     #In the 'provision' command line, stay in the foreground so we can easily kill it
2000     result.slapd_provision_command.append("-d0")
2001
2002     #the command for the final run is the normal script
2003     result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
2004
2005     # If we were just looking for crashes up to this point, it's a
2006     # good time to exit before we realise we don't have Fedora DS on
2007     if ldap_dryrun_mode:
2008         sys.exit(0)
2009
2010     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
2011     if setup_ds_path is None:
2012         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\"!")
2013     if not os.path.exists(setup_ds_path):
2014         message (setup_ds_path)
2015         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
2016
2017     # Run the Fedora DS setup utility
2018     retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
2019     if retcode != 0:
2020         raise ProvisioningError("setup-ds failed")
2021
2022     # Load samba-admin
2023     retcode = subprocess.call([
2024         os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
2025         close_fds=True, shell=False)
2026     if retcode != 0:
2027         raise("ldib2db failed")
2028
2029 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
2030     """Create a PHP LDAP admin configuration file.
2031
2032     :param path: Path to write the configuration to.
2033     :param setup_path: Function to generate setup paths.
2034     """
2035     setup_file(setup_path("phpldapadmin-config.php"), path, 
2036             {"S4_LDAPI_URI": ldapi_uri})
2037
2038
2039 def create_zone_file(path, setup_path, dnsdomain, 
2040                      hostip, hostip6, hostname, realm, domainguid,
2041                      ntdsguid):
2042     """Write out a DNS zone file, from the info in the current database.
2043
2044     :param path: Path of the new zone file.
2045     :param setup_path: Setup path function.
2046     :param dnsdomain: DNS Domain name
2047     :param domaindn: DN of the Domain
2048     :param hostip: Local IPv4 IP
2049     :param hostip6: Local IPv6 IP
2050     :param hostname: Local hostname
2051     :param realm: Realm name
2052     :param domainguid: GUID of the domain.
2053     :param ntdsguid: GUID of the hosts nTDSDSA record.
2054     """
2055     assert isinstance(domainguid, str)
2056
2057     if hostip6 is not None:
2058         hostip6_base_line = "            IN AAAA    " + hostip6
2059         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
2060     else:
2061         hostip6_base_line = ""
2062         hostip6_host_line = ""
2063
2064     if hostip is not None:
2065         hostip_base_line = "            IN A    " + hostip
2066         hostip_host_line = hostname + "        IN A    " + hostip
2067     else:
2068         hostip_base_line = ""
2069         hostip_host_line = ""
2070
2071     setup_file(setup_path("provision.zone"), path, {
2072             "HOSTNAME": hostname,
2073             "DNSDOMAIN": dnsdomain,
2074             "REALM": realm,
2075             "HOSTIP_BASE_LINE": hostip_base_line,
2076             "HOSTIP_HOST_LINE": hostip_host_line,
2077             "DOMAINGUID": domainguid,
2078             "DATESTRING": time.strftime("%Y%m%d%H"),
2079             "DEFAULTSITE": DEFAULTSITE,
2080             "NTDSGUID": ntdsguid,
2081             "HOSTIP6_BASE_LINE": hostip6_base_line,
2082             "HOSTIP6_HOST_LINE": hostip6_host_line,
2083         })
2084
2085
2086 def create_named_conf(path, setup_path, realm, dnsdomain,
2087                       private_dir):
2088     """Write out a file containing zone statements suitable for inclusion in a
2089     named.conf file (including GSS-TSIG configuration).
2090     
2091     :param path: Path of the new named.conf file.
2092     :param setup_path: Setup path function.
2093     :param realm: Realm name
2094     :param dnsdomain: DNS Domain name
2095     :param private_dir: Path to private directory
2096     :param keytab_name: File name of DNS keytab file
2097     """
2098
2099     setup_file(setup_path("named.conf"), path, {
2100             "DNSDOMAIN": dnsdomain,
2101             "REALM": realm,
2102             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
2103             "PRIVATE_DIR": private_dir
2104             })
2105
2106 def create_named_txt(path, setup_path, realm, dnsdomain,
2107                       private_dir, keytab_name):
2108     """Write out a file containing zone statements suitable for inclusion in a
2109     named.conf file (including GSS-TSIG configuration).
2110     
2111     :param path: Path of the new named.conf file.
2112     :param setup_path: Setup path function.
2113     :param realm: Realm name
2114     :param dnsdomain: DNS Domain name
2115     :param private_dir: Path to private directory
2116     :param keytab_name: File name of DNS keytab file
2117     """
2118
2119     setup_file(setup_path("named.txt"), path, {
2120             "DNSDOMAIN": dnsdomain,
2121             "REALM": realm,
2122             "DNS_KEYTAB": keytab_name,
2123             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
2124             "PRIVATE_DIR": private_dir
2125         })
2126
2127 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
2128     """Write out a file containing zone statements suitable for inclusion in a
2129     named.conf file (including GSS-TSIG configuration).
2130     
2131     :param path: Path of the new named.conf file.
2132     :param setup_path: Setup path function.
2133     :param dnsdomain: DNS Domain name
2134     :param hostname: Local hostname
2135     :param realm: Realm name
2136     """
2137
2138     setup_file(setup_path("krb5.conf"), path, {
2139             "DNSDOMAIN": dnsdomain,
2140             "HOSTNAME": hostname,
2141             "REALM": realm,
2142         })
2143
2144