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