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