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