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