e899534954c41579bc97685478472abe8114d102
[kamenim/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        This function does not handle exceptions and transaction on purpose,
753        it's up to the caller to do this job.
754
755     :param path: Path to the secrets database.
756     :param setup_path: Get the path to a setup file.
757     :param session_info: Session info.
758     :param credentials: Credentials
759     :param lp: Loadparm context
760     :return: LDB handle for the created secrets database
761     """
762     if os.path.exists(path):
763         os.unlink(path)
764     secrets_ldb = Ldb(path, session_info=session_info, 
765                       lp=lp)
766     secrets_ldb.erase()
767     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
768     secrets_ldb = Ldb(path, session_info=session_info, 
769                       lp=lp)
770     secrets_ldb.transaction_start()
771     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
772
773     if backend_credentials is not None and backend_credentials.authentication_requested():
774         if backend_credentials.get_bind_dn() is not None:
775             setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
776                     "LDAPMANAGERDN": backend_credentials.get_bind_dn(),
777                     "LDAPMANAGERPASS_B64": b64encode(backend_credentials.get_password())
778                     })
779         else:
780             setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
781                     "LDAPADMINUSER": backend_credentials.get_username(),
782                     "LDAPADMINREALM": backend_credentials.get_realm(),
783                     "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
784                     })
785
786     return secrets_ldb
787
788 def setup_privileges(path, setup_path, session_info, lp):
789     """Setup the privileges database.
790
791     :param path: Path to the privileges database.
792     :param setup_path: Get the path to a setup file.
793     :param session_info: Session info.
794     :param credentials: Credentials
795     :param lp: Loadparm context
796     :return: LDB handle for the created secrets database
797     """
798     if os.path.exists(path):
799         os.unlink(path)
800     privilege_ldb = Ldb(path, session_info=session_info, lp=lp)
801     privilege_ldb.erase()
802     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
803
804
805 def setup_registry(path, setup_path, session_info, lp):
806     """Setup the registry.
807     
808     :param path: Path to the registry database
809     :param setup_path: Function that returns the path to a setup.
810     :param session_info: Session information
811     :param credentials: Credentials
812     :param lp: Loadparm context
813     """
814     reg = samba.registry.Registry()
815     hive = samba.registry.open_ldb(path, session_info=session_info, 
816                          lp_ctx=lp)
817     reg.mount_hive(hive, samba.registry.HKEY_LOCAL_MACHINE)
818     provision_reg = setup_path("provision.reg")
819     assert os.path.exists(provision_reg)
820     reg.diff_apply(provision_reg)
821
822
823 def setup_idmapdb(path, setup_path, session_info, lp):
824     """Setup the idmap database.
825
826     :param path: path to the idmap database
827     :param setup_path: Function that returns a path to a setup file
828     :param session_info: Session information
829     :param credentials: Credentials
830     :param lp: Loadparm context
831     """
832     if os.path.exists(path):
833         os.unlink(path)
834
835     idmap_ldb = IDmapDB(path, session_info=session_info,
836                         lp=lp)
837
838     idmap_ldb.erase()
839     idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
840     return idmap_ldb
841
842
843 def setup_samdb_rootdse(samdb, setup_path, names):
844     """Setup the SamDB rootdse.
845
846     :param samdb: Sam Database handle
847     :param setup_path: Obtain setup path
848     """
849     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
850         "SCHEMADN": names.schemadn, 
851         "NETBIOSNAME": names.netbiosname,
852         "DNSDOMAIN": names.dnsdomain,
853         "REALM": names.realm,
854         "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
855         "DOMAINDN": names.domaindn,
856         "ROOTDN": names.rootdn,
857         "CONFIGDN": names.configdn,
858         "SERVERDN": names.serverdn,
859         })
860         
861
862 def setup_self_join(samdb, names,
863                     machinepass, dnspass, 
864                     domainsid, invocationid, setup_path,
865                     policyguid, policyguid_dc, domainControllerFunctionality,
866                     ntdsguid):
867     """Join a host to its own domain."""
868     assert isinstance(invocationid, str)
869     if ntdsguid is not None:
870         ntdsguid_line = "objectGUID: %s\n"%ntdsguid
871     else:
872         ntdsguid_line = ""
873     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
874               "CONFIGDN": names.configdn, 
875               "SCHEMADN": names.schemadn,
876               "DOMAINDN": names.domaindn,
877               "SERVERDN": names.serverdn,
878               "INVOCATIONID": invocationid,
879               "NETBIOSNAME": names.netbiosname,
880               "DEFAULTSITE": names.sitename,
881               "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
882               "MACHINEPASS_B64": b64encode(machinepass),
883               "REALM": names.realm,
884               "DOMAIN": names.domain,
885               "DOMAINSID": str(domainsid),
886               "DNSDOMAIN": names.dnsdomain,
887               "SAMBA_VERSION_STRING": version,
888               "NTDSGUID": ntdsguid_line,
889               "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
890
891     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
892               "POLICYGUID": policyguid,
893               "POLICYGUID_DC": policyguid_dc,
894               "DNSDOMAIN": names.dnsdomain,
895               "DOMAINSID": str(domainsid),
896               "DOMAINDN": names.domaindn})
897     
898     # add the NTDSGUID based SPNs
899     ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
900     names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
901                                      expression="", scope=ldb.SCOPE_BASE)
902     assert isinstance(names.ntdsguid, str)
903
904     # Setup fSMORoleOwner entries to point at the newly created DC entry
905     setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
906               "DOMAIN": names.domain,
907               "DNSDOMAIN": names.dnsdomain,
908               "DOMAINDN": names.domaindn,
909               "CONFIGDN": names.configdn,
910               "SCHEMADN": names.schemadn, 
911               "DEFAULTSITE": names.sitename,
912               "SERVERDN": names.serverdn,
913               "NETBIOSNAME": names.netbiosname,
914               "NTDSGUID": names.ntdsguid,
915               "DNSPASS_B64": b64encode(dnspass),
916               })
917
918 def getpolicypath(sysvolpath, dnsdomain, guid):
919     if guid[0] != "{":
920         guid = "{%s}" % guid
921     policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
922     return policy_path
923
924 def create_gpo_struct(policy_path):
925     os.makedirs(policy_path, 0755)
926     open(os.path.join(policy_path, "GPT.INI"), 'w').write(
927                       "[General]\r\nVersion=65543")
928     os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
929     os.makedirs(os.path.join(policy_path, "USER"), 0755)
930
931
932 def setup_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
933     policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid)
934     create_gpo_struct(policy_path)
935
936     policy_path = getpolicypath(sysvolpath,dnsdomain,policyguid_dc)
937     create_gpo_struct(policy_path)
938
939
940 def setup_samdb(path, setup_path, session_info, provision_backend, lp, names,
941         logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
942         adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
943         serverrole, am_rodc=False, dom_for_fun_level=None, schema=None):
944     """Setup a complete SAM Database.
945     
946     :note: This will wipe the main SAM database file!
947     """
948
949     # ATTENTION: Do NOT change these default values without discussion with the
950     # team and/or release manager. They have a big impact on the whole program!
951     domainControllerFunctionality = DS_DC_FUNCTION_2008
952
953     if dom_for_fun_level is None:
954         dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
955
956     if dom_for_fun_level > domainControllerFunctionality:
957         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!")
958
959     domainFunctionality = dom_for_fun_level
960     forestFunctionality = dom_for_fun_level
961
962     # Also wipes the database
963     setup_samdb_partitions(path, setup_path, logger=logger, lp=lp,
964         provision_backend=provision_backend, session_info=session_info,
965         names=names, serverrole=serverrole, schema=schema)
966
967     if schema is None:
968         schema = Schema(setup_path, domainsid, schemadn=names.schemadn, serverdn=names.serverdn,
969                         am_rodc=am_rodc)
970
971     # Load the database, but don's load the global schema and don't connect quite yet
972     samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
973                   credentials=provision_backend.credentials, lp=lp, global_schema=False,
974                   am_rodc=am_rodc)
975
976     logger.info("Pre-loading the Samba 4 and AD schema")
977
978     # Load the schema from the one we computed earlier
979     samdb.set_schema(schema)
980
981     # And now we can connect to the DB - the schema won't be loaded from the DB
982     samdb.connect(path)
983
984     if fill == FILL_DRS:
985         return samdb
986         
987     samdb.transaction_start()
988     try:
989         # Set the domain functionality levels onto the database.
990         # Various module (the password_hash module in particular) need
991         # to know what level of AD we are emulating.
992
993         # These will be fixed into the database via the database
994         # modifictions below, but we need them set from the start.
995         samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
996         samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
997         samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
998
999         samdb.set_domain_sid(str(domainsid))
1000         samdb.set_invocation_id(invocationid)
1001         samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
1002
1003         logger.info("Adding DomainDN: %s" % names.domaindn)
1004
1005 #impersonate domain admin
1006         admin_session_info = admin_session(lp, str(domainsid))
1007         samdb.set_session_info(admin_session_info)
1008         if domainguid is not None:
1009             domainguid_line = "objectGUID: %s\n-" % domainguid
1010         else:
1011             domainguid_line = ""
1012
1013         descr = b64encode(get_domain_descriptor(domainsid))
1014         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
1015                 "DOMAINDN": names.domaindn,
1016                 "DOMAINGUID": domainguid_line,
1017                 "DESCRIPTOR": descr
1018                 })
1019
1020
1021         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
1022             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1023             "DOMAINSID": str(domainsid),
1024             "SCHEMADN": names.schemadn, 
1025             "NETBIOSNAME": names.netbiosname,
1026             "DEFAULTSITE": names.sitename,
1027             "CONFIGDN": names.configdn,
1028             "SERVERDN": names.serverdn,
1029             "POLICYGUID": policyguid,
1030             "DOMAINDN": names.domaindn,
1031             "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
1032             "SAMBA_VERSION_STRING": version
1033             })
1034
1035         logger.info("Adding configuration container")
1036         descr = b64encode(get_config_descriptor(domainsid))
1037         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
1038             "CONFIGDN": names.configdn, 
1039             "DESCRIPTOR": descr,
1040             })
1041
1042         # The LDIF here was created when the Schema object was constructed
1043         logger.info("Setting up sam.ldb schema")
1044         samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
1045         samdb.modify_ldif(schema.schema_dn_modify)
1046         samdb.write_prefixes_from_schema()
1047         samdb.add_ldif(schema.schema_data, controls=["relax:0"])
1048         setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"), 
1049                        {"SCHEMADN": names.schemadn})
1050
1051         logger.info("Reopening sam.ldb with new schema")
1052     except:
1053         samdb.transaction_cancel()
1054         raise
1055     else:
1056         samdb.transaction_commit()
1057
1058     samdb = SamDB(session_info=admin_session_info,
1059                 credentials=provision_backend.credentials, lp=lp,
1060                 global_schema=False, am_rodc=am_rodc)
1061     samdb.connect(path)
1062     samdb.transaction_start()
1063     try:
1064         samdb.invocation_id = invocationid
1065
1066         logger.info("Setting up sam.ldb configuration data")
1067         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
1068             "CONFIGDN": names.configdn,
1069             "NETBIOSNAME": names.netbiosname,
1070             "DEFAULTSITE": names.sitename,
1071             "DNSDOMAIN": names.dnsdomain,
1072             "DOMAIN": names.domain,
1073             "SCHEMADN": names.schemadn,
1074             "DOMAINDN": names.domaindn,
1075             "SERVERDN": names.serverdn,
1076             "FOREST_FUNCTIONALITY": str(forestFunctionality),
1077             "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
1078             })
1079
1080         logger.info("Setting up display specifiers")
1081         display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1082         display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
1083         check_all_substituted(display_specifiers_ldif)
1084         samdb.add_ldif(display_specifiers_ldif)
1085
1086         logger.info("Adding users container")
1087         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
1088                 "DOMAINDN": names.domaindn})
1089         logger.info("Modifying users container")
1090         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
1091                 "DOMAINDN": names.domaindn})
1092         logger.info("Adding computers container")
1093         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
1094                 "DOMAINDN": names.domaindn})
1095         logger.info("Modifying computers container")
1096         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
1097                 "DOMAINDN": names.domaindn})
1098         logger.info("Setting up sam.ldb data")
1099         setup_add_ldif(samdb, setup_path("provision.ldif"), {
1100             "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
1101             "DOMAINDN": names.domaindn,
1102             "NETBIOSNAME": names.netbiosname,
1103             "DEFAULTSITE": names.sitename,
1104             "CONFIGDN": names.configdn,
1105             "SERVERDN": names.serverdn,
1106             "POLICYGUID_DC": policyguid_dc
1107             })
1108
1109         setup_modify_ldif(samdb, setup_path("provision_basedn_references.ldif"), {
1110                 "DOMAINDN": names.domaindn})
1111
1112         setup_modify_ldif(samdb, setup_path("provision_configuration_references.ldif"), {
1113                 "CONFIGDN": names.configdn,
1114                 "SCHEMADN": names.schemadn})
1115         if fill == FILL_FULL:
1116             logger.info("Setting up sam.ldb users and groups")
1117             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
1118                 "DOMAINDN": names.domaindn,
1119                 "DOMAINSID": str(domainsid),
1120                 "CONFIGDN": names.configdn,
1121                 "ADMINPASS_B64": b64encode(adminpass),
1122                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1123                 })
1124
1125             logger.info("Setting up self join")
1126             setup_self_join(samdb, names=names, invocationid=invocationid,
1127                             dnspass=dnspass,
1128                             machinepass=machinepass,
1129                             domainsid=domainsid, policyguid=policyguid,
1130                             policyguid_dc=policyguid_dc,
1131                             setup_path=setup_path,
1132                             domainControllerFunctionality=domainControllerFunctionality,
1133                             ntdsguid=ntdsguid)
1134
1135             ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (names.hostname, names.sitename, names.domaindn)
1136             names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1137                 attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
1138             assert isinstance(names.ntdsguid, str)
1139     except:
1140         samdb.transaction_cancel()
1141         raise
1142     else:
1143         samdb.transaction_commit()
1144         return samdb
1145
1146
1147 FILL_FULL = "FULL"
1148 FILL_NT4SYNC = "NT4SYNC"
1149 FILL_DRS = "DRS"
1150 SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
1151 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)"
1152
1153 def set_dir_acl(path, acl, lp, domsid):
1154     setntacl(lp, path, acl, domsid)
1155     for root, dirs, files in os.walk(path, topdown=False):
1156         for name in files:
1157             setntacl(lp, os.path.join(root, name), acl, domsid)
1158         for name in dirs:
1159             setntacl(lp, os.path.join(root, name), acl, domsid)
1160
1161
1162 def set_gpo_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
1163     # Set ACL for GPO
1164     policy_path = os.path.join(sysvol, dnsdomain, "Policies")
1165     set_dir_acl(policy_path,dsacl2fsacl(POLICIES_ACL, str(domainsid)), 
1166         lp, str(domainsid))
1167     res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
1168                         attrs=["cn", "nTSecurityDescriptor"],
1169                         expression="", scope=ldb.SCOPE_ONELEVEL)
1170     for policy in res:
1171         acl = ndr_unpack(security.descriptor, 
1172                          str(policy["nTSecurityDescriptor"])).as_sddl()
1173         policy_path = getpolicypath(sysvol,dnsdomain,str(policy["cn"]))
1174         set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp, 
1175                     str(domainsid))
1176
1177 def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
1178     lp):
1179     try:
1180         os.chown(sysvol,-1,gid)
1181     except:
1182         canchown = False
1183     else:
1184         canchown = True
1185
1186     setntacl(lp,sysvol,SYSVOL_ACL,str(domainsid))
1187     for root, dirs, files in os.walk(sysvol, topdown=False):
1188         for name in files:
1189             if canchown:
1190                 os.chown(os.path.join(root, name),-1,gid)
1191             setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1192         for name in dirs:
1193             if canchown:
1194                 os.chown(os.path.join(root, name),-1,gid)
1195             setntacl(lp,os.path.join(root, name),SYSVOL_ACL,str(domainsid))
1196     set_gpo_acl(sysvol,dnsdomain,domainsid,domaindn,samdb,lp)
1197
1198
1199 def provision(setup_dir, logger, session_info, 
1200               credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1201               realm=None, 
1202               rootdn=None, domaindn=None, schemadn=None, configdn=None, 
1203               serverdn=None,
1204               domain=None, hostname=None, hostip=None, hostip6=None, 
1205               domainsid=None, adminpass=None, ldapadminpass=None, 
1206               krbtgtpass=None, domainguid=None, 
1207               policyguid=None, policyguid_dc=None, invocationid=None,
1208               machinepass=None, ntdsguid=None,
1209               dnspass=None, root=None, nobody=None, users=None, 
1210               wheel=None, backup=None, aci=None, serverrole=None,
1211               dom_for_fun_level=None,
1212               ldap_backend_extra_port=None, backend_type=None,
1213               sitename=None,
1214               ol_mmr_urls=None, ol_olc=None, 
1215               setup_ds_path=None, slapd_path=None, nosync=False,
1216               ldap_dryrun_mode=False, useeadb=False, am_rodc=False):
1217     """Provision samba4
1218     
1219     :note: caution, this wipes all existing data!
1220     """
1221
1222     def setup_path(file):
1223       return os.path.join(setup_dir, file)
1224
1225     if domainsid is None:
1226       domainsid = security.random_sid()
1227     else:
1228       domainsid = security.dom_sid(domainsid)
1229
1230     # create/adapt the group policy GUIDs
1231     if policyguid is None:
1232         policyguid = str(uuid.uuid4())
1233     policyguid = policyguid.upper()
1234     if policyguid_dc is None:
1235         policyguid_dc = str(uuid.uuid4())
1236     policyguid_dc = policyguid_dc.upper()
1237
1238     if adminpass is None:
1239         adminpass = samba.generate_random_password(12, 32)
1240     if krbtgtpass is None:
1241         krbtgtpass = samba.generate_random_password(128, 255)
1242     if machinepass is None:
1243         machinepass  = samba.generate_random_password(128, 255)
1244     if dnspass is None:
1245         dnspass = samba.generate_random_password(128, 255)
1246     if ldapadminpass is None:
1247         #Make a new, random password between Samba and it's LDAP server
1248         ldapadminpass=samba.generate_random_password(128, 255)
1249
1250     if backend_type is None:
1251         backend_type = "ldb"
1252
1253     sid_generator = "internal"
1254     if backend_type == "fedora-ds":
1255         sid_generator = "backend"
1256
1257     root_uid = findnss_uid([root or "root"])
1258     nobody_uid = findnss_uid([nobody or "nobody"])
1259     users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
1260     if wheel is None:
1261         wheel_gid = findnss_gid(["wheel", "adm"])
1262     else:
1263         wheel_gid = findnss_gid([wheel])
1264     try:
1265         bind_gid = findnss_gid(["bind", "named"])
1266     except KeyError:
1267         bind_gid = None
1268
1269     if targetdir is not None:
1270         smbconf = os.path.join(targetdir, "etc", "smb.conf")
1271     elif smbconf is None:
1272         smbconf = samba.param.default_path()
1273     if not os.path.exists(os.path.dirname(smbconf)):
1274         os.makedirs(os.path.dirname(smbconf))
1275
1276     # only install a new smb.conf if there isn't one there already
1277     if os.path.exists(smbconf):
1278         # if Samba Team members can't figure out the weird errors
1279         # loading an empty smb.conf gives, then we need to be smarter.
1280         # Pretend it just didn't exist --abartlet
1281         data = open(smbconf, 'r').read()
1282         data = data.lstrip()
1283         if data is None or data == "":
1284             make_smbconf(smbconf, setup_path, hostname, domain, realm,
1285                          serverrole, targetdir, sid_generator, useeadb)
1286     else: 
1287         make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, 
1288                      targetdir, sid_generator, useeadb)
1289
1290     lp = samba.param.LoadParm()
1291     lp.load(smbconf)
1292
1293     names = guess_names(lp=lp, hostname=hostname, domain=domain,
1294                         dnsdomain=realm, serverrole=serverrole,
1295                         domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1296                         serverdn=serverdn, sitename=sitename)
1297
1298     paths = provision_paths_from_lp(lp, names.dnsdomain)
1299
1300     paths.bind_gid = bind_gid
1301
1302     if hostip is None:
1303         hostips = samba.interface_ips(lp, False)
1304         if len(hostips) == 0:
1305             logger.warning("No external IPv4 address has been found. Using loopback.")
1306             hostip = '127.0.0.1'
1307         else:
1308             hostip = hostips[0]
1309             if len(hostips) > 1:
1310                 logger.warning("More than one IPv4 address found. Using %s.", hostip)
1311
1312     if hostip6 is None:
1313         try:
1314             for ip in socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP):
1315                 if hostip6 is None:
1316                     hostip6 = ip[-1][0]
1317                 if hostip6 == '::1' and ip[-1][0] != '::1':
1318                     hostip6 = ip[-1][0]
1319         except socket.gaierror, (socket.EAI_NODATA, msg): 
1320             hostip6 = None
1321
1322     if serverrole is None:
1323         serverrole = lp.get("server role")
1324
1325     assert serverrole in ("domain controller", "member server", "standalone")
1326     if invocationid is None:
1327         invocationid = str(uuid.uuid4())
1328
1329     if not os.path.exists(paths.private_dir):
1330         os.mkdir(paths.private_dir)
1331     if not os.path.exists(os.path.join(paths.private_dir, "tls")):
1332         os.mkdir(os.path.join(paths.private_dir, "tls"))
1333
1334     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1335  
1336     schema = Schema(setup_path, domainsid, invocationid=invocationid, schemadn=names.schemadn,
1337                     serverdn=names.serverdn, am_rodc=am_rodc)
1338
1339     if backend_type == "ldb":
1340         provision_backend = LDBBackend(backend_type,
1341                                          paths=paths, setup_path=setup_path,
1342                                          lp=lp, credentials=credentials, 
1343                                          names=names,
1344                                          logger=logger)
1345     elif backend_type == "existing":
1346         provision_backend = ExistingBackend(backend_type,
1347                                          paths=paths, setup_path=setup_path,
1348                                          lp=lp, credentials=credentials, 
1349                                          names=names,
1350                                          logger=logger,
1351                                          ldapi_url=ldapi_url)
1352     elif backend_type == "fedora-ds":
1353         provision_backend = FDSBackend(backend_type,
1354                                          paths=paths, setup_path=setup_path,
1355                                          lp=lp, credentials=credentials, 
1356                                          names=names,
1357                                          logger=logger,
1358                                          domainsid=domainsid,
1359                                          schema=schema,
1360                                          hostname=hostname,
1361                                          ldapadminpass=ldapadminpass,
1362                                          slapd_path=slapd_path,
1363                                          ldap_backend_extra_port=ldap_backend_extra_port,
1364                                          ldap_dryrun_mode=ldap_dryrun_mode,
1365                                          root=root,
1366                                          setup_ds_path=setup_ds_path)
1367     elif backend_type == "openldap":
1368         provision_backend = OpenLDAPBackend(backend_type,
1369                                          paths=paths, setup_path=setup_path,
1370                                          lp=lp, credentials=credentials, 
1371                                          names=names,
1372                                          logger=logger,
1373                                          domainsid=domainsid,
1374                                          schema=schema,
1375                                          hostname=hostname,
1376                                          ldapadminpass=ldapadminpass,
1377                                          slapd_path=slapd_path,
1378                                          ldap_backend_extra_port=ldap_backend_extra_port,
1379                                          ldap_dryrun_mode=ldap_dryrun_mode,
1380                                          ol_mmr_urls=ol_mmr_urls, 
1381                                          nosync=nosync)
1382     else:
1383         raise ValueError("Unknown LDAP backend type selected")
1384
1385     provision_backend.init()
1386     provision_backend.start()
1387
1388     # only install a new shares config db if there is none
1389     if not os.path.exists(paths.shareconf):
1390         logger.info("Setting up share.ldb")
1391         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
1392                         lp=lp)
1393         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1394
1395      
1396     logger.info("Setting up secrets.ldb")
1397     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
1398         session_info=session_info,
1399         backend_credentials=provision_backend.secrets_credentials, lp=lp)
1400
1401     logger.info("Setting up the registry")
1402     setup_registry(paths.hklm, setup_path, session_info, 
1403                    lp=lp)
1404
1405     logger.info("Setting up the privileges database")
1406     setup_privileges(paths.privilege, setup_path, session_info, lp=lp)
1407
1408     logger.info("Setting up idmap db")
1409     idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1410                           lp=lp)
1411
1412     logger.info("Setting up SAM db")
1413     samdb = setup_samdb(paths.samdb, setup_path, session_info, 
1414                         provision_backend, lp, names,
1415                         logger=logger, 
1416                         domainsid=domainsid, 
1417                         schema=schema, domainguid=domainguid,
1418                         policyguid=policyguid, policyguid_dc=policyguid_dc,
1419                         fill=samdb_fill, 
1420                         adminpass=adminpass, krbtgtpass=krbtgtpass,
1421                         invocationid=invocationid, 
1422                         machinepass=machinepass, dnspass=dnspass, 
1423                         ntdsguid=ntdsguid, serverrole=serverrole,
1424                         dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc)
1425
1426     if serverrole == "domain controller":
1427         if paths.netlogon is None:
1428             logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1429             logger.info("Please either remove %s or see the template at %s" % 
1430                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1431             assert paths.netlogon is not None
1432
1433         if paths.sysvol is None:
1434             logger.info("Existing smb.conf does not have a [sysvol] share, but you"
1435                     " are configuring a DC.")
1436             logger.info("Please either remove %s or see the template at %s" % 
1437                     (paths.smbconf, setup_path("provision.smb.conf.dc")))
1438             assert paths.sysvol is not None
1439
1440         if not os.path.isdir(paths.netlogon):
1441             os.makedirs(paths.netlogon, 0755)
1442
1443     if samdb_fill == FILL_FULL:
1444         setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1445                             root_uid=root_uid, nobody_uid=nobody_uid,
1446                             users_gid=users_gid, wheel_gid=wheel_gid)
1447
1448         if serverrole == "domain controller":
1449             # Set up group policies (domain policy and domain controller policy)
1450             setup_gpo(paths.sysvol, names.dnsdomain, policyguid, policyguid_dc)
1451             setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid, 
1452                          domainsid, names.dnsdomain, names.domaindn, lp)
1453
1454         logger.info("Setting up sam.ldb rootDSE marking as synchronized")
1455         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1456
1457         secretsdb_self_join(secrets_ldb, domain=names.domain,
1458                             realm=names.realm,
1459                             dnsdomain=names.dnsdomain,
1460                             netbiosname=names.netbiosname,
1461                             domainsid=domainsid, 
1462                             machinepass=machinepass,
1463                             secure_channel_type=SEC_CHAN_BDC)
1464
1465         if serverrole == "domain controller":
1466             secretsdb_setup_dns(secrets_ldb, setup_path,
1467                                 paths.private_dir,
1468                                 realm=names.realm, dnsdomain=names.dnsdomain,
1469                                 dns_keytab_path=paths.dns_keytab,
1470                                 dnspass=dnspass)
1471
1472             domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1473             assert isinstance(domainguid, str)
1474
1475             # Only make a zone file on the first DC, it should be replicated
1476             # with DNS replication
1477             create_zone_file(lp, logger, paths, targetdir, setup_path,
1478                 dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
1479                 hostname=names.hostname, realm=names.realm, 
1480                 domainguid=domainguid, ntdsguid=names.ntdsguid)
1481
1482             create_named_conf(paths, setup_path, realm=names.realm,
1483                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1484
1485             create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1486                               dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1487                               keytab_name=paths.dns_keytab)
1488             logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
1489             logger.info("and %s for further documentation required for secure DNS "
1490                     "updates", paths.namedtxt)
1491
1492             create_krb5_conf(paths.krb5conf, setup_path,
1493                              dnsdomain=names.dnsdomain, hostname=names.hostname,
1494                              realm=names.realm)
1495             logger.info("A Kerberos configuration suitable for Samba 4 has been "
1496                     "generated at %s", paths.krb5conf)
1497
1498         lastProvisionUSNs = get_last_provision_usn(samdb)
1499         maxUSN = get_max_usn(samdb, str(names.rootdn))
1500         if lastProvisionUSNs is not None:
1501             update_provision_usn(samdb, 0, maxUSN, 1)
1502         else:
1503             set_provision_usn(samdb, 0, maxUSN)
1504
1505     if serverrole == "domain controller":
1506         create_dns_update_list(lp, logger, paths, setup_path)
1507
1508     provision_backend.post_setup()
1509     provision_backend.shutdown()
1510     
1511     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
1512                                ldapi_url)
1513
1514     #Now commit the secrets.ldb to disk
1515     secrets_ldb.transaction_commit()
1516
1517     # the commit creates the dns.keytab, now chown it
1518     dns_keytab_path = os.path.join(paths.private_dir, paths.dns_keytab)
1519     if (os.path.isfile(dns_keytab_path) and paths.bind_gid is not None):
1520         try:
1521             os.chmod(dns_keytab_path, 0640)
1522             os.chown(dns_keytab_path, -1, paths.bind_gid)
1523         except OSError:
1524             logger.info("Failed to chown %s to bind gid %u", dns_keytab_path,
1525                 paths.bind_gid)
1526
1527
1528     logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
1529             paths.phpldapadminconfig)
1530
1531     logger.info("Once the above files are installed, your Samba4 server will be ready to use")
1532     logger.info("Server Role:           %s" % serverrole)
1533     logger.info("Hostname:              %s" % names.hostname)
1534     logger.info("NetBIOS Domain:        %s" % names.domain)
1535     logger.info("DNS Domain:            %s" % names.dnsdomain)
1536     logger.info("DOMAIN SID:            %s" % str(domainsid))
1537     if samdb_fill == FILL_FULL:
1538         logger.info("Admin password:        %s" % adminpass)
1539     if provision_backend.type is not "ldb":
1540         if provision_backend.credentials.get_bind_dn() is not None:
1541             logger.info("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1542         else:
1543             logger.info("LDAP Admin User:       %s" % provision_backend.credentials.get_username())
1544
1545         logger.info("LDAP Admin Password:   %s" % provision_backend.credentials.get_password())
1546
1547         if provision_backend.slapd_command_escaped is not None:
1548             # now display slapd_command_file.txt to show how slapd must be started next time
1549             logger.info("Use later the following commandline to start slapd, then Samba:")
1550             logger.info(provision_backend.slapd_command_escaped)
1551             logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh", 
1552                     provision_backend.ldapdir)
1553
1554     result = ProvisionResult()
1555     result.domaindn = domaindn
1556     result.paths = paths
1557     result.lp = lp
1558     result.samdb = samdb
1559     return result
1560
1561
1562 def provision_become_dc(setup_dir=None,
1563                         smbconf=None, targetdir=None, realm=None, 
1564                         rootdn=None, domaindn=None, schemadn=None,
1565                         configdn=None, serverdn=None,
1566                         domain=None, hostname=None, domainsid=None, 
1567                         adminpass=None, krbtgtpass=None, domainguid=None, 
1568                         policyguid=None, policyguid_dc=None, invocationid=None,
1569                         machinepass=None, 
1570                         dnspass=None, root=None, nobody=None, users=None, 
1571                         wheel=None, backup=None, serverrole=None, 
1572                         ldap_backend=None, ldap_backend_type=None,
1573                         sitename=None, debuglevel=1):
1574
1575     logger = logging.getLogger("provision")
1576     samba.set_debug_level(debuglevel)
1577
1578     return provision(setup_dir, logger, system_session(), None,
1579               smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1580               realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1581               configdn=configdn, serverdn=serverdn, domain=domain,
1582               hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1583               machinepass=machinepass, serverrole="domain controller",
1584               sitename=sitename)
1585
1586
1587 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1588     """Create a PHP LDAP admin configuration file.
1589
1590     :param path: Path to write the configuration to.
1591     :param setup_path: Function to generate setup paths.
1592     """
1593     setup_file(setup_path("phpldapadmin-config.php"), path, 
1594             {"S4_LDAPI_URI": ldapi_uri})
1595
1596
1597 def create_zone_file(lp, logger, paths, targetdir, setup_path, dnsdomain,
1598                      hostip, hostip6, hostname, realm, domainguid,
1599                      ntdsguid):
1600     """Write out a DNS zone file, from the info in the current database.
1601
1602     :param paths: paths object
1603     :param setup_path: Setup path function.
1604     :param dnsdomain: DNS Domain name
1605     :param domaindn: DN of the Domain
1606     :param hostip: Local IPv4 IP
1607     :param hostip6: Local IPv6 IP
1608     :param hostname: Local hostname
1609     :param realm: Realm name
1610     :param domainguid: GUID of the domain.
1611     :param ntdsguid: GUID of the hosts nTDSDSA record.
1612     """
1613     assert isinstance(domainguid, str)
1614
1615     if hostip6 is not None:
1616         hostip6_base_line = "            IN AAAA    " + hostip6
1617         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
1618         gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
1619     else:
1620         hostip6_base_line = ""
1621         hostip6_host_line = ""
1622         gc_msdcs_ip6_line = ""
1623
1624     if hostip is not None:
1625         hostip_base_line = "            IN A    " + hostip
1626         hostip_host_line = hostname + "        IN A    " + hostip
1627         gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
1628     else:
1629         hostip_base_line = ""
1630         hostip_host_line = ""
1631         gc_msdcs_ip_line = ""
1632
1633     dns_dir = os.path.dirname(paths.dns)
1634
1635     try:
1636         shutil.rmtree(dns_dir, True)
1637     except OSError:
1638         pass
1639
1640     os.mkdir(dns_dir, 0775)
1641
1642     # we need to freeze the zone while we update the contents
1643     if targetdir is None:
1644         rndc = ' '.join(lp.get("rndc command"))
1645         os.system(rndc + " freeze " + lp.get("realm"))
1646
1647     setup_file(setup_path("provision.zone"), paths.dns, {
1648             "HOSTNAME": hostname,
1649             "DNSDOMAIN": dnsdomain,
1650             "REALM": realm,
1651             "HOSTIP_BASE_LINE": hostip_base_line,
1652             "HOSTIP_HOST_LINE": hostip_host_line,
1653             "DOMAINGUID": domainguid,
1654             "DATESTRING": time.strftime("%Y%m%d%H"),
1655             "DEFAULTSITE": DEFAULTSITE,
1656             "NTDSGUID": ntdsguid,
1657             "HOSTIP6_BASE_LINE": hostip6_base_line,
1658             "HOSTIP6_HOST_LINE": hostip6_host_line,
1659             "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
1660             "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
1661         })
1662
1663     # note that we use no variable substitution on this file
1664     # the substitution is done at runtime by samba_dnsupdate
1665     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1666
1667     # and the SPN update list
1668     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1669
1670     if paths.bind_gid is not None:
1671         try:
1672             os.chown(dns_dir, -1, paths.bind_gid)
1673             os.chown(paths.dns, -1, paths.bind_gid)
1674             # chmod needed to cope with umask
1675             os.chmod(dns_dir, 0775)
1676             os.chmod(paths.dns, 0664)
1677         except OSError:
1678             logger.error("Failed to chown %s to bind gid %u" % (dns_dir, paths.bind_gid))
1679
1680     if targetdir is None:
1681         os.system(rndc + " unfreeze " + lp.get("realm"))
1682
1683
1684 def create_dns_update_list(lp, logger, paths, setup_path):
1685     """Write out a dns_update_list file"""
1686     # note that we use no variable substitution on this file
1687     # the substitution is done at runtime by samba_dnsupdate
1688     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
1689     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
1690
1691
1692 def create_named_conf(paths, setup_path, realm, dnsdomain,
1693                       private_dir):
1694     """Write out a file containing zone statements suitable for inclusion in a
1695     named.conf file (including GSS-TSIG configuration).
1696     
1697     :param paths: all paths
1698     :param setup_path: Setup path function.
1699     :param realm: Realm name
1700     :param dnsdomain: DNS Domain name
1701     :param private_dir: Path to private directory
1702     :param keytab_name: File name of DNS keytab file
1703     """
1704
1705     setup_file(setup_path("named.conf"), paths.namedconf, {
1706             "DNSDOMAIN": dnsdomain,
1707             "REALM": realm,
1708             "ZONE_FILE": paths.dns,
1709             "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1710             "NAMED_CONF": paths.namedconf,
1711             "NAMED_CONF_UPDATE": paths.namedconf_update
1712             })
1713
1714     setup_file(setup_path("named.conf.update"), paths.namedconf_update)
1715
1716
1717 def create_named_txt(path, setup_path, realm, dnsdomain,
1718                       private_dir, keytab_name):
1719     """Write out a file containing zone statements suitable for inclusion in a
1720     named.conf file (including GSS-TSIG configuration).
1721     
1722     :param path: Path of the new named.conf file.
1723     :param setup_path: Setup path function.
1724     :param realm: Realm name
1725     :param dnsdomain: DNS Domain name
1726     :param private_dir: Path to private directory
1727     :param keytab_name: File name of DNS keytab file
1728     """
1729
1730     setup_file(setup_path("named.txt"), path, {
1731             "DNSDOMAIN": dnsdomain,
1732             "REALM": realm,
1733             "DNS_KEYTAB": keytab_name,
1734             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1735             "PRIVATE_DIR": private_dir
1736         })
1737
1738
1739 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1740     """Write out a file containing zone statements suitable for inclusion in a
1741     named.conf file (including GSS-TSIG configuration).
1742     
1743     :param path: Path of the new named.conf file.
1744     :param setup_path: Setup path function.
1745     :param dnsdomain: DNS Domain name
1746     :param hostname: Local hostname
1747     :param realm: Realm name
1748     """
1749     setup_file(setup_path("krb5.conf"), path, {
1750             "DNSDOMAIN": dnsdomain,
1751             "HOSTNAME": hostname,
1752             "REALM": realm,
1753         })
1754
1755
1756 class ProvisioningError(Exception):
1757     """A generic provision error."""
1758
1759     def __init__(self, value):
1760         self.value = value
1761
1762     def __str__(self):
1763         return "ProvisioningError: " + self.value
1764
1765
1766 class InvalidNetbiosName(Exception):
1767     """A specified name was not a valid NetBIOS name."""
1768     def __init__(self, name):
1769         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)