Merge commit 'origin/v4-0-test' into v4-0-test
[ira/wip.git] / source4 / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
7 #
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.fedoradspartitions = os.path.join(paths.ldapdir, 
248                                             "fedorads-partitions.ldif")
249     paths.olmmrserveridsconf = os.path.join(paths.ldapdir, 
250                                             "mmr_serverids.conf")
251     paths.olmmrsyncreplconf = os.path.join(paths.ldapdir, 
252                                            "mmr_syncrepl.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     # Wipes the database
627     try:
628         templates_ldb.erase()
629     except:
630         os.unlink(path)
631
632     templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
633
634     templates_ldb = SamDB(path, session_info=session_info,
635                           credentials=credentials, lp=lp)
636
637     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
638
639
640 def setup_registry(path, setup_path, session_info, credentials, lp):
641     """Setup the registry.
642     
643     :param path: Path to the registry database
644     :param setup_path: Function that returns the path to a setup.
645     :param session_info: Session information
646     :param credentials: Credentials
647     :param lp: Loadparm context
648     """
649     reg = registry.Registry()
650     hive = registry.open_ldb(path, session_info=session_info, 
651                          credentials=credentials, lp_ctx=lp)
652     reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
653     provision_reg = setup_path("provision.reg")
654     assert os.path.exists(provision_reg)
655     reg.diff_apply(provision_reg)
656
657
658 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
659     """Setup the idmap database.
660
661     :param path: path to the idmap database
662     :param setup_path: Function that returns a path to a setup file
663     :param session_info: Session information
664     :param credentials: Credentials
665     :param lp: Loadparm context
666     """
667     if os.path.exists(path):
668         os.unlink(path)
669
670     idmap_ldb = IDmapDB(path, session_info=session_info,
671                         credentials=credentials, lp=lp)
672
673     idmap_ldb.erase()
674     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
675     return idmap_ldb
676
677
678 def setup_samdb_rootdse(samdb, setup_path, names):
679     """Setup the SamDB rootdse.
680
681     :param samdb: Sam Database handle
682     :param setup_path: Obtain setup path
683     """
684     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
685         "SCHEMADN": names.schemadn, 
686         "NETBIOSNAME": names.netbiosname,
687         "DNSDOMAIN": names.dnsdomain,
688         "REALM": names.realm,
689         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
690         "DOMAINDN": names.domaindn,
691         "ROOTDN": names.rootdn,
692         "CONFIGDN": names.configdn,
693         "SERVERDN": names.serverdn,
694         })
695         
696
697 def setup_self_join(samdb, names,
698                     machinepass, dnspass, 
699                     domainsid, invocationid, setup_path,
700                     policyguid):
701     """Join a host to its own domain."""
702     assert isinstance(invocationid, str)
703     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
704               "CONFIGDN": names.configdn, 
705               "SCHEMADN": names.schemadn,
706               "DOMAINDN": names.domaindn,
707               "SERVERDN": names.serverdn,
708               "INVOCATIONID": invocationid,
709               "NETBIOSNAME": names.netbiosname,
710               "DEFAULTSITE": names.sitename,
711               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
712               "MACHINEPASS_B64": b64encode(machinepass),
713               "DNSPASS_B64": b64encode(dnspass),
714               "REALM": names.realm,
715               "DOMAIN": names.domain,
716               "DNSDOMAIN": names.dnsdomain})
717     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
718               "POLICYGUID": policyguid,
719               "DNSDOMAIN": names.dnsdomain,
720               "DOMAINSID": str(domainsid),
721               "DOMAINDN": names.domaindn})
722
723
724 def setup_samdb(path, setup_path, session_info, credentials, lp, 
725                 names, message, 
726                 domainsid, aci, domainguid, policyguid, 
727                 fill, adminpass, krbtgtpass, 
728                 machinepass, invocationid, dnspass,
729                 serverrole, ldap_backend=None, 
730                 ldap_backend_type=None):
731     """Setup a complete SAM Database.
732     
733     :note: This will wipe the main SAM database file!
734     """
735
736     erase = (fill != FILL_DRS)
737
738     # Also wipes the database
739     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
740                            credentials=credentials, session_info=session_info,
741                            names=names, 
742                            ldap_backend=ldap_backend, serverrole=serverrole,
743                            ldap_backend_type=ldap_backend_type, erase=erase)
744
745     samdb = SamDB(path, session_info=session_info, 
746                   credentials=credentials, lp=lp)
747
748     message("Pre-loading the Samba 4 and AD schema")
749     samdb.set_domain_sid(domainsid)
750     if serverrole == "domain controller":
751         samdb.set_invocation_id(invocationid)
752
753     load_schema(setup_path, samdb, names.schemadn, names.netbiosname, 
754                 names.configdn, names.sitename, names.serverdn,
755                 names.hostname)
756
757     samdb.transaction_start()
758         
759     try:
760         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
761         if serverrole == "domain controller":
762             domain_oc = "domainDNS"
763         else:
764             domain_oc = "samba4LocalDomain"
765
766         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
767                 "DOMAINDN": names.domaindn,
768                 "ACI": aci,
769                 "DOMAIN_OC": domain_oc
770                 })
771
772         message("Modifying DomainDN: " + names.domaindn + "")
773         if domainguid is not None:
774             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
775         else:
776             domainguid_mod = ""
777
778         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
779             "LDAPTIME": timestring(int(time.time())),
780             "DOMAINSID": str(domainsid),
781             "SCHEMADN": names.schemadn, 
782             "NETBIOSNAME": names.netbiosname,
783             "DEFAULTSITE": names.sitename,
784             "CONFIGDN": names.configdn,
785             "SERVERDN": names.serverdn,
786             "POLICYGUID": policyguid,
787             "DOMAINDN": names.domaindn,
788             "DOMAINGUID_MOD": domainguid_mod,
789             })
790
791         message("Adding configuration container (permitted to fail)")
792         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
793             "CONFIGDN": names.configdn, 
794             "ACI": aci,
795             })
796         message("Modifying configuration container")
797         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
798             "CONFIGDN": names.configdn, 
799             "SCHEMADN": names.schemadn,
800             })
801
802         message("Adding schema container (permitted to fail)")
803         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
804             "SCHEMADN": names.schemadn,
805             "ACI": aci,
806             })
807         message("Modifying schema container")
808
809         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
810
811         setup_modify_ldif(samdb, 
812             setup_path("provision_schema_basedn_modify.ldif"), {
813             "SCHEMADN": names.schemadn,
814             "NETBIOSNAME": names.netbiosname,
815             "DEFAULTSITE": names.sitename,
816             "CONFIGDN": names.configdn,
817             "SERVERDN": names.serverdn,
818             "PREFIXMAP_B64": b64encode(prefixmap)
819             })
820
821         message("Setting up sam.ldb Samba4 schema")
822         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
823                        {"SCHEMADN": names.schemadn })
824         message("Setting up sam.ldb AD schema")
825         setup_add_ldif(samdb, setup_path("schema.ldif"), 
826                        {"SCHEMADN": names.schemadn})
827
828         message("Setting up sam.ldb configuration data")
829         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
830             "CONFIGDN": names.configdn,
831             "NETBIOSNAME": names.netbiosname,
832             "DEFAULTSITE": names.sitename,
833             "DNSDOMAIN": names.dnsdomain,
834             "DOMAIN": names.domain,
835             "SCHEMADN": names.schemadn,
836             "DOMAINDN": names.domaindn,
837             "SERVERDN": names.serverdn
838             })
839
840         message("Setting up display specifiers")
841         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
842                        {"CONFIGDN": names.configdn})
843
844         message("Adding users container (permitted to fail)")
845         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
846                 "DOMAINDN": names.domaindn})
847         message("Modifying users container")
848         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
849                 "DOMAINDN": names.domaindn})
850         message("Adding computers container (permitted to fail)")
851         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
852                 "DOMAINDN": names.domaindn})
853         message("Modifying computers container")
854         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
855                 "DOMAINDN": names.domaindn})
856         message("Setting up sam.ldb data")
857         setup_add_ldif(samdb, setup_path("provision.ldif"), {
858             "DOMAINDN": names.domaindn,
859             "NETBIOSNAME": names.netbiosname,
860             "DEFAULTSITE": names.sitename,
861             "CONFIGDN": names.configdn,
862             "SERVERDN": names.serverdn
863             })
864
865         if fill == FILL_FULL:
866             message("Setting up sam.ldb users and groups")
867             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
868                 "DOMAINDN": names.domaindn,
869                 "DOMAINSID": str(domainsid),
870                 "CONFIGDN": names.configdn,
871                 "ADMINPASS_B64": b64encode(adminpass),
872                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
873                 })
874
875             if serverrole == "domain controller":
876                 message("Setting up self join")
877                 setup_self_join(samdb, names=names, invocationid=invocationid, 
878                                 dnspass=dnspass,  
879                                 machinepass=machinepass, 
880                                 domainsid=domainsid, policyguid=policyguid,
881                                 setup_path=setup_path)
882
883     except:
884         samdb.transaction_cancel()
885         raise
886
887     samdb.transaction_commit()
888     return samdb
889
890
891 FILL_FULL = "FULL"
892 FILL_NT4SYNC = "NT4SYNC"
893 FILL_DRS = "DRS"
894
895 def provision(setup_dir, message, session_info, 
896               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
897               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
898               serverdn=None,
899               domain=None, hostname=None, hostip=None, hostip6=None, 
900               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
901               policyguid=None, invocationid=None, machinepass=None, 
902               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
903               wheel=None, backup=None, aci=None, serverrole=None, 
904               ldap_backend=None, ldap_backend_type=None, sitename=None):
905     """Provision samba4
906     
907     :note: caution, this wipes all existing data!
908     """
909
910     def setup_path(file):
911         return os.path.join(setup_dir, file)
912
913     if domainsid is None:
914         domainsid = security.random_sid()
915     else:
916         domainsid = security.Sid(domainsid)
917
918     if policyguid is None:
919         policyguid = str(uuid.uuid4())
920     if adminpass is None:
921         adminpass = misc.random_password(12)
922     if krbtgtpass is None:
923         krbtgtpass = misc.random_password(12)
924     if machinepass is None:
925         machinepass  = misc.random_password(12)
926     if dnspass is None:
927         dnspass = misc.random_password(12)
928     root_uid = findnss_uid([root or "root"])
929     nobody_uid = findnss_uid([nobody or "nobody"])
930     users_gid = findnss_gid([users or "users"])
931     if wheel is None:
932         wheel_gid = findnss_gid(["wheel", "adm"])
933     else:
934         wheel_gid = findnss_gid([wheel])
935     if aci is None:
936         aci = "# no aci for local ldb"
937
938     if targetdir is not None:
939         if (not os.path.exists(os.path.join(targetdir, "etc"))):
940             os.makedirs(os.path.join(targetdir, "etc"))
941         smbconf = os.path.join(targetdir, "etc", "smb.conf")
942
943     # only install a new smb.conf if there isn't one there already
944     if not os.path.exists(smbconf):
945         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
946                      targetdir)
947
948     lp = param.LoadParm()
949     lp.load(smbconf)
950
951     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
952                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
953                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
954                         serverdn=serverdn)
955
956     paths = provision_paths_from_lp(lp, names.dnsdomain)
957
958     if hostip is None:
959         hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
960
961     if hostip6 is None:
962         try:
963             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
964         except socket.gaierror: 
965             pass
966
967     if serverrole is None:
968         serverrole = lp.get("server role")
969
970     assert serverrole in ("domain controller", "member server", "standalone")
971     if invocationid is None and serverrole == "domain controller":
972         invocationid = str(uuid.uuid4())
973
974     if not os.path.exists(paths.private_dir):
975         os.mkdir(paths.private_dir)
976
977     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
978     
979     if ldap_backend is not None:
980         if ldap_backend == "ldapi":
981             # provision-backend will set this path suggested slapd command line / fedorads.inf
982             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
983              
984     # only install a new shares config db if there is none
985     if not os.path.exists(paths.shareconf):
986         message("Setting up share.ldb")
987         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
988                         credentials=credentials, lp=lp)
989         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
990
991      
992     message("Setting up secrets.ldb")
993     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
994                                   session_info=session_info, 
995                                   credentials=credentials, lp=lp)
996
997     message("Setting up the registry")
998     setup_registry(paths.hklm, setup_path, session_info, 
999                    credentials=credentials, lp=lp)
1000
1001     message("Setting up templates db")
1002     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
1003                       credentials=credentials, lp=lp)
1004
1005     message("Setting up idmap db")
1006     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1007                           credentials=credentials, lp=lp)
1008
1009     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1010                         credentials=credentials, lp=lp, names=names,
1011                         message=message, 
1012                         domainsid=domainsid, 
1013                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1014                         fill=samdb_fill, 
1015                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1016                         invocationid=invocationid, 
1017                         machinepass=machinepass, dnspass=dnspass,
1018                         serverrole=serverrole, ldap_backend=ldap_backend, 
1019                         ldap_backend_type=ldap_backend_type)
1020
1021     if lp.get("server role") == "domain controller":
1022         if paths.netlogon is None:
1023             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1024             message("Please either remove %s or see the template at %s" % 
1025                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1026             assert(paths.netlogon is not None)
1027
1028         if paths.sysvol is None:
1029             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1030             message("Please either remove %s or see the template at %s" % 
1031                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1032             assert(paths.sysvol is not None)            
1033             
1034         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1035                                    "{" + policyguid + "}")
1036         os.makedirs(policy_path, 0755)
1037         open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1038         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1039         os.makedirs(os.path.join(policy_path, "User"), 0755)
1040         if not os.path.isdir(paths.netlogon):
1041             os.makedirs(paths.netlogon, 0755)
1042
1043     if samdb_fill == FILL_FULL:
1044         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1045                             root_uid=root_uid, nobody_uid=nobody_uid,
1046                             users_gid=users_gid, wheel_gid=wheel_gid)
1047
1048         message("Setting up sam.ldb rootDSE marking as synchronized")
1049         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1050
1051         # Only make a zone file on the first DC, it should be replicated with DNS replication
1052         if serverrole == "domain controller":
1053             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1054                               credentials=credentials, lp=lp)
1055             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1056                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1057                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1058                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1059                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1060
1061             samdb = SamDB(paths.samdb, session_info=session_info, 
1062                       credentials=credentials, lp=lp)
1063
1064             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1065             assert isinstance(domainguid, str)
1066             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1067                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1068                                        scope=SCOPE_SUBTREE)
1069             assert isinstance(hostguid, str)
1070
1071             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1072                              domaindn=names.domaindn, hostip=hostip,
1073                              hostip6=hostip6, hostname=names.hostname,
1074                              dnspass=dnspass, realm=names.realm,
1075                              domainguid=domainguid, hostguid=hostguid)
1076
1077             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1078                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1079
1080             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1081                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1082                               keytab_name=paths.dns_keytab)
1083             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1084             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1085
1086             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1087                              hostname=names.hostname, realm=names.realm)
1088             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1089
1090     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1091                                ldapi_url)
1092
1093     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1094
1095     message("Once the above files are installed, your Samba4 server will be ready to use")
1096     message("Server Role:    %s" % serverrole)
1097     message("Hostname:       %s" % names.hostname)
1098     message("NetBIOS Domain: %s" % names.domain)
1099     message("DNS Domain:     %s" % names.dnsdomain)
1100     message("DOMAIN SID:     %s" % str(domainsid))
1101     message("Admin password: %s" % adminpass)
1102
1103     result = ProvisionResult()
1104     result.domaindn = domaindn
1105     result.paths = paths
1106     result.lp = lp
1107     result.samdb = samdb
1108     return result
1109
1110
1111 def provision_become_dc(setup_dir=None,
1112                         smbconf=None, targetdir=None, realm=None, 
1113                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1114                         serverdn=None,
1115                         domain=None, hostname=None, domainsid=None, 
1116                         adminpass=None, krbtgtpass=None, domainguid=None, 
1117                         policyguid=None, invocationid=None, machinepass=None, 
1118                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1119                         wheel=None, backup=None, aci=None, serverrole=None, 
1120                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1121
1122     def message(text):
1123         """print a message if quiet is not set."""
1124         print text
1125
1126     return provision(setup_dir, message, system_session(), None,
1127               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1128               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1129               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1130     
1131
1132 def setup_db_config(setup_path, dbdir):
1133     """Setup a Berkeley database.
1134     
1135     :param setup_path: Setup path function.
1136     :param dbdir: Database directory."""
1137     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1138         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1139     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1140         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1141     
1142     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1143                {"LDAPDBDIR": dbdir})
1144     
1145
1146
1147 def provision_backend(setup_dir=None, message=None,
1148                       smbconf=None, targetdir=None, realm=None, 
1149                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1150                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1151                       ldap_backend_type=None, ldap_backend_port=None,
1152                       ol_mmr_urls=None):
1153
1154     def setup_path(file):
1155         return os.path.join(setup_dir, file)
1156
1157     if hostname is None:
1158         hostname = socket.gethostname().split(".")[0].lower()
1159
1160     if root is None:
1161         root = findnss(pwd.getpwnam, ["root"])[0]
1162
1163     if adminpass is None:
1164         adminpass = misc.random_password(12)
1165
1166     if targetdir is not None:
1167         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1168             os.makedirs(os.path.join(targetdir, "etc"))
1169         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1170
1171     # only install a new smb.conf if there isn't one there already
1172     if not os.path.exists(smbconf):
1173         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1174                      targetdir)
1175
1176     lp = param.LoadParm()
1177     lp.load(smbconf)
1178
1179     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1180                         dnsdomain=realm, serverrole=serverrole, 
1181                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1182                         schemadn=schemadn)
1183
1184     paths = provision_paths_from_lp(lp, names.dnsdomain)
1185
1186     if not os.path.isdir(paths.ldapdir):
1187         os.makedirs(paths.ldapdir, 0700)
1188     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1189     try:
1190         os.unlink(schemadb_path)
1191     except:
1192         pass
1193
1194     schemadb = Ldb(schemadb_path, lp=lp)
1195  
1196     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1197
1198     setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1199                    {"SCHEMADN": names.schemadn,
1200                     "ACI": "#",
1201                     })
1202     setup_modify_ldif(schemadb, 
1203                       setup_path("provision_schema_basedn_modify.ldif"), \
1204                           {"SCHEMADN": names.schemadn,
1205                            "NETBIOSNAME": names.netbiosname,
1206                            "DEFAULTSITE": DEFAULTSITE,
1207                            "CONFIGDN": names.configdn,
1208                            "SERVERDN": names.serverdn,
1209                            "PREFIXMAP_B64": b64encode(prefixmap)
1210                            })
1211     
1212     setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"), 
1213                    {"SCHEMADN": names.schemadn })
1214     setup_add_ldif(schemadb, setup_path("schema.ldif"), 
1215                    {"SCHEMADN": names.schemadn})
1216
1217     if ldap_backend_type == "fedora-ds":
1218         if ldap_backend_port is not None:
1219             serverport = "ServerPort=%d" % ldap_backend_port
1220         else:
1221             serverport = ""
1222
1223         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1224                    {"ROOT": root,
1225                     "HOSTNAME": hostname,
1226                     "DNSDOMAIN": names.dnsdomain,
1227                     "LDAPDIR": paths.ldapdir,
1228                     "DOMAINDN": names.domaindn,
1229                     "LDAPMANAGERDN": names.ldapmanagerdn,
1230                     "LDAPMANAGERPASS": adminpass, 
1231                     "SERVERPORT": serverport})
1232         
1233         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1234                    {"CONFIGDN": names.configdn,
1235                     "SCHEMADN": names.schemadn,
1236                     })
1237         
1238         mapping = "schema-map-fedora-ds-1.0"
1239         backend_schema = "99_ad.ldif"
1240         
1241         slapdcommand="Initailise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1242        
1243         ldapuser = "--simple-bind-dn=" + names.ldapmanagerdn
1244
1245     elif ldap_backend_type == "openldap":
1246         attrs = ["linkID", "lDAPDisplayName"]
1247         res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs)
1248
1249         memberof_config = "# Generated from schema in %s\n" % schemadb_path
1250         refint_attributes = ""
1251         for i in range (0, len(res)):
1252             expression = "(&(objectclass=attributeSchema)(linkID=%d))" % (int(res[i]["linkID"][0])+1)
1253             target = schemadb.searchone(basedn=names.schemadn, 
1254                                         expression=expression, 
1255                                         attribute="lDAPDisplayName", 
1256                                         scope=SCOPE_SUBTREE)
1257             if target is not None:
1258                 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0]
1259             
1260                 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1261                                                      { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1262                                                        "MEMBEROF_ATTR" : str(target) })
1263
1264         refint_config = read_and_sub_file(setup_path("refint.conf"),
1265                                             { "LINK_ATTRS" : refint_attributes})
1266
1267 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1268         mmr_on_config = ""
1269         mmr_serverids_config = ""
1270         mmr_syncrepl_schema_config = "" 
1271         mmr_syncrepl_config_config = "" 
1272         mmr_syncrepl_user_config = "" 
1273         
1274         if ol_mmr_urls is not None:
1275                 url_list=filter(None,ol_mmr_urls.split(' ')) 
1276                 if (len(url_list) == 1):
1277                     url_list=filter(None,ol_mmr_urls.split(',')) 
1278                      
1279
1280                 mmr_on_config = "MirrorMode On"
1281                 serverid=0
1282                 for url in url_list:
1283                         serverid=serverid+1
1284                         mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1285                                                                      { "SERVERID" : str(serverid),
1286                                                                        "LDAPSERVER" : url })
1287                         rid=serverid*10
1288                         rid=rid+1
1289                         mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1290                                                                      {  "RID" : str(rid),
1291                                                                         "MMRDN": names.schemadn,
1292                                                                         "LDAPSERVER" : url,
1293                                                                         "MMR_PASSWORD": adminpass})
1294
1295                         rid=rid+1
1296                         mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1297                                                                      {  "RID" : str(rid),
1298                                                                         "MMRDN": names.configdn,
1299                                                                         "LDAPSERVER" : url,
1300                                                                         "MMR_PASSWORD": adminpass})
1301
1302                         rid=rid+1
1303                         mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1304                                                                      {  "RID" : str(rid),
1305                                                                         "MMRDN": names.domaindn,
1306                                                                         "LDAPSERVER" : url,
1307                                                                         "MMR_PASSWORD": adminpass })
1308
1309
1310         setup_file(setup_path("slapd.conf"), paths.slapdconf,
1311                    {"DNSDOMAIN": names.dnsdomain,
1312                     "LDAPDIR": paths.ldapdir,
1313                     "DOMAINDN": names.domaindn,
1314                     "CONFIGDN": names.configdn,
1315                     "SCHEMADN": names.schemadn,
1316                     "MEMBEROF_CONFIG": memberof_config,
1317                     "MIRRORMODE": mmr_on_config,
1318                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1319                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1320                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1321                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1322                     "MMR_PASSWORD": adminpass,
1323                     "REFINT_CONFIG": refint_config})
1324         setup_file(setup_path("modules.conf"), paths.modulesconf,
1325                    {"REALM": names.realm})
1326         
1327         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1328         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1329         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1330
1331         if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1332             os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1333
1334         setup_file(setup_path("cn=samba.ldif"), 
1335                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1336                    { "UUID": str(uuid.uuid4()), 
1337                      "LDAPTIME": timestring(int(time.time()))} )
1338         setup_file(setup_path("cn=samba-admin.ldif"), 
1339                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1340                               {"LDAPADMINPASS_B64": b64encode(adminpass),
1341                                "UUID": str(uuid.uuid4()), 
1342                                "LDAPTIME": timestring(int(time.time()))} )
1343
1344         mapping = "schema-map-openldap-2.3"
1345         backend_schema = "backend-schema.schema"
1346
1347         ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1348         if ldap_backend_port is not None:
1349             server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1350         else:
1351             server_port_string = ""
1352
1353         slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1354
1355         ldapuser = "--username=samba-admin"
1356
1357             
1358     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)
1359             
1360     os.system(schema_command)
1361
1362     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type)
1363     message("Server Role:         %s" % serverrole)
1364     message("Hostname:            %s" % names.hostname)
1365     message("DNS Domain:          %s" % names.dnsdomain)
1366     message("Base DN:             %s" % names.domaindn)
1367
1368     if ldap_backend_type == "openldap":
1369         message("LDAP admin user:     samba-admin")
1370     else:
1371         message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1372
1373     message("LDAP admin password: %s" % adminpass)
1374     message(slapdcommand)
1375     message("Run provision with:  --ldap-backend=ldapi --ldap-backend-type=" + ldap_backend_type + " --password=" + adminpass + " " + ldapuser)
1376
1377 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1378     """Create a PHP LDAP admin configuration file.
1379
1380     :param path: Path to write the configuration to.
1381     :param setup_path: Function to generate setup paths.
1382     """
1383     setup_file(setup_path("phpldapadmin-config.php"), path, 
1384             {"S4_LDAPI_URI": ldapi_uri})
1385
1386
1387 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1388                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1389     """Write out a DNS zone file, from the info in the current database.
1390
1391     :param path: Path of the new zone file.
1392     :param setup_path: Setup path function.
1393     :param dnsdomain: DNS Domain name
1394     :param domaindn: DN of the Domain
1395     :param hostip: Local IPv4 IP
1396     :param hostip6: Local IPv6 IP
1397     :param hostname: Local hostname
1398     :param dnspass: Password for DNS
1399     :param realm: Realm name
1400     :param domainguid: GUID of the domain.
1401     :param hostguid: GUID of the host.
1402     """
1403     assert isinstance(domainguid, str)
1404
1405     if hostip6 is not None:
1406         hostip6_base_line = "            IN AAAA    " + hostip6
1407         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1408     else:
1409         hostip6_base_line = ""
1410         hostip6_host_line = ""
1411
1412     setup_file(setup_path("provision.zone"), path, {
1413             "DNSPASS_B64": b64encode(dnspass),
1414             "HOSTNAME": hostname,
1415             "DNSDOMAIN": dnsdomain,
1416             "REALM": realm,
1417             "HOSTIP": hostip,
1418             "DOMAINGUID": domainguid,
1419             "DATESTRING": time.strftime("%Y%m%d%H"),
1420             "DEFAULTSITE": DEFAULTSITE,
1421             "HOSTGUID": hostguid,
1422             "HOSTIP6_BASE_LINE": hostip6_base_line,
1423             "HOSTIP6_HOST_LINE": hostip6_host_line,
1424         })
1425
1426
1427 def create_named_conf(path, setup_path, realm, dnsdomain,
1428                       private_dir):
1429     """Write out a file containing zone statements suitable for inclusion in a
1430     named.conf file (including GSS-TSIG configuration).
1431     
1432     :param path: Path of the new named.conf file.
1433     :param setup_path: Setup path function.
1434     :param realm: Realm name
1435     :param dnsdomain: DNS Domain name
1436     :param private_dir: Path to private directory
1437     :param keytab_name: File name of DNS keytab file
1438     """
1439
1440     setup_file(setup_path("named.conf"), path, {
1441             "DNSDOMAIN": dnsdomain,
1442             "REALM": realm,
1443             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1444             "PRIVATE_DIR": private_dir
1445             })
1446
1447 def create_named_txt(path, setup_path, realm, dnsdomain,
1448                       private_dir, keytab_name):
1449     """Write out a file containing zone statements suitable for inclusion in a
1450     named.conf file (including GSS-TSIG configuration).
1451     
1452     :param path: Path of the new named.conf file.
1453     :param setup_path: Setup path function.
1454     :param realm: Realm name
1455     :param dnsdomain: DNS Domain name
1456     :param private_dir: Path to private directory
1457     :param keytab_name: File name of DNS keytab file
1458     """
1459
1460     setup_file(setup_path("named.txt"), path, {
1461             "DNSDOMAIN": dnsdomain,
1462             "REALM": realm,
1463             "DNS_KEYTAB": keytab_name,
1464             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1465             "PRIVATE_DIR": private_dir
1466         })
1467
1468 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1469     """Write out a file containing zone statements suitable for inclusion in a
1470     named.conf file (including GSS-TSIG configuration).
1471     
1472     :param path: Path of the new named.conf file.
1473     :param setup_path: Setup path function.
1474     :param dnsdomain: DNS Domain name
1475     :param hostname: Local hostname
1476     :param realm: Realm name
1477     """
1478
1479     setup_file(setup_path("krb5.conf"), path, {
1480             "DNSDOMAIN": dnsdomain,
1481             "HOSTNAME": hostname,
1482             "REALM": realm,
1483         })
1484
1485
1486 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename,
1487                 serverdn, servername):
1488     """Load schema for the SamDB.
1489     
1490     :param samdb: Load a schema into a SamDB.
1491     :param setup_path: Setup path function.
1492     :param schemadn: DN of the schema
1493     :param netbiosname: NetBIOS name of the host.
1494     :param configdn: DN of the configuration
1495     :param serverdn: DN of the server
1496     :param servername: Host name of the server
1497     """
1498     schema_data = open(setup_path("schema.ldif"), 'r').read()
1499     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1500     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1501     check_all_substituted(schema_data)
1502     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1503     prefixmap = b64encode(prefixmap)
1504
1505     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1506     head_data = substitute_var(head_data, {
1507                     "SCHEMADN": schemadn,
1508                     "NETBIOSNAME": netbiosname,
1509                     "CONFIGDN": configdn,
1510                     "DEFAULTSITE": sitename,
1511                     "PREFIXMAP_B64": prefixmap,
1512                     "SERVERDN": serverdn,
1513                     "SERVERNAME": servername,
1514     })
1515     check_all_substituted(head_data)
1516     samdb.attach_schema_from_ldif(head_data, schema_data)
1517