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