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