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