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