Merge branch 'master' of ssh://git.samba.org/data/git/samba into wspp-schema
[kai/samba-autobuild/.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
7 # 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                               names.hostname)
790     samdb.transaction_start()
791         
792     try:
793         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
794         if serverrole == "domain controller":
795             domain_oc = "domainDNS"
796         else:
797             domain_oc = "samba4LocalDomain"
798
799         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
800                 "DOMAINDN": names.domaindn,
801                 "ACI": aci,
802                 "DOMAIN_OC": domain_oc
803                 })
804
805         message("Modifying DomainDN: " + names.domaindn + "")
806         if domainguid is not None:
807             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
808         else:
809             domainguid_mod = ""
810
811         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
812             "LDAPTIME": timestring(int(time.time())),
813             "DOMAINSID": str(domainsid),
814             "SCHEMADN": names.schemadn, 
815             "NETBIOSNAME": names.netbiosname,
816             "DEFAULTSITE": names.sitename,
817             "CONFIGDN": names.configdn,
818             "SERVERDN": names.serverdn,
819             "POLICYGUID": policyguid,
820             "DOMAINDN": names.domaindn,
821             "DOMAINGUID_MOD": domainguid_mod,
822             })
823
824         message("Adding configuration container (permitted to fail)")
825         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
826             "CONFIGDN": names.configdn, 
827             "ACI": aci,
828             })
829         message("Modifying configuration container")
830         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
831             "CONFIGDN": names.configdn, 
832             "SCHEMADN": names.schemadn,
833             })
834
835         message("Adding schema container (permitted to fail)")
836         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
837             "SCHEMADN": names.schemadn,
838             "ACI": aci,
839             })
840         message("Modifying schema container")
841
842         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
843
844         setup_modify_ldif(samdb, 
845             setup_path("provision_schema_basedn_modify.ldif"), {
846             "SCHEMADN": names.schemadn,
847             "NETBIOSNAME": names.netbiosname,
848             "DEFAULTSITE": names.sitename,
849             "CONFIGDN": names.configdn,
850             "SERVERDN": names.serverdn,
851             "PREFIXMAP_B64": b64encode(prefixmap)
852             })
853
854         message("Setting up sam.ldb schema")
855         samdb.add_ldif(schema_data)
856         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
857                        {"SCHEMADN": names.schemadn})
858
859         message("Setting up sam.ldb configuration data")
860         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
861             "CONFIGDN": names.configdn,
862             "NETBIOSNAME": names.netbiosname,
863             "DEFAULTSITE": names.sitename,
864             "DNSDOMAIN": names.dnsdomain,
865             "DOMAIN": names.domain,
866             "SCHEMADN": names.schemadn,
867             "DOMAINDN": names.domaindn,
868             "SERVERDN": names.serverdn
869             })
870
871         message("Setting up display specifiers")
872         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
873                        {"CONFIGDN": names.configdn})
874
875         message("Adding users container (permitted to fail)")
876         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
877                 "DOMAINDN": names.domaindn})
878         message("Modifying users container")
879         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
880                 "DOMAINDN": names.domaindn})
881         message("Adding computers container (permitted to fail)")
882         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
883                 "DOMAINDN": names.domaindn})
884         message("Modifying computers container")
885         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
886                 "DOMAINDN": names.domaindn})
887         message("Setting up sam.ldb data")
888         setup_add_ldif(samdb, setup_path("provision.ldif"), {
889             "DOMAINDN": names.domaindn,
890             "NETBIOSNAME": names.netbiosname,
891             "DEFAULTSITE": names.sitename,
892             "CONFIGDN": names.configdn,
893             "SERVERDN": names.serverdn
894             })
895
896         if fill == FILL_FULL:
897             message("Setting up sam.ldb users and groups")
898             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
899                 "DOMAINDN": names.domaindn,
900                 "DOMAINSID": str(domainsid),
901                 "CONFIGDN": names.configdn,
902                 "ADMINPASS_B64": b64encode(adminpass),
903                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
904                 })
905
906             if serverrole == "domain controller":
907                 message("Setting up self join")
908                 setup_self_join(samdb, names=names, invocationid=invocationid, 
909                                 dnspass=dnspass,  
910                                 machinepass=machinepass, 
911                                 domainsid=domainsid, policyguid=policyguid,
912                                 setup_path=setup_path)
913
914     except:
915         samdb.transaction_cancel()
916         raise
917
918     samdb.transaction_commit()
919     return samdb
920
921
922 FILL_FULL = "FULL"
923 FILL_NT4SYNC = "NT4SYNC"
924 FILL_DRS = "DRS"
925
926 def provision(setup_dir, message, session_info, 
927               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
928               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
929               serverdn=None,
930               domain=None, hostname=None, hostip=None, hostip6=None, 
931               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
932               policyguid=None, invocationid=None, machinepass=None, 
933               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
934               wheel=None, backup=None, aci=None, serverrole=None, 
935               ldap_backend=None, ldap_backend_type=None, sitename=None):
936     """Provision samba4
937     
938     :note: caution, this wipes all existing data!
939     """
940
941     def setup_path(file):
942         return os.path.join(setup_dir, file)
943
944     if domainsid is None:
945         domainsid = security.random_sid()
946
947     if policyguid is None:
948         policyguid = str(uuid.uuid4())
949     if adminpass is None:
950         adminpass = glue.generate_random_str(12)
951     if krbtgtpass is None:
952         krbtgtpass = glue.generate_random_str(12)
953     if machinepass is None:
954         machinepass  = glue.generate_random_str(12)
955     if dnspass is None:
956         dnspass = glue.generate_random_str(12)
957     root_uid = findnss_uid([root or "root"])
958     nobody_uid = findnss_uid([nobody or "nobody"])
959     users_gid = findnss_gid([users or "users"])
960     if wheel is None:
961         wheel_gid = findnss_gid(["wheel", "adm"])
962     else:
963         wheel_gid = findnss_gid([wheel])
964     if aci is None:
965         aci = "# no aci for local ldb"
966
967     if targetdir is not None:
968         if (not os.path.exists(os.path.join(targetdir, "etc"))):
969             os.makedirs(os.path.join(targetdir, "etc"))
970         smbconf = os.path.join(targetdir, "etc", "smb.conf")
971     elif smbconf is None:
972         smbconf = param.default_path()
973
974     # only install a new smb.conf if there isn't one there already
975     if not os.path.exists(smbconf):
976         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
977                      targetdir)
978
979     lp = param.LoadParm()
980     lp.load(smbconf)
981
982     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
983                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
984                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
985                         serverdn=serverdn)
986
987     paths = provision_paths_from_lp(lp, names.dnsdomain)
988
989     if hostip is None:
990         try:
991             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
992         except socket.gaierror, (socket.EAI_NODATA, msg):
993             hostip = None
994
995     if hostip6 is None:
996         try:
997             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
998         except socket.gaierror, (socket.EAI_NODATA, msg): 
999             hostip6 = None
1000
1001     if serverrole is None:
1002         serverrole = lp.get("server role")
1003
1004     assert serverrole in ("domain controller", "member server", "standalone")
1005     if invocationid is None and serverrole == "domain controller":
1006         invocationid = str(uuid.uuid4())
1007
1008     if not os.path.exists(paths.private_dir):
1009         os.mkdir(paths.private_dir)
1010
1011     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1012     
1013     if ldap_backend is not None:
1014         if ldap_backend == "ldapi":
1015             # provision-backend will set this path suggested slapd command line / fedorads.inf
1016             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1017              
1018     # only install a new shares config db if there is none
1019     if not os.path.exists(paths.shareconf):
1020         message("Setting up share.ldb")
1021         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1022                         credentials=credentials, lp=lp)
1023         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1024
1025      
1026     message("Setting up secrets.ldb")
1027     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1028                                   session_info=session_info, 
1029                                   credentials=credentials, lp=lp)
1030
1031     message("Setting up the registry")
1032     setup_registry(paths.hklm, setup_path, session_info, 
1033                    credentials=credentials, lp=lp)
1034
1035     message("Setting up templates db")
1036     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
1037                       credentials=credentials, lp=lp)
1038
1039     message("Setting up idmap db")
1040     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1041                           credentials=credentials, lp=lp)
1042
1043     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1044                         credentials=credentials, lp=lp, names=names,
1045                         message=message, 
1046                         domainsid=domainsid, 
1047                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1048                         fill=samdb_fill, 
1049                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1050                         invocationid=invocationid, 
1051                         machinepass=machinepass, dnspass=dnspass,
1052                         serverrole=serverrole, ldap_backend=ldap_backend, 
1053                         ldap_backend_type=ldap_backend_type)
1054
1055     if lp.get("server role") == "domain controller":
1056         if paths.netlogon is None:
1057             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1058             message("Please either remove %s or see the template at %s" % 
1059                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1060             assert(paths.netlogon is not None)
1061
1062         if paths.sysvol is None:
1063             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1064             message("Please either remove %s or see the template at %s" % 
1065                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1066             assert(paths.sysvol is not None)            
1067             
1068         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1069                                    "{" + policyguid + "}")
1070         os.makedirs(policy_path, 0755)
1071         open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1072         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1073         os.makedirs(os.path.join(policy_path, "User"), 0755)
1074         if not os.path.isdir(paths.netlogon):
1075             os.makedirs(paths.netlogon, 0755)
1076
1077     if samdb_fill == FILL_FULL:
1078         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1079                             root_uid=root_uid, nobody_uid=nobody_uid,
1080                             users_gid=users_gid, wheel_gid=wheel_gid)
1081
1082         message("Setting up sam.ldb rootDSE marking as synchronized")
1083         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1084
1085         # Only make a zone file on the first DC, it should be replicated with DNS replication
1086         if serverrole == "domain controller":
1087             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1088                               credentials=credentials, lp=lp)
1089             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1090                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1091                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1092                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1093                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1094
1095             samdb = SamDB(paths.samdb, session_info=session_info, 
1096                       credentials=credentials, lp=lp)
1097
1098             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1099             assert isinstance(domainguid, str)
1100             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1101                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1102                                        scope=SCOPE_SUBTREE)
1103             assert isinstance(hostguid, str)
1104
1105             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1106                              domaindn=names.domaindn, hostip=hostip,
1107                              hostip6=hostip6, hostname=names.hostname,
1108                              dnspass=dnspass, realm=names.realm,
1109                              domainguid=domainguid, hostguid=hostguid)
1110
1111             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1112                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1113
1114             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1115                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1116                               keytab_name=paths.dns_keytab)
1117             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1118             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1119
1120             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1121                              hostname=names.hostname, realm=names.realm)
1122             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1123
1124     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1125                                ldapi_url)
1126
1127     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1128
1129     message("Once the above files are installed, your Samba4 server will be ready to use")
1130     message("Server Role:    %s" % serverrole)
1131     message("Hostname:       %s" % names.hostname)
1132     message("NetBIOS Domain: %s" % names.domain)
1133     message("DNS Domain:     %s" % names.dnsdomain)
1134     message("DOMAIN SID:     %s" % str(domainsid))
1135     if samdb_fill == FILL_FULL:
1136         message("Admin password: %s" % adminpass)
1137
1138     result = ProvisionResult()
1139     result.domaindn = domaindn
1140     result.paths = paths
1141     result.lp = lp
1142     result.samdb = samdb
1143     return result
1144
1145
1146 def provision_become_dc(setup_dir=None,
1147                         smbconf=None, targetdir=None, realm=None, 
1148                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1149                         serverdn=None,
1150                         domain=None, hostname=None, domainsid=None, 
1151                         adminpass=None, krbtgtpass=None, domainguid=None, 
1152                         policyguid=None, invocationid=None, machinepass=None, 
1153                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1154                         wheel=None, backup=None, aci=None, serverrole=None, 
1155                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1156
1157     def message(text):
1158         """print a message if quiet is not set."""
1159         print text
1160
1161     return provision(setup_dir, message, system_session(), None,
1162               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1163               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1164               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1165     
1166
1167 def setup_db_config(setup_path, dbdir):
1168     """Setup a Berkeley database.
1169     
1170     :param setup_path: Setup path function.
1171     :param dbdir: Database directory."""
1172     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1173         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1174     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1175         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1176     
1177     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1178                {"LDAPDBDIR": dbdir})
1179     
1180
1181
1182 def provision_backend(setup_dir=None, message=None,
1183                       smbconf=None, targetdir=None, realm=None, 
1184                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1185                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1186                       ldap_backend_type=None, ldap_backend_port=None,
1187                       ol_mmr_urls=None,ol_olc=None,ol_slaptest=None):
1188
1189     def setup_path(file):
1190         return os.path.join(setup_dir, file)
1191
1192     if hostname is None:
1193         hostname = socket.gethostname().split(".")[0].lower()
1194
1195     if root is None:
1196         root = findnss(pwd.getpwnam, ["root"])[0]
1197
1198     if adminpass is None:
1199         adminpass = glue.generate_random_str(12)
1200
1201     if targetdir is not None:
1202         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1203             os.makedirs(os.path.join(targetdir, "etc"))
1204         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1205     elif smbconf is None:
1206         smbconf = param.default_path()
1207         assert smbconf is not None
1208
1209     # only install a new smb.conf if there isn't one there already
1210     if not os.path.exists(smbconf):
1211         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1212                      targetdir)
1213
1214     # openldap-online-configuration: validation of olc and slaptest
1215     if ol_olc == "yes" and ol_slaptest is None: 
1216         sys.exit("Warning: OpenLDAP-Online-Configuration cant be setup without path to slaptest-Binary!")
1217
1218     if ol_olc == "yes" and ol_slaptest is not None:
1219         ol_slaptest = ol_slaptest + "/slaptest"
1220         if not os.path.exists(ol_slaptest):
1221             message (ol_slaptest)
1222             sys.exit("Warning: Given Path to slaptest-Binary does not exist!")
1223     ###
1224
1225
1226
1227     lp = param.LoadParm()
1228     lp.load(smbconf)
1229
1230     if serverrole is None:
1231         serverrole = lp.get("server role")
1232
1233     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1234                         dnsdomain=realm, serverrole=serverrole, 
1235                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1236                         schemadn=schemadn)
1237
1238     paths = provision_paths_from_lp(lp, names.dnsdomain)
1239
1240     if not os.path.isdir(paths.ldapdir):
1241         os.makedirs(paths.ldapdir, 0700)
1242     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1243     try:
1244         os.unlink(schemadb_path)
1245     except OSError:
1246         pass
1247
1248     schemadb = Ldb(schemadb_path, lp=lp)
1249  
1250     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1251
1252     setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1253                    {"SCHEMADN": names.schemadn,
1254                     "ACI": "#",
1255                     })
1256     setup_modify_ldif(schemadb, 
1257                       setup_path("provision_schema_basedn_modify.ldif"), \
1258                           {"SCHEMADN": names.schemadn,
1259                            "NETBIOSNAME": names.netbiosname,
1260                            "DEFAULTSITE": DEFAULTSITE,
1261                            "CONFIGDN": names.configdn,
1262                            "SERVERDN": names.serverdn,
1263                            "PREFIXMAP_B64": b64encode(prefixmap)
1264                            })
1265     
1266     setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"), 
1267                    {"SCHEMADN": names.schemadn })
1268
1269     data = get_schema_data(setup_path, {"SCHEMADN": names.schemadn})
1270     schemadb.add_ldif(data)
1271
1272     if ldap_backend_type == "fedora-ds":
1273         if ldap_backend_port is not None:
1274             serverport = "ServerPort=%d" % ldap_backend_port
1275         else:
1276             serverport = ""
1277
1278         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1279                    {"ROOT": root,
1280                     "HOSTNAME": hostname,
1281                     "DNSDOMAIN": names.dnsdomain,
1282                     "LDAPDIR": paths.ldapdir,
1283                     "DOMAINDN": names.domaindn,
1284                     "LDAPMANAGERDN": names.ldapmanagerdn,
1285                     "LDAPMANAGERPASS": adminpass, 
1286                     "SERVERPORT": serverport})
1287         
1288         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1289                    {"CONFIGDN": names.configdn,
1290                     "SCHEMADN": names.schemadn,
1291                     })
1292         
1293         mapping = "schema-map-fedora-ds-1.0"
1294         backend_schema = "99_ad.ldif"
1295         
1296         slapdcommand="Initialise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1297        
1298         ldapuser = "--simple-bind-dn=" + names.ldapmanagerdn
1299
1300     elif ldap_backend_type == "openldap":
1301         attrs = ["linkID", "lDAPDisplayName"]
1302         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)
1303
1304         memberof_config = "# Generated from schema in %s\n" % schemadb_path
1305         refint_attributes = ""
1306         for i in range (0, len(res)):
1307             expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1308             target = schemadb.searchone(basedn=names.schemadn, 
1309                                         expression=expression, 
1310                                         attribute="lDAPDisplayName", 
1311                                         scope=SCOPE_SUBTREE)
1312             if target is not None:
1313                 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0]
1314             
1315                 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1316                                                      { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1317                                                        "MEMBEROF_ATTR" : str(target) })
1318
1319         refint_config = read_and_sub_file(setup_path("refint.conf"),
1320                                             { "LINK_ATTRS" : refint_attributes})
1321
1322 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1323         mmr_on_config = ""
1324         mmr_replicator_acl = ""
1325         mmr_serverids_config = ""
1326         mmr_syncrepl_schema_config = "" 
1327         mmr_syncrepl_config_config = "" 
1328         mmr_syncrepl_user_config = "" 
1329        
1330  
1331         if ol_mmr_urls is not None:
1332                 # For now, make these equal
1333                 mmr_pass = adminpass
1334
1335                 url_list=filter(None,ol_mmr_urls.split(' ')) 
1336                 if (len(url_list) == 1):
1337                     url_list=filter(None,ol_mmr_urls.split(',')) 
1338                      
1339
1340                 mmr_on_config = "MirrorMode On"
1341                 mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1342                 serverid=0
1343                 for url in url_list:
1344                         serverid=serverid+1
1345                         mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1346                                                                      { "SERVERID" : str(serverid),
1347                                                                        "LDAPSERVER" : url })
1348                         rid=serverid*10
1349                         rid=rid+1
1350                         mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1351                                                                      {  "RID" : str(rid),
1352                                                                         "MMRDN": names.schemadn,
1353                                                                         "LDAPSERVER" : url,
1354                                                                         "MMR_PASSWORD": mmr_pass})
1355
1356                         rid=rid+1
1357                         mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1358                                                                      {  "RID" : str(rid),
1359                                                                         "MMRDN": names.configdn,
1360                                                                         "LDAPSERVER" : url,
1361                                                                         "MMR_PASSWORD": mmr_pass})
1362
1363                         rid=rid+1
1364                         mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1365                                                                      {  "RID" : str(rid),
1366                                                                         "MMRDN": names.domaindn,
1367                                                                         "LDAPSERVER" : url,
1368                                                                         "MMR_PASSWORD": mmr_pass })
1369         # olc = yes?
1370         olc_config_pass = ""
1371         olc_config_acl = ""
1372         olc_syncrepl_config = ""
1373         olc_mmr_config = "" 
1374         if ol_olc == "yes":
1375                 olc_config_pass += read_and_sub_file(setup_path("olc_pass.conf"),
1376                                                                 { "OLC_PW": adminpass })
1377                 olc_config_acl += read_and_sub_file(setup_path("olc_acl.conf"),{})
1378                 
1379             # if olc = yes + mmr = yes, generate cn=config-replication directives
1380             # and  olc_seed.lif for the other mmr-servers
1381                 if ol_olc == "yes" and ol_mmr_urls is not None:
1382                         serverid=0
1383                         olc_serverids_config = ""
1384                         olc_syncrepl_config = ""
1385                         olc_syncrepl_seed_config = ""
1386                         olc_mmr_config = "" 
1387                         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1388                         rid=1000
1389                         for url in url_list:
1390                                 serverid=serverid+1
1391                                 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1392                                                                      { "SERVERID" : str(serverid),
1393                                                                        "LDAPSERVER" : url })
1394                         
1395                                 rid=rid+1
1396                                 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1397                                                                      {  "RID" : str(rid),
1398                                                                         "LDAPSERVER" : url,
1399                                                                         "MMR_PASSWORD": adminpass})
1400
1401                                 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1402                                                                      {  "RID" : str(rid),
1403                                                                         "LDAPSERVER" : url})
1404
1405                                 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1406                                                                      {"OLC_SERVER_ID_CONF": olc_serverids_config,
1407                                                                       "OLC_PW": adminpass,
1408                                                                       "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1409         
1410
1411                 # end olc
1412
1413         setup_file(setup_path("slapd.conf"), paths.slapdconf,
1414                    {"DNSDOMAIN": names.dnsdomain,
1415                     "LDAPDIR": paths.ldapdir,
1416                     "DOMAINDN": names.domaindn,
1417                     "CONFIGDN": names.configdn,
1418                     "SCHEMADN": names.schemadn,
1419                     "MEMBEROF_CONFIG": memberof_config,
1420                     "MIRRORMODE": mmr_on_config,
1421                     "REPLICATOR_ACL": mmr_replicator_acl,
1422                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1423                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1424                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1425                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1426                     "OLC_CONFIG_PASS": olc_config_pass,
1427                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1428                     "OLC_CONFIG_ACL": olc_config_acl,
1429                     "OLC_MMR_CONFIG": olc_mmr_config,
1430                     "REFINT_CONFIG": refint_config})
1431         setup_file(setup_path("modules.conf"), paths.modulesconf,
1432                    {"REALM": names.realm})
1433         
1434         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1435         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1436         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1437
1438         if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1439             os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1440
1441         setup_file(setup_path("cn=samba.ldif"), 
1442                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1443                    { "UUID": str(uuid.uuid4()), 
1444                      "LDAPTIME": timestring(int(time.time()))} )
1445         setup_file(setup_path("cn=samba-admin.ldif"), 
1446                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1447                               {"LDAPADMINPASS_B64": b64encode(adminpass),
1448                                "UUID": str(uuid.uuid4()), 
1449                                "LDAPTIME": timestring(int(time.time()))} )
1450         
1451         if ol_mmr_urls is not None:
1452            setup_file(setup_path("cn=replicator.ldif"),
1453                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1454                               {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1455                                "UUID": str(uuid.uuid4()),
1456                                "LDAPTIME": timestring(int(time.time()))} )
1457
1458
1459         mapping = "schema-map-openldap-2.3"
1460         backend_schema = "backend-schema.schema"
1461
1462         ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1463         if ldap_backend_port is not None:
1464             server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1465         else:
1466             server_port_string = ""
1467
1468         if ol_olc != "yes" and ol_mmr_urls is None:
1469           slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1470
1471         if ol_olc == "yes" and ol_mmr_urls is None:
1472           slapdcommand="Start slapd with:    slapd -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\"" 
1473
1474         if ol_olc != "yes" and ol_mmr_urls is not None:
1475           slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -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.olcdir + " -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\""
1479
1480
1481         ldapuser = "--username=samba-admin"
1482
1483             
1484     schema_command = "bin/ad2oLschema --option=convert:target=" + ldap_backend_type + " -I " + setup_path(mapping) + " -H tdb://" + schemadb_path + " -O " + os.path.join(paths.ldapdir, backend_schema)
1485             
1486     os.system(schema_command)
1487
1488     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type)
1489     message("Server Role:         %s" % serverrole)
1490     message("Hostname:            %s" % names.hostname)
1491     message("DNS Domain:          %s" % names.dnsdomain)
1492     message("Base DN:             %s" % names.domaindn)
1493
1494     if ldap_backend_type == "openldap":
1495         message("LDAP admin user:     samba-admin")
1496     else:
1497         message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1498
1499     message("LDAP admin password: %s" % adminpass)
1500     message(slapdcommand)
1501     if ol_olc == "yes" or ol_mmr_urls is not None:
1502         message("Attention to slapd-Port: <PORT> must be different than 389!")
1503     assert isinstance(ldap_backend_type, str)
1504     assert isinstance(ldapuser, str)
1505     assert isinstance(adminpass, str)
1506     assert isinstance(names.dnsdomain, str)
1507     assert isinstance(names.domain, str)
1508     assert isinstance(serverrole, str)
1509     args = ["--ldap-backend=ldapi",
1510             "--ldap-backend-type=" + ldap_backend_type,
1511             "--password=" + adminpass,
1512             ldapuser,
1513             "--realm=" + names.dnsdomain,
1514             "--domain=" + names.domain,
1515             "--server-role='" + serverrole + "'"]
1516     message("Run provision with: " + " ".join(args))
1517
1518
1519     # if --ol-olc=yes, generate online-configuration in ../private/ldap/slapd.d 
1520     if ol_olc == "yes":
1521           if not os.path.isdir(paths.olcdir):
1522              os.makedirs(paths.olcdir, 0770)
1523           paths.olslaptest = str(ol_slaptest)
1524           olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" +  paths.olcdir + " >/dev/null 2>&1"
1525           os.system(olc_command)
1526           os.remove(paths.slapdconf)        
1527           # use line below for debugging during olc-conversion with slaptest, instead of olc_command above 
1528           #olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" +  paths.olcdir"
1529
1530
1531 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1532     """Create a PHP LDAP admin configuration file.
1533
1534     :param path: Path to write the configuration to.
1535     :param setup_path: Function to generate setup paths.
1536     """
1537     setup_file(setup_path("phpldapadmin-config.php"), path, 
1538             {"S4_LDAPI_URI": ldapi_uri})
1539
1540
1541 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1542                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1543     """Write out a DNS zone file, from the info in the current database.
1544
1545     :param path: Path of the new zone file.
1546     :param setup_path: Setup path function.
1547     :param dnsdomain: DNS Domain name
1548     :param domaindn: DN of the Domain
1549     :param hostip: Local IPv4 IP
1550     :param hostip6: Local IPv6 IP
1551     :param hostname: Local hostname
1552     :param dnspass: Password for DNS
1553     :param realm: Realm name
1554     :param domainguid: GUID of the domain.
1555     :param hostguid: GUID of the host.
1556     """
1557     assert isinstance(domainguid, str)
1558
1559     if hostip6 is not None:
1560         hostip6_base_line = "            IN AAAA    " + hostip6
1561         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1562     else:
1563         hostip6_base_line = ""
1564         hostip6_host_line = ""
1565
1566     if hostip is not None:
1567         hostip_base_line = "            IN A    " + hostip
1568         hostip_host_line = hostname + "        IN A    " + hostip
1569     else:
1570         hostip_base_line = ""
1571         hostip_host_line = ""
1572
1573     setup_file(setup_path("provision.zone"), path, {
1574             "DNSPASS_B64": b64encode(dnspass),
1575             "HOSTNAME": hostname,
1576             "DNSDOMAIN": dnsdomain,
1577             "REALM": realm,
1578             "HOSTIP_BASE_LINE": hostip_base_line,
1579             "HOSTIP_HOST_LINE": hostip_host_line,
1580             "DOMAINGUID": domainguid,
1581             "DATESTRING": time.strftime("%Y%m%d%H"),
1582             "DEFAULTSITE": DEFAULTSITE,
1583             "HOSTGUID": hostguid,
1584             "HOSTIP6_BASE_LINE": hostip6_base_line,
1585             "HOSTIP6_HOST_LINE": hostip6_host_line,
1586         })
1587
1588
1589 def create_named_conf(path, setup_path, realm, dnsdomain,
1590                       private_dir):
1591     """Write out a file containing zone statements suitable for inclusion in a
1592     named.conf file (including GSS-TSIG configuration).
1593     
1594     :param path: Path of the new named.conf file.
1595     :param setup_path: Setup path function.
1596     :param realm: Realm name
1597     :param dnsdomain: DNS Domain name
1598     :param private_dir: Path to private directory
1599     :param keytab_name: File name of DNS keytab file
1600     """
1601
1602     setup_file(setup_path("named.conf"), path, {
1603             "DNSDOMAIN": dnsdomain,
1604             "REALM": realm,
1605             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1606             "PRIVATE_DIR": private_dir
1607             })
1608
1609 def create_named_txt(path, setup_path, realm, dnsdomain,
1610                       private_dir, keytab_name):
1611     """Write out a file containing zone statements suitable for inclusion in a
1612     named.conf file (including GSS-TSIG configuration).
1613     
1614     :param path: Path of the new named.conf file.
1615     :param setup_path: Setup path function.
1616     :param realm: Realm name
1617     :param dnsdomain: DNS Domain name
1618     :param private_dir: Path to private directory
1619     :param keytab_name: File name of DNS keytab file
1620     """
1621
1622     setup_file(setup_path("named.txt"), path, {
1623             "DNSDOMAIN": dnsdomain,
1624             "REALM": realm,
1625             "DNS_KEYTAB": keytab_name,
1626             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1627             "PRIVATE_DIR": private_dir
1628         })
1629
1630 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1631     """Write out a file containing zone statements suitable for inclusion in a
1632     named.conf file (including GSS-TSIG configuration).
1633     
1634     :param path: Path of the new named.conf file.
1635     :param setup_path: Setup path function.
1636     :param dnsdomain: DNS Domain name
1637     :param hostname: Local hostname
1638     :param realm: Realm name
1639     """
1640
1641     setup_file(setup_path("krb5.conf"), path, {
1642             "DNSDOMAIN": dnsdomain,
1643             "HOSTNAME": hostname,
1644             "REALM": realm,
1645         })
1646
1647
1648 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename,
1649                 serverdn, servername):
1650     """Load schema for the SamDB.
1651     
1652     :param samdb: Load a schema into a SamDB.
1653     :param setup_path: Setup path function.
1654     :param schemadn: DN of the schema
1655     :param netbiosname: NetBIOS name of the host.
1656     :param configdn: DN of the configuration
1657     :param serverdn: DN of the server
1658     :param servername: Host name of the server
1659
1660     Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
1661     """
1662     schema_data = get_schema_data(setup_path, {"SCHEMADN": schemadn})
1663     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1664     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1665     check_all_substituted(schema_data)
1666     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1667     prefixmap = b64encode(prefixmap)
1668
1669     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1670     head_data = substitute_var(head_data, {
1671                     "SCHEMADN": schemadn,
1672                     "NETBIOSNAME": netbiosname,
1673                     "CONFIGDN": configdn,
1674                     "DEFAULTSITE": sitename,
1675                     "PREFIXMAP_B64": prefixmap,
1676                     "SERVERDN": serverdn,
1677                     "SERVERNAME": servername,
1678     })
1679     check_all_substituted(head_data)
1680     samdb.attach_schema_from_ldif(head_data, schema_data)
1681     return schema_data;
1682
1683 def get_schema_data(setup_path, subst_vars = None):
1684     """Get schema data from the AD schema files instead of schema.ldif.
1685
1686     :param setup_path: Setup path function.
1687     :param subst_vars: Optional variables to substitute in the file.
1688     """ 
1689
1690     # this data used to be read from schema.ldif
1691     
1692     data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8Attributes.txt'),
1693                           setup_path('ad-schema/MS-AD_Schema_2K8Classes.txt'))
1694
1695     if subst_vars is not None:
1696         data = substitute_var(data, subst_vars)
1697     check_all_substituted(data)
1698     return data