Merge branch 'master' of ssh://jht@git.samba.org/data/git/samba
[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 version, Ldb, substitute_var, valid_netbios_name, check_all_substituted, \
41   DS_BEHAVIOR_WIN2008
42 from samba.samdb import SamDB
43 from samba.idmap import IDmapDB
44 from samba.dcerpc import security
45 import urllib
46 from ldb import SCOPE_SUBTREE, LdbError, timestring
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 = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
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, domainControllerFunctionality):
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               "SAMBA_VERSION_STRING": version,
750               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
751     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
752               "POLICYGUID": policyguid,
753               "DNSDOMAIN": names.dnsdomain,
754               "DOMAINSID": str(domainsid),
755               "DOMAINDN": names.domaindn})
756
757
758 def setup_samdb(path, setup_path, session_info, credentials, lp, 
759                 names, message, 
760                 domainsid, aci, domainguid, policyguid, 
761                 fill, adminpass, krbtgtpass, 
762                 machinepass, invocationid, dnspass,
763                 serverrole, ldap_backend=None, 
764                 ldap_backend_type=None):
765     """Setup a complete SAM Database.
766     
767     :note: This will wipe the main SAM database file!
768     """
769
770     domainFunctionality = DS_BEHAVIOR_WIN2008
771     forestFunctionality = DS_BEHAVIOR_WIN2008
772     domainControllerFunctionality = DS_BEHAVIOR_WIN2008
773
774     erase = (fill != FILL_DRS)
775
776     # Also wipes the database
777     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
778                            credentials=credentials, session_info=session_info,
779                            names=names, 
780                            ldap_backend=ldap_backend, serverrole=serverrole,
781                            ldap_backend_type=ldap_backend_type, erase=erase)
782
783     samdb = SamDB(path, session_info=session_info, 
784                   credentials=credentials, lp=lp)
785     if fill == FILL_DRS:
786         return samdb
787
788     message("Pre-loading the Samba 4 and AD schema")
789
790     samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
791     samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
792     samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
793
794     samdb.set_domain_sid(str(domainsid))
795     if serverrole == "domain controller":
796         samdb.set_invocation_id(invocationid)
797
798     schema_data = load_schema(setup_path, samdb, names.schemadn, names.netbiosname, 
799                               names.configdn, names.sitename, names.serverdn)
800     samdb.transaction_start()
801         
802     try:
803         message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
804         if serverrole == "domain controller":
805             domain_oc = "domainDNS"
806         else:
807             domain_oc = "samba4LocalDomain"
808
809         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
810                 "DOMAINDN": names.domaindn,
811                 "ACI": aci,
812                 "DOMAIN_OC": domain_oc
813                 })
814
815         message("Modifying DomainDN: " + names.domaindn + "")
816         if domainguid is not None:
817             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
818         else:
819             domainguid_mod = ""
820
821         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
822             "LDAPTIME": timestring(int(time.time())),
823             "DOMAINSID": str(domainsid),
824             "SCHEMADN": names.schemadn, 
825             "NETBIOSNAME": names.netbiosname,
826             "DEFAULTSITE": names.sitename,
827             "CONFIGDN": names.configdn,
828             "SERVERDN": names.serverdn,
829             "POLICYGUID": policyguid,
830             "DOMAINDN": names.domaindn,
831             "DOMAINGUID_MOD": domainguid_mod,
832             "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
833             })
834
835         message("Adding configuration container (permitted to fail)")
836         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
837             "CONFIGDN": names.configdn, 
838             "ACI": aci,
839             })
840         message("Modifying configuration container")
841         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
842             "CONFIGDN": names.configdn, 
843             "SCHEMADN": names.schemadn,
844             })
845
846         message("Adding schema container (permitted to fail)")
847         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
848             "SCHEMADN": names.schemadn,
849             "ACI": aci,
850             })
851         message("Modifying schema container")
852
853         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
854
855         setup_modify_ldif(samdb, 
856             setup_path("provision_schema_basedn_modify.ldif"), {
857             "SCHEMADN": names.schemadn,
858             "NETBIOSNAME": names.netbiosname,
859             "DEFAULTSITE": names.sitename,
860             "CONFIGDN": names.configdn,
861             "SERVERDN": names.serverdn,
862             "PREFIXMAP_B64": b64encode(prefixmap)
863             })
864
865         message("Setting up sam.ldb schema")
866         samdb.add_ldif(schema_data)
867         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
868                        {"SCHEMADN": names.schemadn})
869
870         message("Setting up sam.ldb configuration data")
871         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
872             "CONFIGDN": names.configdn,
873             "NETBIOSNAME": names.netbiosname,
874             "DEFAULTSITE": names.sitename,
875             "DNSDOMAIN": names.dnsdomain,
876             "DOMAIN": names.domain,
877             "SCHEMADN": names.schemadn,
878             "DOMAINDN": names.domaindn,
879             "SERVERDN": names.serverdn,
880             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
881             })
882
883         message("Setting up display specifiers")
884         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
885                        {"CONFIGDN": names.configdn})
886
887         message("Adding users container (permitted to fail)")
888         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
889                 "DOMAINDN": names.domaindn})
890         message("Modifying users container")
891         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
892                 "DOMAINDN": names.domaindn})
893         message("Adding computers container (permitted to fail)")
894         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
895                 "DOMAINDN": names.domaindn})
896         message("Modifying computers container")
897         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
898                 "DOMAINDN": names.domaindn})
899         message("Setting up sam.ldb data")
900         setup_add_ldif(samdb, setup_path("provision.ldif"), {
901             "DOMAINDN": names.domaindn,
902             "NETBIOSNAME": names.netbiosname,
903             "DEFAULTSITE": names.sitename,
904             "CONFIGDN": names.configdn,
905             "SERVERDN": names.serverdn
906             })
907
908         if fill == FILL_FULL:
909             message("Setting up sam.ldb users and groups")
910             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
911                 "DOMAINDN": names.domaindn,
912                 "DOMAINSID": str(domainsid),
913                 "CONFIGDN": names.configdn,
914                 "ADMINPASS_B64": b64encode(adminpass),
915                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
916                 })
917
918             if serverrole == "domain controller":
919                 message("Setting up self join")
920                 setup_self_join(samdb, names=names, invocationid=invocationid, 
921                                 dnspass=dnspass,  
922                                 machinepass=machinepass, 
923                                 domainsid=domainsid, policyguid=policyguid,
924                                 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
925
926     except:
927         samdb.transaction_cancel()
928         raise
929
930     samdb.transaction_commit()
931     return samdb
932
933
934 FILL_FULL = "FULL"
935 FILL_NT4SYNC = "NT4SYNC"
936 FILL_DRS = "DRS"
937
938 def provision(setup_dir, message, session_info, 
939               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None, 
940               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
941               serverdn=None,
942               domain=None, hostname=None, hostip=None, hostip6=None, 
943               domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
944               policyguid=None, invocationid=None, machinepass=None, 
945               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
946               wheel=None, backup=None, aci=None, serverrole=None, 
947               ldap_backend=None, ldap_backend_type=None, sitename=None):
948     """Provision samba4
949     
950     :note: caution, this wipes all existing data!
951     """
952
953     def setup_path(file):
954         return os.path.join(setup_dir, file)
955
956     if domainsid is None:
957         domainsid = security.random_sid()
958
959     if policyguid is None:
960         policyguid = str(uuid.uuid4())
961     if adminpass is None:
962         adminpass = glue.generate_random_str(12)
963     if krbtgtpass is None:
964         krbtgtpass = glue.generate_random_str(12)
965     if machinepass is None:
966         machinepass  = glue.generate_random_str(12)
967     if dnspass is None:
968         dnspass = glue.generate_random_str(12)
969     root_uid = findnss_uid([root or "root"])
970     nobody_uid = findnss_uid([nobody or "nobody"])
971     users_gid = findnss_gid([users or "users"])
972     if wheel is None:
973         wheel_gid = findnss_gid(["wheel", "adm"])
974     else:
975         wheel_gid = findnss_gid([wheel])
976     if aci is None:
977         aci = "# no aci for local ldb"
978
979     if targetdir is not None:
980         if (not os.path.exists(os.path.join(targetdir, "etc"))):
981             os.makedirs(os.path.join(targetdir, "etc"))
982         smbconf = os.path.join(targetdir, "etc", "smb.conf")
983     elif smbconf is None:
984         smbconf = param.default_path()
985
986     # only install a new smb.conf if there isn't one there already
987     if not os.path.exists(smbconf):
988         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
989                      targetdir)
990
991     lp = param.LoadParm()
992     lp.load(smbconf)
993
994     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
995                         dnsdomain=realm, serverrole=serverrole, sitename=sitename,
996                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
997                         serverdn=serverdn)
998
999     paths = provision_paths_from_lp(lp, names.dnsdomain)
1000
1001     if hostip is None:
1002         try:
1003             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1004         except socket.gaierror, (socket.EAI_NODATA, msg):
1005             hostip = None
1006
1007     if hostip6 is None:
1008         try:
1009             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1010         except socket.gaierror, (socket.EAI_NODATA, msg): 
1011             hostip6 = None
1012
1013     if serverrole is None:
1014         serverrole = lp.get("server role")
1015
1016     assert serverrole in ("domain controller", "member server", "standalone")
1017     if invocationid is None and serverrole == "domain controller":
1018         invocationid = str(uuid.uuid4())
1019
1020     if not os.path.exists(paths.private_dir):
1021         os.mkdir(paths.private_dir)
1022
1023     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1024     
1025     if ldap_backend is not None:
1026         if ldap_backend == "ldapi":
1027             # provision-backend will set this path suggested slapd command line / fedorads.inf
1028             ldap_backend = "ldapi://%s" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1029              
1030     # only install a new shares config db if there is none
1031     if not os.path.exists(paths.shareconf):
1032         message("Setting up share.ldb")
1033         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1034                         credentials=credentials, lp=lp)
1035         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1036
1037      
1038     message("Setting up secrets.ldb")
1039     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1040                                   session_info=session_info, 
1041                                   credentials=credentials, lp=lp)
1042
1043     message("Setting up the registry")
1044     setup_registry(paths.hklm, setup_path, session_info, 
1045                    credentials=credentials, lp=lp)
1046
1047     message("Setting up templates db")
1048     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
1049                       credentials=credentials, lp=lp)
1050
1051     message("Setting up idmap db")
1052     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1053                           credentials=credentials, lp=lp)
1054
1055     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
1056                         credentials=credentials, lp=lp, names=names,
1057                         message=message, 
1058                         domainsid=domainsid, 
1059                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
1060                         fill=samdb_fill, 
1061                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1062                         invocationid=invocationid, 
1063                         machinepass=machinepass, dnspass=dnspass,
1064                         serverrole=serverrole, ldap_backend=ldap_backend, 
1065                         ldap_backend_type=ldap_backend_type)
1066
1067     if serverrole == "domain controller":
1068         if paths.netlogon is None:
1069             message("Existing smb.conf does not have a [netlogon] 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.netlogon is not None)
1073
1074         if paths.sysvol is None:
1075             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1076             message("Please either remove %s or see the template at %s" % 
1077                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1078             assert(paths.sysvol is not None)            
1079             
1080         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies", 
1081                                    "{" + policyguid + "}")
1082         os.makedirs(policy_path, 0755)
1083         open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1084         os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1085         os.makedirs(os.path.join(policy_path, "User"), 0755)
1086         if not os.path.isdir(paths.netlogon):
1087             os.makedirs(paths.netlogon, 0755)
1088
1089     if samdb_fill == FILL_FULL:
1090         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1091                             root_uid=root_uid, nobody_uid=nobody_uid,
1092                             users_gid=users_gid, wheel_gid=wheel_gid)
1093
1094         message("Setting up sam.ldb rootDSE marking as synchronized")
1095         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1096
1097         # Only make a zone file on the first DC, it should be replicated with DNS replication
1098         if serverrole == "domain controller":
1099             secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
1100                               credentials=credentials, lp=lp)
1101             secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1102                                 netbiosname=names.netbiosname, domainsid=domainsid, 
1103                                 keytab_path=paths.keytab, samdb_url=paths.samdb, 
1104                                 dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
1105                                 machinepass=machinepass, dnsdomain=names.dnsdomain)
1106
1107             samdb = SamDB(paths.samdb, session_info=session_info, 
1108                       credentials=credentials, lp=lp)
1109
1110             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1111             assert isinstance(domainguid, str)
1112             hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1113                                        expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1114                                        scope=SCOPE_SUBTREE)
1115             assert isinstance(hostguid, str)
1116
1117             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1118                              domaindn=names.domaindn, hostip=hostip,
1119                              hostip6=hostip6, hostname=names.hostname,
1120                              dnspass=dnspass, realm=names.realm,
1121                              domainguid=domainguid, hostguid=hostguid)
1122
1123             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1124                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1125
1126             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1127                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1128                               keytab_name=paths.dns_keytab)
1129             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1130             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1131
1132             create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1133                              hostname=names.hostname, realm=names.realm)
1134             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1135
1136     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1137                                ldapi_url)
1138
1139     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1140
1141     message("Once the above files are installed, your Samba4 server will be ready to use")
1142     message("Server Role:    %s" % serverrole)
1143     message("Hostname:       %s" % names.hostname)
1144     message("NetBIOS Domain: %s" % names.domain)
1145     message("DNS Domain:     %s" % names.dnsdomain)
1146     message("DOMAIN SID:     %s" % str(domainsid))
1147     if samdb_fill == FILL_FULL:
1148         message("Admin password: %s" % adminpass)
1149
1150     result = ProvisionResult()
1151     result.domaindn = domaindn
1152     result.paths = paths
1153     result.lp = lp
1154     result.samdb = samdb
1155     return result
1156
1157
1158 def provision_become_dc(setup_dir=None,
1159                         smbconf=None, targetdir=None, realm=None, 
1160                         rootdn=None, domaindn=None, schemadn=None, configdn=None,
1161                         serverdn=None,
1162                         domain=None, hostname=None, domainsid=None, 
1163                         adminpass=None, krbtgtpass=None, domainguid=None, 
1164                         policyguid=None, invocationid=None, machinepass=None, 
1165                         dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
1166                         wheel=None, backup=None, aci=None, serverrole=None, 
1167                         ldap_backend=None, ldap_backend_type=None, sitename=None):
1168
1169     def message(text):
1170         """print a message if quiet is not set."""
1171         print text
1172
1173     return provision(setup_dir, message, system_session(), None,
1174               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm, 
1175               rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1176               domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1177     
1178
1179 def setup_db_config(setup_path, dbdir):
1180     """Setup a Berkeley database.
1181     
1182     :param setup_path: Setup path function.
1183     :param dbdir: Database directory."""
1184     if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1185         os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1186     if not os.path.isdir(os.path.join(dbdir, "tmp")):
1187         os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1188     
1189     setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1190                {"LDAPDBDIR": dbdir})
1191     
1192
1193
1194 def provision_backend(setup_dir=None, message=None,
1195                       smbconf=None, targetdir=None, realm=None, 
1196                       rootdn=None, domaindn=None, schemadn=None, configdn=None,
1197                       domain=None, hostname=None, adminpass=None, root=None, serverrole=None, 
1198                       ldap_backend_type=None, ldap_backend_port=None,
1199                       ol_mmr_urls=None,ol_olc=None,ol_slaptest=None):
1200
1201     def setup_path(file):
1202         return os.path.join(setup_dir, file)
1203
1204     if hostname is None:
1205         hostname = socket.gethostname().split(".")[0].lower()
1206
1207     if root is None:
1208         root = findnss(pwd.getpwnam, ["root"])[0]
1209
1210     if adminpass is None:
1211         adminpass = glue.generate_random_str(12)
1212
1213     if targetdir is not None:
1214         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1215             os.makedirs(os.path.join(targetdir, "etc"))
1216         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1217     elif smbconf is None:
1218         smbconf = param.default_path()
1219         assert smbconf is not None
1220
1221     # only install a new smb.conf if there isn't one there already
1222     if not os.path.exists(smbconf):
1223         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1224                      targetdir)
1225
1226     # openldap-online-configuration: validation of olc and slaptest
1227     if ol_olc == "yes" and ol_slaptest is None: 
1228         sys.exit("Warning: OpenLDAP-Online-Configuration cant be setup without path to slaptest-Binary!")
1229
1230     if ol_olc == "yes" and ol_slaptest is not None:
1231         ol_slaptest = ol_slaptest + "/slaptest"
1232         if not os.path.exists(ol_slaptest):
1233             message (ol_slaptest)
1234             sys.exit("Warning: Given Path to slaptest-Binary does not exist!")
1235     ###
1236
1237
1238
1239     lp = param.LoadParm()
1240     lp.load(smbconf)
1241
1242     if serverrole is None:
1243         serverrole = lp.get("server role")
1244
1245     names = guess_names(lp=lp, hostname=hostname, domain=domain, 
1246                         dnsdomain=realm, serverrole=serverrole, 
1247                         rootdn=rootdn, domaindn=domaindn, configdn=configdn, 
1248                         schemadn=schemadn)
1249
1250     paths = provision_paths_from_lp(lp, names.dnsdomain)
1251
1252     if not os.path.isdir(paths.ldapdir):
1253         os.makedirs(paths.ldapdir, 0700)
1254     schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1255     try:
1256         os.unlink(schemadb_path)
1257     except OSError:
1258         pass
1259
1260     schemadb = SamDB(schemadb_path, lp=lp)
1261     schemadb.transaction_start()
1262     try:
1263  
1264         prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1265
1266         setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"), 
1267                        {"SCHEMADN": names.schemadn,
1268                         "ACI": "#",
1269                         })
1270         setup_modify_ldif(schemadb, 
1271                           setup_path("provision_schema_basedn_modify.ldif"), \
1272                               {"SCHEMADN": names.schemadn,
1273                                "NETBIOSNAME": names.netbiosname,
1274                                "DEFAULTSITE": DEFAULTSITE,
1275                                "CONFIGDN": names.configdn,
1276                                "SERVERDN": names.serverdn,
1277                                "PREFIXMAP_B64": b64encode(prefixmap)
1278                                })
1279         
1280         data = load_schema(setup_path, schemadb, names.schemadn, names.netbiosname, 
1281                            names.configdn, DEFAULTSITE, names.serverdn)
1282         schemadb.add_ldif(data)
1283     except:
1284         schemadb.transaction_cancel()
1285         raise
1286     schemadb.transaction_commit()
1287
1288     if ldap_backend_type == "fedora-ds":
1289         if ldap_backend_port is not None:
1290             serverport = "ServerPort=%d" % ldap_backend_port
1291         else:
1292             serverport = ""
1293
1294         setup_file(setup_path("fedorads.inf"), paths.fedoradsinf, 
1295                    {"ROOT": root,
1296                     "HOSTNAME": hostname,
1297                     "DNSDOMAIN": names.dnsdomain,
1298                     "LDAPDIR": paths.ldapdir,
1299                     "DOMAINDN": names.domaindn,
1300                     "LDAPMANAGERDN": names.ldapmanagerdn,
1301                     "LDAPMANAGERPASS": adminpass, 
1302                     "SERVERPORT": serverport})
1303         
1304         setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions, 
1305                    {"CONFIGDN": names.configdn,
1306                     "SCHEMADN": names.schemadn,
1307                     })
1308         
1309         mapping = "schema-map-fedora-ds-1.0"
1310         backend_schema = "99_ad.ldif"
1311         
1312         slapdcommand="Initialise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1313        
1314         ldapuser = "--simple-bind-dn=" + names.ldapmanagerdn
1315
1316     elif ldap_backend_type == "openldap":
1317         attrs = ["linkID", "lDAPDisplayName"]
1318         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)
1319
1320         memberof_config = "# Generated from schema in %s\n" % schemadb_path
1321         refint_attributes = ""
1322         for i in range (0, len(res)):
1323             expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1324             target = schemadb.searchone(basedn=names.schemadn, 
1325                                         expression=expression, 
1326                                         attribute="lDAPDisplayName", 
1327                                         scope=SCOPE_SUBTREE)
1328             if target is not None:
1329                 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0]
1330             
1331                 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1332                                                      { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1333                                                        "MEMBEROF_ATTR" : str(target) })
1334
1335         refint_config = read_and_sub_file(setup_path("refint.conf"),
1336                                             { "LINK_ATTRS" : refint_attributes})
1337
1338 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1339         mmr_on_config = ""
1340         mmr_replicator_acl = ""
1341         mmr_serverids_config = ""
1342         mmr_syncrepl_schema_config = "" 
1343         mmr_syncrepl_config_config = "" 
1344         mmr_syncrepl_user_config = "" 
1345        
1346  
1347         if ol_mmr_urls is not None:
1348                 # For now, make these equal
1349                 mmr_pass = adminpass
1350
1351                 url_list=filter(None,ol_mmr_urls.split(' ')) 
1352                 if (len(url_list) == 1):
1353                     url_list=filter(None,ol_mmr_urls.split(',')) 
1354                      
1355
1356                 mmr_on_config = "MirrorMode On"
1357                 mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
1358                 serverid=0
1359                 for url in url_list:
1360                         serverid=serverid+1
1361                         mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1362                                                                      { "SERVERID" : str(serverid),
1363                                                                        "LDAPSERVER" : url })
1364                         rid=serverid*10
1365                         rid=rid+1
1366                         mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1367                                                                      {  "RID" : str(rid),
1368                                                                         "MMRDN": names.schemadn,
1369                                                                         "LDAPSERVER" : url,
1370                                                                         "MMR_PASSWORD": mmr_pass})
1371
1372                         rid=rid+1
1373                         mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1374                                                                      {  "RID" : str(rid),
1375                                                                         "MMRDN": names.configdn,
1376                                                                         "LDAPSERVER" : url,
1377                                                                         "MMR_PASSWORD": mmr_pass})
1378
1379                         rid=rid+1
1380                         mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1381                                                                      {  "RID" : str(rid),
1382                                                                         "MMRDN": names.domaindn,
1383                                                                         "LDAPSERVER" : url,
1384                                                                         "MMR_PASSWORD": mmr_pass })
1385         # olc = yes?
1386         olc_config_pass = ""
1387         olc_config_acl = ""
1388         olc_syncrepl_config = ""
1389         olc_mmr_config = "" 
1390         if ol_olc == "yes":
1391                 olc_config_pass += read_and_sub_file(setup_path("olc_pass.conf"),
1392                                                                 { "OLC_PW": adminpass })
1393                 olc_config_acl += read_and_sub_file(setup_path("olc_acl.conf"),{})
1394                 
1395             # if olc = yes + mmr = yes, generate cn=config-replication directives
1396             # and  olc_seed.lif for the other mmr-servers
1397                 if ol_olc == "yes" and ol_mmr_urls is not None:
1398                         serverid=0
1399                         olc_serverids_config = ""
1400                         olc_syncrepl_config = ""
1401                         olc_syncrepl_seed_config = ""
1402                         olc_mmr_config = "" 
1403                         olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1404                         rid=1000
1405                         for url in url_list:
1406                                 serverid=serverid+1
1407                                 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1408                                                                      { "SERVERID" : str(serverid),
1409                                                                        "LDAPSERVER" : url })
1410                         
1411                                 rid=rid+1
1412                                 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1413                                                                      {  "RID" : str(rid),
1414                                                                         "LDAPSERVER" : url,
1415                                                                         "MMR_PASSWORD": adminpass})
1416
1417                                 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1418                                                                      {  "RID" : str(rid),
1419                                                                         "LDAPSERVER" : url})
1420
1421                                 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1422                                                                      {"OLC_SERVER_ID_CONF": olc_serverids_config,
1423                                                                       "OLC_PW": adminpass,
1424                                                                       "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1425         
1426
1427                 # end olc
1428
1429         setup_file(setup_path("slapd.conf"), paths.slapdconf,
1430                    {"DNSDOMAIN": names.dnsdomain,
1431                     "LDAPDIR": paths.ldapdir,
1432                     "DOMAINDN": names.domaindn,
1433                     "CONFIGDN": names.configdn,
1434                     "SCHEMADN": names.schemadn,
1435                     "MEMBEROF_CONFIG": memberof_config,
1436                     "MIRRORMODE": mmr_on_config,
1437                     "REPLICATOR_ACL": mmr_replicator_acl,
1438                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1439                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1440                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1441                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1442                     "OLC_CONFIG_PASS": olc_config_pass,
1443                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1444                     "OLC_CONFIG_ACL": olc_config_acl,
1445                     "OLC_MMR_CONFIG": olc_mmr_config,
1446                     "REFINT_CONFIG": refint_config})
1447         setup_file(setup_path("modules.conf"), paths.modulesconf,
1448                    {"REALM": names.realm})
1449         
1450         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1451         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1452         setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1453
1454         if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba")):
1455             os.makedirs(os.path.join(paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
1456
1457         setup_file(setup_path("cn=samba.ldif"), 
1458                    os.path.join(paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
1459                    { "UUID": str(uuid.uuid4()), 
1460                      "LDAPTIME": timestring(int(time.time()))} )
1461         setup_file(setup_path("cn=samba-admin.ldif"), 
1462                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
1463                               {"LDAPADMINPASS_B64": b64encode(adminpass),
1464                                "UUID": str(uuid.uuid4()), 
1465                                "LDAPTIME": timestring(int(time.time()))} )
1466         
1467         if ol_mmr_urls is not None:
1468            setup_file(setup_path("cn=replicator.ldif"),
1469                               os.path.join(paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
1470                               {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1471                                "UUID": str(uuid.uuid4()),
1472                                "LDAPTIME": timestring(int(time.time()))} )
1473
1474
1475         mapping = "schema-map-openldap-2.3"
1476         backend_schema = "backend-schema.schema"
1477
1478         ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1479         if ldap_backend_port is not None:
1480             server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1481         else:
1482             server_port_string = ""
1483
1484         if ol_olc != "yes" and ol_mmr_urls is None:
1485           slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1486
1487         if ol_olc == "yes" and ol_mmr_urls is None:
1488           slapdcommand="Start slapd with:    slapd -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\"" 
1489
1490         if ol_olc != "yes" and ol_mmr_urls is not None:
1491           slapdcommand="Start slapd with:    slapd -f " + paths.ldapdir + "/slapd.conf -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\""
1492
1493         if ol_olc == "yes" and ol_mmr_urls is not None:
1494           slapdcommand="Start slapd with:    slapd -F " + paths.olcdir + " -h \"" + ldapi_uri + " ldap://<FQHN>:<PORT>\""
1495
1496
1497         ldapuser = "--username=samba-admin"
1498
1499
1500     backend_schema_data = schemadb.convert_schema_to_openldap(ldap_backend_type, open(setup_path(mapping), 'r').read())
1501     assert backend_schema_data is not None
1502     open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1503
1504     message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ldap_backend_type)
1505     message("Server Role:         %s" % serverrole)
1506     message("Hostname:            %s" % names.hostname)
1507     message("DNS Domain:          %s" % names.dnsdomain)
1508     message("Base DN:             %s" % names.domaindn)
1509
1510     if ldap_backend_type == "openldap":
1511         message("LDAP admin user:     samba-admin")
1512     else:
1513         message("LDAP admin DN:       %s" % names.ldapmanagerdn)
1514
1515     message("LDAP admin password: %s" % adminpass)
1516     message(slapdcommand)
1517     if ol_olc == "yes" or ol_mmr_urls is not None:
1518         message("Attention to slapd-Port: <PORT> must be different than 389!")
1519     assert isinstance(ldap_backend_type, str)
1520     assert isinstance(ldapuser, str)
1521     assert isinstance(adminpass, str)
1522     assert isinstance(names.dnsdomain, str)
1523     assert isinstance(names.domain, str)
1524     assert isinstance(serverrole, str)
1525     args = ["--ldap-backend=ldapi",
1526             "--ldap-backend-type=" + ldap_backend_type,
1527             "--password=" + adminpass,
1528             ldapuser,
1529             "--realm=" + names.dnsdomain,
1530             "--domain=" + names.domain,
1531             "--server-role='" + serverrole + "'"]
1532     message("Run provision with: " + " ".join(args))
1533
1534
1535     # if --ol-olc=yes, generate online-configuration in ../private/ldap/slapd.d 
1536     if ol_olc == "yes":
1537           if not os.path.isdir(paths.olcdir):
1538              os.makedirs(paths.olcdir, 0770)
1539           paths.olslaptest = str(ol_slaptest)
1540           olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" +  paths.olcdir + " >/dev/null 2>&1"
1541           os.system(olc_command)
1542           os.remove(paths.slapdconf)        
1543           # use line below for debugging during olc-conversion with slaptest, instead of olc_command above 
1544           #olc_command = paths.olslaptest + " -f" + paths.slapdconf + " -F" +  paths.olcdir"
1545
1546
1547 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1548     """Create a PHP LDAP admin configuration file.
1549
1550     :param path: Path to write the configuration to.
1551     :param setup_path: Function to generate setup paths.
1552     """
1553     setup_file(setup_path("phpldapadmin-config.php"), path, 
1554             {"S4_LDAPI_URI": ldapi_uri})
1555
1556
1557 def create_zone_file(path, setup_path, dnsdomain, domaindn, 
1558                      hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1559     """Write out a DNS zone file, from the info in the current database.
1560
1561     :param path: Path of the new zone file.
1562     :param setup_path: Setup path function.
1563     :param dnsdomain: DNS Domain name
1564     :param domaindn: DN of the Domain
1565     :param hostip: Local IPv4 IP
1566     :param hostip6: Local IPv6 IP
1567     :param hostname: Local hostname
1568     :param dnspass: Password for DNS
1569     :param realm: Realm name
1570     :param domainguid: GUID of the domain.
1571     :param hostguid: GUID of the host.
1572     """
1573     assert isinstance(domainguid, str)
1574
1575     if hostip6 is not None:
1576         hostip6_base_line = "            IN AAAA    " + hostip6
1577         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1578     else:
1579         hostip6_base_line = ""
1580         hostip6_host_line = ""
1581
1582     if hostip is not None:
1583         hostip_base_line = "            IN A    " + hostip
1584         hostip_host_line = hostname + "        IN A    " + hostip
1585     else:
1586         hostip_base_line = ""
1587         hostip_host_line = ""
1588
1589     setup_file(setup_path("provision.zone"), path, {
1590             "DNSPASS_B64": b64encode(dnspass),
1591             "HOSTNAME": hostname,
1592             "DNSDOMAIN": dnsdomain,
1593             "REALM": realm,
1594             "HOSTIP_BASE_LINE": hostip_base_line,
1595             "HOSTIP_HOST_LINE": hostip_host_line,
1596             "DOMAINGUID": domainguid,
1597             "DATESTRING": time.strftime("%Y%m%d%H"),
1598             "DEFAULTSITE": DEFAULTSITE,
1599             "HOSTGUID": hostguid,
1600             "HOSTIP6_BASE_LINE": hostip6_base_line,
1601             "HOSTIP6_HOST_LINE": hostip6_host_line,
1602         })
1603
1604
1605 def create_named_conf(path, setup_path, realm, dnsdomain,
1606                       private_dir):
1607     """Write out a file containing zone statements suitable for inclusion in a
1608     named.conf file (including GSS-TSIG configuration).
1609     
1610     :param path: Path of the new named.conf file.
1611     :param setup_path: Setup path function.
1612     :param realm: Realm name
1613     :param dnsdomain: DNS Domain name
1614     :param private_dir: Path to private directory
1615     :param keytab_name: File name of DNS keytab file
1616     """
1617
1618     setup_file(setup_path("named.conf"), path, {
1619             "DNSDOMAIN": dnsdomain,
1620             "REALM": realm,
1621             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1622             "PRIVATE_DIR": private_dir
1623             })
1624
1625 def create_named_txt(path, setup_path, realm, dnsdomain,
1626                       private_dir, keytab_name):
1627     """Write out a file containing zone statements suitable for inclusion in a
1628     named.conf file (including GSS-TSIG configuration).
1629     
1630     :param path: Path of the new named.conf file.
1631     :param setup_path: Setup path function.
1632     :param realm: Realm name
1633     :param dnsdomain: DNS Domain name
1634     :param private_dir: Path to private directory
1635     :param keytab_name: File name of DNS keytab file
1636     """
1637
1638     setup_file(setup_path("named.txt"), path, {
1639             "DNSDOMAIN": dnsdomain,
1640             "REALM": realm,
1641             "DNS_KEYTAB": keytab_name,
1642             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1643             "PRIVATE_DIR": private_dir
1644         })
1645
1646 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1647     """Write out a file containing zone statements suitable for inclusion in a
1648     named.conf file (including GSS-TSIG configuration).
1649     
1650     :param path: Path of the new named.conf file.
1651     :param setup_path: Setup path function.
1652     :param dnsdomain: DNS Domain name
1653     :param hostname: Local hostname
1654     :param realm: Realm name
1655     """
1656
1657     setup_file(setup_path("krb5.conf"), path, {
1658             "DNSDOMAIN": dnsdomain,
1659             "HOSTNAME": hostname,
1660             "REALM": realm,
1661         })
1662
1663
1664 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename,
1665                 serverdn):
1666     """Load schema for the SamDB.
1667     
1668     :param samdb: Load a schema into a SamDB.
1669     :param setup_path: Setup path function.
1670     :param schemadn: DN of the schema
1671     :param netbiosname: NetBIOS name of the host.
1672     :param configdn: DN of the configuration
1673     :param serverdn: DN of the server
1674
1675     Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
1676     """
1677     schema_data = get_schema_data(setup_path, {"SCHEMADN": schemadn})
1678     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1679     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1680     check_all_substituted(schema_data)
1681     prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
1682     prefixmap = b64encode(prefixmap)
1683
1684     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1685     head_data = substitute_var(head_data, {
1686                     "SCHEMADN": schemadn,
1687                     "NETBIOSNAME": netbiosname,
1688                     "CONFIGDN": configdn,
1689                     "DEFAULTSITE": sitename,
1690                     "PREFIXMAP_B64": prefixmap,
1691                     "SERVERDN": serverdn,
1692     })
1693     check_all_substituted(head_data)
1694     samdb.attach_schema_from_ldif(head_data, schema_data)
1695     return schema_data;
1696
1697 def get_schema_data(setup_path, subst_vars = None):
1698     """Get schema data from the AD schema files instead of schema.ldif.
1699
1700     :param setup_path: Setup path function.
1701     :param subst_vars: Optional variables to substitute in the file.
1702
1703     Returns the schema data after substitution
1704     """ 
1705
1706     # this data used to be read from schema.ldif
1707     
1708     data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
1709                           setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
1710
1711     if subst_vars is not None:
1712         data = substitute_var(data, subst_vars)
1713     check_all_substituted(data)
1714     return data