s4:provision Split ProvisionBackend out of the main provision script
[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-2009
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 import subprocess
40 import ldb
41
42 import shutil
43
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name, setup_file
46 from samba import check_all_substituted, read_and_sub_file
47 from samba import DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008
48 from samba.samdb import SamDB
49 from samba.idmap import IDmapDB
50 from samba.dcerpc import security
51 from samba.ndr import ndr_pack
52 import urllib
53 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError
54 from ms_display_specifiers import read_ms_ldif
55 from schema import Schema
56 from provisionbackend import ProvisionBackend
57 from signal import SIGTERM
58 from dcerpc.misc import SEC_CHAN_BDC, SEC_CHAN_WKSTA
59
60 __docformat__ = "restructuredText"
61
62 def find_setup_dir():
63     """Find the setup directory used by provision."""
64     dirname = os.path.dirname(__file__)
65     if "/site-packages/" in dirname:
66         prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
67         for suffix in ["share/setup", "share/samba/setup", "setup"]:
68             ret = os.path.join(prefix, suffix)
69             if os.path.isdir(ret):
70                 return ret
71     # In source tree
72     ret = os.path.join(dirname, "../../../setup")
73     if os.path.isdir(ret):
74         return ret
75     raise Exception("Unable to find setup directory.")
76
77 def get_config_descriptor(domain_sid):
78     sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
79            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
80            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
81            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
82            "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
83            "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
84            "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
85            "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
86            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
87            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
88            "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
89            "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
90            "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;S-1-5-21-3191434175-1265308384-3577286990-498)" \
91            "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
92            "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
93     sec = security.descriptor.from_sddl(sddl, domain_sid)
94     return b64encode(ndr_pack(sec))
95
96
97 DEFAULTSITE = "Default-First-Site-Name"
98
99 # Exception classes
100
101 class ProvisioningError(Exception):
102     """A generic provision error."""
103
104 class InvalidNetbiosName(Exception):
105     """A specified name was not a valid NetBIOS name."""
106     def __init__(self, name):
107         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
108
109
110 class ProvisionPaths(object):
111     def __init__(self):
112         self.shareconf = None
113         self.hklm = None
114         self.hkcu = None
115         self.hkcr = None
116         self.hku = None
117         self.hkpd = None
118         self.hkpt = None
119         self.samdb = None
120         self.idmapdb = None
121         self.secrets = None
122         self.keytab = None
123         self.dns_keytab = None
124         self.dns = None
125         self.winsdb = None
126         self.private_dir = None
127         self.ldapdir = None
128         self.slapdconf = None
129         self.modulesconf = None
130         self.memberofconf = None
131         self.fedoradsinf = None
132         self.fedoradspartitions = None
133         self.fedoradssasl = None
134         self.fedoradsdna = None
135         self.fedoradspam = None
136         self.fedoradsrefint = None
137         self.fedoradslinkedattributes = None
138         self.fedoradsindex = None
139         self.fedoradssamba = None
140         self.olmmron = None
141         self.olmmrserveridsconf = None
142         self.olmmrsyncreplconf = None
143         self.olcdir = None
144         self.olslapd = None
145         self.olcseedldif = None
146
147
148 class ProvisionNames(object):
149     def __init__(self):
150         self.rootdn = None
151         self.domaindn = None
152         self.configdn = None
153         self.schemadn = None
154         self.sambadn = None
155         self.ldapmanagerdn = None
156         self.dnsdomain = None
157         self.realm = None
158         self.netbiosname = None
159         self.domain = None
160         self.hostname = None
161         self.sitename = None
162         self.smbconf = None
163     
164
165 class ProvisionResult(object):
166     def __init__(self):
167         self.paths = None
168         self.domaindn = None
169         self.lp = None
170         self.samdb = None
171
172 def check_install(lp, session_info, credentials):
173     """Check whether the current install seems ok.
174     
175     :param lp: Loadparm context
176     :param session_info: Session information
177     :param credentials: Credentials
178     """
179     if lp.get("realm") == "":
180         raise Exception("Realm empty")
181     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
182             credentials=credentials, lp=lp)
183     if len(ldb.search("(cn=Administrator)")) != 1:
184         raise ProvisioningError("No administrator account found")
185
186
187 def findnss(nssfn, names):
188     """Find a user or group from a list of possibilities.
189     
190     :param nssfn: NSS Function to try (should raise KeyError if not found)
191     :param names: Names to check.
192     :return: Value return by first names list.
193     """
194     for name in names:
195         try:
196             return nssfn(name)
197         except KeyError:
198             pass
199     raise KeyError("Unable to find user/group %r" % names)
200
201
202 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
203 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
204
205
206 def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
207     """Setup a ldb in the private dir.
208     
209     :param ldb: LDB file to import data into
210     :param ldif_path: Path of the LDIF file to load
211     :param subst_vars: Optional variables to subsitute in LDIF.
212     :param nocontrols: Optional list of controls, can be None for no controls
213     """
214     assert isinstance(ldif_path, str)
215     data = read_and_sub_file(ldif_path, subst_vars)
216     ldb.add_ldif(data,controls)
217
218
219 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
220     """Modify a ldb in the private dir.
221     
222     :param ldb: LDB object.
223     :param ldif_path: LDIF file path.
224     :param subst_vars: Optional dictionary with substitution variables.
225     """
226     data = read_and_sub_file(ldif_path, subst_vars)
227
228     ldb.modify_ldif(data)
229
230
231 def setup_ldb(ldb, ldif_path, subst_vars):
232     """Import a LDIF a file into a LDB handle, optionally substituting variables.
233
234     :note: Either all LDIF data will be added or none (using transactions).
235
236     :param ldb: LDB file to import into.
237     :param ldif_path: Path to the LDIF file.
238     :param subst_vars: Dictionary with substitution variables.
239     """
240     assert ldb is not None
241     ldb.transaction_start()
242     try:
243         setup_add_ldif(ldb, ldif_path, subst_vars)
244     except:
245         ldb.transaction_cancel()
246         raise
247     ldb.transaction_commit()
248
249
250 def provision_paths_from_lp(lp, dnsdomain):
251     """Set the default paths for provisioning.
252
253     :param lp: Loadparm context.
254     :param dnsdomain: DNS Domain name
255     """
256     paths = ProvisionPaths()
257     paths.private_dir = lp.get("private dir")
258     paths.dns_keytab = "dns.keytab"
259
260     paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
261     paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
262     paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
263     paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
264     paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
265     paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
266     paths.namedconf = os.path.join(paths.private_dir, "named.conf")
267     paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
268     paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
269     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
270     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
271     paths.phpldapadminconfig = os.path.join(paths.private_dir, 
272                                             "phpldapadmin-config.php")
273     paths.ldapdir = os.path.join(paths.private_dir, 
274                                  "ldap")
275     paths.slapdconf = os.path.join(paths.ldapdir, 
276                                    "slapd.conf")
277     paths.slapdpid = os.path.join(paths.ldapdir, 
278                                    "slapd.pid")
279     paths.modulesconf = os.path.join(paths.ldapdir, 
280                                      "modules.conf")
281     paths.memberofconf = os.path.join(paths.ldapdir, 
282                                       "memberof.conf")
283     paths.fedoradsinf = os.path.join(paths.ldapdir, 
284                                      "fedorads.inf")
285     paths.fedoradspartitions = os.path.join(paths.ldapdir, 
286                                             "fedorads-partitions.ldif")
287     paths.fedoradssasl = os.path.join(paths.ldapdir, 
288                                       "fedorads-sasl.ldif")
289     paths.fedoradsdna = os.path.join(paths.ldapdir, 
290                                      "fedorads-dna.ldif")
291     paths.fedoradspam = os.path.join(paths.ldapdir,
292                                      "fedorads-pam.ldif")
293     paths.fedoradsrefint = os.path.join(paths.ldapdir,
294                                         "fedorads-refint.ldif")
295     paths.fedoradslinkedattributes = os.path.join(paths.ldapdir,
296                                                   "fedorads-linked-attributes.ldif")
297     paths.fedoradsindex = os.path.join(paths.ldapdir,
298                                        "fedorads-index.ldif")
299     paths.fedoradssamba = os.path.join(paths.ldapdir, 
300                                        "fedorads-samba.ldif")
301     paths.olmmrserveridsconf = os.path.join(paths.ldapdir, 
302                                             "mmr_serverids.conf")
303     paths.olmmrsyncreplconf = os.path.join(paths.ldapdir, 
304                                            "mmr_syncrepl.conf")
305     paths.olcdir = os.path.join(paths.ldapdir, 
306                                  "slapd.d")
307     paths.olcseedldif = os.path.join(paths.ldapdir, 
308                                  "olc_seed.ldif")
309     paths.hklm = "hklm.ldb"
310     paths.hkcr = "hkcr.ldb"
311     paths.hkcu = "hkcu.ldb"
312     paths.hku = "hku.ldb"
313     paths.hkpd = "hkpd.ldb"
314     paths.hkpt = "hkpt.ldb"
315
316     paths.sysvol = lp.get("path", "sysvol")
317
318     paths.netlogon = lp.get("path", "netlogon")
319
320     paths.smbconf = lp.configfile
321
322     return paths
323
324
325 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
326                 serverrole=None, rootdn=None, domaindn=None, configdn=None,
327                 schemadn=None, serverdn=None, sitename=None, sambadn=None):
328     """Guess configuration settings to use."""
329
330     if hostname is None:
331         hostname = socket.gethostname().split(".")[0]
332
333     netbiosname = lp.get("netbios name")
334     if netbiosname is None:
335         netbiosname = hostname
336     assert netbiosname is not None
337     netbiosname = netbiosname.upper()
338     if not valid_netbios_name(netbiosname):
339         raise InvalidNetbiosName(netbiosname)
340
341     if dnsdomain is None:
342         dnsdomain = lp.get("realm")
343     assert dnsdomain is not None
344     dnsdomain = dnsdomain.lower()
345
346     if serverrole is None:
347         serverrole = lp.get("server role")
348     assert serverrole is not None
349     serverrole = serverrole.lower()
350
351     realm = dnsdomain.upper()
352
353     if lp.get("realm").upper() != realm:
354         raise ProvisioningError("guess_names: Realm '%s' in smb.conf must match chosen realm '%s'!", lp.get("realm").upper(), realm)
355
356     if serverrole == "domain controller":
357         if domain is None:
358             domain = lp.get("workgroup")
359         assert domain is not None
360         domain = domain.upper()
361
362         if lp.get("workgroup").upper() != domain:
363             raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'!", lp.get("workgroup").upper(), domain)
364
365         if domaindn is None:
366             domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
367     else:
368         domain = netbiosname
369         if domaindn is None:
370             domaindn = "DC=" + netbiosname
371         
372     if not valid_netbios_name(domain):
373         raise InvalidNetbiosName(domain)
374         
375     if hostname.upper() == realm:
376         raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!", realm, hostname)
377     if netbiosname == realm:
378         raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!", realm, netbiosname)
379     if domain == realm:
380         raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!", realm, domain)
381
382     if rootdn is None:
383        rootdn = domaindn
384        
385     if configdn is None:
386         configdn = "CN=Configuration," + rootdn
387     if schemadn is None:
388         schemadn = "CN=Schema," + configdn
389     if sambadn is None:
390         sambadn = "CN=Samba"
391
392     if sitename is None:
393         sitename=DEFAULTSITE
394
395     names = ProvisionNames()
396     names.rootdn = rootdn
397     names.domaindn = domaindn
398     names.configdn = configdn
399     names.schemadn = schemadn
400     names.sambadn = sambadn
401     names.ldapmanagerdn = "CN=Manager," + rootdn
402     names.dnsdomain = dnsdomain
403     names.domain = domain
404     names.realm = realm
405     names.netbiosname = netbiosname
406     names.hostname = hostname
407     names.sitename = sitename
408     names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
409  
410     return names
411     
412
413 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
414                  targetdir, sid_generator):
415     """Create a new smb.conf file based on a couple of basic settings.
416     """
417     assert smbconf is not None
418     if hostname is None:
419         hostname = socket.gethostname().split(".")[0]
420     netbiosname = hostname.upper()
421
422     if serverrole is None:
423         serverrole = "standalone"
424
425     assert serverrole in ("domain controller", "member server", "standalone")
426     if serverrole == "domain controller":
427         smbconfsuffix = "dc"
428     elif serverrole == "member server":
429         smbconfsuffix = "member"
430     elif serverrole == "standalone":
431         smbconfsuffix = "standalone"
432
433     if sid_generator is None:
434         sid_generator = "internal"
435
436     assert domain is not None
437     domain = domain.upper()
438
439     assert realm is not None
440     realm = realm.upper()
441
442     default_lp = param.LoadParm()
443     #Load non-existant file
444     if os.path.exists(smbconf):
445         default_lp.load(smbconf)
446     
447     if targetdir is not None:
448         privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
449         lockdir_line = "lock dir = " + os.path.abspath(targetdir)
450
451         default_lp.set("lock dir", os.path.abspath(targetdir))
452     else:
453         privatedir_line = ""
454         lockdir_line = ""
455
456     if sid_generator == "internal":
457         sid_generator_line = ""
458     else:
459         sid_generator_line = "sid generator = " + sid_generator
460
461     sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
462     netlogon = os.path.join(sysvol, realm.lower(), "scripts")
463
464     setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
465                smbconf, {
466             "NETBIOS_NAME": netbiosname,
467             "DOMAIN": domain,
468             "REALM": realm,
469             "SERVERROLE": serverrole,
470             "NETLOGONPATH": netlogon,
471             "SYSVOLPATH": sysvol,
472             "SIDGENERATOR_LINE": sid_generator_line,
473             "PRIVATEDIR_LINE": privatedir_line,
474             "LOCKDIR_LINE": lockdir_line
475             })
476
477
478 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
479                         users_gid, wheel_gid):
480     """setup reasonable name mappings for sam names to unix names.
481
482     :param samdb: SamDB object.
483     :param idmap: IDmap db object.
484     :param sid: The domain sid.
485     :param domaindn: The domain DN.
486     :param root_uid: uid of the UNIX root user.
487     :param nobody_uid: uid of the UNIX nobody user.
488     :param users_gid: gid of the UNIX users group.
489     :param wheel_gid: gid of the UNIX wheel group."""
490
491     idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
492     idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
493     
494     idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
495     idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
496
497 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
498                            provision_backend, names, schema,
499                            serverrole, 
500                            erase=False):
501     """Setup the partitions for the SAM database. 
502     
503     Alternatively, provision() may call this, and then populate the database.
504     
505     :note: This will wipe the Sam Database!
506     
507     :note: This function always removes the local SAM LDB file. The erase 
508         parameter controls whether to erase the existing data, which 
509         may not be stored locally but in LDAP.
510
511     """
512     assert session_info is not None
513
514     old_partitions = None
515     new_partitions = None
516
517     # We use options=["modules:"] to stop the modules loading - we
518     # just want to wipe and re-initialise the database, not start it up
519
520     try:
521         samdb = Ldb(url=samdb_path, session_info=session_info, 
522                       lp=lp, options=["modules:"])
523         res = samdb.search(base="@PARTITION", scope=SCOPE_BASE, attrs=["partition"], expression="partition=*")
524         if len(res) == 1:
525             try:
526                 old_partitions = res[0]["partition"]
527             except KeyError:
528                 pass
529
530         if old_partitions is not None:
531             new_partitions = [];
532             for old_partition in old_partitions:
533                 new_partition = old_partition
534                 if old_partition.endswith(".ldb"):
535                     p = old_partition.split(":")[0]
536                     dn = ldb.Dn(schema.ldb, p)
537                     new_partition = dn.get_casefold()
538                 new_partitions.append(new_partition)
539
540         # Wipes the database
541         samdb.erase_except_schema_controlled()
542     except LdbError:
543         os.unlink(samdb_path)
544         samdb = Ldb(url=samdb_path, session_info=session_info, 
545                       lp=lp, options=["modules:"])
546          # Wipes the database
547         samdb.erase_except_schema_controlled()
548
549     #Add modules to the list to activate them by default
550     #beware often order is important
551     #
552     # Some Known ordering constraints:
553     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
554     # - objectclass must be before password_hash, because password_hash checks
555     #   that the objectclass is of type person (filled in by objectclass
556     #   module when expanding the objectclass list)
557     # - partition must be last
558     # - each partition has its own module list then
559     modules_list = ["resolve_oids",
560                     "rootdse",
561                     "lazy_commit",
562                     "acl",
563                     "paged_results",
564                     "ranged_results",
565                     "anr",
566                     "server_sort",
567                     "asq",
568                     "extended_dn_store",
569                     "extended_dn_in",
570                     "rdn_name",
571                     "objectclass",
572                     "descriptor",
573                     "samldb",
574                     "password_hash",
575                     "operational",
576                     "kludge_acl", 
577                     "instancetype"]
578     tdb_modules_list = [
579                     "subtree_rename",
580                     "subtree_delete",
581                     "linked_attributes",
582                     "extended_dn_out_ldb"]
583     modules_list2 = ["show_deleted",
584                      "schema_load",
585                      "new_partition",
586                      "partition"]
587
588     ldap_backend_line = "# No LDAP backend"
589     if provision_backend.type is not "ldb":
590         ldap_backend_line = "ldapBackend: %s" % provision_backend.ldapi_uri
591         
592         if provision_backend.ldap_backend_type == "fedora-ds":
593             backend_modules = ["nsuniqueid", "paged_searches"]
594             # We can handle linked attributes here, as we don't have directory-side subtree operations
595             tdb_modules_list = ["extended_dn_out_fds"]
596         elif ldap_backend.ldap_backend_type == "openldap":
597             backend_modules = ["entryuuid", "paged_searches"]
598             # OpenLDAP handles subtree renames, so we don't want to do any of these things
599             tdb_modules_list = ["extended_dn_out_openldap"]
600
601     elif serverrole == "domain controller":
602         tdb_modules_list.insert(0, "repl_meta_data")
603         backend_modules = []
604     else:
605         backend_modules = ["objectguid"]
606
607     if tdb_modules_list is None:
608         tdb_modules_list_as_string = ""
609     else:
610         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
611         
612     samdb.transaction_start()
613     try:
614         message("Setting up sam.ldb partitions and settings")
615         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
616                 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), 
617                 "SCHEMADN_MOD2": ",objectguid",
618                 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
619                 "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
620                 "SCHEMADN_MOD": "schema_data",
621                 "CONFIGDN_MOD": "naming_fsmo",
622                 "DOMAINDN_MOD": "pdc_fsmo",
623                 "MODULES_LIST": ",".join(modules_list),
624                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
625                 "MODULES_LIST2": ",".join(modules_list2),
626                 "BACKEND_MOD": ",".join(backend_modules),
627                 "LDAP_BACKEND_LINE": ldap_backend_line,
628         })
629
630         
631         if new_partitions is not None:
632             m = ldb.Message()
633             m.dn = ldb.Dn(samdb, "@PARTITION")
634             
635             m["partition"] = ldb.MessageElement(new_partitions, ldb.FLAG_MOD_ADD, "partition")
636             samdb.modify(m)
637
638         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
639
640         message("Setting up sam.ldb rootDSE")
641         setup_samdb_rootdse(samdb, setup_path, names)
642
643     except:
644         samdb.transaction_cancel()
645         raise
646
647     samdb.transaction_commit()
648
649         
650 def secretsdb_self_join(secretsdb, domain, 
651                         netbiosname, domainsid, machinepass, 
652                         realm=None, dnsdomain=None,
653                         keytab_path=None, 
654                         key_version_number=1,
655                         secure_channel_type=SEC_CHAN_WKSTA):
656     """Add domain join-specific bits to a secrets database.
657     
658     :param secretsdb: Ldb Handle to the secrets database
659     :param machinepass: Machine password
660     """
661     attrs=["whenChanged",
662            "secret",
663            "priorSecret",
664            "priorChanged",
665            "krb5Keytab",
666            "privateKeytab"]
667     
668
669     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
670     msg["secureChannelType"] = str(secure_channel_type)
671     msg["flatname"] = [domain]
672     msg["objectClass"] = ["top", "primaryDomain"]
673     if realm is not None:
674       if dnsdomain is None:
675         dnsdomain = realm.lower()
676       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
677       msg["realm"] = realm
678       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
679       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
680       msg["privateKeytab"] = ["secrets.keytab"];
681
682
683     msg["secret"] = [machinepass]
684     msg["samAccountName"] = ["%s$" % netbiosname]
685     msg["secureChannelType"] = [str(secure_channel_type)]
686     msg["objectSid"] = [ndr_pack(domainsid)]
687     
688     res = secretsdb.search(base="cn=Primary Domains", 
689                            attrs=attrs, 
690                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
691                            scope=SCOPE_ONELEVEL)
692     
693     for del_msg in res:
694       if del_msg.dn is not msg.dn:
695         secretsdb.delete(del_msg.dn)
696
697     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
698
699     if len(res) == 1:
700       msg["priorSecret"] = res[0]["secret"]
701       msg["priorWhenChanged"] = res[0]["whenChanged"]
702
703       if res["privateKeytab"] is not None:
704         msg["privateKeytab"] = res[0]["privateKeytab"]
705
706       if res["krb5Keytab"] is not None:
707         msg["krb5Keytab"] = res[0]["krb5Keytab"]
708
709       for el in msg:
710         el.set_flags(ldb.FLAG_MOD_REPLACE)
711         secretsdb.modify(msg)
712     else:
713       secretsdb.add(msg)
714
715
716 def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain, 
717                         dns_keytab_path, dnspass):
718     """Add DNS specific bits to a secrets database.
719     
720     :param secretsdb: Ldb Handle to the secrets database
721     :param setup_path: Setup path function
722     :param machinepass: Machine password
723     """
724     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
725             "REALM": realm,
726             "DNSDOMAIN": dnsdomain,
727             "DNS_KEYTAB": dns_keytab_path,
728             "DNSPASS_B64": b64encode(dnspass),
729             })
730
731
732 def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
733     """Setup the secrets database.
734
735     :param path: Path to the secrets database.
736     :param setup_path: Get the path to a setup file.
737     :param session_info: Session info.
738     :param credentials: Credentials
739     :param lp: Loadparm context
740     :return: LDB handle for the created secrets database
741     """
742     if os.path.exists(path):
743         os.unlink(path)
744     secrets_ldb = Ldb(path, session_info=session_info, 
745                       lp=lp)
746     secrets_ldb.erase()
747     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
748     secrets_ldb = Ldb(path, session_info=session_info, 
749                       lp=lp)
750     secrets_ldb.transaction_start()
751     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
752
753     if backend_credentials is not None and backend_credentials.authentication_requested():
754         if backend_credentials.get_bind_dn() is not None:
755             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
756                     "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
757                     "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
758                     })
759         else:
760             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
761                     "LDAPADMINUSER": backend_credentials.get_username(),
762                     "LDAPADMINREALM": backend_credentials.get_realm(),
763                     "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
764                     })
765
766     return secrets_ldb
767
768 def setup_privileges(path, setup_path, session_info, lp):
769     """Setup the privileges database.
770
771     :param path: Path to the privileges database.
772     :param setup_path: Get the path to a setup file.
773     :param session_info: Session info.
774     :param credentials: Credentials
775     :param lp: Loadparm context
776     :return: LDB handle for the created secrets database
777     """
778     if os.path.exists(path):
779         os.unlink(path)
780     privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
781     privilege_ldb.erase()
782     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
783
784
785 def setup_registry(path, setup_path, session_info, lp):
786     """Setup the registry.
787     
788     :param path: Path to the registry database
789     :param setup_path: Function that returns the path to a setup.
790     :param session_info: Session information
791     :param credentials: Credentials
792     :param lp: Loadparm context
793     """
794     reg = registry.Registry()
795     hive = registry.open_ldb(path, session_info=session_info, 
796                          lp_ctx=lp)
797     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
798     provision_reg = setup_path("provision.reg")
799     assert os.path.exists(provision_reg)
800     reg.diff_apply(provision_reg)
801
802
803 def setup_idmapdb(path, setup_path, session_info, lp):
804     """Setup the idmap database.
805
806     :param path: path to the idmap database
807     :param setup_path: Function that returns a path to a setup file
808     :param session_info: Session information
809     :param credentials: Credentials
810     :param lp: Loadparm context
811     """
812     if os.path.exists(path):
813         os.unlink(path)
814
815     idmap_ldb = IDmapDB(path, session_info=session_info,
816                         lp=lp)
817
818     idmap_ldb.erase()
819     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
820     return idmap_ldb
821
822
823 def setup_samdb_rootdse(samdb, setup_path, names):
824     """Setup the SamDB rootdse.
825
826     :param samdb: Sam Database handle
827     :param setup_path: Obtain setup path
828     """
829     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
830         "SCHEMADN": names.schemadn, 
831         "NETBIOSNAME": names.netbiosname,
832         "DNSDOMAIN": names.dnsdomain,
833         "REALM": names.realm,
834         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
835         "DOMAINDN": names.domaindn,
836         "ROOTDN": names.rootdn,
837         "CONFIGDN": names.configdn,
838         "SERVERDN": names.serverdn,
839         })
840         
841
842 def setup_self_join(samdb, names,
843                     machinepass, dnspass, 
844                     domainsid, invocationid, setup_path,
845                     policyguid, policyguid_dc, domainControllerFunctionality,
846                     ntdsguid):
847     """Join a host to its own domain."""
848     assert isinstance(invocationid, str)
849     if ntdsguid is not None:
850         ntdsguid_line = "objectGUID: %s\n"%ntdsguid
851     else:
852         ntdsguid_line = ""
853     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
854               "CONFIGDN": names.configdn, 
855               "SCHEMADN": names.schemadn,
856               "DOMAINDN": names.domaindn,
857               "SERVERDN": names.serverdn,
858               "INVOCATIONID": invocationid,
859               "NETBIOSNAME": names.netbiosname,
860               "DEFAULTSITE": names.sitename,
861               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
862               "MACHINEPASS_B64": b64encode(machinepass),
863               "DNSPASS_B64": b64encode(dnspass),
864               "REALM": names.realm,
865               "DOMAIN": names.domain,
866               "DNSDOMAIN": names.dnsdomain,
867               "SAMBA_VERSION_STRING": version,
868               "NTDSGUID": ntdsguid_line,
869               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
870
871     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
872               "POLICYGUID": policyguid,
873               "POLICYGUID_DC": policyguid_dc,
874               "DNSDOMAIN": names.dnsdomain,
875               "DOMAINSID": str(domainsid),
876               "DOMAINDN": names.domaindn})
877     
878     # add the NTDSGUID based SPNs
879     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
880     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
881                                      expression="", scope=SCOPE_BASE)
882     assert isinstance(names.ntdsguid, str)
883
884     # Setup fSMORoleOwner entries to point at the newly created DC entry
885     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
886               "DOMAIN": names.domain,
887               "DNSDOMAIN": names.dnsdomain,
888               "DOMAINDN": names.domaindn,
889               "CONFIGDN": names.configdn,
890               "SCHEMADN": names.schemadn, 
891               "DEFAULTSITE": names.sitename,
892               "SERVERDN": names.serverdn,
893               "NETBIOSNAME": names.netbiosname,
894               "NTDSGUID": names.ntdsguid
895               })
896
897
898 def setup_samdb(path, setup_path, session_info, provision_backend, lp, 
899                 names, message, 
900                 domainsid, domainguid, policyguid, policyguid_dc,
901                 fill, adminpass, krbtgtpass, 
902                 machinepass, invocationid, dnspass, ntdsguid,
903                 serverrole, dom_for_fun_level=None,
904                 schema=None):
905     """Setup a complete SAM Database.
906     
907     :note: This will wipe the main SAM database file!
908     """
909
910     # ATTENTION: Do NOT change these default values without discussion with the
911     # team and/or release manager. They have a big impact on the whole program!
912     domainControllerFunctionality = DS_DC_FUNCTION_2008
913
914     if dom_for_fun_level is None:
915         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
916     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
917         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
918
919     if dom_for_fun_level > domainControllerFunctionality:
920         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008). This won't work!")
921
922     domainFunctionality = dom_for_fun_level
923     forestFunctionality = dom_for_fun_level
924
925     # Also wipes the database
926     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
927                            provision_backend=provision_backend, session_info=session_info,
928                            names=names, 
929                            serverrole=serverrole, schema=schema)
930
931     if (schema == None):
932         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
933                         sambadn=names.sambadn)
934
935     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
936     samdb = Ldb(session_info=session_info, 
937                 credentials=provision_backend.credentials, lp=lp)
938
939     message("Pre-loading the Samba 4 and AD schema")
940
941     # Load the schema from the one we computed earlier
942     samdb.set_schema_from_ldb(schema.ldb)
943
944     # And now we can connect to the DB - the schema won't be loaded from the DB
945     samdb.connect(path)
946
947     if fill == FILL_DRS:
948         return samdb
949         
950     samdb.transaction_start()
951     try:
952         message("Erasing data from partitions")
953         # Load the schema (again).  This time it will force a reindex,
954         # and will therefore make the erase_partitions() below
955         # computationally sane
956         samdb.set_schema_from_ldb(schema.ldb)
957         samdb.erase_partitions()
958     
959         # Set the domain functionality levels onto the database.
960         # Various module (the password_hash module in particular) need
961         # to know what level of AD we are emulating.
962
963         # These will be fixed into the database via the database
964         # modifictions below, but we need them set from the start.
965         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
966         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
967         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
968
969         samdb.set_domain_sid(str(domainsid))
970         if serverrole == "domain controller":
971             samdb.set_invocation_id(invocationid)
972
973         message("Adding DomainDN: %s" % names.domaindn)
974
975 #impersonate domain admin
976         admin_session_info = admin_session(lp, str(domainsid))
977         samdb.set_session_info(admin_session_info)
978         if domainguid is not None:
979             domainguid_line = "objectGUID: %s\n-" % domainguid
980         else:
981             domainguid_line = ""
982         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
983                 "DOMAINDN": names.domaindn,
984                 "DOMAINGUID": domainguid_line
985                 })
986
987
988         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
989             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
990             "DOMAINSID": str(domainsid),
991             "SCHEMADN": names.schemadn, 
992             "NETBIOSNAME": names.netbiosname,
993             "DEFAULTSITE": names.sitename,
994             "CONFIGDN": names.configdn,
995             "SERVERDN": names.serverdn,
996             "POLICYGUID": policyguid,
997             "DOMAINDN": names.domaindn,
998             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
999             "SAMBA_VERSION_STRING": version
1000             })
1001
1002         message("Adding configuration container")
1003         descr = get_config_descriptor(domainsid);
1004         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1005             "CONFIGDN": names.configdn, 
1006             "DESCRIPTOR": descr,
1007             })
1008         message("Modifying configuration container")
1009         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1010             "CONFIGDN": names.configdn, 
1011             "SCHEMADN": names.schemadn,
1012             })
1013
1014         # The LDIF here was created when the Schema object was constructed
1015         message("Setting up sam.ldb schema")
1016         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1017         samdb.modify_ldif(schema.schema_dn_modify)
1018         samdb.write_prefixes_from_schema()
1019         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1020         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1021                        {"SCHEMADN": names.schemadn})
1022
1023         message("Setting up sam.ldb configuration data")
1024         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1025             "CONFIGDN": names.configdn,
1026             "NETBIOSNAME": names.netbiosname,
1027             "DEFAULTSITE": names.sitename,
1028             "DNSDOMAIN": names.dnsdomain,
1029             "DOMAIN": names.domain,
1030             "SCHEMADN": names.schemadn,
1031             "DOMAINDN": names.domaindn,
1032             "SERVERDN": names.serverdn,
1033             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1034             })
1035
1036         message("Setting up display specifiers")
1037         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1038         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1039         check_all_substituted(display_specifiers_ldif)
1040         samdb.add_ldif(display_specifiers_ldif)
1041
1042         message("Adding users container")
1043         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1044                 "DOMAINDN": names.domaindn})
1045         message("Modifying users container")
1046         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1047                 "DOMAINDN": names.domaindn})
1048         message("Adding computers container")
1049         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1050                 "DOMAINDN": names.domaindn})
1051         message("Modifying computers container")
1052         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1053                 "DOMAINDN": names.domaindn})
1054         message("Setting up sam.ldb data")
1055         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1056             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1057             "DOMAINDN": names.domaindn,
1058             "NETBIOSNAME": names.netbiosname,
1059             "DEFAULTSITE": names.sitename,
1060             "CONFIGDN": names.configdn,
1061             "SERVERDN": names.serverdn,
1062             "POLICYGUID_DC": policyguid_dc
1063             })
1064
1065         if fill == FILL_FULL:
1066             message("Setting up sam.ldb users and groups")
1067             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1068                 "DOMAINDN": names.domaindn,
1069                 "DOMAINSID": str(domainsid),
1070                 "CONFIGDN": names.configdn,
1071                 "ADMINPASS_B64": b64encode(adminpass),
1072                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1073                 })
1074
1075             if serverrole == "domain controller":
1076                 message("Setting up self join")
1077                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1078                                 dnspass=dnspass,  
1079                                 machinepass=machinepass, 
1080                                 domainsid=domainsid, policyguid=policyguid,
1081                                 policyguid_dc=policyguid_dc,
1082                                 setup_path=setup_path,
1083                                 domainControllerFunctionality=domainControllerFunctionality,
1084                                 ntdsguid=ntdsguid)
1085
1086                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1087                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1088                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1089                 assert isinstance(names.ntdsguid, str)
1090
1091     except:
1092         samdb.transaction_cancel()
1093         raise
1094
1095     samdb.transaction_commit()
1096     return samdb
1097
1098
1099 FILL_FULL = "FULL"
1100 FILL_NT4SYNC = "NT4SYNC"
1101 FILL_DRS = "DRS"
1102
1103
1104 def provision(setup_dir, message, session_info, 
1105               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1106               realm=None, 
1107               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1108               serverdn=None,
1109               domain=None, hostname=None, hostip=None, hostip6=None, 
1110               domainsid=None, adminpass=None, ldapadminpass=None, 
1111               krbtgtpass=None, domainguid=None, 
1112               policyguid=None, policyguid_dc=None, invocationid=None,
1113               machinepass=None, ntdsguid=None,
1114               dnspass=None, root=None, nobody=None, users=None, 
1115               wheel=None, backup=None, aci=None, serverrole=None,
1116               dom_for_fun_level=None,
1117               ldap_backend_extra_port=None, backend_type=None,
1118               sitename=None,
1119               ol_mmr_urls=None, ol_olc=None, 
1120               setup_ds_path=None, slapd_path=None, nosync=False,
1121               ldap_dryrun_mode=False):
1122     """Provision samba4
1123     
1124     :note: caution, this wipes all existing data!
1125     """
1126
1127     def setup_path(file):
1128       return os.path.join(setup_dir, file)
1129
1130     if domainsid is None:
1131       domainsid = security.random_sid()
1132     else:
1133       domainsid = security.dom_sid(domainsid)
1134
1135     # create/adapt the group policy GUIDs
1136     if policyguid is None:
1137         policyguid = str(uuid.uuid4())
1138     policyguid = policyguid.upper()
1139     if policyguid_dc is None:
1140         policyguid_dc = str(uuid.uuid4())
1141     policyguid_dc = policyguid_dc.upper()
1142
1143     if adminpass is None:
1144         adminpass = glue.generate_random_str(12)
1145     if krbtgtpass is None:
1146         krbtgtpass = glue.generate_random_str(12)
1147     if machinepass is None:
1148         machinepass  = glue.generate_random_str(12)
1149     if dnspass is None:
1150         dnspass = glue.generate_random_str(12)
1151     if ldapadminpass is None:
1152         #Make a new, random password between Samba and it's LDAP server
1153         ldapadminpass=glue.generate_random_str(12)        
1154
1155     if backend_type is None:
1156         backend_type = "ldb"
1157
1158     sid_generator = "internal"
1159     if backend_type == "fedora-ds":
1160         sid_generator = "backend"
1161
1162     root_uid = findnss_uid([root or "root"])
1163     nobody_uid = findnss_uid([nobody or "nobody"])
1164     users_gid = findnss_gid([users or "users"])
1165     if wheel is None:
1166         wheel_gid = findnss_gid(["wheel", "adm"])
1167     else:
1168         wheel_gid = findnss_gid([wheel])
1169
1170     if targetdir is not None:
1171         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1172             os.makedirs(os.path.join(targetdir, "etc"))
1173         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1174     elif smbconf is None:
1175         smbconf = param.default_path()
1176
1177     # only install a new smb.conf if there isn't one there already
1178     if not os.path.exists(smbconf):
1179         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1180                      targetdir, sid_generator)
1181
1182     lp = param.LoadParm()
1183     lp.load(smbconf)
1184
1185     names = guess_names(lp=lp, hostname=hostname, domain=domain,
1186                         dnsdomain=realm, serverrole=serverrole,
1187                         domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1188                         serverdn=serverdn, sitename=sitename)
1189
1190     paths = provision_paths_from_lp(lp, names.dnsdomain)
1191
1192     if hostip is None:
1193         try:
1194             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1195         except socket.gaierror, (socket.EAI_NODATA, msg):
1196             hostip = None
1197
1198     if hostip6 is None:
1199         try:
1200             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1201         except socket.gaierror, (socket.EAI_NODATA, msg): 
1202             hostip6 = None
1203
1204     if serverrole is None:
1205         serverrole = lp.get("server role")
1206
1207     assert serverrole in ("domain controller", "member server", "standalone")
1208     if invocationid is None and serverrole == "domain controller":
1209         invocationid = str(uuid.uuid4())
1210
1211     if not os.path.exists(paths.private_dir):
1212         os.mkdir(paths.private_dir)
1213
1214     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1215     
1216     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
1217                     sambadn=names.sambadn)
1218     
1219     provision_backend = ProvisionBackend(backend_type,
1220                                          paths=paths, setup_path=setup_path,
1221                                          lp=lp, credentials=credentials, 
1222                                          names=names,
1223                                          message=message, hostname=hostname,
1224                                          root=root, schema=schema,
1225                                          ldapadminpass=ldapadminpass,
1226                                          ldap_backend_extra_port=ldap_backend_extra_port,
1227                                          ol_mmr_urls=ol_mmr_urls, 
1228                                          slapd_path=slapd_path,
1229                                          setup_ds_path=setup_ds_path,
1230                                          ldap_dryrun_mode=ldap_dryrun_mode,
1231                                          domainsid=domainsid)
1232
1233     # only install a new shares config db if there is none
1234     if not os.path.exists(paths.shareconf):
1235         message("Setting up share.ldb")
1236         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1237                         lp=lp)
1238         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1239
1240      
1241     message("Setting up secrets.ldb")
1242     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1243                                   session_info=session_info, 
1244                                   backend_credentials=provision_backend.secrets_credentials, lp=lp)
1245
1246     message("Setting up the registry")
1247     setup_registry(paths.hklm, setup_path, session_info, 
1248                    lp=lp)
1249
1250     message("Setting up the privileges database")
1251     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1252
1253     message("Setting up idmap db")
1254     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1255                           lp=lp)
1256
1257     message("Setting up SAM db")
1258     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1259                         provision_backend, lp, names,
1260                         message, 
1261                         domainsid=domainsid, 
1262                         schema=schema, domainguid=domainguid,
1263                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1264                         fill=samdb_fill, 
1265                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1266                         invocationid=invocationid, 
1267                         machinepass=machinepass, dnspass=dnspass, 
1268                         ntdsguid=ntdsguid, serverrole=serverrole,
1269                         dom_for_fun_level=dom_for_fun_level)
1270
1271     if serverrole == "domain controller":
1272         if paths.netlogon is None:
1273             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1274             message("Please either remove %s or see the template at %s" % 
1275                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1276             assert(paths.netlogon is not None)
1277
1278         if paths.sysvol is None:
1279             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1280             message("Please either remove %s or see the template at %s" % 
1281                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1282             assert(paths.sysvol is not None)            
1283             
1284         # Set up group policies (domain policy and domain controller policy)
1285
1286         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1287                                    "{" + policyguid + "}")
1288         os.makedirs(policy_path, 0755)
1289         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1290                                    "[General]\r\nVersion=65543")
1291         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1292         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1293
1294         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1295                                    "{" + policyguid_dc + "}")
1296         os.makedirs(policy_path_dc, 0755)
1297         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1298                                    "[General]\r\nVersion=2")
1299         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1300         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1301
1302         if not os.path.isdir(paths.netlogon):
1303             os.makedirs(paths.netlogon, 0755)
1304
1305     if samdb_fill == FILL_FULL:
1306         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1307                             root_uid=root_uid, nobody_uid=nobody_uid,
1308                             users_gid=users_gid, wheel_gid=wheel_gid)
1309
1310         message("Setting up sam.ldb rootDSE marking as synchronized")
1311         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1312
1313         # Only make a zone file on the first DC, it should be replicated with DNS replication
1314         if serverrole == "domain controller":
1315             secretsdb_self_join(secrets_ldb, domain=domain,
1316                                 realm=names.realm,
1317                                 dnsdomain=names.dnsdomain,
1318                                 netbiosname=names.netbiosname,
1319                                 domainsid=domainsid, 
1320                                 machinepass=machinepass,
1321                                 secure_channel_type=SEC_CHAN_BDC)
1322
1323             secretsdb_setup_dns(secrets_ldb, setup_path, 
1324                                 realm=names.realm, dnsdomain=names.dnsdomain,
1325                                 dns_keytab_path=paths.dns_keytab,
1326                                 dnspass=dnspass)
1327
1328             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1329             assert isinstance(domainguid, str)
1330
1331             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1332                              hostip=hostip,
1333                              hostip6=hostip6, hostname=names.hostname,
1334                              realm=names.realm,
1335                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1336
1337             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1338                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1339
1340             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1341                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1342                               keytab_name=paths.dns_keytab)
1343             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1344             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1345
1346             create_krb5_conf(paths.krb5conf, setup_path,
1347                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1348                              realm=names.realm)
1349             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1350
1351     if provision_backend.post_setup is not None:
1352         provision_backend.post_setup()
1353
1354     if provision_backend.shutdown is not None:
1355         provision_backend.shutdown()
1356     
1357     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1358                                ldapi_url)
1359
1360     #Now commit the secrets.ldb to disk
1361     secrets_ldb.transaction_commit()
1362
1363     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1364
1365     message("Once the above files are installed, your Samba4 server will be ready to use")
1366     message("Server Role:           %s" % serverrole)
1367     message("Hostname:              %s" % names.hostname)
1368     message("NetBIOS Domain:        %s" % names.domain)
1369     message("DNS Domain:            %s" % names.dnsdomain)
1370     message("DOMAIN SID:            %s" % str(domainsid))
1371     if samdb_fill == FILL_FULL:
1372         message("Admin password:    %s" % adminpass)
1373     if provision_backend.type is not "ldb":
1374         if provision_backend.credentials.get_bind_dn() is not None:
1375             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1376         else:
1377             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1378
1379         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1380
1381         if provision_backend.slapd_command_escaped is not None:
1382             # now display slapd_command_file.txt to show how slapd must be started next time
1383             message("Use later the following commandline to start slapd, then Samba:")
1384             message(provision_backend.slapd_command_escaped)
1385             message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1386
1387
1388     result = ProvisionResult()
1389     result.domaindn = domaindn
1390     result.paths = paths
1391     result.lp = lp
1392     result.samdb = samdb
1393     return result
1394
1395
1396
1397 def provision_become_dc(setup_dir=None,
1398                         smbconf=None, targetdir=None, realm=None, 
1399                         rootdn=None, domaindn=None, schemadn=None,
1400                         configdn=None, serverdn=None,
1401                         domain=None, hostname=None, domainsid=None, 
1402                         adminpass=None, krbtgtpass=None, domainguid=None, 
1403                         policyguid=None, policyguid_dc=None, invocationid=None,
1404                         machinepass=None, 
1405                         dnspass=None, root=None, nobody=None, users=None, 
1406                         wheel=None, backup=None, serverrole=None, 
1407                         ldap_backend=None, ldap_backend_type=None,
1408                         sitename=None, debuglevel=1):
1409
1410     def message(text):
1411         """print a message if quiet is not set."""
1412         print text
1413
1414     glue.set_debug_level(debuglevel)
1415
1416     return provision(setup_dir, message, system_session(), None,
1417               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1418               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1419               configdn=configdn, serverdn=serverdn, domain=domain,
1420               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1421               machinepass=machinepass, serverrole="domain controller",
1422               sitename=sitename)
1423
1424
1425 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1426     """Create a PHP LDAP admin configuration file.
1427
1428     :param path: Path to write the configuration to.
1429     :param setup_path: Function to generate setup paths.
1430     """
1431     setup_file(setup_path("phpldapadmin-config.php"), path, 
1432             {"S4_LDAPI_URI": ldapi_uri})
1433
1434
1435 def create_zone_file(path, setup_path, dnsdomain, 
1436                      hostip, hostip6, hostname, realm, domainguid,
1437                      ntdsguid):
1438     """Write out a DNS zone file, from the info in the current database.
1439
1440     :param path: Path of the new zone file.
1441     :param setup_path: Setup path function.
1442     :param dnsdomain: DNS Domain name
1443     :param domaindn: DN of the Domain
1444     :param hostip: Local IPv4 IP
1445     :param hostip6: Local IPv6 IP
1446     :param hostname: Local hostname
1447     :param realm: Realm name
1448     :param domainguid: GUID of the domain.
1449     :param ntdsguid: GUID of the hosts nTDSDSA record.
1450     """
1451     assert isinstance(domainguid, str)
1452
1453     if hostip6 is not None:
1454         hostip6_base_line = "            IN AAAA    " + hostip6
1455         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1456     else:
1457         hostip6_base_line = ""
1458         hostip6_host_line = ""
1459
1460     if hostip is not None:
1461         hostip_base_line = "            IN A    " + hostip
1462         hostip_host_line = hostname + "        IN A    " + hostip
1463     else:
1464         hostip_base_line = ""
1465         hostip_host_line = ""
1466
1467     setup_file(setup_path("provision.zone"), path, {
1468             "HOSTNAME": hostname,
1469             "DNSDOMAIN": dnsdomain,
1470             "REALM": realm,
1471             "HOSTIP_BASE_LINE": hostip_base_line,
1472             "HOSTIP_HOST_LINE": hostip_host_line,
1473             "DOMAINGUID": domainguid,
1474             "DATESTRING": time.strftime("%Y%m%d%H"),
1475             "DEFAULTSITE": DEFAULTSITE,
1476             "NTDSGUID": ntdsguid,
1477             "HOSTIP6_BASE_LINE": hostip6_base_line,
1478             "HOSTIP6_HOST_LINE": hostip6_host_line,
1479         })
1480
1481
1482 def create_named_conf(path, setup_path, realm, dnsdomain,
1483                       private_dir):
1484     """Write out a file containing zone statements suitable for inclusion in a
1485     named.conf file (including GSS-TSIG configuration).
1486     
1487     :param path: Path of the new named.conf file.
1488     :param setup_path: Setup path function.
1489     :param realm: Realm name
1490     :param dnsdomain: DNS Domain name
1491     :param private_dir: Path to private directory
1492     :param keytab_name: File name of DNS keytab file
1493     """
1494
1495     setup_file(setup_path("named.conf"), path, {
1496             "DNSDOMAIN": dnsdomain,
1497             "REALM": realm,
1498             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1499             "PRIVATE_DIR": private_dir
1500             })
1501
1502 def create_named_txt(path, setup_path, realm, dnsdomain,
1503                       private_dir, keytab_name):
1504     """Write out a file containing zone statements suitable for inclusion in a
1505     named.conf file (including GSS-TSIG configuration).
1506     
1507     :param path: Path of the new named.conf file.
1508     :param setup_path: Setup path function.
1509     :param realm: Realm name
1510     :param dnsdomain: DNS Domain name
1511     :param private_dir: Path to private directory
1512     :param keytab_name: File name of DNS keytab file
1513     """
1514
1515     setup_file(setup_path("named.txt"), path, {
1516             "DNSDOMAIN": dnsdomain,
1517             "REALM": realm,
1518             "DNS_KEYTAB": keytab_name,
1519             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1520             "PRIVATE_DIR": private_dir
1521         })
1522
1523 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1524     """Write out a file containing zone statements suitable for inclusion in a
1525     named.conf file (including GSS-TSIG configuration).
1526     
1527     :param path: Path of the new named.conf file.
1528     :param setup_path: Setup path function.
1529     :param dnsdomain: DNS Domain name
1530     :param hostname: Local hostname
1531     :param realm: Realm name
1532     """
1533
1534     setup_file(setup_path("krb5.conf"), path, {
1535             "DNSDOMAIN": dnsdomain,
1536             "HOSTNAME": hostname,
1537             "REALM": realm,
1538         })
1539
1540