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