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