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