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