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