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