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