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