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