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