93a4c8f50222e6fe76a7c22e31aeea7f11300013
[kai/samba-autobuild/.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 targetdir is not None:
934         if (not os.path.exists(os.path.join(targetdir, "etc"))):
935             os.makedirs(os.path.join(targetdir, "etc"))
936         smbconf = os.path.join(targetdir, "etc", "smb.conf")
937
938     # only install a new smb.conf if there isn't one there already
939     if not os.path.exists(smbconf):
940         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
941                      targetdir)
942
943     lp = param.LoadParm()
944     lp.load(smbconf)
945
946     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
947                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
948                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
949                         serverdn=serverdn)
950
951     paths = provision_paths_from_lp(lp, names.dnsdomain)
952
953     if hostip is None:
954         hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
955
956     if hostip6 is None:
957         try:
958             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
959         except socket.gaierror: 
960             pass
961
962     if serverrole is None:
963         serverrole = lp.get("server role")
964
965     assert serverrole in ("domain controller", "member server", "standalone")
966     if invocationid is None and serverrole == "domain controller":
967         invocationid = str(uuid.uuid4())
968
969     if not os.path.exists(paths.private_dir):
970         os.mkdir(paths.private_dir)
971
972     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
973     
974     if ldap_backend is not None:
975         if ldap_backend == "ldapi":
976             # provision-backend will set this path suggested slapd command line / fedorads.inf
977             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
978              
979     # only install a new shares config db if there is none
980     if not os.path.exists(paths.shareconf):
981         message("Setting up share.ldb")
982         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
983                         credentials=credentials, lp=lp)
984         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
985
986      
987     message("Setting up secrets.ldb")
988     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
989                                   session_info=session_info, 
990                                   credentials=credentials, lp=lp)
991
992     message("Setting up the registry")
993     setup_registry(paths.hklm, setup_path, session_info, 
994                    credentials=credentials, lp=lp)
995
996     message("Setting up templates db")
997     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
998                       credentials=credentials, lp=lp)
999
1000     message("Setting up idmap db")
1001     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1002                           credentials=credentials, lp=lp)
1003
1004     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1005                         credentials=credentials, lp=lp, names=names,
1006                         message=message, 
1007                         domainsid=domainsid, 
1008                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1009                         fill=samdb_fill, 
1010                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1011                         invocationid=invocationid, 
1012                         machinepass=machinepass, dnspass=dnspass,
1013                         serverrole=serverrole, ldap_backend=ldap_backend, 
1014                         ldap_backend_type=ldap_backend_type)
1015
1016     if lp.get("server role") == "domain controller":
1017         if paths.netlogon is None:
1018             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1019             message("Please either remove %s or see the template at %s" % 
1020                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1021             assert(paths.netlogon is not None)
1022
1023         if paths.sysvol is None:
1024             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1025             message("Please either remove %s or see the template at %s" % 
1026                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1027             assert(paths.sysvol is not None)            
1028             
1029         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1030                                    "{" + policyguid + "}")
1031         os.makedirs(policy_path, 0755)
1032         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1033         os.makedirs(os.path.join(policy_path, "User"), 0755)
1034         if not os.path.isdir(paths.netlogon):
1035             os.makedirs(paths.netlogon, 0755)
1036
1037     if samdb_fill == FILL_FULL:
1038         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1039                             root_uid=root_uid, nobody_uid=nobody_uid,
1040                             users_gid=users_gid, wheel_gid=wheel_gid)
1041
1042         message("Setting up sam.ldb rootDSE marking as synchronized")
1043         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1044
1045         # Only make a zone file on the first DC, it should be replicated with DNS replication
1046         if serverrole == "domain controller":
1047             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1048                               credentials=credentials, lp=lp)
1049             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1050                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1051                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1052                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1053                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1054
1055             samdb = SamDB(paths.samdb, session_info=session_info, 
1056                       credentials=credentials, lp=lp)
1057
1058             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1059             assert isinstance(domainguid, str)
1060             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1061                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1062                                        scope=SCOPE_SUBTREE)
1063             assert isinstance(hostguid, str)
1064
1065             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1066                              domaindn=names.domaindn, hostip=hostip,
1067                              hostip6=hostip6, hostname=names.hostname,
1068                              dnspass=dnspass, realm=names.realm,
1069                              domainguid=domainguid, hostguid=hostguid)
1070             message("Please install the zone located in %s into your DNS server" % paths.dns)
1071
1072             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1073                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1074                               keytab_name=paths.dns_keytab)
1075             message("See %s for example configuration statements for secure GSS-TSIG updates" % paths.namedconf)
1076
1077             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1078                              hostname=names.hostname, realm=names.realm)
1079             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1080
1081     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1082                                ldapi_url)
1083
1084     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1085
1086     message("Once the above files are installed, your Samba4 server will be ready to use")
1087     message("Server Role:    %s" % serverrole)
1088     message("Hostname:       %s" % names.hostname)
1089     message("NetBIOS Domain: %s" % names.domain)
1090     message("DNS Domain:     %s" % names.dnsdomain)
1091     message("DOMAIN SID:     %s" % str(domainsid))
1092     message("Admin password: %s" % adminpass)
1093
1094     result = ProvisionResult()
1095     result.domaindn = domaindn
1096     result.paths = paths
1097     result.lp = lp
1098     result.samdb = samdb
1099     return result
1100
1101
1102 def provision_become_dc(setup_dir=None,
1103                         smbconf=None, targetdir=None, realm=None, 
1104                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1105                         serverdn=None,
1106                         domain=None, hostname=None, domainsid=None, 
1107                         adminpass=None, krbtgtpass=None, domainguid=None, 
1108                         policyguid=None, invocationid=None, machinepass=None, 
1109                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1110                         wheel=None, backup=None, aci=None, serverrole=None, 
1111                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1112
1113     def message(text):
1114         """print a message if quiet is not set."""
1115         print text
1116
1117     return provision(setup_dir, message, system_session(), None,
1118               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1119               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1120               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1121     
1122
1123 def setup_db_config(setup_path, dbdir):
1124     """Setup a Berkeley database.
1125     
1126     :param setup_path: Setup path function.
1127     :param dbdir: Database directory."""
1128     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1129         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1130     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1131         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1132     
1133     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1134                {"LDAPDBDIR": dbdir})
1135     
1136
1137
1138 def provision_backend(setup_dir=None, message=None,
1139                       smbconf=None, targetdir=None, realm=None, 
1140                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1141                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1142                       ldap_backend_type=None, ldap_backend_port=None):
1143
1144     def setup_path(file):
1145         return os.path.join(setup_dir, file)
1146
1147     if hostname is None:
1148         hostname = socket.gethostname().split(".")[0].lower()
1149
1150     if root is None:
1151         root = findnss(pwd.getpwnam, ["root"])[0]
1152
1153     if smbconf is None:
1154         etcdir = os.path.join(targetdir, "etc")
1155         os.makedirs(etcdir)
1156         smbconf = os.path.join(etcdir, "smb.conf")
1157
1158     # only install a new smb.conf if there isn't one there already
1159     if not os.path.exists(smbconf):
1160         make_smbconf(smbconf, setup_path, hostname, domain, realm, 
1161                               serverrole, targetdir)
1162
1163     lp = param.LoadParm()
1164     lp.load(smbconf)
1165
1166     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1167                         dnsdomain=realm, serverrole=serverrole, 
1168                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1169                         schemadn=schemadn)
1170
1171     paths = provision_paths_from_lp(lp, names.dnsdomain)
1172
1173     if not os.path.isdir(paths.ldapdir):
1174         os.makedirs(paths.ldapdir)
1175     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1176     try:
1177         os.unlink(schemadb_path)
1178     except:
1179         pass
1180
1181     schemadb = Ldb(schemadb_path, lp=lp)
1182  
1183     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1184
1185     setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1186                    {"SCHEMADN": names.schemadn,
1187                     "ACI": "#",
1188                     "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1189                     })
1190     setup_modify_ldif(schemadb, 
1191                       setup_path("provision_schema_basedn_modify.ldif"), \
1192                           {"SCHEMADN": names.schemadn,
1193                            "NETBIOSNAME": names.netbiosname,
1194                            "DEFAULTSITE": DEFAULTSITE,
1195                            "CONFIGDN": names.configdn,
1196                            "SERVERDN": names.serverdn,
1197                            "PREFIXMAP_B64": b64encode(prefixmap)
1198                            })
1199     
1200     setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"), 
1201                    {"SCHEMADN": names.schemadn })
1202     setup_add_ldif(schemadb, setup_path("schema.ldif"), 
1203                    {"SCHEMADN": names.schemadn})
1204
1205     if ldap_backend_type == "fedora-ds":
1206         if ldap_backend_port is not None:
1207             serverport = "ServerPort=%d" % ldap_backend_port
1208         else:
1209             serverport = ""
1210
1211         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1212                    {"ROOT": root,
1213                     "HOSTNAME": hostname,
1214                     "DNSDOMAIN": names.dnsdomain,
1215                     "LDAPDIR": paths.ldapdir,
1216                     "DOMAINDN": names.domaindn,
1217                     "LDAPMANAGERDN": names.ldapmanagerdn,
1218                     "LDAPMANAGERPASS": adminpass, 
1219                     "SERVERPORT": serverport})
1220         
1221         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1222                    {"CONFIGDN": names.configdn,
1223                     "SCHEMADN": names.schemadn,
1224                     })
1225         
1226         mapping = "schema-map-fedora-ds-1.0"
1227         backend_schema = "99_ad.ldif"
1228         
1229         slapdcommand="Initailise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1230        
1231     elif ldap_backend_type == "openldap":
1232         attrs = ["linkID", "lDAPDisplayName"]
1233     res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs)
1234
1235     memberof_config = "# Generated from schema in %s\n" % schemadb_path
1236     refint_attributes = ""
1237     for i in range (0, len(res)):
1238             expression = "(&(objectclass=attributeSchema)(linkID=%d))" % (int(res[i]["linkID"][0])+1)
1239             target = schemadb.searchone(basedn=names.schemadn, 
1240                                         expression=expression, 
1241                                         attribute="lDAPDisplayName", 
1242                                         scope=SCOPE_SUBTREE)
1243             if target is not None:
1244                 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0]
1245                 memberof_config += """overlay memberof
1246 memberof-dangling error
1247 memberof-refint TRUE
1248 memberof-group-oc top
1249 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1250 memberof-memberof-ad """ + target + """
1251 memberof-dangling-error 32
1252
1253 """
1254
1255     memberof_config += """
1256 overlay refint
1257 refint_attributes""" + refint_attributes + "\n"
1258     
1259     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1260                    {"DNSDOMAIN": names.dnsdomain,
1261                     "LDAPDIR": paths.ldapdir,
1262                     "DOMAINDN": names.domaindn,
1263                     "CONFIGDN": names.configdn,
1264                     "SCHEMADN": names.schemadn,
1265                     "LDAPMANAGERDN": names.ldapmanagerdn,
1266                     "LDAPMANAGERPASS": adminpass,
1267                     "MEMBEROF_CONFIG": memberof_config})
1268     setup_file(setup_path("modules.conf"), paths.modulesconf,
1269                    {"REALM": names.realm})
1270         
1271     setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "user")))
1272     setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "config")))
1273     setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "schema")))
1274     mapping = "schema-map-openldap-2.3"
1275     backend_schema = "backend-schema.schema"
1276
1277     ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1278     if ldap_backend_port is not None:
1279         server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1280     else:
1281         server_port_string = ""
1282     slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1283
1284     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)
1285
1286     os.system(schema_command)
1287
1288
1289     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type)
1290     message("Server Role:         %s" % serverrole)
1291     message("Hostname:            %s" % names.hostname)
1292     message("DNS Domain:          %s" % names.dnsdomain)
1293     message("Base DN:             %s" % names.domaindn)
1294     message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1295     message("LDAP admin password: %s" % adminpass)
1296     message(slapdcommand)
1297
1298
1299 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1300     """Create a PHP LDAP admin configuration file.
1301
1302     :param path: Path to write the configuration to.
1303     :param setup_path: Function to generate setup paths.
1304     """
1305     setup_file(setup_path("phpldapadmin-config.php"), path, 
1306             {"S4_LDAPI_URI": ldapi_uri})
1307
1308
1309 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1310                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1311     """Write out a DNS zone file, from the info in the current database.
1312
1313     :param path: Path of the new zone file.
1314     :param setup_path: Setup path function.
1315     :param dnsdomain: DNS Domain name
1316     :param domaindn: DN of the Domain
1317     :param hostip: Local IPv4 IP
1318     :param hostip6: Local IPv6 IP
1319     :param hostname: Local hostname
1320     :param dnspass: Password for DNS
1321     :param realm: Realm name
1322     :param domainguid: GUID of the domain.
1323     :param hostguid: GUID of the host.
1324     """
1325     assert isinstance(domainguid, str)
1326
1327     if hostip6 is not None:
1328         hostip6_base_line = "            IN AAAA    " + hostip6
1329         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1330     else:
1331         hostip6_base_line = ""
1332         hostip6_host_line = ""
1333
1334     setup_file(setup_path("provision.zone"), path, {
1335             "DNSPASS_B64": b64encode(dnspass),
1336             "HOSTNAME": hostname,
1337             "DNSDOMAIN": dnsdomain,
1338             "REALM": realm,
1339             "HOSTIP": hostip,
1340             "DOMAINGUID": domainguid,
1341             "DATESTRING": time.strftime("%Y%m%d%H"),
1342             "DEFAULTSITE": DEFAULTSITE,
1343             "HOSTGUID": hostguid,
1344             "HOSTIP6_BASE_LINE": hostip6_base_line,
1345             "HOSTIP6_HOST_LINE": hostip6_host_line,
1346         })
1347
1348
1349 def create_named_conf(path, setup_path, realm, dnsdomain,
1350                       private_dir, keytab_name):
1351     """Write out a file containing zone statements suitable for inclusion in a
1352     named.conf file (including GSS-TSIG configuration).
1353     
1354     :param path: Path of the new named.conf file.
1355     :param setup_path: Setup path function.
1356     :param realm: Realm name
1357     :param dnsdomain: DNS Domain name
1358     :param private_dir: Path to private directory
1359     :param keytab_name: File name of DNS keytab file
1360     """
1361
1362     setup_file(setup_path("named.conf"), path, {
1363             "DNSDOMAIN": dnsdomain,
1364             "REALM": realm,
1365             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1366             "DNS_KEYTAB": keytab_name,
1367             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1368         })
1369
1370 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1371     """Write out a file containing zone statements suitable for inclusion in a
1372     named.conf file (including GSS-TSIG configuration).
1373     
1374     :param path: Path of the new named.conf file.
1375     :param setup_path: Setup path function.
1376     :param dnsdomain: DNS Domain name
1377     :param hostname: Local hostname
1378     :param realm: Realm name
1379     """
1380
1381     setup_file(setup_path("krb5.conf"), path, {
1382             "DNSDOMAIN": dnsdomain,
1383             "HOSTNAME": hostname,
1384             "REALM": realm,
1385         })
1386
1387
1388 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1389     """Load schema for the SamDB.
1390     
1391     :param samdb: Load a schema into a SamDB.
1392     :param setup_path: Setup path function.
1393     :param schemadn: DN of the schema
1394     :param netbiosname: NetBIOS name of the host.
1395     :param configdn: DN of the configuration
1396     """
1397     schema_data = open(setup_path("schema.ldif"), 'r').read()
1398     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1399     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1400     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1401     prefixmap = b64encode(prefixmap)
1402
1403     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1404     head_data = substitute_var(head_data, {
1405                     "SCHEMADN": schemadn,
1406                     "NETBIOSNAME": netbiosname,
1407                     "CONFIGDN": configdn,
1408                     "DEFAULTSITE":sitename,
1409                     "PREFIXMAP_B64":prefixmap
1410     })
1411     samdb.attach_schema_from_ldif(head_data, schema_data)
1412