Print prefixMap in a human-readable format.
[tprouty/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 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     return secrets_ldb
608
609
610 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
611     """Setup the templates database.
612
613     :param path: Path to the database.
614     :param setup_path: Function for obtaining the path to setup files.
615     :param session_info: Session info
616     :param credentials: Credentials
617     :param lp: Loadparm context
618     """
619     templates_ldb = SamDB(path, session_info=session_info,
620                           credentials=credentials, lp=lp)
621     templates_ldb.erase()
622     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
623
624
625 def setup_registry(path, setup_path, session_info, credentials, lp):
626     """Setup the registry.
627     
628     :param path: Path to the registry database
629     :param setup_path: Function that returns the path to a setup.
630     :param session_info: Session information
631     :param credentials: Credentials
632     :param lp: Loadparm context
633     """
634     reg = registry.Registry()
635     hive = registry.open_ldb(path, session_info=session_info, 
636                          credentials=credentials, lp_ctx=lp)
637     reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
638     provision_reg = setup_path("provision.reg")
639     assert os.path.exists(provision_reg)
640     reg.diff_apply(provision_reg)
641
642
643 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
644     """Setup the idmap database.
645
646     :param path: path to the idmap database
647     :param setup_path: Function that returns a path to a setup file
648     :param session_info: Session information
649     :param credentials: Credentials
650     :param lp: Loadparm context
651     """
652     if os.path.exists(path):
653         os.unlink(path)
654
655     idmap_ldb = IDmapDB(path, session_info=session_info,
656                         credentials=credentials, lp=lp)
657
658     idmap_ldb.erase()
659     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
660     return idmap_ldb
661
662
663 def setup_samdb_rootdse(samdb, setup_path, names):
664     """Setup the SamDB rootdse.
665
666     :param samdb: Sam Database handle
667     :param setup_path: Obtain setup path
668     """
669     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
670         "SCHEMADN": names.schemadn, 
671         "NETBIOSNAME": names.netbiosname,
672         "DNSDOMAIN": names.dnsdomain,
673         "REALM": names.realm,
674         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
675         "DOMAINDN": names.domaindn,
676         "ROOTDN": names.rootdn,
677         "CONFIGDN": names.configdn,
678         "SERVERDN": names.serverdn,
679         })
680         
681
682 def setup_self_join(samdb, names,
683                     machinepass, dnspass, 
684                     domainsid, invocationid, setup_path,
685                     policyguid):
686     """Join a host to its own domain."""
687     assert isinstance(invocationid, str)
688     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
689               "CONFIGDN": names.configdn, 
690               "SCHEMADN": names.schemadn,
691               "DOMAINDN": names.domaindn,
692               "SERVERDN": names.serverdn,
693               "INVOCATIONID": invocationid,
694               "NETBIOSNAME": names.netbiosname,
695               "DEFAULTSITE": names.sitename,
696               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
697               "MACHINEPASS_B64": b64encode(machinepass),
698               "DNSPASS_B64": b64encode(dnspass),
699               "REALM": names.realm,
700               "DOMAIN": names.domain,
701               "DNSDOMAIN": names.dnsdomain})
702     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
703               "POLICYGUID": policyguid,
704               "DNSDOMAIN": names.dnsdomain,
705               "DOMAINSID": str(domainsid),
706               "DOMAINDN": names.domaindn})
707
708
709 def setup_samdb(path, setup_path, session_info, credentials, lp, 
710                 names, message, 
711                 domainsid, aci, domainguid, policyguid, 
712                 fill, adminpass, krbtgtpass, 
713                 machinepass, invocationid, dnspass,
714                 serverrole, ldap_backend=None, 
715                 ldap_backend_type=None):
716     """Setup a complete SAM Database.
717     
718     :note: This will wipe the main SAM database file!
719     """
720
721     erase = (fill != FILL_DRS)
722
723     # Also wipes the database
724     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
725                            credentials=credentials, session_info=session_info,
726                            names=names, 
727                            ldap_backend=ldap_backend, serverrole=serverrole,
728                            ldap_backend_type=ldap_backend_type, erase=erase)
729
730     samdb = SamDB(path, session_info=session_info, 
731                   credentials=credentials, lp=lp)
732
733     if fill == FILL_DRS:
734        # We want to finish here, but setup the index before we do so
735         message("Setting up sam.ldb index")
736         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
737         return samdb
738
739     message("Pre-loading the Samba 4 and AD schema")
740     samdb.set_domain_sid(domainsid)
741     if serverrole == "domain controller":
742         samdb.set_invocation_id(invocationid)
743
744     load_schema(setup_path, samdb, names.schemadn, names.netbiosname, 
745                 names.configdn, names.sitename)
746
747     samdb.transaction_start()
748         
749     try:
750         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
751         if serverrole == "domain controller":
752             domain_oc = "domainDNS"
753         else:
754             domain_oc = "samba4LocalDomain"
755
756         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
757             "DOMAINDN": names.domaindn,
758             "ACI": aci,
759             "DOMAIN_OC": domain_oc
760             })
761
762         message("Modifying DomainDN: " + names.domaindn + "")
763         if domainguid is not None:
764             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
765         else:
766             domainguid_mod = ""
767
768         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
769             "LDAPTIME": timestring(int(time.time())),
770             "DOMAINSID": str(domainsid),
771             "SCHEMADN": names.schemadn, 
772             "NETBIOSNAME": names.netbiosname,
773             "DEFAULTSITE": names.sitename,
774             "CONFIGDN": names.configdn,
775             "SERVERDN": names.serverdn,
776             "POLICYGUID": policyguid,
777             "DOMAINDN": names.domaindn,
778             "DOMAINGUID_MOD": domainguid_mod,
779             })
780
781         message("Adding configuration container (permitted to fail)")
782         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
783             "CONFIGDN": names.configdn, 
784             "ACI": aci,
785             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
786             })
787         message("Modifying configuration container")
788         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
789             "CONFIGDN": names.configdn, 
790             "SCHEMADN": names.schemadn,
791             })
792
793         message("Adding schema container (permitted to fail)")
794         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
795             "SCHEMADN": names.schemadn,
796             "ACI": aci,
797             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
798             })
799         message("Modifying schema container")
800
801         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
802
803         setup_modify_ldif(samdb, 
804             setup_path("provision_schema_basedn_modify.ldif"), {
805             "SCHEMADN": names.schemadn,
806             "NETBIOSNAME": names.netbiosname,
807             "DEFAULTSITE": names.sitename,
808             "CONFIGDN": names.configdn,
809             "SERVERDN": names.serverdn,
810             "PREFIXMAP_B64": b64encode(prefixmap)
811             })
812
813         message("Setting up sam.ldb Samba4 schema")
814         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
815                        {"SCHEMADN": names.schemadn })
816         message("Setting up sam.ldb AD schema")
817         setup_add_ldif(samdb, setup_path("schema.ldif"), 
818                        {"SCHEMADN": names.schemadn})
819
820         message("Setting up sam.ldb configuration data")
821         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
822             "CONFIGDN": names.configdn,
823             "NETBIOSNAME": names.netbiosname,
824             "DEFAULTSITE": names.sitename,
825             "DNSDOMAIN": names.dnsdomain,
826             "DOMAIN": names.domain,
827             "SCHEMADN": names.schemadn,
828             "DOMAINDN": names.domaindn,
829             "SERVERDN": names.serverdn
830             })
831
832         message("Setting up display specifiers")
833         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
834                        {"CONFIGDN": names.configdn})
835
836         message("Adding users container (permitted to fail)")
837         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
838                 "DOMAINDN": names.domaindn})
839         message("Modifying users container")
840         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
841                 "DOMAINDN": names.domaindn})
842         message("Adding computers container (permitted to fail)")
843         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
844                 "DOMAINDN": names.domaindn})
845         message("Modifying computers container")
846         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
847                 "DOMAINDN": names.domaindn})
848         message("Setting up sam.ldb data")
849         setup_add_ldif(samdb, setup_path("provision.ldif"), {
850             "DOMAINDN": names.domaindn,
851             "NETBIOSNAME": names.netbiosname,
852             "DEFAULTSITE": names.sitename,
853             "CONFIGDN": names.configdn,
854             "SERVERDN": names.serverdn
855             })
856
857         if fill == FILL_FULL:
858             message("Setting up sam.ldb users and groups")
859             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
860                 "DOMAINDN": names.domaindn,
861                 "DOMAINSID": str(domainsid),
862                 "CONFIGDN": names.configdn,
863                 "ADMINPASS_B64": b64encode(adminpass),
864                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
865                 })
866
867             if serverrole == "domain controller":
868                 message("Setting up self join")
869                 setup_self_join(samdb, names=names, invocationid=invocationid, 
870                                 dnspass=dnspass,  
871                                 machinepass=machinepass, 
872                                 domainsid=domainsid, policyguid=policyguid,
873                                 setup_path=setup_path)
874
875     #We want to setup the index last, as adds are faster unindexed
876         message("Setting up sam.ldb index")
877         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
878     except:
879         samdb.transaction_cancel()
880         raise
881
882     samdb.transaction_commit()
883     return samdb
884
885
886 FILL_FULL = "FULL"
887 FILL_NT4SYNC = "NT4SYNC"
888 FILL_DRS = "DRS"
889
890 def provision(setup_dir, message, session_info, 
891               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
892               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
893               serverdn=None,
894               domain=None, hostname=None, hostip=None, hostip6=None, 
895               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
896               policyguid=None, invocationid=None, machinepass=None, 
897               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
898               wheel=None, backup=None, aci=None, serverrole=None, 
899               ldap_backend=None, ldap_backend_type=None, sitename=None):
900     """Provision samba4
901     
902     :note: caution, this wipes all existing data!
903     """
904
905     def setup_path(file):
906         return os.path.join(setup_dir, file)
907
908     if domainsid is None:
909         domainsid = security.random_sid()
910     else:
911         domainsid = security.Sid(domainsid)
912
913     if policyguid is None:
914         policyguid = str(uuid.uuid4())
915     if adminpass is None:
916         adminpass = misc.random_password(12)
917     if krbtgtpass is None:
918         krbtgtpass = misc.random_password(12)
919     if machinepass is None:
920         machinepass  = misc.random_password(12)
921     if dnspass is None:
922         dnspass = misc.random_password(12)
923     root_uid = findnss_uid([root or "root"])
924     nobody_uid = findnss_uid([nobody or "nobody"])
925     users_gid = findnss_gid([users or "users"])
926     if wheel is None:
927         wheel_gid = findnss_gid(["wheel", "adm"])
928     else:
929         wheel_gid = findnss_gid([wheel])
930     if aci is None:
931         aci = "# no aci for local ldb"
932
933     if smbconf is None:
934         os.makedirs(os.path.join(targetdir, "etc"))
935         smbconf = os.path.join(targetdir, "etc", "smb.conf")
936
937     # only install a new smb.conf if there isn't one there already
938     if not os.path.exists(smbconf):
939         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
940                      targetdir)
941
942     lp = param.LoadParm()
943     lp.load(smbconf)
944
945     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
946                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
947                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
948                         serverdn=serverdn)
949
950     paths = provision_paths_from_lp(lp, names.dnsdomain)
951
952     if hostip is None:
953         hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
954
955     if hostip6 is None:
956         try:
957             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
958         except socket.gaierror: 
959             pass
960
961     if serverrole is None:
962         serverrole = lp.get("server role")
963
964     assert serverrole in ("domain controller", "member server", "standalone")
965     if invocationid is None and serverrole == "domain controller":
966         invocationid = str(uuid.uuid4())
967
968     if not os.path.exists(paths.private_dir):
969         os.mkdir(paths.private_dir)
970
971     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
972     
973     if ldap_backend is not None:
974         if ldap_backend == "ldapi":
975             # provision-backend will set this path suggested slapd command line / fedorads.inf
976             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
977              
978     # only install a new shares config db if there is none
979     if not os.path.exists(paths.shareconf):
980         message("Setting up share.ldb")
981         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
982                         credentials=credentials, lp=lp)
983         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
984
985      
986     message("Setting up secrets.ldb")
987     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
988                                   session_info=session_info, 
989                                   credentials=credentials, lp=lp)
990
991     message("Setting up the registry")
992     setup_registry(paths.hklm, setup_path, session_info, 
993                    credentials=credentials, lp=lp)
994
995     message("Setting up templates db")
996     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
997                       credentials=credentials, lp=lp)
998
999     message("Setting up idmap db")
1000     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1001                           credentials=credentials, lp=lp)
1002
1003     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1004                         credentials=credentials, lp=lp, names=names,
1005                         message=message, 
1006                         domainsid=domainsid, 
1007                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1008                         fill=samdb_fill, 
1009                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1010                         invocationid=invocationid, 
1011                         machinepass=machinepass, dnspass=dnspass,
1012                         serverrole=serverrole, ldap_backend=ldap_backend, 
1013                         ldap_backend_type=ldap_backend_type)
1014
1015     if lp.get("server role") == "domain controller":
1016         if paths.netlogon is None:
1017             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1018             message("Please either remove %s or see the template at %s" % 
1019                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1020             assert(paths.netlogon is not None)
1021
1022         if paths.sysvol is None:
1023             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1024             message("Please either remove %s or see the template at %s" % 
1025                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1026             assert(paths.sysvol is not None)            
1027             
1028         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1029                                    "{" + policyguid + "}")
1030         os.makedirs(policy_path, 0755)
1031         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1032         os.makedirs(os.path.join(policy_path, "User"), 0755)
1033         if not os.path.isdir(paths.netlogon):
1034             os.makedirs(paths.netlogon, 0755)
1035
1036     if samdb_fill == FILL_FULL:
1037         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1038                             root_uid=root_uid, nobody_uid=nobody_uid,
1039                             users_gid=users_gid, wheel_gid=wheel_gid)
1040
1041         message("Setting up sam.ldb rootDSE marking as synchronized")
1042         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1043
1044         # Only make a zone file on the first DC, it should be replicated with DNS replication
1045         if serverrole == "domain controller":
1046             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1047                               credentials=credentials, lp=lp)
1048             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1049                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1050                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1051                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1052                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1053
1054             samdb = SamDB(paths.samdb, session_info=session_info, 
1055                       credentials=credentials, lp=lp)
1056
1057             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1058             assert isinstance(domainguid, str)
1059             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1060                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1061                                        scope=SCOPE_SUBTREE)
1062             assert isinstance(hostguid, str)
1063
1064             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1065                              domaindn=names.domaindn, hostip=hostip,
1066                              hostip6=hostip6, hostname=names.hostname,
1067                              dnspass=dnspass, realm=names.realm,
1068                              domainguid=domainguid, hostguid=hostguid)
1069             message("Please install the zone located in %s into your DNS server" % paths.dns)
1070
1071             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1072                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1073                               keytab_name=paths.dns_keytab)
1074             message("See %s for example configuration statements for secure GSS-TSIG updates" % paths.namedconf)
1075
1076             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1077                              hostname=names.hostname, realm=names.realm)
1078             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1079
1080     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1081                                ldapi_url)
1082
1083     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1084
1085     message("Once the above files are installed, your Samba4 server will be ready to use")
1086     message("Server Role:    %s" % serverrole)
1087     message("Hostname:       %s" % names.hostname)
1088     message("NetBIOS Domain: %s" % names.domain)
1089     message("DNS Domain:     %s" % names.dnsdomain)
1090     message("DOMAIN SID:     %s" % str(domainsid))
1091     message("Admin password: %s" % adminpass)
1092
1093     result = ProvisionResult()
1094     result.domaindn = domaindn
1095     result.paths = paths
1096     result.lp = lp
1097     result.samdb = samdb
1098     return result
1099
1100
1101 def provision_become_dc(setup_dir=None,
1102                         smbconf=None, targetdir=None, realm=None, 
1103                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1104                         serverdn=None,
1105                         domain=None, hostname=None, domainsid=None, 
1106                         adminpass=None, krbtgtpass=None, domainguid=None, 
1107                         policyguid=None, invocationid=None, machinepass=None, 
1108                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1109                         wheel=None, backup=None, aci=None, serverrole=None, 
1110                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1111
1112     def message(text):
1113         """print a message if quiet is not set."""
1114         print text
1115
1116     return provision(setup_dir, message, system_session(), None,
1117               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1118               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1119               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1120     
1121
1122 def setup_db_config(setup_path, dbdir):
1123     """Setup a Berkeley database.
1124     
1125     :param setup_path: Setup path function.
1126     :param dbdir: Database directory."""
1127     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1128         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1129     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1130         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1131     
1132     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1133                {"LDAPDBDIR": dbdir})
1134     
1135
1136
1137 def provision_backend(setup_dir=None, message=None,
1138                       smbconf=None, targetdir=None, realm=None, 
1139                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1140                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1141                       ldap_backend_type=None, ldap_backend_port=None):
1142
1143     def setup_path(file):
1144         return os.path.join(setup_dir, file)
1145
1146     if hostname is None:
1147         hostname = socket.gethostname().split(".")[0].lower()
1148
1149     if root is None:
1150         root = findnss(pwd.getpwnam, ["root"])[0]
1151
1152     if smbconf is None:
1153         etcdir = os.path.join(targetdir, "etc")
1154         os.makedirs(etcdir)
1155         smbconf = os.path.join(etcdir, "smb.conf")
1156
1157     # only install a new smb.conf if there isn't one there already
1158     if not os.path.exists(smbconf):
1159         make_smbconf(smbconf, setup_path, hostname, domain, realm, 
1160                               serverrole, targetdir)
1161
1162     lp = param.LoadParm()
1163     lp.load(smbconf)
1164
1165     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1166                         dnsdomain=realm, serverrole=serverrole, 
1167                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1168                         schemadn=schemadn)
1169
1170     paths = provision_paths_from_lp(lp, names.dnsdomain)
1171
1172     if not os.path.isdir(paths.ldapdir):
1173         os.makedirs(paths.ldapdir)
1174     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1175     try:
1176         os.unlink(schemadb_path)
1177     except:
1178         pass
1179
1180     schemadb = Ldb(schemadb_path, lp=lp)
1181  
1182     setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1183                    {"SCHEMADN": names.schemadn,
1184                     "ACI": "#",
1185                     "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1186                     })
1187     setup_modify_ldif(schemadb, 
1188                       setup_path("provision_schema_basedn_modify.ldif"), \
1189                           {"SCHEMADN": names.schemadn,
1190                            "NETBIOSNAME": names.netbiosname,
1191                            "DEFAULTSITE": DEFAULTSITE,
1192                            "CONFIGDN": names.configdn,
1193                            "SERVERDN": names.serverdn
1194                            })
1195     
1196     setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"), 
1197                    {"SCHEMADN": names.schemadn })
1198     setup_add_ldif(schemadb, setup_path("schema.ldif"), 
1199                    {"SCHEMADN": names.schemadn})
1200
1201     if ldap_backend_type == "fedora-ds":
1202         if ldap_backend_port is not None:
1203             serverport = "ServerPort=%d" % ldap_backend_port
1204         else:
1205             serverport = ""
1206
1207         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1208                    {"ROOT": root,
1209                     "HOSTNAME": hostname,
1210                     "DNSDOMAIN": names.dnsdomain,
1211                     "LDAPDIR": paths.ldapdir,
1212                     "DOMAINDN": names.domaindn,
1213                     "LDAPMANAGERDN": names.ldapmanagerdn,
1214                     "LDAPMANAGERPASS": adminpass, 
1215                     "SERVERPORT": serverport})
1216         
1217         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1218                    {"CONFIGDN": names.configdn,
1219                     "SCHEMADN": names.schemadn,
1220                     })
1221         
1222         mapping = "schema-map-fedora-ds-1.0"
1223         backend_schema = "99_ad.ldif"
1224         
1225         slapdcommand="Initailise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1226        
1227     elif ldap_backend_type == "openldap":
1228         attrs = ["linkID", "lDAPDisplayName"]
1229     res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs)
1230
1231     memberof_config = "# Generated from schema in %s\n" % schemadb_path
1232     refint_attributes = ""
1233     for i in range (0, len(res)):
1234             expression = "(&(objectclass=attributeSchema)(linkID=%d))" % (int(res[i]["linkID"][0])+1)
1235             target = schemadb.searchone(basedn=names.schemadn, 
1236                                         expression=expression, 
1237                                         attribute="lDAPDisplayName", 
1238                                         scope=SCOPE_SUBTREE)
1239             if target is not None:
1240                 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0]
1241                 memberof_config += """overlay memberof
1242 memberof-dangling error
1243 memberof-refint TRUE
1244 memberof-group-oc top
1245 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1246 memberof-memberof-ad """ + target + """
1247 memberof-dangling-error 32
1248
1249 """
1250
1251     memberof_config += """
1252 overlay refint
1253 refint_attributes""" + refint_attributes + "\n"
1254     
1255     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1256                    {"DNSDOMAIN": names.dnsdomain,
1257                     "LDAPDIR": paths.ldapdir,
1258                     "DOMAINDN": names.domaindn,
1259                     "CONFIGDN": names.configdn,
1260                     "SCHEMADN": names.schemadn,
1261                     "LDAPMANAGERDN": names.ldapmanagerdn,
1262                     "LDAPMANAGERPASS": adminpass,
1263                     "MEMBEROF_CONFIG": memberof_config})
1264     setup_file(setup_path("modules.conf"), paths.modulesconf,
1265                    {"REALM": names.realm})
1266         
1267     setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "user")))
1268     setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "config")))
1269     setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "schema")))
1270     mapping = "schema-map-openldap-2.3"
1271     backend_schema = "backend-schema.schema"
1272
1273     ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1274     if ldap_backend_port is not None:
1275         server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1276     else:
1277         server_port_string = ""
1278     slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1279
1280     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)
1281
1282     os.system(schema_command)
1283
1284
1285     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type)
1286     message("Server Role:         %s" % serverrole)
1287     message("Hostname:            %s" % names.hostname)
1288     message("DNS Domain:          %s" % names.dnsdomain)
1289     message("Base DN:             %s" % names.domaindn)
1290     message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1291     message("LDAP admin password: %s" % adminpass)
1292     message(slapdcommand)
1293
1294
1295 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1296     """Create a PHP LDAP admin configuration file.
1297
1298     :param path: Path to write the configuration to.
1299     :param setup_path: Function to generate setup paths.
1300     """
1301     setup_file(setup_path("phpldapadmin-config.php"), path, 
1302             {"S4_LDAPI_URI": ldapi_uri})
1303
1304
1305 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1306                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1307     """Write out a DNS zone file, from the info in the current database.
1308
1309     :param path: Path of the new zone file.
1310     :param setup_path: Setup path function.
1311     :param dnsdomain: DNS Domain name
1312     :param domaindn: DN of the Domain
1313     :param hostip: Local IPv4 IP
1314     :param hostip6: Local IPv6 IP
1315     :param hostname: Local hostname
1316     :param dnspass: Password for DNS
1317     :param realm: Realm name
1318     :param domainguid: GUID of the domain.
1319     :param hostguid: GUID of the host.
1320     """
1321     assert isinstance(domainguid, str)
1322
1323     hostip6_base_line = ""
1324     hostip6_host_line = ""
1325
1326     if hostip6 is not None:
1327         hostip6_base_line = "            IN AAAA    " + hostip6
1328         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1329
1330     setup_file(setup_path("provision.zone"), path, {
1331             "DNSPASS_B64": b64encode(dnspass),
1332             "HOSTNAME": hostname,
1333             "DNSDOMAIN": dnsdomain,
1334             "REALM": realm,
1335             "HOSTIP": hostip,
1336             "DOMAINGUID": domainguid,
1337             "DATESTRING": time.strftime("%Y%m%d%H"),
1338             "DEFAULTSITE": DEFAULTSITE,
1339             "HOSTGUID": hostguid,
1340             "HOSTIP6_BASE_LINE": hostip6_base_line,
1341             "HOSTIP6_HOST_LINE": hostip6_host_line,
1342         })
1343
1344
1345 def create_named_conf(path, setup_path, realm, dnsdomain,
1346                       private_dir, keytab_name):
1347     """Write out a file containing zone statements suitable for inclusion in a
1348     named.conf file (including GSS-TSIG configuration).
1349     
1350     :param path: Path of the new named.conf file.
1351     :param setup_path: Setup path function.
1352     :param realm: Realm name
1353     :param dnsdomain: DNS Domain name
1354     :param private_dir: Path to private directory
1355     :param keytab_name: File name of DNS keytab file
1356     """
1357
1358     setup_file(setup_path("named.conf"), path, {
1359             "DNSDOMAIN": dnsdomain,
1360             "REALM": realm,
1361             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1362             "DNS_KEYTAB": keytab_name,
1363             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1364         })
1365
1366 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1367     """Write out a file containing zone statements suitable for inclusion in a
1368     named.conf file (including GSS-TSIG configuration).
1369     
1370     :param path: Path of the new named.conf file.
1371     :param setup_path: Setup path function.
1372     :param dnsdomain: DNS Domain name
1373     :param hostname: Local hostname
1374     :param realm: Realm name
1375     """
1376
1377     setup_file(setup_path("krb5.conf"), path, {
1378             "DNSDOMAIN": dnsdomain,
1379             "HOSTNAME": hostname,
1380             "REALM": realm,
1381         })
1382
1383
1384 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1385     """Load schema for the SamDB.
1386     
1387     :param samdb: Load a schema into a SamDB.
1388     :param setup_path: Setup path function.
1389     :param schemadn: DN of the schema
1390     :param netbiosname: NetBIOS name of the host.
1391     :param configdn: DN of the configuration
1392     """
1393     schema_data = open(setup_path("schema.ldif"), 'r').read()
1394     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1395     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1396     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1397     prefixmap = b64encode(prefixmap)
1398
1399     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1400     head_data = substitute_var(head_data, {
1401                     "SCHEMADN": schemadn,
1402                     "NETBIOSNAME": netbiosname,
1403                     "CONFIGDN": configdn,
1404                     "DEFAULTSITE":sitename,
1405                     "PREFIXMAP_B64":prefixmap
1406     })
1407     samdb.attach_schema_from_ldif(head_data, schema_data)
1408