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