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