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