Merge branch 'master' of ssh://git.samba.org/data/git/samba into master-devel
[ira/wip.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
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     # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
651     except:
652         os.unlink(path)
653
654     templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
655
656     templates_ldb = SamDB(path, session_info=session_info,
657                           credentials=credentials, lp=lp)
658
659     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
660
661
662 def setup_registry(path, setup_path, session_info, credentials, lp):
663     """Setup the registry.
664     
665     :param path: Path to the registry database
666     :param setup_path: Function that returns the path to a setup.
667     :param session_info: Session information
668     :param credentials: Credentials
669     :param lp: Loadparm context
670     """
671     reg = registry.Registry()
672     hive = registry.open_ldb(path, session_info=session_info, 
673                          credentials=credentials, lp_ctx=lp)
674     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
675     provision_reg = setup_path("provision.reg")
676     assert os.path.exists(provision_reg)
677     reg.diff_apply(provision_reg)
678
679
680 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
681     """Setup the idmap database.
682
683     :param path: path to the idmap database
684     :param setup_path: Function that returns a path to a setup file
685     :param session_info: Session information
686     :param credentials: Credentials
687     :param lp: Loadparm context
688     """
689     if os.path.exists(path):
690         os.unlink(path)
691
692     idmap_ldb = IDmapDB(path, session_info=session_info,
693                         credentials=credentials, lp=lp)
694
695     idmap_ldb.erase()
696     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
697     return idmap_ldb
698
699
700 def setup_samdb_rootdse(samdb, setup_path, names):
701     """Setup the SamDB rootdse.
702
703     :param samdb: Sam Database handle
704     :param setup_path: Obtain setup path
705     """
706     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
707         "SCHEMADN": names.schemadn, 
708         "NETBIOSNAME": names.netbiosname,
709         "DNSDOMAIN": names.dnsdomain,
710         "REALM": names.realm,
711         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
712         "DOMAINDN": names.domaindn,
713         "ROOTDN": names.rootdn,
714         "CONFIGDN": names.configdn,
715         "SERVERDN": names.serverdn,
716         })
717         
718
719 def setup_self_join(samdb, names,
720                     machinepass, dnspass, 
721                     domainsid, invocationid, setup_path,
722                     policyguid):
723     """Join a host to its own domain."""
724     assert isinstance(invocationid, str)
725     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
726               "CONFIGDN": names.configdn, 
727               "SCHEMADN": names.schemadn,
728               "DOMAINDN": names.domaindn,
729               "SERVERDN": names.serverdn,
730               "INVOCATIONID": invocationid,
731               "NETBIOSNAME": names.netbiosname,
732               "DEFAULTSITE": names.sitename,
733               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
734               "MACHINEPASS_B64": b64encode(machinepass),
735               "DNSPASS_B64": b64encode(dnspass),
736               "REALM": names.realm,
737               "DOMAIN": names.domain,
738               "DNSDOMAIN": names.dnsdomain})
739     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
740               "POLICYGUID": policyguid,
741               "DNSDOMAIN": names.dnsdomain,
742               "DOMAINSID": str(domainsid),
743               "DOMAINDN": names.domaindn})
744
745
746 def setup_samdb(path, setup_path, session_info, credentials, lp, 
747                 names, message, 
748                 domainsid, aci, domainguid, policyguid, 
749                 fill, adminpass, krbtgtpass, 
750                 machinepass, invocationid, dnspass,
751                 serverrole, ldap_backend=None, 
752                 ldap_backend_type=None):
753     """Setup a complete SAM Database.
754     
755     :note: This will wipe the main SAM database file!
756     """
757
758     erase = (fill != FILL_DRS)
759
760     # Also wipes the database
761     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
762                            credentials=credentials, session_info=session_info,
763                            names=names, 
764                            ldap_backend=ldap_backend, serverrole=serverrole,
765                            ldap_backend_type=ldap_backend_type, erase=erase)
766
767     samdb = SamDB(path, session_info=session_info, 
768                   credentials=credentials, lp=lp)
769     if fill == FILL_DRS:
770         return samdb
771
772     message("Pre-loading the Samba 4 and AD schema")
773     samdb.set_domain_sid(str(domainsid))
774     if serverrole == "domain controller":
775         samdb.set_invocation_id(invocationid)
776
777     load_schema(setup_path, samdb, names.schemadn, names.netbiosname, 
778                 names.configdn, names.sitename, names.serverdn,
779                 names.hostname)
780
781     samdb.transaction_start()
782         
783     try:
784         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
785         if serverrole == "domain controller":
786             domain_oc = "domainDNS"
787         else:
788             domain_oc = "samba4LocalDomain"
789
790         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
791                 "DOMAINDN": names.domaindn,
792                 "ACI": aci,
793                 "DOMAIN_OC": domain_oc
794                 })
795
796         message("Modifying DomainDN: " + names.domaindn + "")
797         if domainguid is not None:
798             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
799         else:
800             domainguid_mod = ""
801
802         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
803             "LDAPTIME": timestring(int(time.time())),
804             "DOMAINSID": str(domainsid),
805             "SCHEMADN": names.schemadn, 
806             "NETBIOSNAME": names.netbiosname,
807             "DEFAULTSITE": names.sitename,
808             "CONFIGDN": names.configdn,
809             "SERVERDN": names.serverdn,
810             "POLICYGUID": policyguid,
811             "DOMAINDN": names.domaindn,
812             "DOMAINGUID_MOD": domainguid_mod,
813             })
814
815         message("Adding configuration container (permitted to fail)")
816         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
817             "CONFIGDN": names.configdn, 
818             "ACI": aci,
819             })
820         message("Modifying configuration container")
821         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
822             "CONFIGDN": names.configdn, 
823             "SCHEMADN": names.schemadn,
824             })
825
826         message("Adding schema container (permitted to fail)")
827         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
828             "SCHEMADN": names.schemadn,
829             "ACI": aci,
830             })
831         message("Modifying schema container")
832
833         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
834
835         setup_modify_ldif(samdb, 
836             setup_path("provision_schema_basedn_modify.ldif"), {
837             "SCHEMADN": names.schemadn,
838             "NETBIOSNAME": names.netbiosname,
839             "DEFAULTSITE": names.sitename,
840             "CONFIGDN": names.configdn,
841             "SERVERDN": names.serverdn,
842             "PREFIXMAP_B64": b64encode(prefixmap)
843             })
844
845         message("Setting up sam.ldb Samba4 schema")
846         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
847                        {"SCHEMADN": names.schemadn })
848         message("Setting up sam.ldb AD schema")
849         setup_add_ldif(samdb, setup_path("schema.ldif"), 
850                        {"SCHEMADN": names.schemadn})
851         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
852                        {"SCHEMADN": names.schemadn})
853
854         message("Setting up sam.ldb configuration data")
855         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
856             "CONFIGDN": names.configdn,
857             "NETBIOSNAME": names.netbiosname,
858             "DEFAULTSITE": names.sitename,
859             "DNSDOMAIN": names.dnsdomain,
860             "DOMAIN": names.domain,
861             "SCHEMADN": names.schemadn,
862             "DOMAINDN": names.domaindn,
863             "SERVERDN": names.serverdn
864             })
865
866         message("Setting up display specifiers")
867         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
868                        {"CONFIGDN": names.configdn})
869
870         message("Adding users container (permitted to fail)")
871         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
872                 "DOMAINDN": names.domaindn})
873         message("Modifying users container")
874         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
875                 "DOMAINDN": names.domaindn})
876         message("Adding computers container (permitted to fail)")
877         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
878                 "DOMAINDN": names.domaindn})
879         message("Modifying computers container")
880         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
881                 "DOMAINDN": names.domaindn})
882         message("Setting up sam.ldb data")
883         setup_add_ldif(samdb, setup_path("provision.ldif"), {
884             "DOMAINDN": names.domaindn,
885             "NETBIOSNAME": names.netbiosname,
886             "DEFAULTSITE": names.sitename,
887             "CONFIGDN": names.configdn,
888             "SERVERDN": names.serverdn
889             })
890
891         if fill == FILL_FULL:
892             message("Setting up sam.ldb users and groups")
893             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
894                 "DOMAINDN": names.domaindn,
895                 "DOMAINSID": str(domainsid),
896                 "CONFIGDN": names.configdn,
897                 "ADMINPASS_B64": b64encode(adminpass),
898                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
899                 })
900
901             if serverrole == "domain controller":
902                 message("Setting up self join")
903                 setup_self_join(samdb, names=names, invocationid=invocationid, 
904                                 dnspass=dnspass,  
905                                 machinepass=machinepass, 
906                                 domainsid=domainsid, policyguid=policyguid,
907                                 setup_path=setup_path)
908
909     except:
910         samdb.transaction_cancel()
911         raise
912
913     samdb.transaction_commit()
914     return samdb
915
916
917 FILL_FULL = "FULL"
918 FILL_NT4SYNC = "NT4SYNC"
919 FILL_DRS = "DRS"
920
921 def provision(setup_dir, message, session_info, 
922               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
923               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
924               serverdn=None,
925               domain=None, hostname=None, hostip=None, hostip6=None, 
926               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
927               policyguid=None, invocationid=None, machinepass=None, 
928               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
929               wheel=None, backup=None, aci=None, serverrole=None, 
930               ldap_backend=None, ldap_backend_type=None, sitename=None):
931     """Provision samba4
932     
933     :note: caution, this wipes all existing data!
934     """
935
936     def setup_path(file):
937         return os.path.join(setup_dir, file)
938
939     if domainsid is None:
940         domainsid = security.random_sid()
941
942     if policyguid is None:
943         policyguid = str(uuid.uuid4())
944     if adminpass is None:
945         adminpass = glue.generate_random_str(12)
946     if krbtgtpass is None:
947         krbtgtpass = glue.generate_random_str(12)
948     if machinepass is None:
949         machinepass  = glue.generate_random_str(12)
950     if dnspass is None:
951         dnspass = glue.generate_random_str(12)
952     root_uid = findnss_uid([root or "root"])
953     nobody_uid = findnss_uid([nobody or "nobody"])
954     users_gid = findnss_gid([users or "users"])
955     if wheel is None:
956         wheel_gid = findnss_gid(["wheel", "adm"])
957     else:
958         wheel_gid = findnss_gid([wheel])
959     if aci is None:
960         aci = "# no aci for local ldb"
961
962     if targetdir is not None:
963         if (not os.path.exists(os.path.join(targetdir, "etc"))):
964             os.makedirs(os.path.join(targetdir, "etc"))
965         smbconf = os.path.join(targetdir, "etc", "smb.conf")
966     elif smbconf is None:
967         smbconf = param.default_path()
968
969     # only install a new smb.conf if there isn't one there already
970     if not os.path.exists(smbconf):
971         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
972                      targetdir)
973
974     lp = param.LoadParm()
975     lp.load(smbconf)
976
977     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
978                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
979                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
980                         serverdn=serverdn)
981
982     paths = provision_paths_from_lp(lp, names.dnsdomain)
983
984     if hostip is None:
985         try:
986             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
987         except socket.gaierror, (socket.EAI_NODATA, msg):
988             hostip = None
989
990     if hostip6 is None:
991         try:
992             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
993         except socket.gaierror, (socket.EAI_NODATA, msg): 
994             hostip6 = None
995
996     if serverrole is None:
997         serverrole = lp.get("server role")
998
999     assert serverrole in ("domain controller", "member server", "standalone")
1000     if invocationid is None and serverrole == "domain controller":
1001         invocationid = str(uuid.uuid4())
1002
1003     if not os.path.exists(paths.private_dir):
1004         os.mkdir(paths.private_dir)
1005
1006     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1007     
1008     if ldap_backend is not None:
1009         if ldap_backend == "ldapi":
1010             # provision-backend will set this path suggested slapd command line / fedorads.inf
1011             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1012              
1013     # only install a new shares config db if there is none
1014     if not os.path.exists(paths.shareconf):
1015         message("Setting up share.ldb")
1016         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1017                         credentials=credentials, lp=lp)
1018         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1019
1020      
1021     message("Setting up secrets.ldb")
1022     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1023                                   session_info=session_info, 
1024                                   credentials=credentials, lp=lp)
1025
1026     message("Setting up the registry")
1027     setup_registry(paths.hklm, setup_path, session_info, 
1028                    credentials=credentials, lp=lp)
1029
1030     message("Setting up templates db")
1031     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
1032                       credentials=credentials, lp=lp)
1033
1034     message("Setting up idmap db")
1035     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1036                           credentials=credentials, lp=lp)
1037
1038     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1039                         credentials=credentials, lp=lp, names=names,
1040                         message=message, 
1041                         domainsid=domainsid, 
1042                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1043                         fill=samdb_fill, 
1044                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1045                         invocationid=invocationid, 
1046                         machinepass=machinepass, dnspass=dnspass,
1047                         serverrole=serverrole, ldap_backend=ldap_backend, 
1048                         ldap_backend_type=ldap_backend_type)
1049
1050     if lp.get("server role") == "domain controller":
1051         if paths.netlogon is None:
1052             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1053             message("Please either remove %s or see the template at %s" % 
1054                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1055             assert(paths.netlogon is not None)
1056
1057         if paths.sysvol is None:
1058             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1059             message("Please either remove %s or see the template at %s" % 
1060                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1061             assert(paths.sysvol is not None)            
1062             
1063         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1064                                    "{" + policyguid + "}")
1065         os.makedirs(policy_path, 0755)
1066         open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1067         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1068         os.makedirs(os.path.join(policy_path, "User"), 0755)
1069         if not os.path.isdir(paths.netlogon):
1070             os.makedirs(paths.netlogon, 0755)
1071
1072     if samdb_fill == FILL_FULL:
1073         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1074                             root_uid=root_uid, nobody_uid=nobody_uid,
1075                             users_gid=users_gid, wheel_gid=wheel_gid)
1076
1077         message("Setting up sam.ldb rootDSE marking as synchronized")
1078         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1079
1080         # Only make a zone file on the first DC, it should be replicated with DNS replication
1081         if serverrole == "domain controller":
1082             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1083                               credentials=credentials, lp=lp)
1084             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1085                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1086                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1087                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1088                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1089
1090             samdb = SamDB(paths.samdb, session_info=session_info, 
1091                       credentials=credentials, lp=lp)
1092
1093             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1094             assert isinstance(domainguid, str)
1095             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1096                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1097                                        scope=SCOPE_SUBTREE)
1098             assert isinstance(hostguid, str)
1099
1100             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1101                              domaindn=names.domaindn, hostip=hostip,
1102                              hostip6=hostip6, hostname=names.hostname,
1103                              dnspass=dnspass, realm=names.realm,
1104                              domainguid=domainguid, hostguid=hostguid)
1105
1106             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1107                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1108
1109             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1110                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1111                               keytab_name=paths.dns_keytab)
1112             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1113             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1114
1115             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1116                              hostname=names.hostname, realm=names.realm)
1117             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1118
1119     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1120                                ldapi_url)
1121
1122     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1123
1124     message("Once the above files are installed, your Samba4 server will be ready to use")
1125     message("Server Role:    %s" % serverrole)
1126     message("Hostname:       %s" % names.hostname)
1127     message("NetBIOS Domain: %s" % names.domain)
1128     message("DNS Domain:     %s" % names.dnsdomain)
1129     message("DOMAIN SID:     %s" % str(domainsid))
1130     message("Admin password: %s" % adminpass)
1131
1132     result = ProvisionResult()
1133     result.domaindn = domaindn
1134     result.paths = paths
1135     result.lp = lp
1136     result.samdb = samdb
1137     return result
1138
1139
1140 def provision_become_dc(setup_dir=None,
1141                         smbconf=None, targetdir=None, realm=None, 
1142                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1143                         serverdn=None,
1144                         domain=None, hostname=None, domainsid=None, 
1145                         adminpass=None, krbtgtpass=None, domainguid=None, 
1146                         policyguid=None, invocationid=None, machinepass=None, 
1147                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1148                         wheel=None, backup=None, aci=None, serverrole=None, 
1149                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1150
1151     def message(text):
1152         """print a message if quiet is not set."""
1153         print text
1154
1155     return provision(setup_dir, message, system_session(), None,
1156               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1157               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1158               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1159     
1160
1161 def setup_db_config(setup_path, dbdir):
1162     """Setup a Berkeley database.
1163     
1164     :param setup_path: Setup path function.
1165     :param dbdir: Database directory."""
1166     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1167         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1168     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1169         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1170     
1171     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1172                {"LDAPDBDIR": dbdir})
1173     
1174
1175
1176 def provision_backend(setup_dir=None, message=None,
1177                       smbconf=None, targetdir=None, realm=None, 
1178                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1179                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1180                       ldap_backend_type=None, ldap_backend_port=None,
1181                       ol_mmr_urls=None):
1182
1183     def setup_path(file):
1184         return os.path.join(setup_dir, file)
1185
1186     if hostname is None:
1187         hostname = socket.gethostname().split(".")[0].lower()
1188
1189     if root is None:
1190         root = findnss(pwd.getpwnam, ["root"])[0]
1191
1192     if adminpass is None:
1193         adminpass = glue.generate_random_str(12)
1194
1195     if targetdir is not None:
1196         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1197             os.makedirs(os.path.join(targetdir, "etc"))
1198         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1199     elif smbconf is None:
1200         smbconf = param.default_path()
1201         assert smbconf is not None
1202
1203     # only install a new smb.conf if there isn't one there already
1204     if not os.path.exists(smbconf):
1205         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1206                      targetdir)
1207
1208     lp = param.LoadParm()
1209     lp.load(smbconf)
1210
1211     if serverrole is None:
1212         serverrole = lp.get("server role")
1213
1214     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1215                         dnsdomain=realm, serverrole=serverrole, 
1216                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1217                         schemadn=schemadn)
1218
1219     paths = provision_paths_from_lp(lp, names.dnsdomain)
1220
1221     if not os.path.isdir(paths.ldapdir):
1222         os.makedirs(paths.ldapdir, 0700)
1223     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1224     try:
1225         os.unlink(schemadb_path)
1226     except OSError:
1227         pass
1228
1229     schemadb = Ldb(schemadb_path, lp=lp)
1230  
1231     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1232
1233     setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1234                    {"SCHEMADN": names.schemadn,
1235                     "ACI": "#",
1236                     })
1237     setup_modify_ldif(schemadb, 
1238                       setup_path("provision_schema_basedn_modify.ldif"), \
1239                           {"SCHEMADN": names.schemadn,
1240                            "NETBIOSNAME": names.netbiosname,
1241                            "DEFAULTSITE": DEFAULTSITE,
1242                            "CONFIGDN": names.configdn,
1243                            "SERVERDN": names.serverdn,
1244                            "PREFIXMAP_B64": b64encode(prefixmap)
1245                            })
1246     
1247     setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"), 
1248                    {"SCHEMADN": names.schemadn })
1249     setup_add_ldif(schemadb, setup_path("schema.ldif"), 
1250                    {"SCHEMADN": names.schemadn})
1251
1252     if ldap_backend_type == "fedora-ds":
1253         if ldap_backend_port is not None:
1254             serverport = "ServerPort=%d" % ldap_backend_port
1255         else:
1256             serverport = ""
1257
1258         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1259                    {"ROOT": root,
1260                     "HOSTNAME": hostname,
1261                     "DNSDOMAIN": names.dnsdomain,
1262                     "LDAPDIR": paths.ldapdir,
1263                     "DOMAINDN": names.domaindn,
1264                     "LDAPMANAGERDN": names.ldapmanagerdn,
1265                     "LDAPMANAGERPASS": adminpass, 
1266                     "SERVERPORT": serverport})
1267         
1268         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1269                    {"CONFIGDN": names.configdn,
1270                     "SCHEMADN": names.schemadn,
1271                     })
1272         
1273         mapping = "schema-map-fedora-ds-1.0"
1274         backend_schema = "99_ad.ldif"
1275         
1276         slapdcommand="Initialise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1277        
1278         ldapuser = "--simple-bind-dn=" + names.ldapmanagerdn
1279
1280     elif ldap_backend_type == "openldap":
1281         attrs = ["linkID", "lDAPDisplayName"]
1282         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)
1283
1284         memberof_config = "# Generated from schema in %s\n" % schemadb_path
1285         refint_attributes = ""
1286         for i in range (0, len(res)):
1287             expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1288             target = schemadb.searchone(basedn=names.schemadn, 
1289                                         expression=expression, 
1290                                         attribute="lDAPDisplayName", 
1291                                         scope=SCOPE_SUBTREE)
1292             if target is not None:
1293                 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0]
1294             
1295                 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1296                                                      { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1297                                                        "MEMBEROF_ATTR" : str(target) })
1298
1299         refint_config = read_and_sub_file(setup_path("refint.conf"),
1300                                             { "LINK_ATTRS" : refint_attributes})
1301
1302 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1303         mmr_on_config = ""
1304         mmr_replicator_acl = ""
1305         mmr_serverids_config = ""
1306         mmr_syncrepl_schema_config = "" 
1307         mmr_syncrepl_config_config = "" 
1308         mmr_syncrepl_user_config = "" 
1309         
1310         if ol_mmr_urls is not None:
1311                 # For now, make these equal
1312                 mmr_pass = adminpass
1313
1314                 url_list=filter(None,ol_mmr_urls.split(' ')) 
1315                 if (len(url_list) == 1):
1316                     url_list=filter(None,ol_mmr_urls.split(',')) 
1317                      
1318
1319                 mmr_on_config = "MirrorMode On"
1320                 mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1321                 serverid=0
1322                 for url in url_list:
1323                         serverid=serverid+1
1324                         mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1325                                                                      { "SERVERID" : str(serverid),
1326                                                                        "LDAPSERVER" : url })
1327                         rid=serverid*10
1328                         rid=rid+1
1329                         mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1330                                                                      {  "RID" : str(rid),
1331                                                                         "MMRDN": names.schemadn,
1332                                                                         "LDAPSERVER" : url,
1333                                                                         "MMR_PASSWORD": mmr_pass})
1334
1335                         rid=rid+1
1336                         mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1337                                                                      {  "RID" : str(rid),
1338                                                                         "MMRDN": names.configdn,
1339                                                                         "LDAPSERVER" : url,
1340                                                                         "MMR_PASSWORD": mmr_pass})
1341
1342                         rid=rid+1
1343                         mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1344                                                                      {  "RID" : str(rid),
1345                                                                         "MMRDN": names.domaindn,
1346                                                                         "LDAPSERVER" : url,
1347                                                                         "MMR_PASSWORD": mmr_pass })
1348
1349
1350         setup_file(setup_path("slapd.conf"), paths.slapdconf,
1351                    {"DNSDOMAIN": names.dnsdomain,
1352                     "LDAPDIR": paths.ldapdir,
1353                     "DOMAINDN": names.domaindn,
1354                     "CONFIGDN": names.configdn,
1355                     "SCHEMADN": names.schemadn,
1356                     "MEMBEROF_CONFIG": memberof_config,
1357                     "MIRRORMODE": mmr_on_config,
1358                     "REPLICATOR_ACL": mmr_replicator_acl,
1359                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1360                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1361                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1362                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1363                     "REFINT_CONFIG": refint_config})
1364         setup_file(setup_path("modules.conf"), paths.modulesconf,
1365                    {"REALM": names.realm})
1366         
1367         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1368         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1369         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1370
1371         if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1372             os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1373
1374         setup_file(setup_path("cn=samba.ldif"), 
1375                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1376                    { "UUID": str(uuid.uuid4()), 
1377                      "LDAPTIME": timestring(int(time.time()))} )
1378         setup_file(setup_path("cn=samba-admin.ldif"), 
1379                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1380                               {"LDAPADMINPASS_B64": b64encode(adminpass),
1381                                "UUID": str(uuid.uuid4()), 
1382                                "LDAPTIME": timestring(int(time.time()))} )
1383         
1384         if ol_mmr_urls is not None:
1385            setup_file(setup_path("cn=replicator.ldif"),
1386                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1387                               {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1388                                "UUID": str(uuid.uuid4()),
1389                                "LDAPTIME": timestring(int(time.time()))} )
1390
1391
1392
1393         mapping = "schema-map-openldap-2.3"
1394         backend_schema = "backend-schema.schema"
1395
1396         ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1397         if ldap_backend_port is not None:
1398             server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1399         else:
1400             server_port_string = ""
1401
1402         slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1403
1404         ldapuser = "--username=samba-admin"
1405
1406             
1407     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)
1408             
1409     os.system(schema_command)
1410
1411     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type)
1412     message("Server Role:         %s" % serverrole)
1413     message("Hostname:            %s" % names.hostname)
1414     message("DNS Domain:          %s" % names.dnsdomain)
1415     message("Base DN:             %s" % names.domaindn)
1416
1417     if ldap_backend_type == "openldap":
1418         message("LDAP admin user:     samba-admin")
1419     else:
1420         message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1421
1422     message("LDAP admin password: %s" % adminpass)
1423     message(slapdcommand)
1424     assert isinstance(ldap_backend_type, str)
1425     assert isinstance(ldapuser, str)
1426     assert isinstance(adminpass, str)
1427     assert isinstance(names.dnsdomain, str)
1428     assert isinstance(names.domain, str)
1429     assert isinstance(serverrole, str)
1430     args = ["--ldap-backend=ldapi",
1431             "--ldap-backend-type=" + ldap_backend_type,
1432             "--password=" + adminpass,
1433             ldapuser,
1434             "--realm=" + names.dnsdomain,
1435             "--domain=" + names.domain,
1436             "--server-role='" + serverrole + "'"]
1437     message("Run provision with: " + " ".join(args))
1438
1439
1440 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1441     """Create a PHP LDAP admin configuration file.
1442
1443     :param path: Path to write the configuration to.
1444     :param setup_path: Function to generate setup paths.
1445     """
1446     setup_file(setup_path("phpldapadmin-config.php"), path, 
1447             {"S4_LDAPI_URI": ldapi_uri})
1448
1449
1450 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1451                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1452     """Write out a DNS zone file, from the info in the current database.
1453
1454     :param path: Path of the new zone file.
1455     :param setup_path: Setup path function.
1456     :param dnsdomain: DNS Domain name
1457     :param domaindn: DN of the Domain
1458     :param hostip: Local IPv4 IP
1459     :param hostip6: Local IPv6 IP
1460     :param hostname: Local hostname
1461     :param dnspass: Password for DNS
1462     :param realm: Realm name
1463     :param domainguid: GUID of the domain.
1464     :param hostguid: GUID of the host.
1465     """
1466     assert isinstance(domainguid, str)
1467
1468     if hostip6 is not None:
1469         hostip6_base_line = "            IN AAAA    " + hostip6
1470         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1471     else:
1472         hostip6_base_line = ""
1473         hostip6_host_line = ""
1474
1475     if hostip is not None:
1476         hostip_base_line = "            IN A    " + hostip
1477         hostip_host_line = hostname + "        IN A    " + hostip
1478     else:
1479         hostip_base_line = ""
1480         hostip_host_line = ""
1481
1482     setup_file(setup_path("provision.zone"), path, {
1483             "DNSPASS_B64": b64encode(dnspass),
1484             "HOSTNAME": hostname,
1485             "DNSDOMAIN": dnsdomain,
1486             "REALM": realm,
1487             "HOSTIP_BASE_LINE": hostip_base_line,
1488             "HOSTIP_HOST_LINE": hostip_host_line,
1489             "DOMAINGUID": domainguid,
1490             "DATESTRING": time.strftime("%Y%m%d%H"),
1491             "DEFAULTSITE": DEFAULTSITE,
1492             "HOSTGUID": hostguid,
1493             "HOSTIP6_BASE_LINE": hostip6_base_line,
1494             "HOSTIP6_HOST_LINE": hostip6_host_line,
1495         })
1496
1497
1498 def create_named_conf(path, setup_path, realm, dnsdomain,
1499                       private_dir):
1500     """Write out a file containing zone statements suitable for inclusion in a
1501     named.conf file (including GSS-TSIG configuration).
1502     
1503     :param path: Path of the new named.conf file.
1504     :param setup_path: Setup path function.
1505     :param realm: Realm name
1506     :param dnsdomain: DNS Domain name
1507     :param private_dir: Path to private directory
1508     :param keytab_name: File name of DNS keytab file
1509     """
1510
1511     setup_file(setup_path("named.conf"), path, {
1512             "DNSDOMAIN": dnsdomain,
1513             "REALM": realm,
1514             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1515             "PRIVATE_DIR": private_dir
1516             })
1517
1518 def create_named_txt(path, setup_path, realm, dnsdomain,
1519                       private_dir, keytab_name):
1520     """Write out a file containing zone statements suitable for inclusion in a
1521     named.conf file (including GSS-TSIG configuration).
1522     
1523     :param path: Path of the new named.conf file.
1524     :param setup_path: Setup path function.
1525     :param realm: Realm name
1526     :param dnsdomain: DNS Domain name
1527     :param private_dir: Path to private directory
1528     :param keytab_name: File name of DNS keytab file
1529     """
1530
1531     setup_file(setup_path("named.txt"), path, {
1532             "DNSDOMAIN": dnsdomain,
1533             "REALM": realm,
1534             "DNS_KEYTAB": keytab_name,
1535             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1536             "PRIVATE_DIR": private_dir
1537         })
1538
1539 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1540     """Write out a file containing zone statements suitable for inclusion in a
1541     named.conf file (including GSS-TSIG configuration).
1542     
1543     :param path: Path of the new named.conf file.
1544     :param setup_path: Setup path function.
1545     :param dnsdomain: DNS Domain name
1546     :param hostname: Local hostname
1547     :param realm: Realm name
1548     """
1549
1550     setup_file(setup_path("krb5.conf"), path, {
1551             "DNSDOMAIN": dnsdomain,
1552             "HOSTNAME": hostname,
1553             "REALM": realm,
1554         })
1555
1556
1557 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename,
1558                 serverdn, servername):
1559     """Load schema for the SamDB.
1560     
1561     :param samdb: Load a schema into a SamDB.
1562     :param setup_path: Setup path function.
1563     :param schemadn: DN of the schema
1564     :param netbiosname: NetBIOS name of the host.
1565     :param configdn: DN of the configuration
1566     :param serverdn: DN of the server
1567     :param servername: Host name of the server
1568     """
1569     schema_data = open(setup_path("schema.ldif"), 'r').read()
1570     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1571     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1572     check_all_substituted(schema_data)
1573     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1574     prefixmap = b64encode(prefixmap)
1575
1576     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1577     head_data = substitute_var(head_data, {
1578                     "SCHEMADN": schemadn,
1579                     "NETBIOSNAME": netbiosname,
1580                     "CONFIGDN": configdn,
1581                     "DEFAULTSITE": sitename,
1582                     "PREFIXMAP_B64": prefixmap,
1583                     "SERVERDN": serverdn,
1584                     "SERVERNAME": servername,
1585     })
1586     check_all_substituted(head_data)
1587     samdb.attach_schema_from_ldif(head_data, schema_data)
1588