provision: Generate krb5.conf template separate from named.conf template.
[kai/samba.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
7 #
8 # Based on the original in EJS:
9 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #   
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #   
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #
24
25 from base64 import b64encode
26 import os
27 import pwd
28 import grp
29 import time
30 import uuid, misc
31 import socket
32 import param
33 import registry
34 import samba
35 from auth import system_session
36 from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted
37 from samba.samdb import SamDB
38 from samba.idmap import IDmapDB
39 import security
40 import urllib
41 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
42         LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
43
44 """Functions for setting up a Samba configuration."""
45
46 DEFAULTSITE = "Default-First-Site-Name"
47
48 class InvalidNetbiosName(Exception):
49     def __init__(self, name):
50         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
51
52
53 class ProvisionPaths:
54     def __init__(self):
55         self.shareconf = None
56         self.hklm = None
57         self.hkcu = None
58         self.hkcr = None
59         self.hku = None
60         self.hkpd = None
61         self.hkpt = None
62         self.samdb = None
63         self.idmapdb = None
64         self.secrets = None
65         self.keytab = None
66         self.dns_keytab = None
67         self.dns = None
68         self.winsdb = None
69         self.private_dir = None
70         self.ldapdir = None
71         self.slapdconf = None
72         self.modulesconf = None
73         self.memberofconf = None
74         self.fedoradsinf = None
75         self.fedoradspartitions = None
76  
77 class ProvisionNames:
78     def __init__(self):
79         self.rootdn = None
80         self.domaindn = None
81         self.configdn = None
82         self.schemadn = None
83         self.ldapmanagerdn = None
84         self.dnsdomain = None
85         self.realm = None
86         self.netbiosname = None
87         self.domain = None
88         self.hostname = None
89         self.sitename = None
90         self.smbconf = None
91     
92 class ProvisionResult:
93     def __init__(self):
94         self.paths = None
95         self.domaindn = None
96         self.lp = None
97         self.samdb = None
98
99 def check_install(lp, session_info, credentials):
100     """Check whether the current install seems ok.
101     
102     :param lp: Loadparm context
103     :param session_info: Session information
104     :param credentials: Credentials
105     """
106     if lp.get("realm") == "":
107         raise Error("Realm empty")
108     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
109             credentials=credentials, lp=lp)
110     if len(ldb.search("(cn=Administrator)")) != 1:
111         raise "No administrator account found"
112
113
114 def findnss(nssfn, names):
115     """Find a user or group from a list of possibilities.
116     
117     :param nssfn: NSS Function to try (should raise KeyError if not found)
118     :param names: Names to check.
119     :return: Value return by first names list.
120     """
121     for name in names:
122         try:
123             return nssfn(name)
124         except KeyError:
125             pass
126     raise KeyError("Unable to find user/group %r" % names)
127
128
129 def open_ldb(session_info, credentials, lp, dbname):
130     """Open a LDB, thrashing it if it is corrupt.
131
132     :param session_info: auth session information
133     :param credentials: credentials
134     :param lp: Loadparm context
135     :param dbname: Path of the database to open.
136     :return: a Ldb object
137     """
138     assert session_info is not None
139     try:
140         return Ldb(dbname, session_info=session_info, credentials=credentials, 
141                    lp=lp)
142     except LdbError, e:
143         print e
144         os.unlink(dbname)
145         return Ldb(dbname, session_info=session_info, credentials=credentials,
146                    lp=lp)
147
148
149 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
150     """Setup a ldb in the private dir.
151     
152     :param ldb: LDB file to import data into
153     :param ldif_path: Path of the LDIF file to load
154     :param subst_vars: Optional variables to subsitute in LDIF.
155     """
156     assert isinstance(ldif_path, str)
157
158     data = open(ldif_path, 'r').read()
159     if subst_vars is not None:
160         data = substitute_var(data, subst_vars)
161
162     check_all_substituted(data)
163
164     ldb.add_ldif(data)
165
166
167 def setup_modify_ldif(ldb, ldif_path, substvars=None):
168     """Modify a ldb in the private dir.
169     
170     :param ldb: LDB object.
171     :param ldif_path: LDIF file path.
172     :param substvars: Optional dictionary with substitution variables.
173     """
174     data = open(ldif_path, 'r').read()
175     if substvars is not None:
176         data = substitute_var(data, substvars)
177
178     check_all_substituted(data)
179
180     ldb.modify_ldif(data)
181
182
183 def setup_ldb(ldb, ldif_path, subst_vars):
184     """Import a LDIF a file into a LDB handle, optionally substituting variables.
185
186     :note: Either all LDIF data will be added or none (using transactions).
187
188     :param ldb: LDB file to import into.
189     :param ldif_path: Path to the LDIF file.
190     :param subst_vars: Dictionary with substitution variables.
191     """
192     assert ldb is not None
193     ldb.transaction_start()
194     try:
195         setup_add_ldif(ldb, ldif_path, subst_vars)
196     except:
197         ldb.transaction_cancel()
198         raise
199     ldb.transaction_commit()
200
201
202 def setup_file(template, fname, substvars):
203     """Setup a file in the private dir.
204
205     :param template: Path of the template file.
206     :param fname: Path of the file to create.
207     :param substvars: Substitution variables.
208     """
209     f = fname
210
211     if os.path.exists(f):
212         os.unlink(f)
213
214     data = open(template, 'r').read()
215     if substvars:
216         data = substitute_var(data, substvars)
217     check_all_substituted(data)
218
219     open(f, 'w').write(data)
220
221
222 def provision_paths_from_lp(lp, dnsdomain):
223     """Set the default paths for provisioning.
224
225     :param lp: Loadparm context.
226     :param dnsdomain: DNS Domain name
227     """
228     paths = ProvisionPaths()
229     paths.private_dir = lp.get("private dir")
230     paths.keytab = "secrets.keytab"
231     paths.dns_keytab = "dns.keytab"
232
233     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
234     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
235     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
236     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
237     paths.templates = os.path.join(paths.private_dir, "templates.ldb")
238     paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
239     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
240     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
241     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
242     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
243     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
244                                             "phpldapadmin-config.php")
245     paths.ldapdir = os.path.join(paths.private_dir, 
246                                  "ldap")
247     paths.slapdconf = os.path.join(paths.ldapdir, 
248                                    "slapd.conf")
249     paths.modulesconf = os.path.join(paths.ldapdir, 
250                                      "modules.conf")
251     paths.memberofconf = os.path.join(paths.ldapdir, 
252                                       "memberof.conf")
253     paths.fedoradsinf = os.path.join(paths.ldapdir, 
254                                    "fedorads.inf")
255     paths.fedoradspartitions = os.path.join(paths.ldapdir, 
256                                             "fedorads-partitions.ldif")
257     paths.hklm = "hklm.ldb"
258     paths.hkcr = "hkcr.ldb"
259     paths.hkcu = "hkcu.ldb"
260     paths.hku = "hku.ldb"
261     paths.hkpd = "hkpd.ldb"
262     paths.hkpt = "hkpt.ldb"
263
264     paths.sysvol = lp.get("path", "sysvol")
265
266     paths.netlogon = lp.get("path", "netlogon")
267
268     paths.smbconf = lp.configfile()
269
270     return paths
271
272
273 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
274                 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None, 
275                 sitename=None):
276     """Guess configuration settings to use."""
277
278     if hostname is None:
279         hostname = socket.gethostname().split(".")[0].lower()
280
281     netbiosname = hostname.upper()
282     if not valid_netbios_name(netbiosname):
283         raise InvalidNetbiosName(netbiosname)
284
285     hostname = hostname.lower()
286
287     if dnsdomain is None:
288         dnsdomain = lp.get("realm")
289
290     if serverrole is None:
291         serverrole = lp.get("server role")
292
293     assert dnsdomain is not None
294     realm = dnsdomain.upper()
295
296     if lp.get("realm").upper() != realm:
297         raise Exception("realm '%s' in %s must match chosen realm '%s'" %
298                         (lp.get("realm"), lp.configfile(), realm))
299     
300     dnsdomain = dnsdomain.lower()
301
302     if (serverrole == "domain controller"):
303         if domain is None:
304             domain = lp.get("workgroup")
305         if domaindn is None:
306             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
307         if lp.get("workgroup").upper() != domain.upper():
308             raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
309                         lp.get("workgroup"), domain)
310     else:
311         domain = netbiosname
312         if domaindn is None:
313             domaindn = "CN=" + netbiosname
314         
315     assert domain is not None
316     domain = domain.upper()
317     if not valid_netbios_name(domain):
318         raise InvalidNetbiosName(domain)
319         
320     if rootdn is None:
321        rootdn = domaindn
322        
323     if configdn is None:
324         configdn = "CN=Configuration," + rootdn
325     if schemadn is None:
326         schemadn = "CN=Schema," + configdn
327
328     if sitename is None:
329         sitename=DEFAULTSITE
330
331     names = ProvisionNames()
332     names.rootdn = rootdn
333     names.domaindn = domaindn
334     names.configdn = configdn
335     names.schemadn = schemadn
336     names.ldapmanagerdn = "CN=Manager," + rootdn
337     names.dnsdomain = dnsdomain
338     names.domain = domain
339     names.realm = realm
340     names.netbiosname = netbiosname
341     names.hostname = hostname
342     names.sitename = sitename
343     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
344     
345     return names
346     
347
348 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir):
349     if targetdir is not None:
350         if not os.path.exists(targetdir):
351             os.mkdir(targetdir)
352         if not os.path.exists(os.path.join(targetdir, "etc")):
353            os.mkdir(os.path.join(targetdir, "etc"))
354
355         smbconf = os.path.join(targetdir, "etc", "smb.conf")
356
357     # only install a new smb.conf if there isn't one there already
358
359     if not os.path.exists(smbconf):
360         if hostname is None:
361             hostname = socket.gethostname().split(".")[0].lower()
362
363         if serverrole is None:
364             serverrole = "standalone"
365
366         assert serverrole in ("domain controller", "member server", "standalone")
367         if serverrole == "domain controller":
368             smbconfsuffix = "dc"
369         elif serverrole == "member server":
370             smbconfsuffix = "member"
371         elif serverrole == "standalone":
372             smbconfsuffix = "standalone"
373
374         assert domain is not None
375         assert realm is not None
376
377         default_lp = param.LoadParm()
378         #Load non-existant file
379         default_lp.load(smbconf)
380         
381         if targetdir is not None:
382             privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
383             lockdir_line = "lock dir = " + os.path.abspath(targetdir)
384
385             default_lp.set("lock dir", os.path.abspath(targetdir))
386         else:
387             privatedir_line = ""
388             lockdir_line = ""
389
390         sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
391         netlogon = os.path.join(sysvol, realm.lower(), "scripts")
392
393         setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
394                    smbconf, {
395                 "HOSTNAME": hostname,
396                 "DOMAIN": domain,
397                 "REALM": realm,
398                 "SERVERROLE": serverrole,
399                 "NETLOGONPATH": netlogon,
400                 "SYSVOLPATH": sysvol,
401                 "PRIVATEDIR_LINE": privatedir_line,
402                 "LOCKDIR_LINE": lockdir_line
403                 })
404
405     lp = param.LoadParm()
406     lp.load(smbconf)
407
408     return lp
409
410
411 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
412                         users_gid, wheel_gid):
413     """setup reasonable name mappings for sam names to unix names.
414
415     :param samdb: SamDB object.
416     :param idmap: IDmap db object.
417     :param sid: The domain sid.
418     :param domaindn: The domain DN.
419     :param root_uid: uid of the UNIX root user.
420     :param nobody_uid: uid of the UNIX nobody user.
421     :param users_gid: gid of the UNIX users group.
422     :param wheel_gid: gid of the UNIX wheel group."""
423     # add some foreign sids if they are not present already
424     samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
425     samdb.add_foreign(domaindn, "S-1-1-0", "World")
426     samdb.add_foreign(domaindn, "S-1-5-2", "Network")
427     samdb.add_foreign(domaindn, "S-1-5-18", "System")
428     samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
429
430     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
431     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
432
433     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
434     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
435
436
437 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
438                            credentials, names,
439                            serverrole, ldap_backend=None, 
440                            ldap_backend_type=None, erase=False):
441     """Setup the partitions for the SAM database. 
442     
443     Alternatively, provision() may call this, and then populate the database.
444     
445     :note: This will wipe the Sam Database!
446     
447     :note: This function always removes the local SAM LDB file. The erase 
448         parameter controls whether to erase the existing data, which 
449         may not be stored locally but in LDAP.
450     """
451     assert session_info is not None
452
453     samdb = SamDB(samdb_path, session_info=session_info, 
454                   credentials=credentials, lp=lp)
455
456     # Wipes the database
457     try:
458         samdb.erase()
459     except:
460         os.unlink(samdb_path)
461
462     samdb = SamDB(samdb_path, session_info=session_info, 
463                   credentials=credentials, lp=lp)
464
465     #Add modules to the list to activate them by default
466     #beware often order is important
467     #
468     # Some Known ordering constraints:
469     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
470     # - objectclass must be before password_hash, because password_hash checks
471     #   that the objectclass is of type person (filled in by objectclass
472     #   module when expanding the objectclass list)
473     # - partition must be last
474     # - each partition has its own module list then
475     modules_list = ["rootdse",
476                     "paged_results",
477                     "ranged_results",
478                     "anr",
479                     "server_sort",
480                     "extended_dn",
481                     "asq",
482                     "rdn_name",
483                     "objectclass",
484                     "samldb",
485                     "kludge_acl",
486                     "operational"]
487     tdb_modules_list = [
488                     "subtree_rename",
489                     "subtree_delete",
490                     "linked_attributes"]
491     modules_list2 = ["show_deleted",
492                     "partition"]
493  
494     domaindn_ldb = "users.ldb"
495     if ldap_backend is not None:
496         domaindn_ldb = ldap_backend
497     configdn_ldb = "configuration.ldb"
498     if ldap_backend is not None:
499         configdn_ldb = ldap_backend
500     schemadn_ldb = "schema.ldb"
501     if ldap_backend is not None:
502         schema_ldb = ldap_backend
503         schemadn_ldb = ldap_backend
504         
505     if ldap_backend_type == "fedora-ds":
506         backend_modules = ["nsuniqueid", "paged_searches"]
507         # We can handle linked attributes here, as we don't have directory-side subtree operations
508         tdb_modules_list = ["linked_attributes"]
509     elif ldap_backend_type == "openldap":
510         backend_modules = ["normalise", "entryuuid", "paged_searches"]
511         # OpenLDAP handles subtree renames, so we don't want to do any of these things
512         tdb_modules_list = None
513     elif serverrole == "domain controller":
514         backend_modules = ["repl_meta_data"]
515     else:
516         backend_modules = ["objectguid"]
517
518     if tdb_modules_list is None:
519         tdb_modules_list_as_string = ""
520     else:
521         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
522         
523     samdb.transaction_start()
524     try:
525         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
526                 "SCHEMADN": names.schemadn, 
527                 "SCHEMADN_LDB": schemadn_ldb,
528                 "SCHEMADN_MOD2": ",objectguid",
529                 "CONFIGDN": names.configdn,
530                 "CONFIGDN_LDB": configdn_ldb,
531                 "DOMAINDN": names.domaindn,
532                 "DOMAINDN_LDB": domaindn_ldb,
533                 "SCHEMADN_MOD": "schema_fsmo,instancetype",
534                 "CONFIGDN_MOD": "naming_fsmo,instancetype",
535                 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
536                 "MODULES_LIST": ",".join(modules_list),
537                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
538                 "MODULES_LIST2": ",".join(modules_list2),
539                 "BACKEND_MOD": ",".join(backend_modules),
540         })
541
542     except:
543         samdb.transaction_cancel()
544         raise
545
546     samdb.transaction_commit()
547     
548     samdb = SamDB(samdb_path, session_info=session_info, 
549                   credentials=credentials, lp=lp)
550
551     samdb.transaction_start()
552     try:
553         message("Setting up sam.ldb attributes")
554         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
555
556         message("Setting up sam.ldb rootDSE")
557         setup_samdb_rootdse(samdb, setup_path, names)
558
559         if erase:
560             message("Erasing data from partitions")
561             samdb.erase_partitions()
562
563     except:
564         samdb.transaction_cancel()
565         raise
566
567     samdb.transaction_commit()
568     
569     return samdb
570
571
572 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, 
573                         netbiosname, domainsid, keytab_path, samdb_url, 
574                         dns_keytab_path, dnspass, machinepass):
575     """Add DC-specific bits to a secrets database.
576     
577     :param secretsdb: Ldb Handle to the secrets database
578     :param setup_path: Setup path function
579     :param machinepass: Machine password
580     """
581     setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { 
582             "MACHINEPASS_B64": b64encode(machinepass),
583             "DOMAIN": domain,
584             "REALM": realm,
585             "DNSDOMAIN": dnsdomain,
586             "DOMAINSID": str(domainsid),
587             "SECRETS_KEYTAB": keytab_path,
588             "NETBIOSNAME": netbiosname,
589             "SAM_LDB": samdb_url,
590             "DNS_KEYTAB": dns_keytab_path,
591             "DNSPASS_B64": b64encode(dnspass),
592             })
593
594
595 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
596     """Setup the secrets database.
597
598     :param path: Path to the secrets database.
599     :param setup_path: Get the path to a setup file.
600     :param session_info: Session info.
601     :param credentials: Credentials
602     :param lp: Loadparm context
603     :return: LDB handle for the created secrets database
604     """
605     if os.path.exists(path):
606         os.unlink(path)
607     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
608                       lp=lp)
609     secrets_ldb.erase()
610     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
611     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
612                       lp=lp)
613     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
614     return secrets_ldb
615
616
617 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
618     """Setup the templates database.
619
620     :param path: Path to the database.
621     :param setup_path: Function for obtaining the path to setup files.
622     :param session_info: Session info
623     :param credentials: Credentials
624     :param lp: Loadparm context
625     """
626     templates_ldb = SamDB(path, session_info=session_info,
627                           credentials=credentials, lp=lp)
628     templates_ldb.erase()
629     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
630
631
632 def setup_registry(path, setup_path, session_info, credentials, lp):
633     """Setup the registry.
634     
635     :param path: Path to the registry database
636     :param setup_path: Function that returns the path to a setup.
637     :param session_info: Session information
638     :param credentials: Credentials
639     :param lp: Loadparm context
640     """
641     reg = registry.Registry()
642     hive = registry.open_ldb(path, session_info=session_info, 
643                          credentials=credentials, lp_ctx=lp)
644     reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
645     provision_reg = setup_path("provision.reg")
646     assert os.path.exists(provision_reg)
647     reg.diff_apply(provision_reg)
648
649
650 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
651     """Setup the idmap database.
652
653     :param path: path to the idmap database
654     :param setup_path: Function that returns a path to a setup file
655     :param session_info: Session information
656     :param credentials: Credentials
657     :param lp: Loadparm context
658     """
659     if os.path.exists(path):
660         os.unlink(path)
661
662     idmap_ldb = IDmapDB(path, session_info=session_info,
663                         credentials=credentials, lp=lp)
664
665     idmap_ldb.erase()
666     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
667     return idmap_ldb
668
669
670 def setup_samdb_rootdse(samdb, setup_path, names):
671     """Setup the SamDB rootdse.
672
673     :param samdb: Sam Database handle
674     :param setup_path: Obtain setup path
675     """
676     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
677         "SCHEMADN": names.schemadn, 
678         "NETBIOSNAME": names.netbiosname,
679         "DNSDOMAIN": names.dnsdomain,
680         "REALM": names.realm,
681         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
682         "DOMAINDN": names.domaindn,
683         "ROOTDN": names.rootdn,
684         "CONFIGDN": names.configdn,
685         "SERVERDN": names.serverdn,
686         })
687         
688
689 def setup_self_join(samdb, names,
690                     machinepass, dnspass, 
691                     domainsid, invocationid, setup_path,
692                     policyguid):
693     """Join a host to its own domain."""
694     assert isinstance(invocationid, str)
695     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
696               "CONFIGDN": names.configdn, 
697               "SCHEMADN": names.schemadn,
698               "DOMAINDN": names.domaindn,
699               "SERVERDN": names.serverdn,
700               "INVOCATIONID": invocationid,
701               "NETBIOSNAME": names.netbiosname,
702               "DEFAULTSITE": names.sitename,
703               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
704               "MACHINEPASS_B64": b64encode(machinepass),
705               "DNSPASS_B64": b64encode(dnspass),
706               "REALM": names.realm,
707               "DOMAIN": names.domain,
708               "DNSDOMAIN": names.dnsdomain})
709     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
710               "POLICYGUID": policyguid,
711               "DNSDOMAIN": names.dnsdomain,
712               "DOMAINSID": str(domainsid),
713               "DOMAINDN": names.domaindn})
714
715
716 def setup_samdb(path, setup_path, session_info, credentials, lp, 
717                 names, message, 
718                 domainsid, aci, domainguid, policyguid, 
719                 fill, adminpass, krbtgtpass, 
720                 machinepass, invocationid, dnspass,
721                 serverrole, ldap_backend=None, 
722                 ldap_backend_type=None):
723     """Setup a complete SAM Database.
724     
725     :note: This will wipe the main SAM database file!
726     """
727
728     erase = (fill != FILL_DRS)
729
730     # Also wipes the database
731     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
732                            credentials=credentials, session_info=session_info,
733                            names=names, 
734                            ldap_backend=ldap_backend, serverrole=serverrole,
735                            ldap_backend_type=ldap_backend_type, erase=erase)
736
737     samdb = SamDB(path, session_info=session_info, 
738                   credentials=credentials, lp=lp)
739
740     if fill == FILL_DRS:
741        # We want to finish here, but setup the index before we do so
742         message("Setting up sam.ldb index")
743         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
744         return samdb
745
746     message("Pre-loading the Samba 4 and AD schema")
747     samdb.set_domain_sid(domainsid)
748     if serverrole == "domain controller":
749         samdb.set_invocation_id(invocationid)
750
751     load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
752
753     samdb.transaction_start()
754         
755     try:
756         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
757         if serverrole == "domain controller":
758             domain_oc = "domainDNS"
759         else:
760             domain_oc = "samba4LocalDomain"
761
762         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
763             "DOMAINDN": names.domaindn,
764             "ACI": aci,
765             "DOMAIN_OC": domain_oc
766             })
767
768         message("Modifying DomainDN: " + names.domaindn + "")
769         if domainguid is not None:
770             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
771         else:
772             domainguid_mod = ""
773
774         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
775             "LDAPTIME": timestring(int(time.time())),
776             "DOMAINSID": str(domainsid),
777             "SCHEMADN": names.schemadn, 
778             "NETBIOSNAME": names.netbiosname,
779             "DEFAULTSITE": names.sitename,
780             "CONFIGDN": names.configdn,
781             "SERVERDN": names.serverdn,
782             "POLICYGUID": policyguid,
783             "DOMAINDN": names.domaindn,
784             "DOMAINGUID_MOD": domainguid_mod,
785             })
786
787         message("Adding configuration container (permitted to fail)")
788         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
789             "CONFIGDN": names.configdn, 
790             "ACI": aci,
791             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
792             })
793         message("Modifying configuration container")
794         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
795             "CONFIGDN": names.configdn, 
796             "SCHEMADN": names.schemadn,
797             })
798
799         message("Adding schema container (permitted to fail)")
800         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
801             "SCHEMADN": names.schemadn,
802             "ACI": aci,
803             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
804             })
805         message("Modifying schema container")
806         setup_modify_ldif(samdb, 
807             setup_path("provision_schema_basedn_modify.ldif"), {
808             "SCHEMADN": names.schemadn,
809             "NETBIOSNAME": names.netbiosname,
810             "DEFAULTSITE": names.sitename,
811             "CONFIGDN": names.configdn,
812             "SERVERDN": names.serverdn
813             })
814
815         message("Setting up sam.ldb Samba4 schema")
816         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
817                        {"SCHEMADN": names.schemadn })
818         message("Setting up sam.ldb AD schema")
819         setup_add_ldif(samdb, setup_path("schema.ldif"), 
820                        {"SCHEMADN": names.schemadn})
821
822         message("Setting up sam.ldb configuration data")
823         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
824             "CONFIGDN": names.configdn,
825             "NETBIOSNAME": names.netbiosname,
826             "DEFAULTSITE": names.sitename,
827             "DNSDOMAIN": names.dnsdomain,
828             "DOMAIN": names.domain,
829             "SCHEMADN": names.schemadn,
830             "DOMAINDN": names.domaindn,
831             "SERVERDN": names.serverdn
832             })
833
834         message("Setting up display specifiers")
835         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
836                        {"CONFIGDN": names.configdn})
837
838         message("Adding users container (permitted to fail)")
839         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
840                 "DOMAINDN": names.domaindn})
841         message("Modifying users container")
842         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
843                 "DOMAINDN": names.domaindn})
844         message("Adding computers container (permitted to fail)")
845         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
846                 "DOMAINDN": names.domaindn})
847         message("Modifying computers container")
848         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
849                 "DOMAINDN": names.domaindn})
850         message("Setting up sam.ldb data")
851         setup_add_ldif(samdb, setup_path("provision.ldif"), {
852             "DOMAINDN": names.domaindn,
853             "NETBIOSNAME": names.netbiosname,
854             "DEFAULTSITE": names.sitename,
855             "CONFIGDN": names.configdn,
856             "SERVERDN": names.serverdn
857             })
858
859         if fill == FILL_FULL:
860             message("Setting up sam.ldb users and groups")
861             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
862                 "DOMAINDN": names.domaindn,
863                 "DOMAINSID": str(domainsid),
864                 "CONFIGDN": names.configdn,
865                 "ADMINPASS_B64": b64encode(adminpass),
866                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
867                 })
868
869             if serverrole == "domain controller":
870                 message("Setting up self join")
871                 setup_self_join(samdb, names=names, invocationid=invocationid, 
872                                 dnspass=dnspass,  
873                                 machinepass=machinepass, 
874                                 domainsid=domainsid, policyguid=policyguid,
875                                 setup_path=setup_path)
876
877     #We want to setup the index last, as adds are faster unindexed
878         message("Setting up sam.ldb index")
879         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
880     except:
881         samdb.transaction_cancel()
882         raise
883
884     samdb.transaction_commit()
885     return samdb
886
887
888 FILL_FULL = "FULL"
889 FILL_NT4SYNC = "NT4SYNC"
890 FILL_DRS = "DRS"
891
892 def provision(setup_dir, message, session_info, 
893               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
894               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
895               serverdn=None,
896               domain=None, hostname=None, hostip=None, hostip6=None, 
897               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
898               policyguid=None, invocationid=None, machinepass=None, 
899               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
900               wheel=None, backup=None, aci=None, serverrole=None, 
901               ldap_backend=None, ldap_backend_type=None, sitename=None):
902     """Provision samba4
903     
904     :note: caution, this wipes all existing data!
905     """
906
907     def setup_path(file):
908         return os.path.join(setup_dir, file)
909
910     if domainsid is None:
911         domainsid = security.random_sid()
912     else:
913         domainsid = security.Sid(domainsid)
914
915     if policyguid is None:
916         policyguid = str(uuid.uuid4())
917     if adminpass is None:
918         adminpass = misc.random_password(12)
919     if krbtgtpass is None:
920         krbtgtpass = misc.random_password(12)
921     if machinepass is None:
922         machinepass  = misc.random_password(12)
923     if dnspass is None:
924         dnspass = misc.random_password(12)
925     if root is None:
926         root_uid = findnss(pwd.getpwnam, ["root"])[2]
927     else:
928         root_uid = findnss(pwd.getpwnam, [root])[2]
929     if nobody is None:
930         nobody_uid = findnss(pwd.getpwnam, ["nobody"])[2]
931     else:
932         nobody_uid = findnss(pwd.getpwnam, [nobody])[2]
933     if users is None:
934         users_gid = findnss(grp.getgrnam, ["users"])[2]
935     else:
936         users_gid = findnss(grp.getgrnam, [users])[2]
937     if wheel is None:
938         wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
939     else:
940         wheel_gid = findnss(grp.getgrnam, [wheel])[2]
941     if aci is None:
942         aci = "# no aci for local ldb"
943
944     lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
945
946     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
947                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
948                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
949                         serverdn=serverdn)
950
951     paths = provision_paths_from_lp(lp, names.dnsdomain)
952
953     if hostip is None:
954         hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
955
956     if hostip6 is None:
957         try:
958             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
959         except socket.gaierror: pass
960
961     if serverrole is None:
962         serverrole = lp.get("server role")
963
964     assert serverrole in ("domain controller", "member server", "standalone")
965     if invocationid is None and serverrole == "domain controller":
966         invocationid = str(uuid.uuid4())
967
968     if not os.path.exists(paths.private_dir):
969         os.mkdir(paths.private_dir)
970
971     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
972     
973     if ldap_backend is not None:
974         if ldap_backend == "ldapi":
975             # provision-backend will set this path suggested slapd command line / fedorads.inf
976             ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
977              
978     # only install a new shares config db if there is none
979     if not os.path.exists(paths.shareconf):
980         message("Setting up share.ldb")
981         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
982                         credentials=credentials, lp=lp)
983         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
984
985      
986     message("Setting up secrets.ldb")
987     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
988                                   session_info=session_info, 
989                                   credentials=credentials, lp=lp)
990
991     message("Setting up the registry")
992     setup_registry(paths.hklm, setup_path, session_info, 
993                    credentials=credentials, lp=lp)
994
995     message("Setting up templates db")
996     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
997                       credentials=credentials, lp=lp)
998
999     message("Setting up idmap db")
1000     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1001                           credentials=credentials, lp=lp)
1002
1003     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1004                         credentials=credentials, lp=lp, names=names,
1005                         message=message, 
1006                         domainsid=domainsid, 
1007                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1008                         fill=samdb_fill, 
1009                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1010                         invocationid=invocationid, 
1011                         machinepass=machinepass, dnspass=dnspass,
1012                         serverrole=serverrole, ldap_backend=ldap_backend, 
1013                         ldap_backend_type=ldap_backend_type)
1014
1015     if lp.get("server role") == "domain controller":
1016         if paths.netlogon is None:
1017             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1018             message("Please either remove %s or see the template at %s" % 
1019                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1020             assert(paths.netlogon is not None)
1021
1022         if paths.sysvol is None:
1023             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1024             message("Please either remove %s or see the template at %s" % 
1025                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1026             assert(paths.sysvol is not None)            
1027             
1028         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1029                                    "{" + policyguid + "}")
1030         os.makedirs(policy_path, 0755)
1031         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1032         os.makedirs(os.path.join(policy_path, "User"), 0755)
1033         if not os.path.isdir(paths.netlogon):
1034             os.makedirs(paths.netlogon, 0755)
1035
1036     if samdb_fill == FILL_FULL:
1037         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1038                             root_uid=root_uid, nobody_uid=nobody_uid,
1039                             users_gid=users_gid, wheel_gid=wheel_gid)
1040
1041         message("Setting up sam.ldb rootDSE marking as synchronized")
1042         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1043
1044         # Only make a zone file on the first DC, it should be replicated with DNS replication
1045         if serverrole == "domain controller":
1046             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1047                               credentials=credentials, lp=lp)
1048             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1049                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1050                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1051                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1052                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1053
1054             samdb = SamDB(paths.samdb, session_info=session_info, 
1055                       credentials=credentials, lp=lp)
1056
1057             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1058             assert isinstance(domainguid, str)
1059             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1060                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1061                                        scope=SCOPE_SUBTREE)
1062             assert isinstance(hostguid, str)
1063
1064             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1065                              domaindn=names.domaindn, hostip=hostip,
1066                              hostip6=hostip6, hostname=names.hostname,
1067                              dnspass=dnspass, realm=names.realm,
1068                              domainguid=domainguid, hostguid=hostguid)
1069             message("Please install the zone located in %s into your DNS server" % paths.dns)
1070
1071             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1072                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1073                               keytab_name=paths.dns_keytab)
1074             message("See %s for example configuration statements for secure GSS-TSIG updates" % paths.namedconf)
1075
1076             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1077                              hostname=names.hostname, realm=names.realm)
1078             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1079
1080     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1081                                ldapi_url)
1082
1083     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1084
1085     message("Once the above files are installed, your Samba4 server will be ready to use")
1086     message("Server Role:    %s" % serverrole)
1087     message("Hostname:       %s" % names.hostname)
1088     message("NetBIOS Domain: %s" % names.domain)
1089     message("DNS Domain:     %s" % names.dnsdomain)
1090     message("DOMAIN SID:     %s" % str(domainsid))
1091     message("Admin password: %s" % adminpass)
1092
1093     result = ProvisionResult()
1094     result.domaindn = domaindn
1095     result.paths = paths
1096     result.lp = lp
1097     result.samdb = samdb
1098     return result
1099
1100
1101 def provision_become_dc(setup_dir=None,
1102                         smbconf=None, targetdir=None, realm=None, 
1103                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1104                         serverdn=None,
1105                         domain=None, hostname=None, domainsid=None, 
1106                         adminpass=None, krbtgtpass=None, domainguid=None, 
1107                         policyguid=None, invocationid=None, machinepass=None, 
1108                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1109                         wheel=None, backup=None, aci=None, serverrole=None, 
1110                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1111
1112     def message(text):
1113         """print a message if quiet is not set."""
1114         print text
1115
1116     return provision(setup_dir, message, system_session(), None,
1117               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1118               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1119               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1120     
1121
1122 def setup_db_config(setup_path, dbdir):
1123     """Setup a Berkeley database.
1124     
1125     :param setup_path: Setup path function.
1126     :param dbdir: Database directory."""
1127     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1128         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1129     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1130         os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1131     
1132     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1133                {"LDAPDBDIR": dbdir})
1134     
1135
1136
1137 def provision_backend(setup_dir=None, message=None,
1138                       smbconf=None, targetdir=None, realm=None, 
1139                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1140                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1141                       ldap_backend_type=None, ldap_backend_port=None):
1142
1143     def setup_path(file):
1144         return os.path.join(setup_dir, file)
1145
1146     if hostname is None:
1147         hostname = socket.gethostname().split(".")[0].lower()
1148
1149     if root is None:
1150         root = findnss(pwd.getpwnam, ["root"])[0]
1151
1152     lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1153
1154     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1155                         dnsdomain=realm, serverrole=serverrole, 
1156                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1157
1158     paths = provision_paths_from_lp(lp, names.dnsdomain)
1159
1160     if not os.path.isdir(paths.ldapdir):
1161         os.makedirs(paths.ldapdir)
1162     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1163     try:
1164         os.unlink(schemadb_path)
1165     except:
1166         pass
1167
1168     schemadb = Ldb(schemadb_path, lp=lp)
1169  
1170     setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1171                    {"SCHEMADN": names.schemadn,
1172                     "ACI": "#",
1173                     "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1174                     })
1175     setup_modify_ldif(schemadb, 
1176                       setup_path("provision_schema_basedn_modify.ldif"), \
1177                           {"SCHEMADN": names.schemadn,
1178                            "NETBIOSNAME": names.netbiosname,
1179                            "DEFAULTSITE": DEFAULTSITE,
1180                            "CONFIGDN": names.configdn,
1181                            "SERVERDN": names.serverdn
1182                            })
1183     
1184     setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"), 
1185                    {"SCHEMADN": names.schemadn })
1186     setup_add_ldif(schemadb, setup_path("schema.ldif"), 
1187                    {"SCHEMADN": names.schemadn})
1188
1189     if ldap_backend_type == "fedora-ds":
1190         if ldap_backend_port is not None:
1191             serverport = "ServerPort=%d" % ldap_backend_port
1192         else:
1193             serverport = ""
1194
1195         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1196                    {"ROOT": root,
1197                     "HOSTNAME": hostname,
1198                     "DNSDOMAIN": names.dnsdomain,
1199                     "LDAPDIR": paths.ldapdir,
1200                     "DOMAINDN": names.domaindn,
1201                     "LDAPMANAGERDN": names.ldapmanagerdn,
1202                     "LDAPMANAGERPASS": adminpass, 
1203                     "SERVERPORT": serverport})
1204         
1205         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1206                    {"CONFIGDN": names.configdn,
1207                     "SCHEMADN": names.schemadn,
1208                     })
1209         
1210         mapping = "schema-map-fedora-ds-1.0"
1211         backend_schema = "99_ad.ldif"
1212         
1213         slapdcommand="Initailise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1214        
1215     elif ldap_backend_type == "openldap":
1216         attrs = ["linkID", "lDAPDisplayName"]
1217         res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1218
1219         memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1220         refint_attributes = "";
1221         for i in range (0, len(res)):
1222             linkid = res[i]["linkID"][0]
1223             linkid = str(int(linkid) + 1)
1224             expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1225             target = schemadb.searchone(basedn=names.schemadn, 
1226                                         expression=expression, 
1227                                         attribute="lDAPDisplayName", 
1228                                         scope=SCOPE_SUBTREE);
1229             if target is not None:
1230                 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1231                 memberof_config = memberof_config + """overlay memberof
1232 memberof-dangling error
1233 memberof-refint TRUE
1234 memberof-group-oc top
1235 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1236 memberof-memberof-ad """ + target + """
1237 memberof-dangling-error 32
1238
1239 """;
1240
1241         memberof_config = memberof_config + """
1242 overlay refint
1243 refint_attributes""" + refint_attributes + "\n";
1244         
1245         setup_file(setup_path("slapd.conf"), paths.slapdconf,
1246                    {"DNSDOMAIN": names.dnsdomain,
1247                     "LDAPDIR": paths.ldapdir,
1248                     "DOMAINDN": names.domaindn,
1249                     "CONFIGDN": names.configdn,
1250                     "SCHEMADN": names.schemadn,
1251                     "LDAPMANAGERDN": names.ldapmanagerdn,
1252                     "LDAPMANAGERPASS": adminpass,
1253                     "MEMBEROF_CONFIG": memberof_config})
1254         setup_file(setup_path("modules.conf"), paths.modulesconf,
1255                    {"REALM": names.realm})
1256         
1257         setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "user")))
1258         setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "config")))
1259         setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "schema")))
1260         mapping = "schema-map-openldap-2.3"
1261         backend_schema = "backend-schema.schema"
1262
1263         ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1264         if ldap_backend_port is not None:
1265             server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1266         else:
1267             server_port_string = ""
1268         slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1269
1270     schema_command = "bin/ad2oLschema --option=convert:target=" + ldap_backend_type + " -I " + setup_path(mapping) + " -H tdb://" + schemadb_path + " -O " + os.path.join(paths.ldapdir, backend_schema);
1271
1272     os.system(schema_command)
1273
1274
1275     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ( ldap_backend_type) )
1276     message("Server Role:         %s" % serverrole)
1277     message("Hostname:            %s" % names.hostname)
1278     message("DNS Domain:          %s" % names.dnsdomain)
1279     message("Base DN:             %s" % names.domaindn)
1280     message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1281     message("LDAP admin password: %s" % adminpass)
1282     message(slapdcommand)
1283
1284
1285 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1286     """Create a PHP LDAP admin configuration file.
1287
1288     :param path: Path to write the configuration to.
1289     :param setup_path: Function to generate setup paths.
1290     """
1291     setup_file(setup_path("phpldapadmin-config.php"), path, 
1292             {"S4_LDAPI_URI": ldapi_uri})
1293
1294
1295 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1296                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1297     """Write out a DNS zone file, from the info in the current database.
1298
1299     :param path: Path of the new zone file.
1300     :param setup_path: Setup path function.
1301     :param dnsdomain: DNS Domain name
1302     :param domaindn: DN of the Domain
1303     :param hostip: Local IPv4 IP
1304     :param hostip6: Local IPv6 IP
1305     :param hostname: Local hostname
1306     :param dnspass: Password for DNS
1307     :param realm: Realm name
1308     :param domainguid: GUID of the domain.
1309     :param hostguid: GUID of the host.
1310     """
1311     assert isinstance(domainguid, str)
1312
1313     hostip6_base_line = ""
1314     hostip6_host_line = ""
1315
1316     if hostip6 is not None:
1317         hostip6_base_line = "                   IN AAAA " + hostip6
1318         hostip6_host_line = hostname + "                IN AAAA " + hostip6
1319
1320     setup_file(setup_path("provision.zone"), path, {
1321             "DNSPASS_B64": b64encode(dnspass),
1322             "HOSTNAME": hostname,
1323             "DNSDOMAIN": dnsdomain,
1324             "REALM": realm,
1325             "HOSTIP": hostip,
1326             "DOMAINGUID": domainguid,
1327             "DATESTRING": time.strftime("%Y%m%d%H"),
1328             "DEFAULTSITE": DEFAULTSITE,
1329             "HOSTGUID": hostguid,
1330             "HOSTIP6_BASE_LINE": hostip6_base_line,
1331             "HOSTIP6_HOST_LINE": hostip6_host_line,
1332         })
1333
1334 def create_named_conf(path, setup_path, realm, dnsdomain,
1335                       private_dir, keytab_name):
1336     """Write out a file containing zone statements suitable for inclusion in a
1337     named.conf file (including GSS-TSIG configuration).
1338     
1339     :param path: Path of the new named.conf file.
1340     :param setup_path: Setup path function.
1341     :param realm: Realm name
1342     :param dnsdomain: DNS Domain name
1343     :param private_dir: Path to private directory
1344     :param keytab_name: File name of DNS keytab file
1345     """
1346
1347     setup_file(setup_path("named.conf"), path, {
1348             "DNSDOMAIN": dnsdomain,
1349             "REALM": realm,
1350             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1351             "DNS_KEYTAB": keytab_name,
1352             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1353         })
1354
1355 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1356     """Write out a file containing zone statements suitable for inclusion in a
1357     named.conf file (including GSS-TSIG configuration).
1358     
1359     :param path: Path of the new named.conf file.
1360     :param setup_path: Setup path function.
1361     :param dnsdomain: DNS Domain name
1362     :param hostname: Local hostname
1363     :param realm: Realm name
1364     """
1365
1366     setup_file(setup_path("krb5.conf"), path, {
1367             "DNSDOMAIN": dnsdomain,
1368             "HOSTNAME": hostname,
1369             "REALM": realm,
1370         })
1371
1372 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1373     """Load schema for the SamDB.
1374     
1375     :param samdb: Load a schema into a SamDB.
1376     :param setup_path: Setup path function.
1377     :param schemadn: DN of the schema
1378     :param netbiosname: NetBIOS name of the host.
1379     :param configdn: DN of the configuration
1380     """
1381     schema_data = open(setup_path("schema.ldif"), 'r').read()
1382     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1383     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1384     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1385     head_data = substitute_var(head_data, {
1386                     "SCHEMADN": schemadn,
1387                     "NETBIOSNAME": netbiosname,
1388                     "CONFIGDN": configdn,
1389                     "DEFAULTSITE":sitename 
1390     })
1391     samdb.attach_schema_from_ldif(head_data, schema_data)
1392