Owner and group defaulting.
[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                     "descriptor",
588                     "samldb",
589                     "password_hash",
590                     "operational",
591                     "kludge_acl"]
592     tdb_modules_list = [
593                     "subtree_rename",
594                     "subtree_delete",
595                     "linked_attributes",
596                     "extended_dn_out_ldb"]
597     modules_list2 = ["show_deleted",
598                     "partition"]
599  
600     domaindn_ldb = "users.ldb"
601     configdn_ldb = "configuration.ldb"
602     schemadn_ldb = "schema.ldb"
603     if ldap_backend is not None:
604         domaindn_ldb = ldap_backend.ldapi_uri
605         configdn_ldb = ldap_backend.ldapi_uri
606         schemadn_ldb = ldap_backend.ldapi_uri
607         
608         if ldap_backend.ldap_backend_type == "fedora-ds":
609             backend_modules = ["nsuniqueid", "paged_searches"]
610             # We can handle linked attributes here, as we don't have directory-side subtree operations
611             tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
612         elif ldap_backend.ldap_backend_type == "openldap":
613             backend_modules = ["entryuuid", "paged_searches"]
614             # OpenLDAP handles subtree renames, so we don't want to do any of these things
615             tdb_modules_list = ["extended_dn_out_dereference"]
616
617     elif serverrole == "domain controller":
618         tdb_modules_list.insert(0, "repl_meta_data")
619         backend_modules = []
620     else:
621         backend_modules = ["objectguid"]
622
623     if tdb_modules_list is None:
624         tdb_modules_list_as_string = ""
625     else:
626         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
627         
628     samdb.transaction_start()
629     try:
630         message("Setting up sam.ldb partitions and settings")
631         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
632                 "SCHEMADN": names.schemadn, 
633                 "SCHEMADN_LDB": schemadn_ldb,
634                 "SCHEMADN_MOD2": ",objectguid",
635                 "CONFIGDN": names.configdn,
636                 "CONFIGDN_LDB": configdn_ldb,
637                 "DOMAINDN": names.domaindn,
638                 "DOMAINDN_LDB": domaindn_ldb,
639                 "SCHEMADN_MOD": "schema_fsmo,instancetype",
640                 "CONFIGDN_MOD": "naming_fsmo,instancetype",
641                 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
642                 "MODULES_LIST": ",".join(modules_list),
643                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
644                 "MODULES_LIST2": ",".join(modules_list2),
645                 "BACKEND_MOD": ",".join(backend_modules),
646         })
647
648         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
649
650         message("Setting up sam.ldb rootDSE")
651         setup_samdb_rootdse(samdb, setup_path, names)
652
653     except:
654         samdb.transaction_cancel()
655         raise
656
657     samdb.transaction_commit()
658     
659
660
661 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, 
662                         netbiosname, domainsid, keytab_path, samdb_url, 
663                         dns_keytab_path, dnspass, machinepass):
664     """Add DC-specific bits to a secrets database.
665     
666     :param secretsdb: Ldb Handle to the secrets database
667     :param setup_path: Setup path function
668     :param machinepass: Machine password
669     """
670     setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { 
671             "MACHINEPASS_B64": b64encode(machinepass),
672             "DOMAIN": domain,
673             "REALM": realm,
674             "DNSDOMAIN": dnsdomain,
675             "DOMAINSID": str(domainsid),
676             "SECRETS_KEYTAB": keytab_path,
677             "NETBIOSNAME": netbiosname,
678             "SAM_LDB": samdb_url,
679             "DNS_KEYTAB": dns_keytab_path,
680             "DNSPASS_B64": b64encode(dnspass),
681             })
682
683
684 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
685     """Setup the secrets database.
686
687     :param path: Path to the secrets database.
688     :param setup_path: Get the path to a setup file.
689     :param session_info: Session info.
690     :param credentials: Credentials
691     :param lp: Loadparm context
692     :return: LDB handle for the created secrets database
693     """
694     if os.path.exists(path):
695         os.unlink(path)
696     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
697                       lp=lp)
698     secrets_ldb.erase()
699     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
700     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
701                       lp=lp)
702     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
703
704     if credentials is not None and credentials.authentication_requested():
705         if credentials.get_bind_dn() is not None:
706             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
707                     "LDAPMANAGERDN": credentials.get_bind_dn(),
708                     "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
709                     })
710         else:
711             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
712                     "LDAPADMINUSER": credentials.get_username(),
713                     "LDAPADMINREALM": credentials.get_realm(),
714                     "LDAPADMINPASS_B64": b64encode(credentials.get_password())
715                     })
716
717     return secrets_ldb
718
719 def setup_registry(path, setup_path, session_info, lp):
720     """Setup the registry.
721     
722     :param path: Path to the registry database
723     :param setup_path: Function that returns the path to a setup.
724     :param session_info: Session information
725     :param credentials: Credentials
726     :param lp: Loadparm context
727     """
728     reg = registry.Registry()
729     hive = registry.open_ldb(path, session_info=session_info, 
730                          lp_ctx=lp)
731     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
732     provision_reg = setup_path("provision.reg")
733     assert os.path.exists(provision_reg)
734     reg.diff_apply(provision_reg)
735
736
737 def setup_idmapdb(path, setup_path, session_info, lp):
738     """Setup the idmap database.
739
740     :param path: path to the idmap database
741     :param setup_path: Function that returns a path to a setup file
742     :param session_info: Session information
743     :param credentials: Credentials
744     :param lp: Loadparm context
745     """
746     if os.path.exists(path):
747         os.unlink(path)
748
749     idmap_ldb = IDmapDB(path, session_info=session_info,
750                         lp=lp)
751
752     idmap_ldb.erase()
753     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
754     return idmap_ldb
755
756
757 def setup_samdb_rootdse(samdb, setup_path, names):
758     """Setup the SamDB rootdse.
759
760     :param samdb: Sam Database handle
761     :param setup_path: Obtain setup path
762     """
763     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
764         "SCHEMADN": names.schemadn, 
765         "NETBIOSNAME": names.netbiosname,
766         "DNSDOMAIN": names.dnsdomain,
767         "REALM": names.realm,
768         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
769         "DOMAINDN": names.domaindn,
770         "ROOTDN": names.rootdn,
771         "CONFIGDN": names.configdn,
772         "SERVERDN": names.serverdn,
773         })
774         
775
776 def setup_self_join(samdb, names,
777                     machinepass, dnspass, 
778                     domainsid, invocationid, setup_path,
779                     policyguid, policyguid_dc, domainControllerFunctionality):
780     """Join a host to its own domain."""
781     assert isinstance(invocationid, str)
782     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
783               "CONFIGDN": names.configdn, 
784               "SCHEMADN": names.schemadn,
785               "DOMAINDN": names.domaindn,
786               "SERVERDN": names.serverdn,
787               "INVOCATIONID": invocationid,
788               "NETBIOSNAME": names.netbiosname,
789               "DEFAULTSITE": names.sitename,
790               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
791               "MACHINEPASS_B64": b64encode(machinepass),
792               "DNSPASS_B64": b64encode(dnspass),
793               "REALM": names.realm,
794               "DOMAIN": names.domain,
795               "DNSDOMAIN": names.dnsdomain,
796               "SAMBA_VERSION_STRING": version,
797               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
798
799     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
800               "POLICYGUID": policyguid,
801               "POLICYGUID_DC": policyguid_dc,
802               "DNSDOMAIN": names.dnsdomain,
803               "DOMAINSID": str(domainsid),
804               "DOMAINDN": names.domaindn})
805     
806     # add the NTDSGUID based SPNs
807     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
808     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
809                                      expression="", scope=SCOPE_BASE)
810     assert isinstance(names.ntdsguid, str)
811
812     # Setup fSMORoleOwner entries to point at the newly created DC entry
813     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
814               "DOMAIN": names.domain,
815               "DNSDOMAIN": names.dnsdomain,
816               "DOMAINDN": names.domaindn,
817               "CONFIGDN": names.configdn,
818               "SCHEMADN": names.schemadn, 
819               "DEFAULTSITE": names.sitename,
820               "SERVERDN": names.serverdn,
821               "NETBIOSNAME": names.netbiosname,
822               "NTDSGUID": names.ntdsguid
823               })
824
825
826 def setup_samdb(path, setup_path, session_info, credentials, lp, 
827                 names, message, 
828                 domainsid, domainguid, policyguid, policyguid_dc,
829                 fill, adminpass, krbtgtpass, 
830                 machinepass, invocationid, dnspass,
831                 serverrole, schema=None, ldap_backend=None):
832     """Setup a complete SAM Database.
833     
834     :note: This will wipe the main SAM database file!
835     """
836
837     domainFunctionality = DS_BEHAVIOR_WIN2008
838     forestFunctionality = DS_BEHAVIOR_WIN2008
839     domainControllerFunctionality = DS_BEHAVIOR_WIN2008
840
841     # Also wipes the database
842     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
843                            credentials=credentials, session_info=session_info,
844                            names=names, 
845                            ldap_backend=ldap_backend, serverrole=serverrole)
846
847     if (schema == None):
848         schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
849             sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
850
851     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
852     samdb = Ldb(session_info=session_info, 
853                 credentials=credentials, lp=lp)
854
855     message("Pre-loading the Samba 4 and AD schema")
856
857     # Load the schema from the one we computed earlier
858     samdb.set_schema_from_ldb(schema.ldb)
859
860     # And now we can connect to the DB - the schema won't be loaded from the DB
861     samdb.connect(path)
862
863     # Load @OPTIONS
864     samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
865
866     if fill == FILL_DRS:
867         return samdb
868
869     samdb.transaction_start()
870     try:
871         message("Erasing data from partitions")
872         # Load the schema (again).  This time it will force a reindex,
873         # and will therefore make the erase_partitions() below
874         # computationally sane
875         samdb.set_schema_from_ldb(schema.ldb)
876         samdb.erase_partitions()
877     
878         # Set the domain functionality levels onto the database.
879         # Various module (the password_hash module in particular) need
880         # to know what level of AD we are emulating.
881
882         # These will be fixed into the database via the database
883         # modifictions below, but we need them set from the start.
884         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
885         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
886         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
887
888         samdb.set_domain_sid(str(domainsid))
889         if serverrole == "domain controller":
890             samdb.set_invocation_id(invocationid)
891
892         message("Adding DomainDN: %s" % names.domaindn)
893         if serverrole == "domain controller":
894             domain_oc = "domainDNS"
895         else:
896             domain_oc = "samba4LocalDomain"
897
898 #impersonate domain admin
899         admin_session_info = admin_session(lp, str(domainsid))
900         samdb.set_session_info(admin_session_info)
901
902         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
903                 "DOMAINDN": names.domaindn,
904                 "DOMAIN_OC": domain_oc
905                 })
906
907         message("Modifying DomainDN: " + names.domaindn + "")
908         if domainguid is not None:
909             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
910         else:
911             domainguid_mod = ""
912
913         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
914             "LDAPTIME": timestring(int(time.time())),
915             "DOMAINSID": str(domainsid),
916             "SCHEMADN": names.schemadn, 
917             "NETBIOSNAME": names.netbiosname,
918             "DEFAULTSITE": names.sitename,
919             "CONFIGDN": names.configdn,
920             "SERVERDN": names.serverdn,
921             "POLICYGUID": policyguid,
922             "DOMAINDN": names.domaindn,
923             "DOMAINGUID_MOD": domainguid_mod,
924             "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
925             })
926
927         message("Adding configuration container")
928         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
929             "CONFIGDN": names.configdn, 
930             })
931         message("Modifying configuration container")
932         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
933             "CONFIGDN": names.configdn, 
934             "SCHEMADN": names.schemadn,
935             })
936
937         # The LDIF here was created when the Schema object was constructed
938         message("Setting up sam.ldb schema")
939         samdb.add_ldif(schema.schema_dn_add)
940         samdb.modify_ldif(schema.schema_dn_modify)
941         samdb.write_prefixes_from_schema()
942         samdb.add_ldif(schema.schema_data)
943         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
944                        {"SCHEMADN": names.schemadn})
945
946         message("Setting up sam.ldb configuration data")
947         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
948             "CONFIGDN": names.configdn,
949             "NETBIOSNAME": names.netbiosname,
950             "DEFAULTSITE": names.sitename,
951             "DNSDOMAIN": names.dnsdomain,
952             "DOMAIN": names.domain,
953             "SCHEMADN": names.schemadn,
954             "DOMAINDN": names.domaindn,
955             "SERVERDN": names.serverdn,
956             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
957             })
958
959         message("Setting up display specifiers")
960         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
961         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
962         check_all_substituted(display_specifiers_ldif)
963         samdb.add_ldif(display_specifiers_ldif)
964
965         message("Adding users container")
966         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
967                 "DOMAINDN": names.domaindn})
968         message("Modifying users container")
969         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
970                 "DOMAINDN": names.domaindn})
971         message("Adding computers container")
972         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
973                 "DOMAINDN": names.domaindn})
974         message("Modifying computers container")
975         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
976                 "DOMAINDN": names.domaindn})
977         message("Setting up sam.ldb data")
978         setup_add_ldif(samdb, setup_path("provision.ldif"), {
979             "DOMAINDN": names.domaindn,
980             "NETBIOSNAME": names.netbiosname,
981             "DEFAULTSITE": names.sitename,
982             "CONFIGDN": names.configdn,
983             "SERVERDN": names.serverdn,
984             "POLICYGUID_DC": policyguid_dc
985             })
986
987         if fill == FILL_FULL:
988             message("Setting up sam.ldb users and groups")
989             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
990                 "DOMAINDN": names.domaindn,
991                 "DOMAINSID": str(domainsid),
992                 "CONFIGDN": names.configdn,
993                 "ADMINPASS_B64": b64encode(adminpass),
994                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
995                 })
996
997             if serverrole == "domain controller":
998                 message("Setting up self join")
999                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1000                                 dnspass=dnspass,  
1001                                 machinepass=machinepass, 
1002                                 domainsid=domainsid, policyguid=policyguid,
1003                                 policyguid_dc=policyguid_dc,
1004                                 setup_path=setup_path,
1005                                 domainControllerFunctionality=domainControllerFunctionality)
1006                 # add the NTDSGUID based SPNs
1007                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1008                 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
1009                                                  expression="", scope=SCOPE_BASE)
1010                 assert isinstance(names.ntdsguid, str)
1011
1012     except:
1013         samdb.transaction_cancel()
1014         raise
1015
1016     samdb.transaction_commit()
1017     return samdb
1018
1019
1020 FILL_FULL = "FULL"
1021 FILL_NT4SYNC = "NT4SYNC"
1022 FILL_DRS = "DRS"
1023
1024
1025 def provision(setup_dir, message, session_info, 
1026               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1027               realm=None, 
1028               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1029               serverdn=None,
1030               domain=None, hostname=None, hostip=None, hostip6=None, 
1031               domainsid=None, adminpass=None, ldapadminpass=None, 
1032               krbtgtpass=None, domainguid=None, 
1033               policyguid=None, policyguid_dc=None, invocationid=None,
1034               machinepass=None, 
1035               dnspass=None, root=None, nobody=None, users=None, 
1036               wheel=None, backup=None, aci=None, serverrole=None, 
1037               ldap_backend_extra_port=None, ldap_backend_type=None,
1038               sitename=None,
1039               ol_mmr_urls=None, ol_olc=None, 
1040               setup_ds_path=None, slapd_path=None, nosync=False,
1041               ldap_dryrun_mode=False):
1042     """Provision samba4
1043     
1044     :note: caution, this wipes all existing data!
1045     """
1046
1047     def setup_path(file):
1048         return os.path.join(setup_dir, file)
1049
1050     if domainsid is None:
1051         domainsid = security.random_sid()
1052
1053     # create/adapt the group policy GUIDs
1054     if policyguid is None:
1055         policyguid = str(uuid.uuid4())
1056     policyguid = policyguid.upper()
1057     if policyguid_dc is None:
1058         policyguid_dc = str(uuid.uuid4())
1059     policyguid_dc = policyguid_dc.upper()
1060
1061     if adminpass is None:
1062         adminpass = glue.generate_random_str(12)
1063     if krbtgtpass is None:
1064         krbtgtpass = glue.generate_random_str(12)
1065     if machinepass is None:
1066         machinepass  = glue.generate_random_str(12)
1067     if dnspass is None:
1068         dnspass = glue.generate_random_str(12)
1069     if ldapadminpass is None:
1070         #Make a new, random password between Samba and it's LDAP server
1071         ldapadminpass=glue.generate_random_str(12)        
1072
1073
1074     root_uid = findnss_uid([root or "root"])
1075     nobody_uid = findnss_uid([nobody or "nobody"])
1076     users_gid = findnss_gid([users or "users"])
1077     if wheel is None:
1078         wheel_gid = findnss_gid(["wheel", "adm"])
1079     else:
1080         wheel_gid = findnss_gid([wheel])
1081
1082     if targetdir is not None:
1083         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1084             os.makedirs(os.path.join(targetdir, "etc"))
1085         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1086     elif smbconf is None:
1087         smbconf = param.default_path()
1088
1089     # only install a new smb.conf if there isn't one there already
1090     if not os.path.exists(smbconf):
1091         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1092                      targetdir)
1093
1094     lp = param.LoadParm()
1095     lp.load(smbconf)
1096
1097     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1098                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1099                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1100                         serverdn=serverdn)
1101
1102     paths = provision_paths_from_lp(lp, names.dnsdomain)
1103
1104     if hostip is None:
1105         try:
1106             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1107         except socket.gaierror, (socket.EAI_NODATA, msg):
1108             hostip = None
1109
1110     if hostip6 is None:
1111         try:
1112             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1113         except socket.gaierror, (socket.EAI_NODATA, msg): 
1114             hostip6 = None
1115
1116     if serverrole is None:
1117         serverrole = lp.get("server role")
1118
1119     assert serverrole in ("domain controller", "member server", "standalone")
1120     if invocationid is None and serverrole == "domain controller":
1121         invocationid = str(uuid.uuid4())
1122
1123     if not os.path.exists(paths.private_dir):
1124         os.mkdir(paths.private_dir)
1125
1126     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1127     
1128     schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1129         sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1130     
1131     secrets_credentials = credentials
1132     provision_backend = None
1133     if ldap_backend_type:
1134         # We only support an LDAP backend over ldapi://
1135
1136         provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1137                                              lp=lp, credentials=credentials, 
1138                                              names=names,
1139                                              message=message, hostname=hostname,
1140                                              root=root, schema=schema,
1141                                              ldap_backend_type=ldap_backend_type,
1142                                              ldapadminpass=ldapadminpass,
1143                                              ldap_backend_extra_port=ldap_backend_extra_port,
1144                                              ol_mmr_urls=ol_mmr_urls, 
1145                                              slapd_path=slapd_path,
1146                                              setup_ds_path=setup_ds_path,
1147                                              ldap_dryrun_mode=ldap_dryrun_mode)
1148
1149         # Now use the backend credentials to access the databases
1150         credentials = provision_backend.credentials
1151         secrets_credentials = provision_backend.adminCredentials
1152         ldapi_url = provision_backend.ldapi_uri
1153
1154     # only install a new shares config db if there is none
1155     if not os.path.exists(paths.shareconf):
1156         message("Setting up share.ldb")
1157         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1158                         credentials=credentials, lp=lp)
1159         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1160
1161      
1162     message("Setting up secrets.ldb")
1163     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1164                                   session_info=session_info, 
1165                                   credentials=secrets_credentials, lp=lp)
1166
1167     message("Setting up the registry")
1168     setup_registry(paths.hklm, setup_path, session_info, 
1169                    lp=lp)
1170
1171     message("Setting up idmap db")
1172     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1173                           lp=lp)
1174
1175     message("Setting up SAM db")
1176     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1177                         credentials=credentials, lp=lp, names=names,
1178                         message=message, 
1179                         domainsid=domainsid, 
1180                         schema=schema, domainguid=domainguid,
1181                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1182                         fill=samdb_fill, 
1183                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1184                         invocationid=invocationid, 
1185                         machinepass=machinepass, dnspass=dnspass,
1186                         serverrole=serverrole, ldap_backend=provision_backend)
1187
1188     if serverrole == "domain controller":
1189         if paths.netlogon is None:
1190             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1191             message("Please either remove %s or see the template at %s" % 
1192                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1193             assert(paths.netlogon is not None)
1194
1195         if paths.sysvol is None:
1196             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1197             message("Please either remove %s or see the template at %s" % 
1198                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1199             assert(paths.sysvol is not None)            
1200             
1201         # Set up group policies (domain policy and domain controller policy)
1202
1203         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1204                                    "{" + policyguid + "}")
1205         os.makedirs(policy_path, 0755)
1206         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1207                                    "[General]\r\nVersion=65544")
1208         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1209         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1210
1211         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1212                                    "{" + policyguid_dc + "}")
1213         os.makedirs(policy_path_dc, 0755)
1214         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1215                                    "[General]\r\nVersion=2")
1216         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1217         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1218
1219         if not os.path.isdir(paths.netlogon):
1220             os.makedirs(paths.netlogon, 0755)
1221
1222     if samdb_fill == FILL_FULL:
1223         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1224                             root_uid=root_uid, nobody_uid=nobody_uid,
1225                             users_gid=users_gid, wheel_gid=wheel_gid)
1226
1227         message("Setting up sam.ldb rootDSE marking as synchronized")
1228         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1229
1230         # Only make a zone file on the first DC, it should be replicated with DNS replication
1231         if serverrole == "domain controller":
1232             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1233                               credentials=credentials, lp=lp)
1234             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1235                                 realm=names.realm,
1236                                 netbiosname=names.netbiosname,
1237                                 domainsid=domainsid, 
1238                                 keytab_path=paths.keytab, samdb_url=paths.samdb,
1239                                 dns_keytab_path=paths.dns_keytab,
1240                                 dnspass=dnspass, machinepass=machinepass,
1241                                 dnsdomain=names.dnsdomain)
1242
1243             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1244             assert isinstance(domainguid, str)
1245
1246             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1247                              domaindn=names.domaindn, hostip=hostip,
1248                              hostip6=hostip6, hostname=names.hostname,
1249                              dnspass=dnspass, realm=names.realm,
1250                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1251
1252             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1253                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1254
1255             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1256                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1257                               keytab_name=paths.dns_keytab)
1258             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1259             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1260
1261             create_krb5_conf(paths.krb5conf, setup_path,
1262                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1263                              realm=names.realm)
1264             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1265
1266
1267     if provision_backend is not None: 
1268       if ldap_backend_type == "fedora-ds":
1269         ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1270
1271         # delete default SASL mappings
1272         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1273
1274         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1275         for i in range (0, len(res)):
1276           dn = str(res[i]["dn"])
1277           ldapi_db.delete(dn)
1278
1279           aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1280
1281           m = ldb.Message()
1282           m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1283         
1284           m.dn = ldb.Dn(1, names.domaindn)
1285           ldapi_db.modify(m)
1286
1287           m.dn = ldb.Dn(1, names.configdn)
1288           ldapi_db.modify(m)
1289
1290           m.dn = ldb.Dn(1, names.schemadn)
1291           ldapi_db.modify(m)
1292
1293       # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1294       if provision_backend.slapd.poll() is None:
1295         #Kill the slapd
1296         if hasattr(provision_backend.slapd, "terminate"):
1297           provision_backend.slapd.terminate()
1298         else:
1299           # Older python versions don't have .terminate()
1300           import signal
1301           os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1302             
1303         #and now wait for it to die
1304         provision_backend.slapd.communicate()
1305             
1306     # now display slapd_command_file.txt to show how slapd must be started next time
1307         message("Use later the following commandline to start slapd, then Samba:")
1308         slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1309         message(slapd_command)
1310         message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1311
1312         setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1313                 "SLAPD_COMMAND" : slapd_command})
1314
1315     
1316     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1317                                ldapi_url)
1318
1319     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1320
1321     message("Once the above files are installed, your Samba4 server will be ready to use")
1322     message("Server Role:           %s" % serverrole)
1323     message("Hostname:              %s" % names.hostname)
1324     message("NetBIOS Domain:        %s" % names.domain)
1325     message("DNS Domain:            %s" % names.dnsdomain)
1326     message("DOMAIN SID:            %s" % str(domainsid))
1327     if samdb_fill == FILL_FULL:
1328         message("Admin password:    %s" % adminpass)
1329     if provision_backend:
1330         if provision_backend.credentials.get_bind_dn() is not None:
1331             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1332         else:
1333             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1334
1335         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1336   
1337     result = ProvisionResult()
1338     result.domaindn = domaindn
1339     result.paths = paths
1340     result.lp = lp
1341     result.samdb = samdb
1342     return result
1343
1344
1345
1346 def provision_become_dc(setup_dir=None,
1347                         smbconf=None, targetdir=None, realm=None, 
1348                         rootdn=None, domaindn=None, schemadn=None,
1349                         configdn=None, serverdn=None,
1350                         domain=None, hostname=None, domainsid=None, 
1351                         adminpass=None, krbtgtpass=None, domainguid=None, 
1352                         policyguid=None, policyguid_dc=None, invocationid=None,
1353                         machinepass=None, 
1354                         dnspass=None, root=None, nobody=None, users=None, 
1355                         wheel=None, backup=None, serverrole=None, 
1356                         ldap_backend=None, ldap_backend_type=None,
1357                         sitename=None, debuglevel=1):
1358
1359     def message(text):
1360         """print a message if quiet is not set."""
1361         print text
1362
1363     glue.set_debug_level(debuglevel)
1364
1365     return provision(setup_dir, message, system_session(), None,
1366               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1367               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1368               configdn=configdn, serverdn=serverdn, domain=domain,
1369               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1370               machinepass=machinepass, serverrole="domain controller",
1371               sitename=sitename)
1372
1373
1374 def setup_db_config(setup_path, dbdir):
1375     """Setup a Berkeley database.
1376     
1377     :param setup_path: Setup path function.
1378     :param dbdir: Database directory."""
1379     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1380         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1381         if not os.path.isdir(os.path.join(dbdir, "tmp")):
1382             os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1383
1384     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1385                {"LDAPDBDIR": dbdir})
1386     
1387 class ProvisionBackend(object):
1388     def __init__(self, paths=None, setup_path=None, lp=None, credentials=None, 
1389                  names=None, message=None, 
1390                  hostname=None, root=None, 
1391                  schema=None, ldapadminpass=None,
1392                  ldap_backend_type=None, ldap_backend_extra_port=None,
1393                  ol_mmr_urls=None, 
1394                  setup_ds_path=None, slapd_path=None, 
1395                  nosync=False, ldap_dryrun_mode=False):
1396         """Provision an LDAP backend for samba4
1397         
1398         This works for OpenLDAP and Fedora DS
1399         """
1400
1401         self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1402         
1403         if not os.path.isdir(paths.ldapdir):
1404             os.makedirs(paths.ldapdir, 0700)
1405             
1406         if ldap_backend_type == "existing":
1407             #Check to see that this 'existing' LDAP backend in fact exists
1408             ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1409             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1410                                                 expression="(objectClass=OpenLDAProotDSE)")
1411
1412             # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1413             # This caused them to be set into the long-term database later in the script.
1414             self.credentials = credentials
1415             self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1416             return
1417     
1418         # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1419         # if another instance of slapd is already running 
1420         try:
1421             ldapi_db = Ldb(self.ldapi_uri)
1422             search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1423                                                 expression="(objectClass=OpenLDAProotDSE)");
1424             try:
1425                 f = open(paths.slapdpid, "r")
1426                 p = f.read()
1427                 f.close()
1428                 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1429             except:
1430                 pass
1431             
1432             raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1433         
1434         except LdbError, e:
1435             pass
1436
1437         # Try to print helpful messages when the user has not specified the path to slapd
1438         if slapd_path is None:
1439             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1440         if not os.path.exists(slapd_path):
1441             message (slapd_path)
1442             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1443
1444         schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1445         try:
1446             os.unlink(schemadb_path)
1447         except OSError:
1448             pass
1449
1450
1451         # Put the LDIF of the schema into a database so we can search on
1452         # it to generate schema-dependent configurations in Fedora DS and
1453         # OpenLDAP
1454         os.path.join(paths.ldapdir, "schema-tmp.ldb")
1455         schema.ldb.connect(schemadb_path)
1456         schema.ldb.transaction_start()
1457     
1458         # These bits of LDIF are supplied when the Schema object is created
1459         schema.ldb.add_ldif(schema.schema_dn_add)
1460         schema.ldb.modify_ldif(schema.schema_dn_modify)
1461         schema.ldb.add_ldif(schema.schema_data)
1462         schema.ldb.transaction_commit()
1463
1464         self.credentials = Credentials()
1465         self.credentials.guess(lp)
1466         #Kerberos to an ldapi:// backend makes no sense
1467         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1468
1469         self.adminCredentials = Credentials()
1470         self.adminCredentials.guess(lp)
1471         #Kerberos to an ldapi:// backend makes no sense
1472         self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1473
1474         self.ldap_backend_type = ldap_backend_type
1475
1476         if ldap_backend_type == "fedora-ds":
1477             provision_fds_backend(self, paths=paths, setup_path=setup_path,
1478                                   names=names, message=message, 
1479                                   hostname=hostname,
1480                                   ldapadminpass=ldapadminpass, root=root, 
1481                                   schema=schema,
1482                                   ldap_backend_extra_port=ldap_backend_extra_port, 
1483                                   setup_ds_path=setup_ds_path,
1484                                   slapd_path=slapd_path,
1485                                   nosync=nosync,
1486                                   ldap_dryrun_mode=ldap_dryrun_mode)
1487             
1488         elif ldap_backend_type == "openldap":
1489             provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1490                                        names=names, message=message, 
1491                                        hostname=hostname,
1492                                        ldapadminpass=ldapadminpass, root=root, 
1493                                        schema=schema,
1494                                        ldap_backend_extra_port=ldap_backend_extra_port, 
1495                                        ol_mmr_urls=ol_mmr_urls, 
1496                                        slapd_path=slapd_path,
1497                                        nosync=nosync,
1498                                        ldap_dryrun_mode=ldap_dryrun_mode)
1499         else:
1500             raise ProvisioningError("Unknown LDAP backend type selected")
1501
1502         self.credentials.set_password(ldapadminpass)
1503         self.adminCredentials.set_username("samba-admin")
1504         self.adminCredentials.set_password(ldapadminpass)
1505
1506         # Now start the slapd, so we can provision onto it.  We keep the
1507         # subprocess context around, to kill this off at the successful
1508         # end of the script
1509         self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1510     
1511         while self.slapd.poll() is None:
1512             # Wait until the socket appears
1513             try:
1514                 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1515                 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1516                                                     expression="(objectClass=OpenLDAProotDSE)")
1517                 # If we have got here, then we must have a valid connection to the LDAP server!
1518                 return
1519             except LdbError, e:
1520                 time.sleep(1)
1521                 pass
1522         
1523         raise ProvisioningError("slapd died before we could make a connection to it")
1524
1525
1526 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1527                                message=None, 
1528                                hostname=None, ldapadminpass=None, root=None, 
1529                                schema=None, 
1530                                ldap_backend_extra_port=None,
1531                                ol_mmr_urls=None, 
1532                                slapd_path=None, nosync=False,
1533                                ldap_dryrun_mode=False):
1534
1535     #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1536     nosync_config = ""
1537     if nosync:
1538         nosync_config = "dbnosync"
1539         
1540     lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1541     refint_attributes = ""
1542     memberof_config = "# Generated from Samba4 schema\n"
1543     for att in  lnkattr.keys():
1544         if lnkattr[att] is not None:
1545             refint_attributes = refint_attributes + " " + att 
1546             
1547             memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1548                                                  { "MEMBER_ATTR" : att ,
1549                                                    "MEMBEROF_ATTR" : lnkattr[att] })
1550             
1551     refint_config = read_and_sub_file(setup_path("refint.conf"),
1552                                       { "LINK_ATTRS" : refint_attributes})
1553     
1554     attrs = ["linkID", "lDAPDisplayName"]
1555     res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1556     index_config = ""
1557     for i in range (0, len(res)):
1558         index_attr = res[i]["lDAPDisplayName"][0]
1559         if index_attr == "objectGUID":
1560             index_attr = "entryUUID"
1561             
1562         index_config += "index " + index_attr + " eq\n"
1563
1564 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1565     mmr_on_config = ""
1566     mmr_replicator_acl = ""
1567     mmr_serverids_config = ""
1568     mmr_syncrepl_schema_config = "" 
1569     mmr_syncrepl_config_config = "" 
1570     mmr_syncrepl_user_config = "" 
1571        
1572     
1573     if ol_mmr_urls is not None:
1574         # For now, make these equal
1575         mmr_pass = ldapadminpass
1576         
1577         url_list=filter(None,ol_mmr_urls.split(' ')) 
1578         if (len(url_list) == 1):
1579             url_list=filter(None,ol_mmr_urls.split(',')) 
1580                      
1581             
1582             mmr_on_config = "MirrorMode On"
1583             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1584             serverid=0
1585             for url in url_list:
1586                 serverid=serverid+1
1587                 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1588                                                           { "SERVERID" : str(serverid),
1589                                                             "LDAPSERVER" : url })
1590                 rid=serverid*10
1591                 rid=rid+1
1592                 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1593                                                                 {  "RID" : str(rid),
1594                                                                    "MMRDN": names.schemadn,
1595                                                                    "LDAPSERVER" : url,
1596                                                                    "MMR_PASSWORD": mmr_pass})
1597                 
1598                 rid=rid+1
1599                 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1600                                                                 {  "RID" : str(rid),
1601                                                                    "MMRDN": names.configdn,
1602                                                                    "LDAPSERVER" : url,
1603                                                                    "MMR_PASSWORD": mmr_pass})
1604                 
1605                 rid=rid+1
1606                 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1607                                                               {  "RID" : str(rid),
1608                                                                  "MMRDN": names.domaindn,
1609                                                                  "LDAPSERVER" : url,
1610                                                                  "MMR_PASSWORD": mmr_pass })
1611     # OpenLDAP cn=config initialisation
1612     olc_syncrepl_config = ""
1613     olc_mmr_config = "" 
1614     # if mmr = yes, generate cn=config-replication directives
1615     # and olc_seed.lif for the other mmr-servers
1616     if ol_mmr_urls is not None:
1617         serverid=0
1618         olc_serverids_config = ""
1619         olc_syncrepl_seed_config = ""
1620         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1621         rid=1000
1622         for url in url_list:
1623             serverid=serverid+1
1624             olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1625                                                       { "SERVERID" : str(serverid),
1626                                                         "LDAPSERVER" : url })
1627             
1628             rid=rid+1
1629             olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1630                                                      {  "RID" : str(rid),
1631                                                         "LDAPSERVER" : url,
1632                                                         "MMR_PASSWORD": mmr_pass})
1633             
1634             olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1635                                                           {  "RID" : str(rid),
1636                                                              "LDAPSERVER" : url})
1637                 
1638         setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1639                    {"OLC_SERVER_ID_CONF": olc_serverids_config,
1640                     "OLC_PW": ldapadminpass,
1641                     "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1642     # end olc
1643                 
1644     setup_file(setup_path("slapd.conf"), paths.slapdconf,
1645                {"DNSDOMAIN": names.dnsdomain,
1646                 "LDAPDIR": paths.ldapdir,
1647                 "DOMAINDN": names.domaindn,
1648                 "CONFIGDN": names.configdn,
1649                 "SCHEMADN": names.schemadn,
1650                 "MEMBEROF_CONFIG": memberof_config,
1651                 "MIRRORMODE": mmr_on_config,
1652                 "REPLICATOR_ACL": mmr_replicator_acl,
1653                 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1654                 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1655                 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1656                 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1657                 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1658                 "OLC_MMR_CONFIG": olc_mmr_config,
1659                 "REFINT_CONFIG": refint_config,
1660                 "INDEX_CONFIG": index_config,
1661                 "NOSYNC": nosync_config})
1662         
1663     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1664     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1665     setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1666     
1667     if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1668         os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1669         
1670     setup_file(setup_path("cn=samba.ldif"), 
1671                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1672                { "UUID": str(uuid.uuid4()), 
1673                  "LDAPTIME": timestring(int(time.time()))} )
1674     setup_file(setup_path("cn=samba-admin.ldif"), 
1675                os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1676                {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1677                 "UUID": str(uuid.uuid4()), 
1678                 "LDAPTIME": timestring(int(time.time()))} )
1679     
1680     if ol_mmr_urls is not None:
1681         setup_file(setup_path("cn=replicator.ldif"),
1682                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1683                    {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1684                     "UUID": str(uuid.uuid4()),
1685                     "LDAPTIME": timestring(int(time.time()))} )
1686         
1687
1688     mapping = "schema-map-openldap-2.3"
1689     backend_schema = "backend-schema.schema"
1690
1691     backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1692     assert backend_schema_data is not None
1693     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1694
1695     # now we generate the needed strings to start slapd automatically,
1696     # first ldapi_uri...
1697     if ldap_backend_extra_port is not None:
1698         # When we use MMR, we can't use 0.0.0.0 as it uses the name
1699         # specified there as part of it's clue as to it's own name,
1700         # and not to replicate to itself
1701         if ol_mmr_urls is None:
1702             server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1703         else:
1704             server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1705     else:
1706         server_port_string = ""
1707
1708     # Prepare the 'result' information - the commands to return in particular
1709     result.slapd_provision_command = [slapd_path]
1710
1711     result.slapd_provision_command.append("-F" + paths.olcdir)
1712
1713     result.slapd_provision_command.append("-h")
1714
1715     # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1716     result.slapd_command = list(result.slapd_provision_command)
1717     
1718     result.slapd_provision_command.append(result.ldapi_uri)
1719     result.slapd_provision_command.append("-d0")
1720
1721     uris = result.ldapi_uri
1722     if server_port_string is not "":
1723         uris = uris + " " + server_port_string
1724
1725     result.slapd_command.append(uris)
1726
1727     # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1728     result.credentials.set_username("samba-admin")
1729     
1730     # If we were just looking for crashes up to this point, it's a
1731     # good time to exit before we realise we don't have OpenLDAP on
1732     # this system
1733     if ldap_dryrun_mode:
1734         sys.exit(0)
1735
1736     # Finally, convert the configuration into cn=config style!
1737     if not os.path.isdir(paths.olcdir):
1738         os.makedirs(paths.olcdir, 0770)
1739
1740         retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1741
1742 #        We can't do this, as OpenLDAP is strange.  It gives an error
1743 #        output to the above, but does the conversion sucessfully...
1744 #
1745 #        if retcode != 0:
1746 #            raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1747
1748         if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1749             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1750
1751         # Don't confuse the admin by leaving the slapd.conf around
1752         os.remove(paths.slapdconf)        
1753           
1754
1755 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1756                           message=None, 
1757                           hostname=None, ldapadminpass=None, root=None, 
1758                           schema=None,
1759                           ldap_backend_extra_port=None,
1760                           setup_ds_path=None,
1761                           slapd_path=None,
1762                           nosync=False, 
1763                           ldap_dryrun_mode=False):
1764
1765     if ldap_backend_extra_port is not None:
1766         serverport = "ServerPort=%d" % ldap_backend_extra_port
1767     else:
1768         serverport = ""
1769         
1770     setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1771                {"ROOT": root,
1772                 "HOSTNAME": hostname,
1773                 "DNSDOMAIN": names.dnsdomain,
1774                 "LDAPDIR": paths.ldapdir,
1775                 "DOMAINDN": names.domaindn,
1776                 "LDAPMANAGERDN": names.ldapmanagerdn,
1777                 "LDAPMANAGERPASS": ldapadminpass, 
1778                 "SERVERPORT": serverport})
1779
1780     setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1781                {"CONFIGDN": names.configdn,
1782                 "SCHEMADN": names.schemadn,
1783                 "SAMBADN": names.sambadn,
1784                 })
1785
1786     setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl, 
1787                {"SAMBADN": names.sambadn,
1788                 })
1789
1790     setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1791                 {"SAMBADN": names.sambadn, 
1792                  "LDAPADMINPASS": ldapadminpass
1793                 })
1794
1795     mapping = "schema-map-fedora-ds-1.0"
1796     backend_schema = "99_ad.ldif"
1797     
1798     # Build a schema file in Fedora DS format
1799     backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1800     assert backend_schema_data is not None
1801     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1802
1803     result.credentials.set_bind_dn(names.ldapmanagerdn)
1804
1805     # Destory the target directory, or else setup-ds.pl will complain
1806     fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1807     shutil.rmtree(fedora_ds_dir, True)
1808
1809     result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1810     #In the 'provision' command line, stay in the foreground so we can easily kill it
1811     result.slapd_provision_command.append("-d0")
1812
1813     #the command for the final run is the normal script
1814     result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1815
1816     # If we were just looking for crashes up to this point, it's a
1817     # good time to exit before we realise we don't have Fedora DS on
1818     if ldap_dryrun_mode:
1819         sys.exit(0)
1820
1821     # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1822     if setup_ds_path is None:
1823         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\"!")
1824     if not os.path.exists(setup_ds_path):
1825         message (setup_ds_path)
1826         raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1827
1828     # Run the Fedora DS setup utility
1829     retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1830     if retcode != 0:
1831         raise ProvisioningError("setup-ds failed")
1832
1833     # Load samba-admin
1834     retcode = subprocess.call([
1835         os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1836         close_fds=True, shell=False)
1837     if retcode != 0:
1838         raise("ldib2db failed")
1839
1840 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1841     """Create a PHP LDAP admin configuration file.
1842
1843     :param path: Path to write the configuration to.
1844     :param setup_path: Function to generate setup paths.
1845     """
1846     setup_file(setup_path("phpldapadmin-config.php"), path, 
1847             {"S4_LDAPI_URI": ldapi_uri})
1848
1849
1850 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1851                      hostip, hostip6, hostname, dnspass, realm, domainguid,
1852                      ntdsguid):
1853     """Write out a DNS zone file, from the info in the current database.
1854
1855     :param path: Path of the new zone file.
1856     :param setup_path: Setup path function.
1857     :param dnsdomain: DNS Domain name
1858     :param domaindn: DN of the Domain
1859     :param hostip: Local IPv4 IP
1860     :param hostip6: Local IPv6 IP
1861     :param hostname: Local hostname
1862     :param dnspass: Password for DNS
1863     :param realm: Realm name
1864     :param domainguid: GUID of the domain.
1865     :param ntdsguid: GUID of the hosts nTDSDSA record.
1866     """
1867     assert isinstance(domainguid, str)
1868
1869     if hostip6 is not None:
1870         hostip6_base_line = "            IN AAAA    " + hostip6
1871         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1872     else:
1873         hostip6_base_line = ""
1874         hostip6_host_line = ""
1875
1876     if hostip is not None:
1877         hostip_base_line = "            IN A    " + hostip
1878         hostip_host_line = hostname + "        IN A    " + hostip
1879     else:
1880         hostip_base_line = ""
1881         hostip_host_line = ""
1882
1883     setup_file(setup_path("provision.zone"), path, {
1884             "DNSPASS_B64": b64encode(dnspass),
1885             "HOSTNAME": hostname,
1886             "DNSDOMAIN": dnsdomain,
1887             "REALM": realm,
1888             "HOSTIP_BASE_LINE": hostip_base_line,
1889             "HOSTIP_HOST_LINE": hostip_host_line,
1890             "DOMAINGUID": domainguid,
1891             "DATESTRING": time.strftime("%Y%m%d%H"),
1892             "DEFAULTSITE": DEFAULTSITE,
1893             "NTDSGUID": ntdsguid,
1894             "HOSTIP6_BASE_LINE": hostip6_base_line,
1895             "HOSTIP6_HOST_LINE": hostip6_host_line,
1896         })
1897
1898
1899 def create_named_conf(path, setup_path, realm, dnsdomain,
1900                       private_dir):
1901     """Write out a file containing zone statements suitable for inclusion in a
1902     named.conf file (including GSS-TSIG configuration).
1903     
1904     :param path: Path of the new named.conf file.
1905     :param setup_path: Setup path function.
1906     :param realm: Realm name
1907     :param dnsdomain: DNS Domain name
1908     :param private_dir: Path to private directory
1909     :param keytab_name: File name of DNS keytab file
1910     """
1911
1912     setup_file(setup_path("named.conf"), path, {
1913             "DNSDOMAIN": dnsdomain,
1914             "REALM": realm,
1915             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1916             "PRIVATE_DIR": private_dir
1917             })
1918
1919 def create_named_txt(path, setup_path, realm, dnsdomain,
1920                       private_dir, keytab_name):
1921     """Write out a file containing zone statements suitable for inclusion in a
1922     named.conf file (including GSS-TSIG configuration).
1923     
1924     :param path: Path of the new named.conf file.
1925     :param setup_path: Setup path function.
1926     :param realm: Realm name
1927     :param dnsdomain: DNS Domain name
1928     :param private_dir: Path to private directory
1929     :param keytab_name: File name of DNS keytab file
1930     """
1931
1932     setup_file(setup_path("named.txt"), path, {
1933             "DNSDOMAIN": dnsdomain,
1934             "REALM": realm,
1935             "DNS_KEYTAB": keytab_name,
1936             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1937             "PRIVATE_DIR": private_dir
1938         })
1939
1940 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1941     """Write out a file containing zone statements suitable for inclusion in a
1942     named.conf file (including GSS-TSIG configuration).
1943     
1944     :param path: Path of the new named.conf file.
1945     :param setup_path: Setup path function.
1946     :param dnsdomain: DNS Domain name
1947     :param hostname: Local hostname
1948     :param realm: Realm name
1949     """
1950
1951     setup_file(setup_path("krb5.conf"), path, {
1952             "DNSDOMAIN": dnsdomain,
1953             "HOSTNAME": hostname,
1954             "REALM": realm,
1955         })
1956
1957