Make it even clearer what to do next in the LDAP backend setup
[amitay/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:
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 class ProvisionNames:
81     def __init__(self):
82         self.rootdn = None
83         self.domaindn = None
84         self.configdn = None
85         self.schemadn = None
86         self.ldapmanagerdn = None
87         self.dnsdomain = None
88         self.realm = None
89         self.netbiosname = None
90         self.domain = None
91         self.hostname = None
92         self.sitename = None
93         self.smbconf = None
94     
95 class ProvisionResult:
96     def __init__(self):
97         self.paths = None
98         self.domaindn = None
99         self.lp = None
100         self.samdb = None
101
102 def check_install(lp, session_info, credentials):
103     """Check whether the current install seems ok.
104     
105     :param lp: Loadparm context
106     :param session_info: Session information
107     :param credentials: Credentials
108     """
109     if lp.get("realm") == "":
110         raise Exception("Realm empty")
111     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
112             credentials=credentials, lp=lp)
113     if len(ldb.search("(cn=Administrator)")) != 1:
114         raise "No administrator account found"
115
116
117 def findnss(nssfn, names):
118     """Find a user or group from a list of possibilities.
119     
120     :param nssfn: NSS Function to try (should raise KeyError if not found)
121     :param names: Names to check.
122     :return: Value return by first names list.
123     """
124     for name in names:
125         try:
126             return nssfn(name)
127         except KeyError:
128             pass
129     raise KeyError("Unable to find user/group %r" % names)
130
131
132 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
133 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
134
135
136 def read_and_sub_file(file, subst_vars):
137     """Read a file and sub in variables found in it
138     
139     :param file: File to be read (typically from setup directory)
140      param subst_vars: Optional variables to subsitute in the file.
141     """
142     data = open(file, 'r').read()
143     if subst_vars is not None:
144         data = substitute_var(data, subst_vars)
145     check_all_substituted(data)
146     return data
147
148
149 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
150     """Setup a ldb in the private dir.
151     
152     :param ldb: LDB file to import data into
153     :param ldif_path: Path of the LDIF file to load
154     :param subst_vars: Optional variables to subsitute in LDIF.
155     """
156     assert isinstance(ldif_path, str)
157
158     data = read_and_sub_file(ldif_path, subst_vars)
159     ldb.add_ldif(data)
160
161
162 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
163     """Modify a ldb in the private dir.
164     
165     :param ldb: LDB object.
166     :param ldif_path: LDIF file path.
167     :param subst_vars: Optional dictionary with substitution variables.
168     """
169     data = read_and_sub_file(ldif_path, subst_vars)
170
171     ldb.modify_ldif(data)
172
173
174 def setup_ldb(ldb, ldif_path, subst_vars):
175     """Import a LDIF a file into a LDB handle, optionally substituting variables.
176
177     :note: Either all LDIF data will be added or none (using transactions).
178
179     :param ldb: LDB file to import into.
180     :param ldif_path: Path to the LDIF file.
181     :param subst_vars: Dictionary with substitution variables.
182     """
183     assert ldb is not None
184     ldb.transaction_start()
185     try:
186         setup_add_ldif(ldb, ldif_path, subst_vars)
187     except:
188         ldb.transaction_cancel()
189         raise
190     ldb.transaction_commit()
191
192
193 def setup_file(template, fname, subst_vars):
194     """Setup a file in the private dir.
195
196     :param template: Path of the template file.
197     :param fname: Path of the file to create.
198     :param subst_vars: Substitution variables.
199     """
200     f = fname
201
202     if os.path.exists(f):
203         os.unlink(f)
204
205     data = read_and_sub_file(template, subst_vars)
206     open(f, 'w').write(data)
207
208
209 def provision_paths_from_lp(lp, dnsdomain):
210     """Set the default paths for provisioning.
211
212     :param lp: Loadparm context.
213     :param dnsdomain: DNS Domain name
214     """
215     paths = ProvisionPaths()
216     paths.private_dir = lp.get("private dir")
217     paths.keytab = "secrets.keytab"
218     paths.dns_keytab = "dns.keytab"
219
220     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
221     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
222     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
223     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
224     paths.templates = os.path.join(paths.private_dir, "templates.ldb")
225     paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
226     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
227     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
228     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
229     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
230     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
231     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
232                                             "phpldapadmin-config.php")
233     paths.ldapdir = os.path.join(paths.private_dir, 
234                                  "ldap")
235     paths.slapdconf = os.path.join(paths.ldapdir, 
236                                    "slapd.conf")
237     paths.modulesconf = os.path.join(paths.ldapdir, 
238                                      "modules.conf")
239     paths.memberofconf = os.path.join(paths.ldapdir, 
240                                       "memberof.conf")
241     paths.fedoradsinf = os.path.join(paths.ldapdir, 
242                                    "fedorads.inf")
243     paths.fedoradspartitions = os.path.join(paths.ldapdir, 
244                                             "fedorads-partitions.ldif")
245     paths.hklm = "hklm.ldb"
246     paths.hkcr = "hkcr.ldb"
247     paths.hkcu = "hkcu.ldb"
248     paths.hku = "hku.ldb"
249     paths.hkpd = "hkpd.ldb"
250     paths.hkpt = "hkpt.ldb"
251
252     paths.sysvol = lp.get("path", "sysvol")
253
254     paths.netlogon = lp.get("path", "netlogon")
255
256     paths.smbconf = lp.configfile()
257
258     return paths
259
260
261 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
262                 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None, 
263                 sitename=None):
264     """Guess configuration settings to use."""
265
266     if hostname is None:
267         hostname = socket.gethostname().split(".")[0].lower()
268
269     netbiosname = hostname.upper()
270     if not valid_netbios_name(netbiosname):
271         raise InvalidNetbiosName(netbiosname)
272
273     hostname = hostname.lower()
274
275     if dnsdomain is None:
276         dnsdomain = lp.get("realm")
277
278     if serverrole is None:
279         serverrole = lp.get("server role")
280
281     assert dnsdomain is not None
282     realm = dnsdomain.upper()
283
284     if lp.get("realm").upper() != realm:
285         raise Exception("realm '%s' in %s must match chosen realm '%s'" %
286                         (lp.get("realm"), lp.configfile(), realm))
287     
288     dnsdomain = dnsdomain.lower()
289
290     if serverrole == "domain controller":
291         if domain is None:
292             domain = lp.get("workgroup")
293         if domaindn is None:
294             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
295         if lp.get("workgroup").upper() != domain.upper():
296             raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
297                         lp.get("workgroup"), domain)
298     else:
299         domain = netbiosname
300         if domaindn is None:
301             domaindn = "CN=" + netbiosname
302         
303     assert domain is not None
304     domain = domain.upper()
305     if not valid_netbios_name(domain):
306         raise InvalidNetbiosName(domain)
307         
308     if rootdn is None:
309        rootdn = domaindn
310        
311     if configdn is None:
312         configdn = "CN=Configuration," + rootdn
313     if schemadn is None:
314         schemadn = "CN=Schema," + configdn
315
316     if sitename is None:
317         sitename=DEFAULTSITE
318
319     names = ProvisionNames()
320     names.rootdn = rootdn
321     names.domaindn = domaindn
322     names.configdn = configdn
323     names.schemadn = schemadn
324     names.ldapmanagerdn = "CN=Manager," + rootdn
325     names.dnsdomain = dnsdomain
326     names.domain = domain
327     names.realm = realm
328     names.netbiosname = netbiosname
329     names.hostname = hostname
330     names.sitename = sitename
331     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
332     
333     return names
334     
335
336 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
337                  targetdir):
338     if hostname is None:
339         hostname = socket.gethostname().split(".")[0].lower()
340
341     if serverrole is None:
342         serverrole = "standalone"
343
344     assert serverrole in ("domain controller", "member server", "standalone")
345     if serverrole == "domain controller":
346         smbconfsuffix = "dc"
347     elif serverrole == "member server":
348         smbconfsuffix = "member"
349     elif serverrole == "standalone":
350         smbconfsuffix = "standalone"
351
352     assert domain is not None
353     assert realm is not None
354
355     default_lp = param.LoadParm()
356     #Load non-existant file
357     default_lp.load(smbconf)
358     
359     if targetdir is not None:
360         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
361         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
362
363         default_lp.set("lock dir", os.path.abspath(targetdir))
364     else:
365         privatedir_line = ""
366         lockdir_line = ""
367
368     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
369     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
370
371     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
372                smbconf, {
373             "HOSTNAME": hostname,
374             "DOMAIN": domain,
375             "REALM": realm,
376             "SERVERROLE": serverrole,
377             "NETLOGONPATH": netlogon,
378             "SYSVOLPATH": sysvol,
379             "PRIVATEDIR_LINE": privatedir_line,
380             "LOCKDIR_LINE": lockdir_line
381             })
382
383
384
385 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
386                         users_gid, wheel_gid):
387     """setup reasonable name mappings for sam names to unix names.
388
389     :param samdb: SamDB object.
390     :param idmap: IDmap db object.
391     :param sid: The domain sid.
392     :param domaindn: The domain DN.
393     :param root_uid: uid of the UNIX root user.
394     :param nobody_uid: uid of the UNIX nobody user.
395     :param users_gid: gid of the UNIX users group.
396     :param wheel_gid: gid of the UNIX wheel group."""
397     # add some foreign sids if they are not present already
398     samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
399     samdb.add_foreign(domaindn, "S-1-1-0", "World")
400     samdb.add_foreign(domaindn, "S-1-5-2", "Network")
401     samdb.add_foreign(domaindn, "S-1-5-18", "System")
402     samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
403
404     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
405     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
406
407     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
408     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
409
410
411 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
412                            credentials, names,
413                            serverrole, ldap_backend=None, 
414                            ldap_backend_type=None, erase=False):
415     """Setup the partitions for the SAM database. 
416     
417     Alternatively, provision() may call this, and then populate the database.
418     
419     :note: This will wipe the Sam Database!
420     
421     :note: This function always removes the local SAM LDB file. The erase 
422         parameter controls whether to erase the existing data, which 
423         may not be stored locally but in LDAP.
424     """
425     assert session_info is not None
426
427     samdb = SamDB(samdb_path, session_info=session_info, 
428                   credentials=credentials, lp=lp)
429
430     # Wipes the database
431     try:
432         samdb.erase()
433     except:
434         os.unlink(samdb_path)
435
436     samdb = SamDB(samdb_path, session_info=session_info, 
437                   credentials=credentials, lp=lp)
438
439     #Add modules to the list to activate them by default
440     #beware often order is important
441     #
442     # Some Known ordering constraints:
443     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
444     # - objectclass must be before password_hash, because password_hash checks
445     #   that the objectclass is of type person (filled in by objectclass
446     #   module when expanding the objectclass list)
447     # - partition must be last
448     # - each partition has its own module list then
449     modules_list = ["rootdse",
450                     "paged_results",
451                     "ranged_results",
452                     "anr",
453                     "server_sort",
454                     "extended_dn",
455                     "asq",
456                     "rdn_name",
457                     "objectclass",
458                     "samldb",
459                     "kludge_acl",
460                     "operational"]
461     tdb_modules_list = [
462                     "subtree_rename",
463                     "subtree_delete",
464                     "linked_attributes"]
465     modules_list2 = ["show_deleted",
466                     "partition"]
467  
468     domaindn_ldb = "users.ldb"
469     if ldap_backend is not None:
470         domaindn_ldb = ldap_backend
471     configdn_ldb = "configuration.ldb"
472     if ldap_backend is not None:
473         configdn_ldb = ldap_backend
474     schemadn_ldb = "schema.ldb"
475     if ldap_backend is not None:
476         schema_ldb = ldap_backend
477         schemadn_ldb = ldap_backend
478         
479     if ldap_backend_type == "fedora-ds":
480         backend_modules = ["nsuniqueid", "paged_searches"]
481         # We can handle linked attributes here, as we don't have directory-side subtree operations
482         tdb_modules_list = ["linked_attributes"]
483     elif ldap_backend_type == "openldap":
484         backend_modules = ["normalise", "entryuuid", "paged_searches"]
485         # OpenLDAP handles subtree renames, so we don't want to do any of these things
486         tdb_modules_list = None
487     elif ldap_backend is not None:
488         raise "LDAP Backend specified, but LDAP Backend Type not specified"
489     elif serverrole == "domain controller":
490         backend_modules = ["repl_meta_data"]
491     else:
492         backend_modules = ["objectguid"]
493
494     if tdb_modules_list is None:
495         tdb_modules_list_as_string = ""
496     else:
497         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
498         
499     samdb.transaction_start()
500     try:
501         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
502                 "SCHEMADN": names.schemadn, 
503                 "SCHEMADN_LDB": schemadn_ldb,
504                 "SCHEMADN_MOD2": ",objectguid",
505                 "CONFIGDN": names.configdn,
506                 "CONFIGDN_LDB": configdn_ldb,
507                 "DOMAINDN": names.domaindn,
508                 "DOMAINDN_LDB": domaindn_ldb,
509                 "SCHEMADN_MOD": "schema_fsmo,instancetype",
510                 "CONFIGDN_MOD": "naming_fsmo,instancetype",
511                 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
512                 "MODULES_LIST": ",".join(modules_list),
513                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
514                 "MODULES_LIST2": ",".join(modules_list2),
515                 "BACKEND_MOD": ",".join(backend_modules),
516         })
517
518     except:
519         samdb.transaction_cancel()
520         raise
521
522     samdb.transaction_commit()
523     
524     samdb = SamDB(samdb_path, session_info=session_info, 
525                   credentials=credentials, lp=lp)
526
527     samdb.transaction_start()
528     try:
529         message("Setting up sam.ldb attributes")
530         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
531
532         message("Setting up sam.ldb rootDSE")
533         setup_samdb_rootdse(samdb, setup_path, names)
534
535         if erase:
536             message("Erasing data from partitions")
537             samdb.erase_partitions()
538
539     except:
540         samdb.transaction_cancel()
541         raise
542
543     samdb.transaction_commit()
544     
545     return samdb
546
547
548 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, 
549                         netbiosname, domainsid, keytab_path, samdb_url, 
550                         dns_keytab_path, dnspass, machinepass):
551     """Add DC-specific bits to a secrets database.
552     
553     :param secretsdb: Ldb Handle to the secrets database
554     :param setup_path: Setup path function
555     :param machinepass: Machine password
556     """
557     setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { 
558             "MACHINEPASS_B64": b64encode(machinepass),
559             "DOMAIN": domain,
560             "REALM": realm,
561             "DNSDOMAIN": dnsdomain,
562             "DOMAINSID": str(domainsid),
563             "SECRETS_KEYTAB": keytab_path,
564             "NETBIOSNAME": netbiosname,
565             "SAM_LDB": samdb_url,
566             "DNS_KEYTAB": dns_keytab_path,
567             "DNSPASS_B64": b64encode(dnspass),
568             })
569
570
571 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
572     """Setup the secrets database.
573
574     :param path: Path to the secrets database.
575     :param setup_path: Get the path to a setup file.
576     :param session_info: Session info.
577     :param credentials: Credentials
578     :param lp: Loadparm context
579     :return: LDB handle for the created secrets database
580     """
581     if os.path.exists(path):
582         os.unlink(path)
583     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
584                       lp=lp)
585     secrets_ldb.erase()
586     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
587     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
588                       lp=lp)
589     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
590
591     if credentials is not None and credentials.authentication_requested():
592         if credentials.get_bind_dn() is not None:
593             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
594                     "LDAPMANAGERDN": credentials.get_bind_dn(),
595                     "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
596                     })
597         else:
598             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
599                     "LDAPADMINUSER": credentials.get_username(),
600                     "LDAPADMINREALM": credentials.get_realm(),
601                     "LDAPADMINPASS_B64": b64encode(credentials.get_password())
602                     })
603
604     return secrets_ldb
605
606
607 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
608     """Setup the templates database.
609
610     :param path: Path to the database.
611     :param setup_path: Function for obtaining the path to setup files.
612     :param session_info: Session info
613     :param credentials: Credentials
614     :param lp: Loadparm context
615     """
616     templates_ldb = SamDB(path, session_info=session_info,
617                           credentials=credentials, lp=lp)
618     templates_ldb.erase()
619     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
620
621
622 def setup_registry(path, setup_path, session_info, credentials, lp):
623     """Setup the registry.
624     
625     :param path: Path to the registry database
626     :param setup_path: Function that returns the path to a setup.
627     :param session_info: Session information
628     :param credentials: Credentials
629     :param lp: Loadparm context
630     """
631     reg = registry.Registry()
632     hive = registry.open_ldb(path, session_info=session_info, 
633                          credentials=credentials, lp_ctx=lp)
634     reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
635     provision_reg = setup_path("provision.reg")
636     assert os.path.exists(provision_reg)
637     reg.diff_apply(provision_reg)
638
639
640 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
641     """Setup the idmap database.
642
643     :param path: path to the idmap database
644     :param setup_path: Function that returns a path to a setup file
645     :param session_info: Session information
646     :param credentials: Credentials
647     :param lp: Loadparm context
648     """
649     if os.path.exists(path):
650         os.unlink(path)
651
652     idmap_ldb = IDmapDB(path, session_info=session_info,
653                         credentials=credentials, lp=lp)
654
655     idmap_ldb.erase()
656     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
657     return idmap_ldb
658
659
660 def setup_samdb_rootdse(samdb, setup_path, names):
661     """Setup the SamDB rootdse.
662
663     :param samdb: Sam Database handle
664     :param setup_path: Obtain setup path
665     """
666     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
667         "SCHEMADN": names.schemadn, 
668         "NETBIOSNAME": names.netbiosname,
669         "DNSDOMAIN": names.dnsdomain,
670         "REALM": names.realm,
671         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
672         "DOMAINDN": names.domaindn,
673         "ROOTDN": names.rootdn,
674         "CONFIGDN": names.configdn,
675         "SERVERDN": names.serverdn,
676         })
677         
678
679 def setup_self_join(samdb, names,
680                     machinepass, dnspass, 
681                     domainsid, invocationid, setup_path,
682                     policyguid):
683     """Join a host to its own domain."""
684     assert isinstance(invocationid, str)
685     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
686               "CONFIGDN": names.configdn, 
687               "SCHEMADN": names.schemadn,
688               "DOMAINDN": names.domaindn,
689               "SERVERDN": names.serverdn,
690               "INVOCATIONID": invocationid,
691               "NETBIOSNAME": names.netbiosname,
692               "DEFAULTSITE": names.sitename,
693               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
694               "MACHINEPASS_B64": b64encode(machinepass),
695               "DNSPASS_B64": b64encode(dnspass),
696               "REALM": names.realm,
697               "DOMAIN": names.domain,
698               "DNSDOMAIN": names.dnsdomain})
699     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
700               "POLICYGUID": policyguid,
701               "DNSDOMAIN": names.dnsdomain,
702               "DOMAINSID": str(domainsid),
703               "DOMAINDN": names.domaindn})
704
705
706 def setup_samdb(path, setup_path, session_info, credentials, lp, 
707                 names, message, 
708                 domainsid, aci, domainguid, policyguid, 
709                 fill, adminpass, krbtgtpass, 
710                 machinepass, invocationid, dnspass,
711                 serverrole, ldap_backend=None, 
712                 ldap_backend_type=None):
713     """Setup a complete SAM Database.
714     
715     :note: This will wipe the main SAM database file!
716     """
717
718     erase = (fill != FILL_DRS)
719
720     # Also wipes the database
721     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
722                            credentials=credentials, session_info=session_info,
723                            names=names, 
724                            ldap_backend=ldap_backend, serverrole=serverrole,
725                            ldap_backend_type=ldap_backend_type, erase=erase)
726
727     samdb = SamDB(path, session_info=session_info, 
728                   credentials=credentials, lp=lp)
729
730     if fill == FILL_DRS:
731        # We want to finish here, but setup the index before we do so
732         message("Setting up sam.ldb index")
733         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
734         return samdb
735
736     message("Pre-loading the Samba 4 and AD schema")
737     samdb.set_domain_sid(domainsid)
738     if serverrole == "domain controller":
739         samdb.set_invocation_id(invocationid)
740
741     load_schema(setup_path, samdb, names.schemadn, names.netbiosname, 
742                 names.configdn, names.sitename)
743
744     samdb.transaction_start()
745         
746     try:
747         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
748         if serverrole == "domain controller":
749             domain_oc = "domainDNS"
750         else:
751             domain_oc = "samba4LocalDomain"
752
753         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
754                 "DOMAINDN": names.domaindn,
755                 "ACI": aci,
756                 "DOMAIN_OC": domain_oc
757                 })
758
759         message("Modifying DomainDN: " + names.domaindn + "")
760         if domainguid is not None:
761             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
762         else:
763             domainguid_mod = ""
764
765         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
766             "LDAPTIME": timestring(int(time.time())),
767             "DOMAINSID": str(domainsid),
768             "SCHEMADN": names.schemadn, 
769             "NETBIOSNAME": names.netbiosname,
770             "DEFAULTSITE": names.sitename,
771             "CONFIGDN": names.configdn,
772             "SERVERDN": names.serverdn,
773             "POLICYGUID": policyguid,
774             "DOMAINDN": names.domaindn,
775             "DOMAINGUID_MOD": domainguid_mod,
776             })
777
778         message("Adding configuration container (permitted to fail)")
779         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
780             "CONFIGDN": names.configdn, 
781             "ACI": aci,
782             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
783             })
784         message("Modifying configuration container")
785         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
786             "CONFIGDN": names.configdn, 
787             "SCHEMADN": names.schemadn,
788             })
789
790         message("Adding schema container (permitted to fail)")
791         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
792             "SCHEMADN": names.schemadn,
793             "ACI": aci,
794             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
795             })
796         message("Modifying schema container")
797
798         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
799
800         setup_modify_ldif(samdb, 
801             setup_path("provision_schema_basedn_modify.ldif"), {
802             "SCHEMADN": names.schemadn,
803             "NETBIOSNAME": names.netbiosname,
804             "DEFAULTSITE": names.sitename,
805             "CONFIGDN": names.configdn,
806             "SERVERDN": names.serverdn,
807             "PREFIXMAP_B64": b64encode(prefixmap)
808             })
809
810         message("Setting up sam.ldb Samba4 schema")
811         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
812                        {"SCHEMADN": names.schemadn })
813         message("Setting up sam.ldb AD schema")
814         setup_add_ldif(samdb, setup_path("schema.ldif"), 
815                        {"SCHEMADN": names.schemadn})
816
817         message("Setting up sam.ldb configuration data")
818         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
819             "CONFIGDN": names.configdn,
820             "NETBIOSNAME": names.netbiosname,
821             "DEFAULTSITE": names.sitename,
822             "DNSDOMAIN": names.dnsdomain,
823             "DOMAIN": names.domain,
824             "SCHEMADN": names.schemadn,
825             "DOMAINDN": names.domaindn,
826             "SERVERDN": names.serverdn
827             })
828
829         message("Setting up display specifiers")
830         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
831                        {"CONFIGDN": names.configdn})
832
833         message("Adding users container (permitted to fail)")
834         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
835                 "DOMAINDN": names.domaindn})
836         message("Modifying users container")
837         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
838                 "DOMAINDN": names.domaindn})
839         message("Adding computers container (permitted to fail)")
840         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
841                 "DOMAINDN": names.domaindn})
842         message("Modifying computers container")
843         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
844                 "DOMAINDN": names.domaindn})
845         message("Setting up sam.ldb data")
846         setup_add_ldif(samdb, setup_path("provision.ldif"), {
847             "DOMAINDN": names.domaindn,
848             "NETBIOSNAME": names.netbiosname,
849             "DEFAULTSITE": names.sitename,
850             "CONFIGDN": names.configdn,
851             "SERVERDN": names.serverdn
852             })
853
854         if fill == FILL_FULL:
855             message("Setting up sam.ldb users and groups")
856             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
857                 "DOMAINDN": names.domaindn,
858                 "DOMAINSID": str(domainsid),
859                 "CONFIGDN": names.configdn,
860                 "ADMINPASS_B64": b64encode(adminpass),
861                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
862                 })
863
864             if serverrole == "domain controller":
865                 message("Setting up self join")
866                 setup_self_join(samdb, names=names, invocationid=invocationid, 
867                                 dnspass=dnspass,  
868                                 machinepass=machinepass, 
869                                 domainsid=domainsid, policyguid=policyguid,
870                                 setup_path=setup_path)
871
872     #We want to setup the index last, as adds are faster unindexed
873         message("Setting up sam.ldb index")
874         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
875     except:
876         samdb.transaction_cancel()
877         raise
878
879     samdb.transaction_commit()
880     return samdb
881
882
883 FILL_FULL = "FULL"
884 FILL_NT4SYNC = "NT4SYNC"
885 FILL_DRS = "DRS"
886
887 def provision(setup_dir, message, session_info, 
888               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
889               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
890               serverdn=None,
891               domain=None, hostname=None, hostip=None, hostip6=None, 
892               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
893               policyguid=None, invocationid=None, machinepass=None, 
894               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
895               wheel=None, backup=None, aci=None, serverrole=None, 
896               ldap_backend=None, ldap_backend_type=None, sitename=None):
897     """Provision samba4
898     
899     :note: caution, this wipes all existing data!
900     """
901
902     def setup_path(file):
903         return os.path.join(setup_dir, file)
904
905     if domainsid is None:
906         domainsid = security.random_sid()
907     else:
908         domainsid = security.Sid(domainsid)
909
910     if policyguid is None:
911         policyguid = str(uuid.uuid4())
912     if adminpass is None:
913         adminpass = misc.random_password(12)
914     if krbtgtpass is None:
915         krbtgtpass = misc.random_password(12)
916     if machinepass is None:
917         machinepass  = misc.random_password(12)
918     if dnspass is None:
919         dnspass = misc.random_password(12)
920     root_uid = findnss_uid([root or "root"])
921     nobody_uid = findnss_uid([nobody or "nobody"])
922     users_gid = findnss_gid([users or "users"])
923     if wheel is None:
924         wheel_gid = findnss_gid(["wheel", "adm"])
925     else:
926         wheel_gid = findnss_gid([wheel])
927     if aci is None:
928         aci = "# no aci for local ldb"
929
930     if targetdir is not None:
931         if (not os.path.exists(os.path.join(targetdir, "etc"))):
932             os.makedirs(os.path.join(targetdir, "etc"))
933         smbconf = os.path.join(targetdir, "etc", "smb.conf")
934
935     # only install a new smb.conf if there isn't one there already
936     if not os.path.exists(smbconf):
937         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
938                      targetdir)
939
940     lp = param.LoadParm()
941     lp.load(smbconf)
942
943     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
944                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
945                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
946                         serverdn=serverdn)
947
948     paths = provision_paths_from_lp(lp, names.dnsdomain)
949
950     if hostip is None:
951         hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
952
953     if hostip6 is None:
954         try:
955             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
956         except socket.gaierror: 
957             pass
958
959     if serverrole is None:
960         serverrole = lp.get("server role")
961
962     assert serverrole in ("domain controller", "member server", "standalone")
963     if invocationid is None and serverrole == "domain controller":
964         invocationid = str(uuid.uuid4())
965
966     if not os.path.exists(paths.private_dir):
967         os.mkdir(paths.private_dir)
968
969     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
970     
971     if ldap_backend is not None:
972         if ldap_backend == "ldapi":
973             # provision-backend will set this path suggested slapd command line / fedorads.inf
974             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
975              
976     # only install a new shares config db if there is none
977     if not os.path.exists(paths.shareconf):
978         message("Setting up share.ldb")
979         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
980                         credentials=credentials, lp=lp)
981         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
982
983      
984     message("Setting up secrets.ldb")
985     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
986                                   session_info=session_info, 
987                                   credentials=credentials, lp=lp)
988
989     message("Setting up the registry")
990     setup_registry(paths.hklm, setup_path, session_info, 
991                    credentials=credentials, lp=lp)
992
993     message("Setting up templates db")
994     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
995                       credentials=credentials, lp=lp)
996
997     message("Setting up idmap db")
998     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
999                           credentials=credentials, lp=lp)
1000
1001     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1002                         credentials=credentials, lp=lp, names=names,
1003                         message=message, 
1004                         domainsid=domainsid, 
1005                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1006                         fill=samdb_fill, 
1007                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1008                         invocationid=invocationid, 
1009                         machinepass=machinepass, dnspass=dnspass,
1010                         serverrole=serverrole, ldap_backend=ldap_backend, 
1011                         ldap_backend_type=ldap_backend_type)
1012
1013     if lp.get("server role") == "domain controller":
1014         if paths.netlogon is None:
1015             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1016             message("Please either remove %s or see the template at %s" % 
1017                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1018             assert(paths.netlogon is not None)
1019
1020         if paths.sysvol is None:
1021             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1022             message("Please either remove %s or see the template at %s" % 
1023                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1024             assert(paths.sysvol is not None)            
1025             
1026         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1027                                    "{" + policyguid + "}")
1028         os.makedirs(policy_path, 0755)
1029         open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1030         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1031         os.makedirs(os.path.join(policy_path, "User"), 0755)
1032         if not os.path.isdir(paths.netlogon):
1033             os.makedirs(paths.netlogon, 0755)
1034
1035     if samdb_fill == FILL_FULL:
1036         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1037                             root_uid=root_uid, nobody_uid=nobody_uid,
1038                             users_gid=users_gid, wheel_gid=wheel_gid)
1039
1040         message("Setting up sam.ldb rootDSE marking as synchronized")
1041         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1042
1043         # Only make a zone file on the first DC, it should be replicated with DNS replication
1044         if serverrole == "domain controller":
1045             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1046                               credentials=credentials, lp=lp)
1047             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1048                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1049                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1050                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1051                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1052
1053             samdb = SamDB(paths.samdb, session_info=session_info, 
1054                       credentials=credentials, lp=lp)
1055
1056             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1057             assert isinstance(domainguid, str)
1058             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1059                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1060                                        scope=SCOPE_SUBTREE)
1061             assert isinstance(hostguid, str)
1062
1063             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1064                              domaindn=names.domaindn, hostip=hostip,
1065                              hostip6=hostip6, hostname=names.hostname,
1066                              dnspass=dnspass, realm=names.realm,
1067                              domainguid=domainguid, hostguid=hostguid)
1068
1069             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1070                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1071
1072             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1073                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1074                               keytab_name=paths.dns_keytab)
1075             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1076             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1077
1078             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1079                              hostname=names.hostname, realm=names.realm)
1080             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1081
1082     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1083                                ldapi_url)
1084
1085     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1086
1087     message("Once the above files are installed, your Samba4 server will be ready to use")
1088     message("Server Role:    %s" % serverrole)
1089     message("Hostname:       %s" % names.hostname)
1090     message("NetBIOS Domain: %s" % names.domain)
1091     message("DNS Domain:     %s" % names.dnsdomain)
1092     message("DOMAIN SID:     %s" % str(domainsid))
1093     message("Admin password: %s" % adminpass)
1094
1095     result = ProvisionResult()
1096     result.domaindn = domaindn
1097     result.paths = paths
1098     result.lp = lp
1099     result.samdb = samdb
1100     return result
1101
1102
1103 def provision_become_dc(setup_dir=None,
1104                         smbconf=None, targetdir=None, realm=None, 
1105                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1106                         serverdn=None,
1107                         domain=None, hostname=None, domainsid=None, 
1108                         adminpass=None, krbtgtpass=None, domainguid=None, 
1109                         policyguid=None, invocationid=None, machinepass=None, 
1110                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1111                         wheel=None, backup=None, aci=None, serverrole=None, 
1112                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1113
1114     def message(text):
1115         """print a message if quiet is not set."""
1116         print text
1117
1118     return provision(setup_dir, message, system_session(), None,
1119               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1120               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1121               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1122     
1123
1124 def setup_db_config(setup_path, dbdir):
1125     """Setup a Berkeley database.
1126     
1127     :param setup_path: Setup path function.
1128     :param dbdir: Database directory."""
1129     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1130         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1131     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1132         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1133     
1134     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1135                {"LDAPDBDIR": dbdir})
1136     
1137
1138
1139 def provision_backend(setup_dir=None, message=None,
1140                       smbconf=None, targetdir=None, realm=None, 
1141                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1142                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1143                       ldap_backend_type=None, ldap_backend_port=None):
1144
1145     def setup_path(file):
1146         return os.path.join(setup_dir, file)
1147
1148     if hostname is None:
1149         hostname = socket.gethostname().split(".")[0].lower()
1150
1151     if root is None:
1152         root = findnss(pwd.getpwnam, ["root"])[0]
1153
1154     if adminpass is None:
1155         adminpass = misc.random_password(12)
1156
1157     if targetdir is not None:
1158         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1159             os.makedirs(os.path.join(targetdir, "etc"))
1160         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1161
1162     # only install a new smb.conf if there isn't one there already
1163     if not os.path.exists(smbconf):
1164         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1165                      targetdir)
1166
1167     lp = param.LoadParm()
1168     lp.load(smbconf)
1169
1170     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1171                         dnsdomain=realm, serverrole=serverrole, 
1172                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1173                         schemadn=schemadn)
1174
1175     paths = provision_paths_from_lp(lp, names.dnsdomain)
1176
1177     if not os.path.isdir(paths.ldapdir):
1178         os.makedirs(paths.ldapdir, 0700)
1179     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1180     try:
1181         os.unlink(schemadb_path)
1182     except:
1183         pass
1184
1185     schemadb = Ldb(schemadb_path, lp=lp)
1186  
1187     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1188
1189     setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1190                    {"SCHEMADN": names.schemadn,
1191                     "ACI": "#",
1192                     "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
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     """Load schema for the SamDB.
1431     
1432     :param samdb: Load a schema into a SamDB.
1433     :param setup_path: Setup path function.
1434     :param schemadn: DN of the schema
1435     :param netbiosname: NetBIOS name of the host.
1436     :param configdn: DN of the configuration
1437     """
1438     schema_data = open(setup_path("schema.ldif"), 'r').read()
1439     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1440     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1441     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1442     prefixmap = b64encode(prefixmap)
1443
1444     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1445     head_data = substitute_var(head_data, {
1446                     "SCHEMADN": schemadn,
1447                     "NETBIOSNAME": netbiosname,
1448                     "CONFIGDN": configdn,
1449                     "DEFAULTSITE":sitename,
1450                     "PREFIXMAP_B64":prefixmap
1451     })
1452     samdb.attach_schema_from_ldif(head_data, schema_data)
1453