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