s4:provision Rework and further automate setup of OpenLDAP backend
[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 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #   
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #   
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 """Functions for setting up a Samba configuration."""
27
28 from base64 import b64encode
29 import os
30 import sys
31 import pwd
32 import grp
33 import time
34 import uuid, glue
35 import socket
36 import param
37 import registry
38 import samba
39 import subprocess
40 from auth import system_session
41 from samba import version, Ldb, substitute_var, valid_netbios_name, check_all_substituted, \
42   DS_BEHAVIOR_WIN2008
43 from samba.samdb import SamDB
44 from samba.idmap import IDmapDB
45 from samba.dcerpc import security
46 import urllib
47 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
48 from ms_schema import read_ms_schema
49 from signal import SIGTERM
50
51 __docformat__ = "restructuredText"
52
53
54 def find_setup_dir():
55     """Find the setup directory used by provision."""
56     dirname = os.path.dirname(__file__)
57     if "/site-packages/" in dirname:
58         prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
59         for suffix in ["share/setup", "share/samba/setup", "setup"]:
60             ret = os.path.join(prefix, suffix)
61             if os.path.isdir(ret):
62                 return ret
63     # In source tree
64     ret = os.path.join(dirname, "../../../setup")
65     if os.path.isdir(ret):
66         return ret
67     raise Exception("Unable to find setup directory.")
68
69
70 DEFAULTSITE = "Default-First-Site-Name"
71
72 class InvalidNetbiosName(Exception):
73     """A specified name was not a valid NetBIOS name."""
74     def __init__(self, name):
75         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
76
77
78 class ProvisionPaths(object):
79     def __init__(self):
80         self.shareconf = None
81         self.hklm = None
82         self.hkcu = None
83         self.hkcr = None
84         self.hku = None
85         self.hkpd = None
86         self.hkpt = None
87         self.samdb = None
88         self.idmapdb = None
89         self.secrets = None
90         self.keytab = None
91         self.dns_keytab = None
92         self.dns = None
93         self.winsdb = None
94         self.private_dir = None
95         self.ldapdir = None
96         self.slapdconf = None
97         self.modulesconf = None
98         self.memberofconf = None
99         self.fedoradsinf = None
100         self.fedoradspartitions = None
101         self.olmmron = None
102         self.olmmrserveridsconf = None
103         self.olmmrsyncreplconf = None
104         self.olcdir = None
105         self.olslapd = None
106         self.olcseedldif = None
107
108
109 class ProvisionNames(object):
110     def __init__(self):
111         self.rootdn = None
112         self.domaindn = None
113         self.configdn = None
114         self.schemadn = None
115         self.ldapmanagerdn = None
116         self.dnsdomain = None
117         self.realm = None
118         self.netbiosname = None
119         self.domain = None
120         self.hostname = None
121         self.sitename = None
122         self.smbconf = None
123     
124
125 class ProvisionResult(object):
126     def __init__(self):
127         self.paths = None
128         self.domaindn = None
129         self.lp = None
130         self.samdb = None
131
132 def check_install(lp, session_info, credentials):
133     """Check whether the current install seems ok.
134     
135     :param lp: Loadparm context
136     :param session_info: Session information
137     :param credentials: Credentials
138     """
139     if lp.get("realm") == "":
140         raise Exception("Realm empty")
141     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
142             credentials=credentials, lp=lp)
143     if len(ldb.search("(cn=Administrator)")) != 1:
144         raise "No administrator account found"
145
146
147 def findnss(nssfn, names):
148     """Find a user or group from a list of possibilities.
149     
150     :param nssfn: NSS Function to try (should raise KeyError if not found)
151     :param names: Names to check.
152     :return: Value return by first names list.
153     """
154     for name in names:
155         try:
156             return nssfn(name)
157         except KeyError:
158             pass
159     raise KeyError("Unable to find user/group %r" % names)
160
161
162 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
163 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
164
165
166 def read_and_sub_file(file, subst_vars):
167     """Read a file and sub in variables found in it
168     
169     :param file: File to be read (typically from setup directory)
170      param subst_vars: Optional variables to subsitute in the file.
171     """
172     data = open(file, 'r').read()
173     if subst_vars is not None:
174         data = substitute_var(data, subst_vars)
175     check_all_substituted(data)
176     return data
177
178
179 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
180     """Setup a ldb in the private dir.
181     
182     :param ldb: LDB file to import data into
183     :param ldif_path: Path of the LDIF file to load
184     :param subst_vars: Optional variables to subsitute in LDIF.
185     """
186     assert isinstance(ldif_path, str)
187
188     data = read_and_sub_file(ldif_path, subst_vars)
189     ldb.add_ldif(data)
190
191
192 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
193     """Modify a ldb in the private dir.
194     
195     :param ldb: LDB object.
196     :param ldif_path: LDIF file path.
197     :param subst_vars: Optional dictionary with substitution variables.
198     """
199     data = read_and_sub_file(ldif_path, subst_vars)
200
201     ldb.modify_ldif(data)
202
203
204 def setup_ldb(ldb, ldif_path, subst_vars):
205     """Import a LDIF a file into a LDB handle, optionally substituting variables.
206
207     :note: Either all LDIF data will be added or none (using transactions).
208
209     :param ldb: LDB file to import into.
210     :param ldif_path: Path to the LDIF file.
211     :param subst_vars: Dictionary with substitution variables.
212     """
213     assert ldb is not None
214     ldb.transaction_start()
215     try:
216         setup_add_ldif(ldb, ldif_path, subst_vars)
217     except:
218         ldb.transaction_cancel()
219         raise
220     ldb.transaction_commit()
221
222
223 def setup_file(template, fname, subst_vars):
224     """Setup a file in the private dir.
225
226     :param template: Path of the template file.
227     :param fname: Path of the file to create.
228     :param subst_vars: Substitution variables.
229     """
230     f = fname
231
232     if os.path.exists(f):
233         os.unlink(f)
234
235     data = read_and_sub_file(template, subst_vars)
236     open(f, 'w').write(data)
237
238
239 def provision_paths_from_lp(lp, dnsdomain):
240     """Set the default paths for provisioning.
241
242     :param lp: Loadparm context.
243     :param dnsdomain: DNS Domain name
244     """
245     paths = ProvisionPaths()
246     paths.private_dir = lp.get("private dir")
247     paths.keytab = "secrets.keytab"
248     paths.dns_keytab = "dns.keytab"
249
250     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
251     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
252     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
253     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
254     paths.templates = os.path.join(paths.private_dir, "templates.ldb")
255     paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
256     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
257     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
258     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
259     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
260     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
261     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
262                                             "phpldapadmin-config.php")
263     paths.ldapdir = os.path.join(paths.private_dir, 
264                                  "ldap")
265     paths.slapdconf = os.path.join(paths.ldapdir, 
266                                    "slapd.conf")
267     paths.slapdpid = os.path.join(paths.ldapdir, 
268                                    "slapd.pid")
269     paths.modulesconf = os.path.join(paths.ldapdir, 
270                                      "modules.conf")
271     paths.memberofconf = os.path.join(paths.ldapdir, 
272                                       "memberof.conf")
273     paths.fedoradsinf = os.path.join(paths.ldapdir, 
274                                      "fedorads.inf")
275     paths.fedoradspartitions = os.path.join(paths.ldapdir, 
276                                             "fedorads-partitions.ldif")
277     paths.olmmrserveridsconf = os.path.join(paths.ldapdir, 
278                                             "mmr_serverids.conf")
279     paths.olmmrsyncreplconf = os.path.join(paths.ldapdir, 
280                                            "mmr_syncrepl.conf")
281     paths.olcdir = os.path.join(paths.ldapdir, 
282                                  "slapd.d")
283     paths.olcseedldif = os.path.join(paths.ldapdir, 
284                                  "olc_seed.ldif")
285     paths.hklm = "hklm.ldb"
286     paths.hkcr = "hkcr.ldb"
287     paths.hkcu = "hkcu.ldb"
288     paths.hku = "hku.ldb"
289     paths.hkpd = "hkpd.ldb"
290     paths.hkpt = "hkpt.ldb"
291
292     paths.sysvol = lp.get("path", "sysvol")
293
294     paths.netlogon = lp.get("path", "netlogon")
295
296     paths.smbconf = lp.configfile
297
298     return paths
299
300
301 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
302                 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None, 
303                 sitename=None):
304     """Guess configuration settings to use."""
305
306     if hostname is None:
307         hostname = socket.gethostname().split(".")[0].lower()
308
309     netbiosname = hostname.upper()
310     if not valid_netbios_name(netbiosname):
311         raise InvalidNetbiosName(netbiosname)
312
313     hostname = hostname.lower()
314
315     if dnsdomain is None:
316         dnsdomain = lp.get("realm")
317
318     if serverrole is None:
319         serverrole = lp.get("server role")
320
321     assert dnsdomain is not None
322     realm = dnsdomain.upper()
323
324     if lp.get("realm").upper() != realm:
325         raise Exception("realm '%s' in %s must match chosen realm '%s'" %
326                         (lp.get("realm"), lp.configfile, realm))
327     
328     dnsdomain = dnsdomain.lower()
329
330     if serverrole == "domain controller":
331         if domain is None:
332             domain = lp.get("workgroup")
333         if domaindn is None:
334             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
335         if lp.get("workgroup").upper() != domain.upper():
336             raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
337                         lp.get("workgroup"), domain)
338     else:
339         domain = netbiosname
340         if domaindn is None:
341             domaindn = "CN=" + netbiosname
342         
343     assert domain is not None
344     domain = domain.upper()
345     if not valid_netbios_name(domain):
346         raise InvalidNetbiosName(domain)
347         
348     if rootdn is None:
349        rootdn = domaindn
350        
351     if configdn is None:
352         configdn = "CN=Configuration," + rootdn
353     if schemadn is None:
354         schemadn = "CN=Schema," + configdn
355
356     if sitename is None:
357         sitename=DEFAULTSITE
358
359     names = ProvisionNames()
360     names.rootdn = rootdn
361     names.domaindn = domaindn
362     names.configdn = configdn
363     names.schemadn = schemadn
364     names.ldapmanagerdn = "CN=Manager," + rootdn
365     names.dnsdomain = dnsdomain
366     names.domain = domain
367     names.realm = realm
368     names.netbiosname = netbiosname
369     names.hostname = hostname
370     names.sitename = sitename
371     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
372  
373     return names
374     
375
376 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
377                  targetdir):
378     """Create a new smb.conf file based on a couple of basic settings.
379     """
380     assert smbconf is not None
381     if hostname is None:
382         hostname = socket.gethostname().split(".")[0].lower()
383
384     if serverrole is None:
385         serverrole = "standalone"
386
387     assert serverrole in ("domain controller", "member server", "standalone")
388     if serverrole == "domain controller":
389         smbconfsuffix = "dc"
390     elif serverrole == "member server":
391         smbconfsuffix = "member"
392     elif serverrole == "standalone":
393         smbconfsuffix = "standalone"
394
395     assert domain is not None
396     assert realm is not None
397
398     default_lp = param.LoadParm()
399     #Load non-existant file
400     if os.path.exists(smbconf):
401         default_lp.load(smbconf)
402     
403     if targetdir is not None:
404         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
405         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
406
407         default_lp.set("lock dir", os.path.abspath(targetdir))
408     else:
409         privatedir_line = ""
410         lockdir_line = ""
411
412     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
413     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
414
415     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
416                smbconf, {
417             "HOSTNAME": hostname,
418             "DOMAIN": domain,
419             "REALM": realm,
420             "SERVERROLE": serverrole,
421             "NETLOGONPATH": netlogon,
422             "SYSVOLPATH": sysvol,
423             "PRIVATEDIR_LINE": privatedir_line,
424             "LOCKDIR_LINE": lockdir_line
425             })
426
427
428 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
429                         users_gid, wheel_gid):
430     """setup reasonable name mappings for sam names to unix names.
431
432     :param samdb: SamDB object.
433     :param idmap: IDmap db object.
434     :param sid: The domain sid.
435     :param domaindn: The domain DN.
436     :param root_uid: uid of the UNIX root user.
437     :param nobody_uid: uid of the UNIX nobody user.
438     :param users_gid: gid of the UNIX users group.
439     :param wheel_gid: gid of the UNIX wheel group."""
440     # add some foreign sids if they are not present already
441     samdb.add_stock_foreign_sids()
442
443     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
444     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
445
446     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
447     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
448
449
450 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
451                            credentials, names,
452                            serverrole, ldap_backend=None, 
453                            ldap_backend_type=None, erase=False):
454     """Setup the partitions for the SAM database. 
455     
456     Alternatively, provision() may call this, and then populate the database.
457     
458     :note: This will wipe the Sam Database!
459     
460     :note: This function always removes the local SAM LDB file. The erase 
461         parameter controls whether to erase the existing data, which 
462         may not be stored locally but in LDAP.
463     """
464     assert session_info is not None
465
466     try:
467         samdb = SamDB(samdb_path, session_info=session_info, 
468                       credentials=credentials, lp=lp)
469         # Wipes the database
470         samdb.erase()
471     except LdbError:
472         os.unlink(samdb_path)
473         samdb = SamDB(samdb_path, session_info=session_info, 
474                       credentials=credentials, lp=lp)
475          # Wipes the database
476         samdb.erase()
477         
478
479     #Add modules to the list to activate them by default
480     #beware often order is important
481     #
482     # Some Known ordering constraints:
483     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
484     # - objectclass must be before password_hash, because password_hash checks
485     #   that the objectclass is of type person (filled in by objectclass
486     #   module when expanding the objectclass list)
487     # - partition must be last
488     # - each partition has its own module list then
489     modules_list = ["rootdse",
490                     "paged_results",
491                     "ranged_results",
492                     "anr",
493                     "server_sort",
494                     "asq",
495                     "extended_dn_store",
496                     "extended_dn_in",
497                     "rdn_name",
498                     "objectclass",
499                     "samldb",
500                     "kludge_acl",
501                     "password_hash",
502                     "operational"]
503     tdb_modules_list = [
504                     "subtree_rename",
505                     "subtree_delete",
506                     "linked_attributes",
507                     "extended_dn_out_ldb"]
508     modules_list2 = ["show_deleted",
509                     "partition"]
510  
511     domaindn_ldb = "users.ldb"
512     if ldap_backend is not None:
513         domaindn_ldb = ldap_backend
514     configdn_ldb = "configuration.ldb"
515     if ldap_backend is not None:
516         configdn_ldb = ldap_backend
517     schemadn_ldb = "schema.ldb"
518     if ldap_backend is not None:
519         schema_ldb = ldap_backend
520         schemadn_ldb = ldap_backend
521         
522     if ldap_backend_type == "fedora-ds":
523         backend_modules = ["nsuniqueid", "paged_searches"]
524         # We can handle linked attributes here, as we don't have directory-side subtree operations
525         tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
526     elif ldap_backend_type == "openldap":
527         backend_modules = ["entryuuid", "paged_searches"]
528         # OpenLDAP handles subtree renames, so we don't want to do any of these things
529         tdb_modules_list = ["extended_dn_out_dereference"]
530     elif ldap_backend is not None:
531         raise "LDAP Backend specified, but LDAP Backend Type not specified"
532     elif serverrole == "domain controller":
533         backend_modules = ["repl_meta_data"]
534     else:
535         backend_modules = ["objectguid"]
536
537     if tdb_modules_list is None:
538         tdb_modules_list_as_string = ""
539     else:
540         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
541         
542     samdb.transaction_start()
543     try:
544         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
545                 "SCHEMADN": names.schemadn, 
546                 "SCHEMADN_LDB": schemadn_ldb,
547                 "SCHEMADN_MOD2": ",objectguid",
548                 "CONFIGDN": names.configdn,
549                 "CONFIGDN_LDB": configdn_ldb,
550                 "DOMAINDN": names.domaindn,
551                 "DOMAINDN_LDB": domaindn_ldb,
552                 "SCHEMADN_MOD": "schema_fsmo,instancetype",
553                 "CONFIGDN_MOD": "naming_fsmo,instancetype",
554                 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
555                 "MODULES_LIST": ",".join(modules_list),
556                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
557                 "MODULES_LIST2": ",".join(modules_list2),
558                 "BACKEND_MOD": ",".join(backend_modules),
559         })
560
561     except:
562         samdb.transaction_cancel()
563         raise
564
565     samdb.transaction_commit()
566     
567     samdb = SamDB(samdb_path, session_info=session_info, 
568                   credentials=credentials, lp=lp)
569
570     samdb.transaction_start()
571     try:
572         message("Setting up sam.ldb attributes")
573         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
574
575         message("Setting up sam.ldb rootDSE")
576         setup_samdb_rootdse(samdb, setup_path, names)
577
578         if erase:
579             message("Erasing data from partitions")
580             samdb.erase_partitions()
581
582     except:
583         samdb.transaction_cancel()
584         raise
585
586     samdb.transaction_commit()
587     
588     return samdb
589
590
591 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, 
592                         netbiosname, domainsid, keytab_path, samdb_url, 
593                         dns_keytab_path, dnspass, machinepass):
594     """Add DC-specific bits to a secrets database.
595     
596     :param secretsdb: Ldb Handle to the secrets database
597     :param setup_path: Setup path function
598     :param machinepass: Machine password
599     """
600     setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { 
601             "MACHINEPASS_B64": b64encode(machinepass),
602             "DOMAIN": domain,
603             "REALM": realm,
604             "DNSDOMAIN": dnsdomain,
605             "DOMAINSID": str(domainsid),
606             "SECRETS_KEYTAB": keytab_path,
607             "NETBIOSNAME": netbiosname,
608             "SAM_LDB": samdb_url,
609             "DNS_KEYTAB": dns_keytab_path,
610             "DNSPASS_B64": b64encode(dnspass),
611             })
612
613
614 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
615     """Setup the secrets database.
616
617     :param path: Path to the secrets database.
618     :param setup_path: Get the path to a setup file.
619     :param session_info: Session info.
620     :param credentials: Credentials
621     :param lp: Loadparm context
622     :return: LDB handle for the created secrets database
623     """
624     if os.path.exists(path):
625         os.unlink(path)
626     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
627                       lp=lp)
628     secrets_ldb.erase()
629     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
630     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
631                       lp=lp)
632     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
633
634     if credentials is not None and credentials.authentication_requested():
635         if credentials.get_bind_dn() is not None:
636             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
637                     "LDAPMANAGERDN": credentials.get_bind_dn(),
638                     "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
639                     })
640         else:
641             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
642                     "LDAPADMINUSER": credentials.get_username(),
643                     "LDAPADMINREALM": credentials.get_realm(),
644                     "LDAPADMINPASS_B64": b64encode(credentials.get_password())
645                     })
646
647     return secrets_ldb
648
649
650 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
651     """Setup the templates database.
652
653     :param path: Path to the database.
654     :param setup_path: Function for obtaining the path to setup files.
655     :param session_info: Session info
656     :param credentials: Credentials
657     :param lp: Loadparm context
658     """
659     templates_ldb = SamDB(path, session_info=session_info,
660                           credentials=credentials, lp=lp)
661     # Wipes the database
662     try:
663         templates_ldb.erase()
664     # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
665     except:
666         os.unlink(path)
667
668     templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
669
670     templates_ldb = SamDB(path, session_info=session_info,
671                           credentials=credentials, lp=lp)
672
673     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
674
675
676 def setup_registry(path, setup_path, session_info, credentials, lp):
677     """Setup the registry.
678     
679     :param path: Path to the registry database
680     :param setup_path: Function that returns the path to a setup.
681     :param session_info: Session information
682     :param credentials: Credentials
683     :param lp: Loadparm context
684     """
685     reg = registry.Registry()
686     hive = registry.open_ldb(path, session_info=session_info, 
687                          credentials=credentials, lp_ctx=lp)
688     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
689     provision_reg = setup_path("provision.reg")
690     assert os.path.exists(provision_reg)
691     reg.diff_apply(provision_reg)
692
693
694 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
695     """Setup the idmap database.
696
697     :param path: path to the idmap database
698     :param setup_path: Function that returns a path to a setup file
699     :param session_info: Session information
700     :param credentials: Credentials
701     :param lp: Loadparm context
702     """
703     if os.path.exists(path):
704         os.unlink(path)
705
706     idmap_ldb = IDmapDB(path, session_info=session_info,
707                         credentials=credentials, lp=lp)
708
709     idmap_ldb.erase()
710     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
711     return idmap_ldb
712
713
714 def setup_samdb_rootdse(samdb, setup_path, names):
715     """Setup the SamDB rootdse.
716
717     :param samdb: Sam Database handle
718     :param setup_path: Obtain setup path
719     """
720     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
721         "SCHEMADN": names.schemadn, 
722         "NETBIOSNAME": names.netbiosname,
723         "DNSDOMAIN": names.dnsdomain,
724         "REALM": names.realm,
725         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
726         "DOMAINDN": names.domaindn,
727         "ROOTDN": names.rootdn,
728         "CONFIGDN": names.configdn,
729         "SERVERDN": names.serverdn,
730         })
731         
732
733 def setup_self_join(samdb, names,
734                     machinepass, dnspass, 
735                     domainsid, invocationid, setup_path,
736                     policyguid, domainControllerFunctionality):
737     """Join a host to its own domain."""
738     assert isinstance(invocationid, str)
739     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
740               "CONFIGDN": names.configdn, 
741               "SCHEMADN": names.schemadn,
742               "DOMAINDN": names.domaindn,
743               "SERVERDN": names.serverdn,
744               "INVOCATIONID": invocationid,
745               "NETBIOSNAME": names.netbiosname,
746               "DEFAULTSITE": names.sitename,
747               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
748               "MACHINEPASS_B64": b64encode(machinepass),
749               "DNSPASS_B64": b64encode(dnspass),
750               "REALM": names.realm,
751               "DOMAIN": names.domain,
752               "DNSDOMAIN": names.dnsdomain,
753               "SAMBA_VERSION_STRING": version,
754               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
755     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
756               "POLICYGUID": policyguid,
757               "DNSDOMAIN": names.dnsdomain,
758               "DOMAINSID": str(domainsid),
759               "DOMAINDN": names.domaindn})
760
761
762 def setup_samdb(path, setup_path, session_info, credentials, lp, 
763                 names, message, 
764                 domainsid, aci, domainguid, policyguid, 
765                 fill, adminpass, krbtgtpass, 
766                 machinepass, invocationid, dnspass,
767                 serverrole, ldap_backend=None, 
768                 ldap_backend_type=None):
769     """Setup a complete SAM Database.
770     
771     :note: This will wipe the main SAM database file!
772     """
773
774     domainFunctionality = DS_BEHAVIOR_WIN2008
775     forestFunctionality = DS_BEHAVIOR_WIN2008
776     domainControllerFunctionality = DS_BEHAVIOR_WIN2008
777
778     erase = (fill != FILL_DRS)
779
780     # Also wipes the database
781     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
782                            credentials=credentials, session_info=session_info,
783                            names=names, 
784                            ldap_backend=ldap_backend, serverrole=serverrole,
785                            ldap_backend_type=ldap_backend_type, erase=erase)
786
787     samdb = SamDB(path, session_info=session_info, 
788                   credentials=credentials, lp=lp)
789     if fill == FILL_DRS:
790         return samdb
791
792     message("Pre-loading the Samba 4 and AD schema")
793
794     samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
795     samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
796     samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
797
798     samdb.set_domain_sid(str(domainsid))
799     if serverrole == "domain controller":
800         samdb.set_invocation_id(invocationid)
801
802     schema_data = load_schema(setup_path, samdb, names.schemadn, names.netbiosname, 
803                               names.configdn, names.sitename, names.serverdn)
804     samdb.transaction_start()
805         
806     try:
807         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
808         if serverrole == "domain controller":
809             domain_oc = "domainDNS"
810         else:
811             domain_oc = "samba4LocalDomain"
812
813         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
814                 "DOMAINDN": names.domaindn,
815                 "ACI": aci,
816                 "DOMAIN_OC": domain_oc
817                 })
818
819         message("Modifying DomainDN: " + names.domaindn + "")
820         if domainguid is not None:
821             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
822         else:
823             domainguid_mod = ""
824
825         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
826             "LDAPTIME": timestring(int(time.time())),
827             "DOMAINSID": str(domainsid),
828             "SCHEMADN": names.schemadn, 
829             "NETBIOSNAME": names.netbiosname,
830             "DEFAULTSITE": names.sitename,
831             "CONFIGDN": names.configdn,
832             "SERVERDN": names.serverdn,
833             "POLICYGUID": policyguid,
834             "DOMAINDN": names.domaindn,
835             "DOMAINGUID_MOD": domainguid_mod,
836             "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
837             })
838
839         message("Adding configuration container (permitted to fail)")
840         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
841             "CONFIGDN": names.configdn, 
842             "ACI": aci,
843             })
844         message("Modifying configuration container")
845         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
846             "CONFIGDN": names.configdn, 
847             "SCHEMADN": names.schemadn,
848             })
849
850         message("Adding schema container (permitted to fail)")
851         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
852             "SCHEMADN": names.schemadn,
853             "ACI": aci,
854             })
855         message("Modifying schema container")
856
857         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
858
859         setup_modify_ldif(samdb, 
860             setup_path("provision_schema_basedn_modify.ldif"), {
861             "SCHEMADN": names.schemadn,
862             "NETBIOSNAME": names.netbiosname,
863             "DEFAULTSITE": names.sitename,
864             "CONFIGDN": names.configdn,
865             "SERVERDN": names.serverdn,
866             "PREFIXMAP_B64": b64encode(prefixmap)
867             })
868
869         message("Setting up sam.ldb schema")
870         samdb.add_ldif(schema_data)
871         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
872                        {"SCHEMADN": names.schemadn})
873
874         message("Setting up sam.ldb configuration data")
875         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
876             "CONFIGDN": names.configdn,
877             "NETBIOSNAME": names.netbiosname,
878             "DEFAULTSITE": names.sitename,
879             "DNSDOMAIN": names.dnsdomain,
880             "DOMAIN": names.domain,
881             "SCHEMADN": names.schemadn,
882             "DOMAINDN": names.domaindn,
883             "SERVERDN": names.serverdn,
884             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
885             })
886
887         message("Setting up display specifiers")
888         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
889                        {"CONFIGDN": names.configdn})
890
891         message("Adding users container (permitted to fail)")
892         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
893                 "DOMAINDN": names.domaindn})
894         message("Modifying users container")
895         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
896                 "DOMAINDN": names.domaindn})
897         message("Adding computers container (permitted to fail)")
898         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
899                 "DOMAINDN": names.domaindn})
900         message("Modifying computers container")
901         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
902                 "DOMAINDN": names.domaindn})
903         message("Setting up sam.ldb data")
904         setup_add_ldif(samdb, setup_path("provision.ldif"), {
905             "DOMAINDN": names.domaindn,
906             "NETBIOSNAME": names.netbiosname,
907             "DEFAULTSITE": names.sitename,
908             "CONFIGDN": names.configdn,
909             "SERVERDN": names.serverdn
910             })
911
912         if fill == FILL_FULL:
913             message("Setting up sam.ldb users and groups")
914             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
915                 "DOMAINDN": names.domaindn,
916                 "DOMAINSID": str(domainsid),
917                 "CONFIGDN": names.configdn,
918                 "ADMINPASS_B64": b64encode(adminpass),
919                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
920                 })
921
922             if serverrole == "domain controller":
923                 message("Setting up self join")
924                 setup_self_join(samdb, names=names, invocationid=invocationid, 
925                                 dnspass=dnspass,  
926                                 machinepass=machinepass, 
927                                 domainsid=domainsid, policyguid=policyguid,
928                                 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
929
930     except:
931         samdb.transaction_cancel()
932         raise
933
934     samdb.transaction_commit()
935     return samdb
936
937
938 FILL_FULL = "FULL"
939 FILL_NT4SYNC = "NT4SYNC"
940 FILL_DRS = "DRS"
941
942
943 def provision(setup_dir, message, session_info, 
944               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
945               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
946               serverdn=None,
947               domain=None, hostname=None, hostip=None, hostip6=None, 
948               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
949               policyguid=None, invocationid=None, machinepass=None, 
950               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
951               wheel=None, backup=None, aci=None, serverrole=None, 
952               ldap_backend=None, ldap_backend_type=None, sitename=None):
953     """Provision samba4
954     
955     :note: caution, this wipes all existing data!
956     """
957
958     def setup_path(file):
959         return os.path.join(setup_dir, file)
960
961     if domainsid is None:
962         domainsid = security.random_sid()
963
964     if policyguid is None:
965         policyguid = str(uuid.uuid4())
966     if adminpass is None:
967         adminpass = glue.generate_random_str(12)
968     if krbtgtpass is None:
969         krbtgtpass = glue.generate_random_str(12)
970     if machinepass is None:
971         machinepass  = glue.generate_random_str(12)
972     if dnspass is None:
973         dnspass = glue.generate_random_str(12)
974     root_uid = findnss_uid([root or "root"])
975     nobody_uid = findnss_uid([nobody or "nobody"])
976     users_gid = findnss_gid([users or "users"])
977     if wheel is None:
978         wheel_gid = findnss_gid(["wheel", "adm"])
979     else:
980         wheel_gid = findnss_gid([wheel])
981     if aci is None:
982         aci = "# no aci for local ldb"
983
984     if targetdir is not None:
985         if (not os.path.exists(os.path.join(targetdir, "etc"))):
986             os.makedirs(os.path.join(targetdir, "etc"))
987         smbconf = os.path.join(targetdir, "etc", "smb.conf")
988     elif smbconf is None:
989         smbconf = param.default_path()
990
991     # only install a new smb.conf if there isn't one there already
992     if not os.path.exists(smbconf):
993         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
994                      targetdir)
995
996     lp = param.LoadParm()
997     lp.load(smbconf)
998
999     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1000                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1001                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1002                         serverdn=serverdn)
1003
1004     paths = provision_paths_from_lp(lp, names.dnsdomain)
1005
1006     if hostip is None:
1007         try:
1008             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1009         except socket.gaierror, (socket.EAI_NODATA, msg):
1010             hostip = None
1011
1012     if hostip6 is None:
1013         try:
1014             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1015         except socket.gaierror, (socket.EAI_NODATA, msg): 
1016             hostip6 = None
1017
1018     if serverrole is None:
1019         serverrole = lp.get("server role")
1020
1021     assert serverrole in ("domain controller", "member server", "standalone")
1022     if invocationid is None and serverrole == "domain controller":
1023         invocationid = str(uuid.uuid4())
1024
1025     if not os.path.exists(paths.private_dir):
1026         os.mkdir(paths.private_dir)
1027
1028     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1029     
1030     if ldap_backend is not None:
1031         if ldap_backend == "ldapi":
1032             # provision-backend will set this path suggested slapd command line / fedorads.inf
1033             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1034              
1035     # only install a new shares config db if there is none
1036     if not os.path.exists(paths.shareconf):
1037         message("Setting up share.ldb")
1038         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1039                         credentials=credentials, lp=lp)
1040         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1041
1042      
1043     message("Setting up secrets.ldb")
1044     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1045                                   session_info=session_info, 
1046                                   credentials=credentials, lp=lp)
1047
1048     message("Setting up the registry")
1049     setup_registry(paths.hklm, setup_path, session_info, 
1050                    credentials=credentials, lp=lp)
1051
1052     message("Setting up templates db")
1053     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
1054                       credentials=credentials, lp=lp)
1055
1056     message("Setting up idmap db")
1057     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1058                           credentials=credentials, lp=lp)
1059
1060     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1061                         credentials=credentials, lp=lp, names=names,
1062                         message=message, 
1063                         domainsid=domainsid, 
1064                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1065                         fill=samdb_fill, 
1066                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1067                         invocationid=invocationid, 
1068                         machinepass=machinepass, dnspass=dnspass,
1069                         serverrole=serverrole, ldap_backend=ldap_backend, 
1070                         ldap_backend_type=ldap_backend_type)
1071
1072     if serverrole == "domain controller":
1073         if paths.netlogon is None:
1074             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1075             message("Please either remove %s or see the template at %s" % 
1076                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1077             assert(paths.netlogon is not None)
1078
1079         if paths.sysvol is None:
1080             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1081             message("Please either remove %s or see the template at %s" % 
1082                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1083             assert(paths.sysvol is not None)            
1084             
1085         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1086                                    "{" + policyguid + "}")
1087         os.makedirs(policy_path, 0755)
1088         open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1089         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1090         os.makedirs(os.path.join(policy_path, "User"), 0755)
1091         if not os.path.isdir(paths.netlogon):
1092             os.makedirs(paths.netlogon, 0755)
1093
1094     if samdb_fill == FILL_FULL:
1095         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1096                             root_uid=root_uid, nobody_uid=nobody_uid,
1097                             users_gid=users_gid, wheel_gid=wheel_gid)
1098
1099         message("Setting up sam.ldb rootDSE marking as synchronized")
1100         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1101
1102         # Only make a zone file on the first DC, it should be replicated with DNS replication
1103         if serverrole == "domain controller":
1104             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1105                               credentials=credentials, lp=lp)
1106             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1107                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1108                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1109                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1110                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1111
1112             samdb = SamDB(paths.samdb, session_info=session_info, 
1113                       credentials=credentials, lp=lp)
1114
1115             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1116             assert isinstance(domainguid, str)
1117             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1118                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1119                                        scope=SCOPE_SUBTREE)
1120             assert isinstance(hostguid, str)
1121
1122             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1123                              domaindn=names.domaindn, hostip=hostip,
1124                              hostip6=hostip6, hostname=names.hostname,
1125                              dnspass=dnspass, realm=names.realm,
1126                              domainguid=domainguid, hostguid=hostguid)
1127
1128             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1129                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1130
1131             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1132                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1133                               keytab_name=paths.dns_keytab)
1134             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1135             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1136
1137             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1138                              hostname=names.hostname, realm=names.realm)
1139             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1140
1141
1142
1143             # if backend is openldap, terminate slapd after final provision and check its proper termination
1144             if ldap_backend_type == "openldap":
1145             
1146               # We look if our "provision-slapd" is still up and running,
1147               # listening with the stored PID on our ldapi_uri.
1148               # first we check with ldapsearch -> rootDSE via ldapi_uri
1149               # if slapd is running
1150               try:
1151                  # in this case we got a running slapd listening on our ldapi_uri
1152                  ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1153                  ldapi_db = Ldb(ldapi_uri)
1154                  search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1155                                    expression="(objectClass=OpenLDAProotDSE)");
1156                  message("LDAP Debug-Output:" + str(search_ol_rootdse))
1157
1158                  # now we check if slapds PID file exists AND is identical to our stored  
1159                  if os.path.exists(paths.slapdpid):
1160                     f = open(paths.slapdpid, "r")
1161                     p1 = f.read()
1162                     f.close()
1163                     message("slapd-PID-File found. PID is :" + str(p1))
1164                     # validation against stored PID of "provision-slapd". 
1165                     # is this the slapd we started from provision-backend?
1166                     if os.path.exists(paths.ldapdir + "/slapd_provision_pid"):
1167                        f = open(paths.ldapdir + "/slapd_provision_pid", "r")
1168                        p2 = f.read()
1169                        f.close()
1170                        message("File from provision-backend with stored PID found. PID is :" + str(p2))
1171                        if int(p1) == int(p2):
1172                           message("slapd-Process used for provisioning with PID: " + str(p1) + " will now be shut down.")
1173                           os.kill(int(p1),SIGTERM)
1174                        else:
1175                           message("Warning: PIDs are not identical! Locate the active slapd and shut it down before you continue!")
1176                     else:
1177                        message("Stored PID File could not be found. Sorry. You have to locate the PID and terminate slapd manually.")
1178                  else:
1179                     message("slapd-PID File could not be found. Sorry. You have to locate the PID and terminate slapd manually.")
1180  
1181                  # Now verify proper slapd-termination...
1182                  try:
1183                     # in this case we got still a running slapd listening on our ldapi_uri: bad
1184                     ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1185                     ldapi_db = Ldb(ldapi_uri)
1186                     search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1187                                       expression="(objectClass=OpenLDAProotDSE)");
1188                     message("slapd seems still to be running and listening to our "+ ldapi_uri + " -socket. Locate an terminate it manually.")
1189                  except LdbError, e:
1190                     message("slapd-Process used for final provision was properly shut down.") 
1191                     # in this case we got no running slapd listening on our ldapi_uri: everythings good - do nothing.
1192          
1193               except LdbError, e:
1194                   # in this case we got no running slapd
1195                   message("LDAP Debug-Output:")
1196                   print e
1197                   message("No running slapd on: " + ldapi_uri + " detected. Maybe final provision is incomplete.")
1198  
1199              # end slapd-termination check
1200
1201              # now display slapd_command_file.txt to show how slapd must be started next time
1202               if os.path.exists(paths.ldapdir +"/slapd_command_file.txt"):
1203                   message("Use later the following commandline to start slapd, then Samba:")
1204                   f = open(paths.ldapdir +"/slapd_command_file.txt", "r")
1205                   x = f.read()
1206                   f.close()
1207                   message(x)
1208                   message("This slapd-Commandline is also stored under: " + str(paths.ldapdir) + "/slapd_command_file.txt")
1209               else:
1210                   message("Error. slapd-commandline-File could not be found.")
1211
1212
1213     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1214                                ldapi_url)
1215
1216     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1217
1218     message("Once the above files are installed, your Samba4 server will be ready to use")
1219     message("Server Role:    %s" % serverrole)
1220     message("Hostname:       %s" % names.hostname)
1221     message("NetBIOS Domain: %s" % names.domain)
1222     message("DNS Domain:     %s" % names.dnsdomain)
1223     message("DOMAIN SID:     %s" % str(domainsid))
1224     if samdb_fill == FILL_FULL:
1225         message("Admin password: %s" % adminpass)
1226
1227     result = ProvisionResult()
1228     result.domaindn = domaindn
1229     result.paths = paths
1230     result.lp = lp
1231     result.samdb = samdb
1232     return result
1233
1234
1235
1236 def provision_become_dc(setup_dir=None,
1237                         smbconf=None, targetdir=None, realm=None, 
1238                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1239                         serverdn=None,
1240                         domain=None, hostname=None, domainsid=None, 
1241                         adminpass=None, krbtgtpass=None, domainguid=None, 
1242                         policyguid=None, invocationid=None, machinepass=None, 
1243                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1244                         wheel=None, backup=None, aci=None, serverrole=None, 
1245                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1246
1247     def message(text):
1248         """print a message if quiet is not set."""
1249         print text
1250
1251     return provision(setup_dir, message, system_session(), None,
1252               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1253               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1254               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1255     
1256
1257 def setup_db_config(setup_path, dbdir):
1258     """Setup a Berkeley database.
1259     
1260     :param setup_path: Setup path function.
1261     :param dbdir: Database directory."""
1262     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1263         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1264     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1265         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1266     
1267     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1268                {"LDAPDBDIR": dbdir})
1269     
1270
1271
1272 def provision_backend(setup_dir=None, message=None,
1273                       smbconf=None, targetdir=None, realm=None,
1274                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1275                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1276                       ldap_backend_type=None, ldap_backend_port=None,
1277                       ol_mmr_urls=None, ol_olc=None, 
1278                       ol_slapd=None, nosync=False):
1279
1280     def setup_path(file):
1281         return os.path.join(setup_dir, file)
1282
1283     if hostname is None:
1284         hostname = socket.gethostname().split(".")[0].lower()
1285
1286     if root is None:
1287         root = findnss(pwd.getpwnam, ["root"])[0]
1288
1289     if adminpass is None:
1290         adminpass = glue.generate_random_str(12)
1291
1292     if targetdir is not None:
1293         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1294             os.makedirs(os.path.join(targetdir, "etc"))
1295         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1296     elif smbconf is None:
1297         smbconf = param.default_path()
1298         assert smbconf is not None
1299
1300     # only install a new smb.conf if there isn't one there already
1301     if not os.path.exists(smbconf):
1302         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1303                      targetdir)
1304
1305     # if openldap-backend was chosen, check if path to slapd was given and exists
1306     if ldap_backend_type == "openldap" and ol_slapd is None:
1307         sys.exit("Warning: OpenLDAP-Backend must be setup with path to slapd (OpenLDAP-Daemon), e.g. --ol-slapd=\"/usr/local/libexec\"!")
1308     if ldap_backend_type == "openldap" and ol_slapd is not None:
1309        ol_slapd = ol_slapd + "/slapd"
1310        if not os.path.exists(ol_slapd):
1311             message (ol_slapd)
1312             sys.exit("Warning: Given Path to slapd (OpenLDAP-Daemon) does not exist!")
1313
1314     # openldap-online-configuration: validation of olc and slapd
1315     if ol_olc == "yes" and ol_slapd is None: 
1316         sys.exit("Warning: OpenLDAP-Online-Configuration cant be setup without path to slapd!")
1317
1318
1319     lp = param.LoadParm()
1320     lp.load(smbconf)
1321
1322     if serverrole is None:
1323         serverrole = lp.get("server role")
1324
1325     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1326                         dnsdomain=realm, serverrole=serverrole, 
1327                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1328                         schemadn=schemadn)
1329
1330     paths = provision_paths_from_lp(lp, names.dnsdomain)
1331
1332     if not os.path.isdir(paths.ldapdir):
1333         os.makedirs(paths.ldapdir, 0700)
1334     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1335     try:
1336         os.unlink(schemadb_path)
1337     except OSError:
1338         pass
1339
1340     schemadb = SamDB(schemadb_path, lp=lp)
1341     schemadb.transaction_start()
1342     try:
1343  
1344         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1345
1346         setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1347                        {"SCHEMADN": names.schemadn,
1348                         "ACI": "#",
1349                         })
1350         setup_modify_ldif(schemadb, 
1351                           setup_path("provision_schema_basedn_modify.ldif"), \
1352                               {"SCHEMADN": names.schemadn,
1353                                "NETBIOSNAME": names.netbiosname,
1354                                "DEFAULTSITE": DEFAULTSITE,
1355                                "CONFIGDN": names.configdn,
1356                                "SERVERDN": names.serverdn,
1357                                "PREFIXMAP_B64": b64encode(prefixmap)
1358                                })
1359         
1360         data = load_schema(setup_path, schemadb, names.schemadn, names.netbiosname, 
1361                            names.configdn, DEFAULTSITE, names.serverdn)
1362         schemadb.add_ldif(data)
1363     except:
1364         schemadb.transaction_cancel()
1365         raise
1366     schemadb.transaction_commit()
1367
1368     if ldap_backend_type == "fedora-ds":
1369         if ldap_backend_port is not None:
1370             serverport = "ServerPort=%d" % ldap_backend_port
1371         else:
1372             serverport = ""
1373
1374         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1375                    {"ROOT": root,
1376                     "HOSTNAME": hostname,
1377                     "DNSDOMAIN": names.dnsdomain,
1378                     "LDAPDIR": paths.ldapdir,
1379                     "DOMAINDN": names.domaindn,
1380                     "LDAPMANAGERDN": names.ldapmanagerdn,
1381                     "LDAPMANAGERPASS": adminpass, 
1382                     "SERVERPORT": serverport})
1383         
1384         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1385                    {"CONFIGDN": names.configdn,
1386                     "SCHEMADN": names.schemadn,
1387                     })
1388         
1389         mapping = "schema-map-fedora-ds-1.0"
1390         backend_schema = "99_ad.ldif"
1391         
1392         slapdcommand="Initialise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1393        
1394         ldapuser = "--simple-bind-dn=" + names.ldapmanagerdn
1395
1396     elif ldap_backend_type == "openldap":
1397         #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1398         nosync_config = ""
1399         if nosync:
1400             nosync_config = "dbnosync"
1401
1402
1403         attrs = ["linkID", "lDAPDisplayName"]
1404         res = schemadb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1405
1406         memberof_config = "# Generated from schema in %s\n" % schemadb_path
1407         refint_attributes = ""
1408         for i in range (0, len(res)):
1409             expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1410             target = schemadb.searchone(basedn=names.schemadn, 
1411                                         expression=expression, 
1412                                         attribute="lDAPDisplayName", 
1413                                         scope=SCOPE_SUBTREE)
1414             if target is not None:
1415                 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1416             
1417                 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1418                                                      { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1419                                                        "MEMBEROF_ATTR" : str(target) })
1420
1421         refint_config = read_and_sub_file(setup_path("refint.conf"),
1422                                             { "LINK_ATTRS" : refint_attributes})
1423
1424         res = schemadb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1425         index_config = ""
1426         for i in range (0, len(res)):
1427             index_attr = res[i]["lDAPDisplayName"][0]
1428             if index_attr == "objectGUID":
1429                 index_attr = "entryUUID"
1430
1431             index_config += "index " + index_attr + " eq\n"
1432
1433 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1434         mmr_on_config = ""
1435         mmr_replicator_acl = ""
1436         mmr_serverids_config = ""
1437         mmr_syncrepl_schema_config = "" 
1438         mmr_syncrepl_config_config = "" 
1439         mmr_syncrepl_user_config = "" 
1440        
1441  
1442         if ol_mmr_urls is not None:
1443                 # For now, make these equal
1444                 mmr_pass = adminpass
1445
1446                 url_list=filter(None,ol_mmr_urls.split(' ')) 
1447                 if (len(url_list) == 1):
1448                     url_list=filter(None,ol_mmr_urls.split(',')) 
1449                      
1450
1451                 mmr_on_config = "MirrorMode On"
1452                 mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1453                 serverid=0
1454                 for url in url_list:
1455                         serverid=serverid+1
1456                         mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1457                                                                      { "SERVERID" : str(serverid),
1458                                                                        "LDAPSERVER" : url })
1459                         rid=serverid*10
1460                         rid=rid+1
1461                         mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1462                                                                      {  "RID" : str(rid),
1463                                                                         "MMRDN": names.schemadn,
1464                                                                         "LDAPSERVER" : url,
1465                                                                         "MMR_PASSWORD": mmr_pass})
1466
1467                         rid=rid+1
1468                         mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1469                                                                      {  "RID" : str(rid),
1470                                                                         "MMRDN": names.configdn,
1471                                                                         "LDAPSERVER" : url,
1472                                                                         "MMR_PASSWORD": mmr_pass})
1473
1474                         rid=rid+1
1475                         mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1476                                                                      {  "RID" : str(rid),
1477                                                                         "MMRDN": names.domaindn,
1478                                                                         "LDAPSERVER" : url,
1479                                                                         "MMR_PASSWORD": mmr_pass })
1480         # olc = yes?
1481         olc_config_pass = ""
1482         olc_config_acl = ""
1483         olc_syncrepl_config = ""
1484         olc_mmr_config = "" 
1485         if ol_olc == "yes":
1486                 olc_config_pass += read_and_sub_file(setup_path("olc_pass.conf"),
1487                                                                 { "OLC_PW": adminpass })
1488                 olc_config_acl += read_and_sub_file(setup_path("olc_acl.conf"),{})
1489                 
1490             # if olc = yes + mmr = yes, generate cn=config-replication directives
1491             # and  olc_seed.lif for the other mmr-servers
1492                 if ol_olc == "yes" and ol_mmr_urls is not None:
1493                         serverid=0
1494                         olc_serverids_config = ""
1495                         olc_syncrepl_config = ""
1496                         olc_syncrepl_seed_config = ""
1497                         olc_mmr_config = "" 
1498                         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1499                         rid=1000
1500                         for url in url_list:
1501                                 serverid=serverid+1
1502                                 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1503                                                                      { "SERVERID" : str(serverid),
1504                                                                        "LDAPSERVER" : url })
1505                         
1506                                 rid=rid+1
1507                                 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1508                                                                      {  "RID" : str(rid),
1509                                                                         "LDAPSERVER" : url,
1510                                                                         "MMR_PASSWORD": adminpass})
1511
1512                                 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1513                                                                      {  "RID" : str(rid),
1514                                                                         "LDAPSERVER" : url})
1515
1516                                 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1517                                                                      {"OLC_SERVER_ID_CONF": olc_serverids_config,
1518                                                                       "OLC_PW": adminpass,
1519                                                                       "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1520         
1521
1522                 # end olc
1523
1524         setup_file(setup_path("slapd.conf"), paths.slapdconf,
1525                    {"DNSDOMAIN": names.dnsdomain,
1526                     "LDAPDIR": paths.ldapdir,
1527                     "DOMAINDN": names.domaindn,
1528                     "CONFIGDN": names.configdn,
1529                     "SCHEMADN": names.schemadn,
1530                     "MEMBEROF_CONFIG": memberof_config,
1531                     "MIRRORMODE": mmr_on_config,
1532                     "REPLICATOR_ACL": mmr_replicator_acl,
1533                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1534                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1535                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1536                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1537                     "OLC_CONFIG_PASS": olc_config_pass,
1538                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1539                     "OLC_CONFIG_ACL": olc_config_acl,
1540                     "OLC_MMR_CONFIG": olc_mmr_config,
1541                     "REFINT_CONFIG": refint_config,
1542                     "INDEX_CONFIG": index_config,
1543                     "NOSYNC": nosync_config})
1544         setup_file(setup_path("modules.conf"), paths.modulesconf,
1545                    {"REALM": names.realm})
1546         
1547         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1548         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1549         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1550
1551         if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1552             os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1553
1554         setup_file(setup_path("cn=samba.ldif"), 
1555                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1556                    { "UUID": str(uuid.uuid4()), 
1557                      "LDAPTIME": timestring(int(time.time()))} )
1558         setup_file(setup_path("cn=samba-admin.ldif"), 
1559                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1560                               {"LDAPADMINPASS_B64": b64encode(adminpass),
1561                                "UUID": str(uuid.uuid4()), 
1562                                "LDAPTIME": timestring(int(time.time()))} )
1563         
1564         if ol_mmr_urls is not None:
1565            setup_file(setup_path("cn=replicator.ldif"),
1566                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1567                               {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1568                                "UUID": str(uuid.uuid4()),
1569                                "LDAPTIME": timestring(int(time.time()))} )
1570
1571
1572         mapping = "schema-map-openldap-2.3"
1573         backend_schema = "backend-schema.schema"
1574
1575         # now we generate the needed strings to start slapd automatically,
1576         # first ldapi_uri...
1577         ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1578         if ldap_backend_port is not None:
1579             server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1580         else:
1581             server_port_string = ""
1582
1583         # ..and now the slapd-commandline with (optional) mmr and/or olc
1584         if ol_olc != "yes" and ol_mmr_urls is None:
1585            slapdcommand_prov=ol_slapd + " -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri
1586            slapdcommand=ol_slapd + " -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1587
1588         if ol_olc == "yes" and ol_mmr_urls is None:
1589            slapdcommand_prov=ol_slapd + " -F " + paths.olcdir + " -h " + ldapi_uri
1590            slapdcommand=ol_slapd + " -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://" + names.hostname + "." + names.dnsdomain +":<Chosen PORT (<> 389!)>\"" 
1591
1592         if ol_olc != "yes" and ol_mmr_urls is not None:
1593            slapdcommand_prov=ol_slapd + " -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri
1594            slapdcommand=ol_slapd + " -f " + paths.ldapdir + "/slapd.conf -h \"" + ldapi_uri + " ldap://" + names.hostname + "." + names.dnsdomain +":<Chosen PORT (<> 389!)>\"" 
1595
1596         if ol_olc == "yes" and ol_mmr_urls is not None:
1597            slapdcommand_prov=ol_slapd + " -F " + paths.olcdir + " -h " + ldapi_uri
1598            slapdcommand=ol_slapd + " -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://" + names.hostname + "." + names.dnsdomain +":<Chosen PORT (<> 389!)>\""
1599
1600         # now generate file with complete slapd-commandline. 
1601         # it is read out later when final provision's done and ol/s4 are ready to be started manually 
1602         f = open(paths.ldapdir +"/slapd_command_file.txt", "w")
1603         f.write(str(slapdcommand + "\n"))
1604         f.close()
1605
1606         ldapuser = "--username=samba-admin"
1607
1608
1609     backend_schema_data = schemadb.convert_schema_to_openldap(ldap_backend_type, open(setup_path(mapping), 'r').read())
1610     assert backend_schema_data is not None
1611     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1612
1613     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type)
1614     message("Server Role:         %s" % serverrole)
1615     message("Hostname:            %s" % names.hostname)
1616     message("DNS Domain:          %s" % names.dnsdomain)
1617     message("Base DN:             %s" % names.domaindn)
1618
1619     if ldap_backend_type == "openldap":
1620         message("LDAP admin user:     samba-admin")
1621     else:
1622         message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1623
1624     message("LDAP admin password: %s" % adminpass)
1625     if ol_olc == "yes" or ol_mmr_urls is not None:
1626         message("Attention! You are using olc and/or MMR: The slapd-PORT you choose later must be different than 389!")
1627
1628     # if --ol-olc=yes, generate online-configuration in ../private/ldap/slapd.d 
1629     if ol_olc == "yes":
1630           if not os.path.isdir(paths.olcdir):
1631              os.makedirs(paths.olcdir, 0770)
1632           paths.olslapd = str(ol_slapd)
1633           olc_command = paths.olslapd + " -Ttest -f " + paths.slapdconf + " -F " +  paths.olcdir + " >/dev/null 2>&1"
1634           os.system(olc_command)
1635           os.remove(paths.slapdconf)        
1636           # for debugging purposes, remove: ' +  ">/dev/null 2>&1" ' from the line 'olc_command =' above 
1637           
1638
1639
1640     # start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via ldapi_uri
1641     # if another instance of slapd is already running
1642     try:
1643         ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1644         ldapi_db = Ldb(ldapi_uri)
1645         search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1646                           expression="(objectClass=OpenLDAProotDSE)");
1647         message("LDAP Debug-Output:" + str(search_ol_rootdse))
1648         if os.path.exists(paths.slapdpid):
1649            f = open(paths.slapdpid, "r")
1650            p = f.read()
1651            f.close()
1652            message("Check for slapd-Process with PID: " + str(p) + " and terminate it manually.")
1653         else:
1654            message("slapd-PID File could not be found. Sorry. You have to locate the PID manually.")
1655  
1656         sys.exit("Warning: Another slapd Instance seems already running on this host, listening to " + ldapi_uri + ". Please shut it down before you continue. Exiting now.")
1657
1658     except LdbError, e:
1659         message("LDAP Debug-Output:")
1660         print e
1661         message("Ok. - No other slapd-Instance listening on: " + ldapi_uri + ". Starting slapd now for final provision.")
1662
1663         p = subprocess.Popen(slapdcommand_prov, shell=True)
1664
1665         # after startup: store slapd-provision-pid also in a separate file. 
1666         # this is needed as long as provision/provision-backend are not fully merged,
1667         # to compare pids before shutting down
1668
1669         # wait for pidfile to be created
1670         time.sleep(3)
1671         if os.path.exists(paths.slapdpid):
1672            f = open(paths.slapdpid, "r")
1673            p = f.read()
1674            f.close()
1675            f = open(paths.ldapdir +"/slapd_provision_pid", "w")
1676            f.write(str(p) + "\n")
1677            f.close()
1678            message("Started slapd for final provisioning with PID: "+ str(p))
1679         else:
1680            message("slapd-PID File could not be found. Sorry")
1681         
1682         # done slapd checking + start 
1683
1684
1685           
1686     assert isinstance(ldap_backend_type, str)
1687     assert isinstance(ldapuser, str)
1688     assert isinstance(adminpass, str)
1689     assert isinstance(names.dnsdomain, str)
1690     assert isinstance(names.domain, str)
1691     assert isinstance(serverrole, str)
1692     args = ["--ldap-backend=ldapi",
1693             "--ldap-backend-type=" + ldap_backend_type,
1694             "--password=" + adminpass,
1695             ldapuser,
1696             "--realm=" + names.dnsdomain,
1697             "--domain=" + names.domain,
1698             "--server-role='" + serverrole + "'"]
1699     message("Now run final provision with: " + " ".join(args))
1700
1701
1702
1703 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1704     """Create a PHP LDAP admin configuration file.
1705
1706     :param path: Path to write the configuration to.
1707     :param setup_path: Function to generate setup paths.
1708     """
1709     setup_file(setup_path("phpldapadmin-config.php"), path, 
1710             {"S4_LDAPI_URI": ldapi_uri})
1711
1712
1713 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1714                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1715     """Write out a DNS zone file, from the info in the current database.
1716
1717     :param path: Path of the new zone file.
1718     :param setup_path: Setup path function.
1719     :param dnsdomain: DNS Domain name
1720     :param domaindn: DN of the Domain
1721     :param hostip: Local IPv4 IP
1722     :param hostip6: Local IPv6 IP
1723     :param hostname: Local hostname
1724     :param dnspass: Password for DNS
1725     :param realm: Realm name
1726     :param domainguid: GUID of the domain.
1727     :param hostguid: GUID of the host.
1728     """
1729     assert isinstance(domainguid, str)
1730
1731     if hostip6 is not None:
1732         hostip6_base_line = "            IN AAAA    " + hostip6
1733         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1734     else:
1735         hostip6_base_line = ""
1736         hostip6_host_line = ""
1737
1738     if hostip is not None:
1739         hostip_base_line = "            IN A    " + hostip
1740         hostip_host_line = hostname + "        IN A    " + hostip
1741     else:
1742         hostip_base_line = ""
1743         hostip_host_line = ""
1744
1745     setup_file(setup_path("provision.zone"), path, {
1746             "DNSPASS_B64": b64encode(dnspass),
1747             "HOSTNAME": hostname,
1748             "DNSDOMAIN": dnsdomain,
1749             "REALM": realm,
1750             "HOSTIP_BASE_LINE": hostip_base_line,
1751             "HOSTIP_HOST_LINE": hostip_host_line,
1752             "DOMAINGUID": domainguid,
1753             "DATESTRING": time.strftime("%Y%m%d%H"),
1754             "DEFAULTSITE": DEFAULTSITE,
1755             "HOSTGUID": hostguid,
1756             "HOSTIP6_BASE_LINE": hostip6_base_line,
1757             "HOSTIP6_HOST_LINE": hostip6_host_line,
1758         })
1759
1760
1761 def create_named_conf(path, setup_path, realm, dnsdomain,
1762                       private_dir):
1763     """Write out a file containing zone statements suitable for inclusion in a
1764     named.conf file (including GSS-TSIG configuration).
1765     
1766     :param path: Path of the new named.conf file.
1767     :param setup_path: Setup path function.
1768     :param realm: Realm name
1769     :param dnsdomain: DNS Domain name
1770     :param private_dir: Path to private directory
1771     :param keytab_name: File name of DNS keytab file
1772     """
1773
1774     setup_file(setup_path("named.conf"), path, {
1775             "DNSDOMAIN": dnsdomain,
1776             "REALM": realm,
1777             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1778             "PRIVATE_DIR": private_dir
1779             })
1780
1781 def create_named_txt(path, setup_path, realm, dnsdomain,
1782                       private_dir, keytab_name):
1783     """Write out a file containing zone statements suitable for inclusion in a
1784     named.conf file (including GSS-TSIG configuration).
1785     
1786     :param path: Path of the new named.conf file.
1787     :param setup_path: Setup path function.
1788     :param realm: Realm name
1789     :param dnsdomain: DNS Domain name
1790     :param private_dir: Path to private directory
1791     :param keytab_name: File name of DNS keytab file
1792     """
1793
1794     setup_file(setup_path("named.txt"), path, {
1795             "DNSDOMAIN": dnsdomain,
1796             "REALM": realm,
1797             "DNS_KEYTAB": keytab_name,
1798             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1799             "PRIVATE_DIR": private_dir
1800         })
1801
1802 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1803     """Write out a file containing zone statements suitable for inclusion in a
1804     named.conf file (including GSS-TSIG configuration).
1805     
1806     :param path: Path of the new named.conf file.
1807     :param setup_path: Setup path function.
1808     :param dnsdomain: DNS Domain name
1809     :param hostname: Local hostname
1810     :param realm: Realm name
1811     """
1812
1813     setup_file(setup_path("krb5.conf"), path, {
1814             "DNSDOMAIN": dnsdomain,
1815             "HOSTNAME": hostname,
1816             "REALM": realm,
1817         })
1818
1819
1820 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename,
1821                 serverdn):
1822     """Load schema for the SamDB.
1823     
1824     :param samdb: Load a schema into a SamDB.
1825     :param setup_path: Setup path function.
1826     :param schemadn: DN of the schema
1827     :param netbiosname: NetBIOS name of the host.
1828     :param configdn: DN of the configuration
1829     :param serverdn: DN of the server
1830
1831     Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
1832     """
1833     schema_data = get_schema_data(setup_path, {"SCHEMADN": schemadn})
1834     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1835     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1836     check_all_substituted(schema_data)
1837     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1838     prefixmap = b64encode(prefixmap)
1839
1840     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1841     head_data = substitute_var(head_data, {
1842                     "SCHEMADN": schemadn,
1843                     "NETBIOSNAME": netbiosname,
1844                     "CONFIGDN": configdn,
1845                     "DEFAULTSITE": sitename,
1846                     "PREFIXMAP_B64": prefixmap,
1847                     "SERVERDN": serverdn,
1848     })
1849     check_all_substituted(head_data)
1850     samdb.attach_schema_from_ldif(head_data, schema_data)
1851     return schema_data;
1852
1853 def get_schema_data(setup_path, subst_vars = None):
1854     """Get schema data from the AD schema files instead of schema.ldif.
1855
1856     :param setup_path: Setup path function.
1857     :param subst_vars: Optional variables to substitute in the file.
1858
1859     Returns the schema data after substitution
1860     """ 
1861
1862     # this data used to be read from schema.ldif
1863     
1864     data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
1865                           setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
1866
1867     if subst_vars is not None:
1868         data = substitute_var(data, subst_vars)
1869     check_all_substituted(data)
1870     return data