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