s4:provision - Removed dependency on full Samba 3 schema from FDS
[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
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     # - objectclass must be before password_hash, because password_hash checks
558     #   that the objectclass is of type person (filled in by objectclass
559     #   module when expanding the objectclass list)
560     # - partition must be last
561     # - each partition has its own module list then
562     modules_list = ["resolve_oids",
563                     "rootdse",
564                     "lazy_commit",
565                     "paged_results",
566                     "ranged_results",
567                     "anr",
568                     "server_sort",
569                     "asq",
570                     "extended_dn_store",
571                     "extended_dn_in",
572                     "rdn_name",
573                     "objectclass",
574                     "descriptor",
575                     "acl",
576                     "samldb",
577                     "password_hash",
578                     "operational",
579                     "kludge_acl", 
580                     "instancetype"]
581     tdb_modules_list = [
582                     "subtree_rename",
583                     "subtree_delete",
584                     "linked_attributes",
585                     "extended_dn_out_ldb"]
586     modules_list2 = ["show_deleted",
587                      "schema_load",
588                      "new_partition",
589                      "partition"]
590
591     ldap_backend_line = "# No LDAP backend"
592     if provision_backend.type is not "ldb":
593         ldap_backend_line = "ldapBackend: %s" % provision_backend.ldapi_uri
594         
595         if provision_backend.ldap_backend_type == "fedora-ds":
596             backend_modules = ["nsuniqueid", "paged_searches"]
597             # We can handle linked attributes here, as we don't have directory-side subtree operations
598             tdb_modules_list = ["extended_dn_out_fds"]
599         elif provision_backend.ldap_backend_type == "openldap":
600             backend_modules = ["entryuuid", "paged_searches"]
601             # OpenLDAP handles subtree renames, so we don't want to do any of these things
602             tdb_modules_list = ["extended_dn_out_openldap"]
603
604     elif serverrole == "domain controller":
605         tdb_modules_list.insert(0, "repl_meta_data")
606         backend_modules = []
607     else:
608         backend_modules = ["objectguid"]
609
610     if tdb_modules_list is None:
611         tdb_modules_list_as_string = ""
612     else:
613         tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
614         
615     samdb.transaction_start()
616     try:
617         message("Setting up sam.ldb partitions and settings")
618         setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
619                 "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(), 
620                 "SCHEMADN_MOD2": ",objectguid",
621                 "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
622                 "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
623                 "SCHEMADN_MOD": "schema_data",
624                 "CONFIGDN_MOD": "naming_fsmo",
625                 "DOMAINDN_MOD": "pdc_fsmo",
626                 "MODULES_LIST": ",".join(modules_list),
627                 "TDB_MODULES_LIST": tdb_modules_list_as_string,
628                 "MODULES_LIST2": ",".join(modules_list2),
629                 "BACKEND_MOD": ",".join(backend_modules),
630                 "LDAP_BACKEND_LINE": ldap_backend_line,
631         })
632
633         
634         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
635
636         message("Setting up sam.ldb rootDSE")
637         setup_samdb_rootdse(samdb, setup_path, names)
638
639     except:
640         samdb.transaction_cancel()
641         raise
642
643     samdb.transaction_commit()
644
645         
646 def secretsdb_self_join(secretsdb, domain, 
647                         netbiosname, domainsid, machinepass, 
648                         realm=None, dnsdomain=None,
649                         keytab_path=None, 
650                         key_version_number=1,
651                         secure_channel_type=SEC_CHAN_WKSTA):
652     """Add domain join-specific bits to a secrets database.
653     
654     :param secretsdb: Ldb Handle to the secrets database
655     :param machinepass: Machine password
656     """
657     attrs=["whenChanged",
658            "secret",
659            "priorSecret",
660            "priorChanged",
661            "krb5Keytab",
662            "privateKeytab"]
663     
664
665     msg = ldb.Message(ldb.Dn(secretsdb, "flatname=%s,cn=Primary Domains" % domain));
666     msg["secureChannelType"] = str(secure_channel_type)
667     msg["flatname"] = [domain]
668     msg["objectClass"] = ["top", "primaryDomain"]
669     if realm is not None:
670       if dnsdomain is None:
671         dnsdomain = realm.lower()
672       msg["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
673       msg["realm"] = realm
674       msg["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname.lower(), dnsdomain.lower(), realm.upper())
675       msg["msDS-KeyVersionNumber"] = [str(key_version_number)]
676       msg["privateKeytab"] = ["secrets.keytab"];
677
678
679     msg["secret"] = [machinepass]
680     msg["samAccountName"] = ["%s$" % netbiosname]
681     msg["secureChannelType"] = [str(secure_channel_type)]
682     msg["objectSid"] = [ndr_pack(domainsid)]
683     
684     res = secretsdb.search(base="cn=Primary Domains", 
685                            attrs=attrs, 
686                            expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain, realm, str(domainsid))), 
687                            scope=SCOPE_ONELEVEL)
688     
689     for del_msg in res:
690       if del_msg.dn is not msg.dn:
691         secretsdb.delete(del_msg.dn)
692
693     res = secretsdb.search(base=msg.dn, attrs=attrs, scope=SCOPE_BASE)
694
695     if len(res) == 1:
696       msg["priorSecret"] = res[0]["secret"]
697       msg["priorWhenChanged"] = res[0]["whenChanged"]
698
699       if res["privateKeytab"] is not None:
700         msg["privateKeytab"] = res[0]["privateKeytab"]
701
702       if res["krb5Keytab"] is not None:
703         msg["krb5Keytab"] = res[0]["krb5Keytab"]
704
705       for el in msg:
706         el.set_flags(ldb.FLAG_MOD_REPLACE)
707         secretsdb.modify(msg)
708     else:
709       secretsdb.add(msg)
710
711
712 def secretsdb_setup_dns(secretsdb, setup_path, realm, dnsdomain, 
713                         dns_keytab_path, dnspass):
714     """Add DNS specific bits to a secrets database.
715     
716     :param secretsdb: Ldb Handle to the secrets database
717     :param setup_path: Setup path function
718     :param machinepass: Machine password
719     """
720     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), { 
721             "REALM": realm,
722             "DNSDOMAIN": dnsdomain,
723             "DNS_KEYTAB": dns_keytab_path,
724             "DNSPASS_B64": b64encode(dnspass),
725             })
726
727
728 def setup_secretsdb(path, setup_path, session_info, backend_credentials, lp):
729     """Setup the secrets database.
730
731     :param path: Path to the secrets database.
732     :param setup_path: Get the path to a setup file.
733     :param session_info: Session info.
734     :param credentials: Credentials
735     :param lp: Loadparm context
736     :return: LDB handle for the created secrets database
737     """
738     if os.path.exists(path):
739         os.unlink(path)
740     secrets_ldb = Ldb(path, session_info=session_info, 
741                       lp=lp)
742     secrets_ldb.erase()
743     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
744     secrets_ldb = Ldb(path, session_info=session_info, 
745                       lp=lp)
746     secrets_ldb.transaction_start()
747     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
748
749     if backend_credentials is not None and backend_credentials.authentication_requested():
750         if backend_credentials.get_bind_dn() is not None:
751             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
752                     "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
753                     "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
754                     })
755         else:
756             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
757                     "LDAPADMINUSER": backend_credentials.get_username(),
758                     "LDAPADMINREALM": backend_credentials.get_realm(),
759                     "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
760                     })
761
762     return secrets_ldb
763
764 def setup_privileges(path, setup_path, session_info, lp):
765     """Setup the privileges database.
766
767     :param path: Path to the privileges database.
768     :param setup_path: Get the path to a setup file.
769     :param session_info: Session info.
770     :param credentials: Credentials
771     :param lp: Loadparm context
772     :return: LDB handle for the created secrets database
773     """
774     if os.path.exists(path):
775         os.unlink(path)
776     privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
777     privilege_ldb.erase()
778     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
779
780
781 def setup_registry(path, setup_path, session_info, lp):
782     """Setup the registry.
783     
784     :param path: Path to the registry database
785     :param setup_path: Function that returns the path to a setup.
786     :param session_info: Session information
787     :param credentials: Credentials
788     :param lp: Loadparm context
789     """
790     reg = registry.Registry()
791     hive = registry.open_ldb(path, session_info=session_info, 
792                          lp_ctx=lp)
793     reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
794     provision_reg = setup_path("provision.reg")
795     assert os.path.exists(provision_reg)
796     reg.diff_apply(provision_reg)
797
798
799 def setup_idmapdb(path, setup_path, session_info, lp):
800     """Setup the idmap database.
801
802     :param path: path to the idmap database
803     :param setup_path: Function that returns a path to a setup file
804     :param session_info: Session information
805     :param credentials: Credentials
806     :param lp: Loadparm context
807     """
808     if os.path.exists(path):
809         os.unlink(path)
810
811     idmap_ldb = IDmapDB(path, session_info=session_info,
812                         lp=lp)
813
814     idmap_ldb.erase()
815     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
816     return idmap_ldb
817
818
819 def setup_samdb_rootdse(samdb, setup_path, names):
820     """Setup the SamDB rootdse.
821
822     :param samdb: Sam Database handle
823     :param setup_path: Obtain setup path
824     """
825     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
826         "SCHEMADN": names.schemadn, 
827         "NETBIOSNAME": names.netbiosname,
828         "DNSDOMAIN": names.dnsdomain,
829         "REALM": names.realm,
830         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
831         "DOMAINDN": names.domaindn,
832         "ROOTDN": names.rootdn,
833         "CONFIGDN": names.configdn,
834         "SERVERDN": names.serverdn,
835         })
836         
837
838 def setup_self_join(samdb, names,
839                     machinepass, dnspass, 
840                     domainsid, invocationid, setup_path,
841                     policyguid, policyguid_dc, domainControllerFunctionality,
842                     ntdsguid):
843     """Join a host to its own domain."""
844     assert isinstance(invocationid, str)
845     if ntdsguid is not None:
846         ntdsguid_line = "objectGUID: %s\n"%ntdsguid
847     else:
848         ntdsguid_line = ""
849     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
850               "CONFIGDN": names.configdn, 
851               "SCHEMADN": names.schemadn,
852               "DOMAINDN": names.domaindn,
853               "SERVERDN": names.serverdn,
854               "INVOCATIONID": invocationid,
855               "NETBIOSNAME": names.netbiosname,
856               "DEFAULTSITE": names.sitename,
857               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
858               "MACHINEPASS_B64": b64encode(machinepass),
859               "DNSPASS_B64": b64encode(dnspass),
860               "REALM": names.realm,
861               "DOMAIN": names.domain,
862               "DNSDOMAIN": names.dnsdomain,
863               "SAMBA_VERSION_STRING": version,
864               "NTDSGUID": ntdsguid_line,
865               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
866
867     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
868               "POLICYGUID": policyguid,
869               "POLICYGUID_DC": policyguid_dc,
870               "DNSDOMAIN": names.dnsdomain,
871               "DOMAINSID": str(domainsid),
872               "DOMAINDN": names.domaindn})
873     
874     # add the NTDSGUID based SPNs
875     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
876     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
877                                      expression="", scope=SCOPE_BASE)
878     assert isinstance(names.ntdsguid, str)
879
880     # Setup fSMORoleOwner entries to point at the newly created DC entry
881     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
882               "DOMAIN": names.domain,
883               "DNSDOMAIN": names.dnsdomain,
884               "DOMAINDN": names.domaindn,
885               "CONFIGDN": names.configdn,
886               "SCHEMADN": names.schemadn, 
887               "DEFAULTSITE": names.sitename,
888               "SERVERDN": names.serverdn,
889               "NETBIOSNAME": names.netbiosname,
890               "NTDSGUID": names.ntdsguid
891               })
892
893
894 def setup_samdb(path, setup_path, session_info, provision_backend, lp, 
895                 names, message, 
896                 domainsid, domainguid, policyguid, policyguid_dc,
897                 fill, adminpass, krbtgtpass, 
898                 machinepass, invocationid, dnspass, ntdsguid,
899                 serverrole, dom_for_fun_level=None,
900                 schema=None):
901     """Setup a complete SAM Database.
902     
903     :note: This will wipe the main SAM database file!
904     """
905
906     # ATTENTION: Do NOT change these default values without discussion with the
907     # team and/or release manager. They have a big impact on the whole program!
908     domainControllerFunctionality = DS_DC_FUNCTION_2008
909
910     if dom_for_fun_level is None:
911         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
912     if dom_for_fun_level < DS_DOMAIN_FUNCTION_2003:
913         raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
914
915     if dom_for_fun_level > domainControllerFunctionality:
916         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!")
917
918     domainFunctionality = dom_for_fun_level
919     forestFunctionality = dom_for_fun_level
920
921     # Also wipes the database
922     setup_samdb_partitions(path, setup_path, message=message, lp=lp,
923                            provision_backend=provision_backend, session_info=session_info,
924                            names=names, 
925                            serverrole=serverrole, schema=schema)
926
927     if (schema == None):
928         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn)
929
930     # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
931     samdb = Ldb(session_info=session_info, 
932                 credentials=provision_backend.credentials, lp=lp)
933
934     message("Pre-loading the Samba 4 and AD schema")
935
936     # Load the schema from the one we computed earlier
937     samdb.set_schema_from_ldb(schema.ldb)
938
939     # And now we can connect to the DB - the schema won't be loaded from the DB
940     samdb.connect(path)
941
942     if fill == FILL_DRS:
943         return samdb
944         
945     samdb.transaction_start()
946     try:
947         # Set the domain functionality levels onto the database.
948         # Various module (the password_hash module in particular) need
949         # to know what level of AD we are emulating.
950
951         # These will be fixed into the database via the database
952         # modifictions below, but we need them set from the start.
953         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
954         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
955         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
956
957         samdb.set_domain_sid(str(domainsid))
958         if serverrole == "domain controller":
959             samdb.set_invocation_id(invocationid)
960
961         message("Adding DomainDN: %s" % names.domaindn)
962
963 #impersonate domain admin
964         admin_session_info = admin_session(lp, str(domainsid))
965         samdb.set_session_info(admin_session_info)
966         if domainguid is not None:
967             domainguid_line = "objectGUID: %s\n-" % domainguid
968         else:
969             domainguid_line = ""
970
971         descr = get_domain_descriptor(domainsid)
972         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
973                 "DOMAINDN": names.domaindn,
974                 "DOMAINGUID": domainguid_line,
975                 "DESCRIPTOR": descr
976                 })
977
978
979         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
980             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
981             "DOMAINSID": str(domainsid),
982             "SCHEMADN": names.schemadn, 
983             "NETBIOSNAME": names.netbiosname,
984             "DEFAULTSITE": names.sitename,
985             "CONFIGDN": names.configdn,
986             "SERVERDN": names.serverdn,
987             "POLICYGUID": policyguid,
988             "DOMAINDN": names.domaindn,
989             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
990             "SAMBA_VERSION_STRING": version
991             })
992
993         message("Adding configuration container")
994         descr = get_config_descriptor(domainsid);
995         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
996             "CONFIGDN": names.configdn, 
997             "DESCRIPTOR": descr,
998             })
999         message("Modifying configuration container")
1000         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
1001             "CONFIGDN": names.configdn, 
1002             "SCHEMADN": names.schemadn,
1003             })
1004
1005         # The LDIF here was created when the Schema object was constructed
1006         message("Setting up sam.ldb schema")
1007         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1008         samdb.modify_ldif(schema.schema_dn_modify)
1009         samdb.write_prefixes_from_schema()
1010         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1011         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1012                        {"SCHEMADN": names.schemadn})
1013
1014         message("Setting up sam.ldb configuration data")
1015         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1016             "CONFIGDN": names.configdn,
1017             "NETBIOSNAME": names.netbiosname,
1018             "DEFAULTSITE": names.sitename,
1019             "DNSDOMAIN": names.dnsdomain,
1020             "DOMAIN": names.domain,
1021             "SCHEMADN": names.schemadn,
1022             "DOMAINDN": names.domaindn,
1023             "SERVERDN": names.serverdn,
1024             "FOREST_FUNCTIONALALITY": str(forestFunctionality)
1025             })
1026
1027         message("Setting up display specifiers")
1028         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1029         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1030         check_all_substituted(display_specifiers_ldif)
1031         samdb.add_ldif(display_specifiers_ldif)
1032
1033         message("Adding users container")
1034         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1035                 "DOMAINDN": names.domaindn})
1036         message("Modifying users container")
1037         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1038                 "DOMAINDN": names.domaindn})
1039         message("Adding computers container")
1040         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1041                 "DOMAINDN": names.domaindn})
1042         message("Modifying computers container")
1043         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1044                 "DOMAINDN": names.domaindn})
1045         message("Setting up sam.ldb data")
1046         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1047             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1048             "DOMAINDN": names.domaindn,
1049             "NETBIOSNAME": names.netbiosname,
1050             "DEFAULTSITE": names.sitename,
1051             "CONFIGDN": names.configdn,
1052             "SERVERDN": names.serverdn,
1053             "POLICYGUID_DC": policyguid_dc
1054             })
1055
1056         if fill == FILL_FULL:
1057             message("Setting up sam.ldb users and groups")
1058             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1059                 "DOMAINDN": names.domaindn,
1060                 "DOMAINSID": str(domainsid),
1061                 "CONFIGDN": names.configdn,
1062                 "ADMINPASS_B64": b64encode(adminpass),
1063                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1064                 })
1065
1066             if serverrole == "domain controller":
1067                 message("Setting up self join")
1068                 setup_self_join(samdb, names=names, invocationid=invocationid, 
1069                                 dnspass=dnspass,  
1070                                 machinepass=machinepass, 
1071                                 domainsid=domainsid, policyguid=policyguid,
1072                                 policyguid_dc=policyguid_dc,
1073                                 setup_path=setup_path,
1074                                 domainControllerFunctionality=domainControllerFunctionality,
1075                                 ntdsguid=ntdsguid)
1076
1077                 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1078                 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1079                   attribute="objectGUID", expression="", scope=SCOPE_BASE)
1080                 assert isinstance(names.ntdsguid, str)
1081
1082     except:
1083         samdb.transaction_cancel()
1084         raise
1085
1086     samdb.transaction_commit()
1087     return samdb
1088
1089
1090 FILL_FULL = "FULL"
1091 FILL_NT4SYNC = "NT4SYNC"
1092 FILL_DRS = "DRS"
1093
1094
1095 def provision(setup_dir, message, session_info, 
1096               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1097               realm=None, 
1098               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1099               serverdn=None,
1100               domain=None, hostname=None, hostip=None, hostip6=None, 
1101               domainsid=None, adminpass=None, ldapadminpass=None, 
1102               krbtgtpass=None, domainguid=None, 
1103               policyguid=None, policyguid_dc=None, invocationid=None,
1104               machinepass=None, ntdsguid=None,
1105               dnspass=None, root=None, nobody=None, users=None, 
1106               wheel=None, backup=None, aci=None, serverrole=None,
1107               dom_for_fun_level=None,
1108               ldap_backend_extra_port=None, backend_type=None,
1109               sitename=None,
1110               ol_mmr_urls=None, ol_olc=None, 
1111               setup_ds_path=None, slapd_path=None, nosync=False,
1112               ldap_dryrun_mode=False):
1113     """Provision samba4
1114     
1115     :note: caution, this wipes all existing data!
1116     """
1117
1118     def setup_path(file):
1119       return os.path.join(setup_dir, file)
1120
1121     if domainsid is None:
1122       domainsid = security.random_sid()
1123     else:
1124       domainsid = security.dom_sid(domainsid)
1125
1126     # create/adapt the group policy GUIDs
1127     if policyguid is None:
1128         policyguid = str(uuid.uuid4())
1129     policyguid = policyguid.upper()
1130     if policyguid_dc is None:
1131         policyguid_dc = str(uuid.uuid4())
1132     policyguid_dc = policyguid_dc.upper()
1133
1134     if adminpass is None:
1135         adminpass = glue.generate_random_str(12)
1136     if krbtgtpass is None:
1137         krbtgtpass = glue.generate_random_str(12)
1138     if machinepass is None:
1139         machinepass  = glue.generate_random_str(12)
1140     if dnspass is None:
1141         dnspass = glue.generate_random_str(12)
1142     if ldapadminpass is None:
1143         #Make a new, random password between Samba and it's LDAP server
1144         ldapadminpass=glue.generate_random_str(12)        
1145
1146     if backend_type is None:
1147         backend_type = "ldb"
1148
1149     sid_generator = "internal"
1150     if backend_type == "fedora-ds":
1151         sid_generator = "backend"
1152
1153     root_uid = findnss_uid([root or "root"])
1154     nobody_uid = findnss_uid([nobody or "nobody"])
1155     users_gid = findnss_gid([users or "users"])
1156     if wheel is None:
1157         wheel_gid = findnss_gid(["wheel", "adm"])
1158     else:
1159         wheel_gid = findnss_gid([wheel])
1160
1161     if targetdir is not None:
1162         if (not os.path.exists(os.path.join(targetdir, "etc"))):
1163             os.makedirs(os.path.join(targetdir, "etc"))
1164         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1165     elif smbconf is None:
1166         smbconf = param.default_path()
1167
1168     # only install a new smb.conf if there isn't one there already
1169     if not os.path.exists(smbconf):
1170         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1171                      targetdir, sid_generator)
1172
1173     lp = param.LoadParm()
1174     lp.load(smbconf)
1175
1176     names = guess_names(lp=lp, hostname=hostname, domain=domain,
1177                         dnsdomain=realm, serverrole=serverrole,
1178                         domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1179                         serverdn=serverdn, sitename=sitename)
1180
1181     paths = provision_paths_from_lp(lp, names.dnsdomain)
1182
1183     if hostip is None:
1184         try:
1185             hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1186         except socket.gaierror, (socket.EAI_NODATA, msg):
1187             hostip = None
1188
1189     if hostip6 is None:
1190         try:
1191             hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1192         except socket.gaierror, (socket.EAI_NODATA, msg): 
1193             hostip6 = None
1194
1195     if serverrole is None:
1196         serverrole = lp.get("server role")
1197
1198     assert serverrole in ("domain controller", "member server", "standalone")
1199     if invocationid is None and serverrole == "domain controller":
1200         invocationid = str(uuid.uuid4())
1201
1202     if not os.path.exists(paths.private_dir):
1203         os.mkdir(paths.private_dir)
1204
1205     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1206     
1207     schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn)
1208     
1209     if backend_type == "ldb":
1210         provision_backend = LDBBackend(backend_type,
1211                                          paths=paths, setup_path=setup_path,
1212                                          lp=lp, credentials=credentials, 
1213                                          names=names,
1214                                          message=message)
1215     elif backend_type == "existing":
1216         provision_backend = ExistingBackend(backend_type,
1217                                          paths=paths, setup_path=setup_path,
1218                                          lp=lp, credentials=credentials, 
1219                                          names=names,
1220                                          message=message)
1221     elif backend_type == "fedora-ds":
1222         provision_backend = FDSBackend(backend_type,
1223                                          paths=paths, setup_path=setup_path,
1224                                          lp=lp, credentials=credentials, 
1225                                          names=names,
1226                                          message=message,
1227                                          domainsid=domainsid,
1228                                          schema=schema,
1229                                          hostname=hostname,
1230                                          ldapadminpass=ldapadminpass,
1231                                          slapd_path=slapd_path,
1232                                          ldap_backend_extra_port=ldap_backend_extra_port,
1233                                          ldap_dryrun_mode=ldap_dryrun_mode,
1234                                          root=root,
1235                                          setup_ds_path=setup_ds_path)
1236     elif backend_type == "openldap":
1237         provision_backend = OpenLDAPBackend(backend_type,
1238                                          paths=paths, setup_path=setup_path,
1239                                          lp=lp, credentials=credentials, 
1240                                          names=names,
1241                                          message=message,
1242                                          domainsid=domainsid,
1243                                          schema=schema,
1244                                          hostname=hostname,
1245                                          ldapadminpass=ldapadminpass,
1246                                          slapd_path=slapd_path,
1247                                          ldap_backend_extra_port=ldap_backend_extra_port,
1248                                          ldap_dryrun_mode=ldap_dryrun_mode,
1249                                          ol_mmr_urls=ol_mmr_urls, 
1250                                          nosync=nosync)
1251     else:
1252         raise ProvisioningError("Unknown LDAP backend type selected")
1253
1254     provision_backend.init()
1255     provision_backend.start()
1256
1257     # only install a new shares config db if there is none
1258     if not os.path.exists(paths.shareconf):
1259         message("Setting up share.ldb")
1260         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1261                         lp=lp)
1262         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1263
1264      
1265     message("Setting up secrets.ldb")
1266     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1267                                   session_info=session_info, 
1268                                   backend_credentials=provision_backend.secrets_credentials, lp=lp)
1269
1270     message("Setting up the registry")
1271     setup_registry(paths.hklm, setup_path, session_info, 
1272                    lp=lp)
1273
1274     message("Setting up the privileges database")
1275     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1276
1277     message("Setting up idmap db")
1278     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1279                           lp=lp)
1280
1281     message("Setting up SAM db")
1282     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1283                         provision_backend, lp, names,
1284                         message, 
1285                         domainsid=domainsid, 
1286                         schema=schema, domainguid=domainguid,
1287                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1288                         fill=samdb_fill, 
1289                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1290                         invocationid=invocationid, 
1291                         machinepass=machinepass, dnspass=dnspass, 
1292                         ntdsguid=ntdsguid, serverrole=serverrole,
1293                         dom_for_fun_level=dom_for_fun_level)
1294
1295     if serverrole == "domain controller":
1296         if paths.netlogon is None:
1297             message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1298             message("Please either remove %s or see the template at %s" % 
1299                     ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1300             assert(paths.netlogon is not None)
1301
1302         if paths.sysvol is None:
1303             message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1304             message("Please either remove %s or see the template at %s" % 
1305                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1306             assert(paths.sysvol is not None)            
1307             
1308         # Set up group policies (domain policy and domain controller policy)
1309
1310         policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1311                                    "{" + policyguid + "}")
1312         os.makedirs(policy_path, 0755)
1313         open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1314                                    "[General]\r\nVersion=65543")
1315         os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1316         os.makedirs(os.path.join(policy_path, "USER"), 0755)
1317
1318         policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1319                                    "{" + policyguid_dc + "}")
1320         os.makedirs(policy_path_dc, 0755)
1321         open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1322                                    "[General]\r\nVersion=2")
1323         os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1324         os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1325
1326         if not os.path.isdir(paths.netlogon):
1327             os.makedirs(paths.netlogon, 0755)
1328
1329     if samdb_fill == FILL_FULL:
1330         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1331                             root_uid=root_uid, nobody_uid=nobody_uid,
1332                             users_gid=users_gid, wheel_gid=wheel_gid)
1333
1334         message("Setting up sam.ldb rootDSE marking as synchronized")
1335         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1336
1337         # Only make a zone file on the first DC, it should be replicated with DNS replication
1338         if serverrole == "domain controller":
1339             secretsdb_self_join(secrets_ldb, domain=domain,
1340                                 realm=names.realm,
1341                                 dnsdomain=names.dnsdomain,
1342                                 netbiosname=names.netbiosname,
1343                                 domainsid=domainsid, 
1344                                 machinepass=machinepass,
1345                                 secure_channel_type=SEC_CHAN_BDC)
1346
1347             secretsdb_setup_dns(secrets_ldb, setup_path, 
1348                                 realm=names.realm, dnsdomain=names.dnsdomain,
1349                                 dns_keytab_path=paths.dns_keytab,
1350                                 dnspass=dnspass)
1351
1352             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1353             assert isinstance(domainguid, str)
1354
1355             create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1356                              hostip=hostip,
1357                              hostip6=hostip6, hostname=names.hostname,
1358                              realm=names.realm,
1359                              domainguid=domainguid, ntdsguid=names.ntdsguid)
1360
1361             create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1362                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1363
1364             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1365                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1366                               keytab_name=paths.dns_keytab)
1367             message("See %s for an example configuration include file for BIND" % paths.namedconf)
1368             message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1369
1370             create_krb5_conf(paths.krb5conf, setup_path,
1371                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1372                              realm=names.realm)
1373             message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1374
1375     provision_backend.post_setup()
1376     provision_backend.shutdown()
1377     
1378     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1379                                ldapi_url)
1380
1381     #Now commit the secrets.ldb to disk
1382     secrets_ldb.transaction_commit()
1383
1384     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1385
1386     message("Once the above files are installed, your Samba4 server will be ready to use")
1387     message("Server Role:           %s" % serverrole)
1388     message("Hostname:              %s" % names.hostname)
1389     message("NetBIOS Domain:        %s" % names.domain)
1390     message("DNS Domain:            %s" % names.dnsdomain)
1391     message("DOMAIN SID:            %s" % str(domainsid))
1392     if samdb_fill == FILL_FULL:
1393         message("Admin password:    %s" % adminpass)
1394     if provision_backend.type is not "ldb":
1395         if provision_backend.credentials.get_bind_dn() is not None:
1396             message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1397         else:
1398             message("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1399
1400         message("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1401
1402         if provision_backend.slapd_command_escaped is not None:
1403             # now display slapd_command_file.txt to show how slapd must be started next time
1404             message("Use later the following commandline to start slapd, then Samba:")
1405             message(provision_backend.slapd_command_escaped)
1406             message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1407
1408
1409     result = ProvisionResult()
1410     result.domaindn = domaindn
1411     result.paths = paths
1412     result.lp = lp
1413     result.samdb = samdb
1414     return result
1415
1416
1417
1418 def provision_become_dc(setup_dir=None,
1419                         smbconf=None, targetdir=None, realm=None, 
1420                         rootdn=None, domaindn=None, schemadn=None,
1421                         configdn=None, serverdn=None,
1422                         domain=None, hostname=None, domainsid=None, 
1423                         adminpass=None, krbtgtpass=None, domainguid=None, 
1424                         policyguid=None, policyguid_dc=None, invocationid=None,
1425                         machinepass=None, 
1426                         dnspass=None, root=None, nobody=None, users=None, 
1427                         wheel=None, backup=None, serverrole=None, 
1428                         ldap_backend=None, ldap_backend_type=None,
1429                         sitename=None, debuglevel=1):
1430
1431     def message(text):
1432         """print a message if quiet is not set."""
1433         print text
1434
1435     glue.set_debug_level(debuglevel)
1436
1437     return provision(setup_dir, message, system_session(), None,
1438               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1439               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1440               configdn=configdn, serverdn=serverdn, domain=domain,
1441               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1442               machinepass=machinepass, serverrole="domain controller",
1443               sitename=sitename)
1444
1445
1446 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1447     """Create a PHP LDAP admin configuration file.
1448
1449     :param path: Path to write the configuration to.
1450     :param setup_path: Function to generate setup paths.
1451     """
1452     setup_file(setup_path("phpldapadmin-config.php"), path, 
1453             {"S4_LDAPI_URI": ldapi_uri})
1454
1455
1456 def create_zone_file(path, setup_path, dnsdomain, 
1457                      hostip, hostip6, hostname, realm, domainguid,
1458                      ntdsguid):
1459     """Write out a DNS zone file, from the info in the current database.
1460
1461     :param path: Path of the new zone file.
1462     :param setup_path: Setup path function.
1463     :param dnsdomain: DNS Domain name
1464     :param domaindn: DN of the Domain
1465     :param hostip: Local IPv4 IP
1466     :param hostip6: Local IPv6 IP
1467     :param hostname: Local hostname
1468     :param realm: Realm name
1469     :param domainguid: GUID of the domain.
1470     :param ntdsguid: GUID of the hosts nTDSDSA record.
1471     """
1472     assert isinstance(domainguid, str)
1473
1474     if hostip6 is not None:
1475         hostip6_base_line = "            IN AAAA    " + hostip6
1476         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1477     else:
1478         hostip6_base_line = ""
1479         hostip6_host_line = ""
1480
1481     if hostip is not None:
1482         hostip_base_line = "            IN A    " + hostip
1483         hostip_host_line = hostname + "        IN A    " + hostip
1484     else:
1485         hostip_base_line = ""
1486         hostip_host_line = ""
1487
1488     setup_file(setup_path("provision.zone"), path, {
1489             "HOSTNAME": hostname,
1490             "DNSDOMAIN": dnsdomain,
1491             "REALM": realm,
1492             "HOSTIP_BASE_LINE": hostip_base_line,
1493             "HOSTIP_HOST_LINE": hostip_host_line,
1494             "DOMAINGUID": domainguid,
1495             "DATESTRING": time.strftime("%Y%m%d%H"),
1496             "DEFAULTSITE": DEFAULTSITE,
1497             "NTDSGUID": ntdsguid,
1498             "HOSTIP6_BASE_LINE": hostip6_base_line,
1499             "HOSTIP6_HOST_LINE": hostip6_host_line,
1500         })
1501
1502
1503 def create_named_conf(path, setup_path, realm, dnsdomain,
1504                       private_dir):
1505     """Write out a file containing zone statements suitable for inclusion in a
1506     named.conf file (including GSS-TSIG configuration).
1507     
1508     :param path: Path of the new named.conf file.
1509     :param setup_path: Setup path function.
1510     :param realm: Realm name
1511     :param dnsdomain: DNS Domain name
1512     :param private_dir: Path to private directory
1513     :param keytab_name: File name of DNS keytab file
1514     """
1515
1516     setup_file(setup_path("named.conf"), path, {
1517             "DNSDOMAIN": dnsdomain,
1518             "REALM": realm,
1519             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1520             "PRIVATE_DIR": private_dir
1521             })
1522
1523 def create_named_txt(path, setup_path, realm, dnsdomain,
1524                       private_dir, keytab_name):
1525     """Write out a file containing zone statements suitable for inclusion in a
1526     named.conf file (including GSS-TSIG configuration).
1527     
1528     :param path: Path of the new named.conf file.
1529     :param setup_path: Setup path function.
1530     :param realm: Realm name
1531     :param dnsdomain: DNS Domain name
1532     :param private_dir: Path to private directory
1533     :param keytab_name: File name of DNS keytab file
1534     """
1535
1536     setup_file(setup_path("named.txt"), path, {
1537             "DNSDOMAIN": dnsdomain,
1538             "REALM": realm,
1539             "DNS_KEYTAB": keytab_name,
1540             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1541             "PRIVATE_DIR": private_dir
1542         })
1543
1544 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1545     """Write out a file containing zone statements suitable for inclusion in a
1546     named.conf file (including GSS-TSIG configuration).
1547     
1548     :param path: Path of the new named.conf file.
1549     :param setup_path: Setup path function.
1550     :param dnsdomain: DNS Domain name
1551     :param hostname: Local hostname
1552     :param realm: Realm name
1553     """
1554
1555     setup_file(setup_path("krb5.conf"), path, {
1556             "DNSDOMAIN": dnsdomain,
1557             "HOSTNAME": hostname,
1558             "REALM": realm,
1559         })
1560
1561