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