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