Merge branch 'v4-0-test' of ssh://git.samba.org/data/git/samba into v4-0-trivial
[ira/wip.git] / source / scripting / python / samba / provision.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
7 #
8 # Based on the original in EJS:
9 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #   
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #   
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #
24
25 from base64 import b64encode
26 import os
27 import pwd
28 import grp
29 import time
30 import uuid, misc
31 from socket import gethostname, gethostbyname
32 import param
33 import registry
34 import samba
35 from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted
36 from samba.samdb import SamDB
37 import security
38 import urllib
39 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
40         LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
41
42 """Functions for setting up a Samba configuration."""
43
44 DEFAULTSITE = "Default-First-Site-Name"
45
46 class InvalidNetbiosName(Exception):
47     def __init__(self, name):
48         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
49
50
51 class ProvisionPaths:
52     def __init__(self):
53         self.smbconf = None
54         self.shareconf = None
55         self.hklm = None
56         self.hkcu = None
57         self.hkcr = None
58         self.hku = None
59         self.hkpd = None
60         self.hkpt = None
61         self.samdb = None
62         self.secrets = None
63         self.keytab = None
64         self.dns_keytab = None
65         self.dns = None
66         self.winsdb = None
67         self.ldap_basedn_ldif = None
68         self.ldap_config_basedn_ldif = None
69         self.ldap_schema_basedn_ldif = None
70
71
72 def check_install(lp, session_info, credentials):
73     """Check whether the current install seems ok.
74     
75     :param lp: Loadparm context
76     :param session_info: Session information
77     :param credentials: Credentials
78     """
79     if lp.get("realm") == "":
80         raise Error("Realm empty")
81     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
82             credentials=credentials, lp=lp)
83     if len(ldb.search("(cn=Administrator)")) != 1:
84         raise "No administrator account found"
85
86
87 def findnss(nssfn, *names):
88     """Find a user or group from a list of possibilities."""
89     for name in names:
90         try:
91             return nssfn(name)
92         except KeyError:
93             pass
94     raise Exception("Unable to find user/group for %s" % arguments[1])
95
96
97 def open_ldb(session_info, credentials, lp, dbname):
98     """Open a LDB, thrashing it if it is corrupt.
99
100     :param session_info: auth session information
101     :param credentials: credentials
102     :param lp: Loadparm context
103     :param dbname: Path of the database to open.
104     :return: a Ldb object
105     """
106     assert session_info is not None
107     try:
108         return Ldb(dbname, session_info=session_info, credentials=credentials, 
109                    lp=lp)
110     except LdbError, e:
111         print e
112         os.unlink(dbname)
113         return Ldb(dbname, session_info=session_info, credentials=credentials,
114                    lp=lp)
115
116
117 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
118     """Setup a ldb in the private dir.
119     
120     :param ldb: LDB file to import data into
121     :param ldif_path: Path of the LDIF file to load
122     :param subst_vars: Optional variables to subsitute in LDIF.
123     """
124     assert isinstance(ldif_path, str)
125
126     data = open(ldif_path, 'r').read()
127     if subst_vars is not None:
128         data = substitute_var(data, subst_vars)
129
130     check_all_substituted(data)
131
132     ldb.add_ldif(data)
133
134
135 def setup_modify_ldif(ldb, ldif_path, substvars=None):
136     """Modify a ldb in the private dir.
137     
138     :param ldb: LDB object.
139     :param ldif_path: LDIF file path.
140     :param substvars: Optional dictionary with substitution variables.
141     """
142     data = open(ldif_path, 'r').read()
143     if substvars is not None:
144         data = substitute_var(data, substvars)
145
146     check_all_substituted(data)
147
148     ldb.modify_ldif(data)
149
150
151 def setup_ldb(ldb, ldif_path, subst_vars):
152     assert ldb is not None
153     ldb.transaction_start()
154     try:
155         setup_add_ldif(ldb, ldif_path, subst_vars)
156     except:
157         ldb.transaction_cancel()
158         raise
159     ldb.transaction_commit()
160
161
162 def setup_file(template, fname, substvars):
163     """Setup a file in the private dir.
164
165     :param template: Path of the template file.
166     :param fname: Path of the file to create.
167     :param substvars: Substitution variables.
168     """
169     f = fname
170
171     if os.path.exists(f):
172         os.unlink(f)
173
174     data = open(template, 'r').read()
175     if substvars:
176         data = substitute_var(data, substvars)
177     check_all_substituted(data)
178
179     open(f, 'w').write(data)
180
181
182 def provision_paths_from_lp(lp, dnsdomain, private_dir=None):
183     """Set the default paths for provisioning.
184
185     :param lp: Loadparm context.
186     :param dnsdomain: DNS Domain name
187     """
188     paths = ProvisionPaths()
189     if private_dir is None:
190         private_dir = lp.get("private dir")
191         paths.keytab = "secrets.keytab"
192         paths.dns_keytab = "dns.keytab"
193     else:
194         paths.keytab = os.path.join(private_dir, "secrets.keytab")
195         paths.dns_keytab = os.path.join(private_dir, "dns.keytab")
196
197     paths.shareconf = os.path.join(private_dir, "share.ldb")
198     paths.samdb = os.path.join(private_dir, lp.get("sam database") or "samdb.ldb")
199     paths.secrets = os.path.join(private_dir, lp.get("secrets database") or "secrets.ldb")
200     paths.templates = os.path.join(private_dir, "templates.ldb")
201     paths.dns = os.path.join(private_dir, dnsdomain + ".zone")
202     paths.winsdb = os.path.join(private_dir, "wins.ldb")
203     paths.s4_ldapi_path = os.path.join(private_dir, "ldapi")
204     paths.phpldapadminconfig = os.path.join(private_dir, 
205                                             "phpldapadmin-config.php")
206     paths.hklm = os.path.join(private_dir, "hklm.ldb")
207     paths.sysvol = lp.get("sysvol", "path")
208     if paths.sysvol is None:
209         paths.sysvol = os.path.join(lp.get("lock dir"), "sysvol")
210
211     paths.netlogon = lp.get("netlogon", "path")
212     if paths.netlogon is None:
213         paths.netlogon = os.path.join(os.path.join(paths.sysvol, "scripts"))
214
215     return paths
216
217
218 def setup_name_mappings(ldb, sid, domaindn, root, nobody, nogroup, users, 
219                         wheel, backup):
220     """setup reasonable name mappings for sam names to unix names.
221     
222     :param ldb: SamDB object.
223     :param sid: The domain sid.
224     :param domaindn: The domain DN.
225     :param root: Name of the UNIX root user.
226     :param nobody: Name of the UNIX nobody user.
227     :param nogroup: Name of the unix nobody group.
228     :param users: Name of the unix users group.
229     :param wheel: Name of the wheel group (users that can become root).
230     :param backup: Name of the backup group."""
231     # add some foreign sids if they are not present already
232     ldb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
233     ldb.add_foreign(domaindn, "S-1-1-0", "World")
234     ldb.add_foreign(domaindn, "S-1-5-2", "Network")
235     ldb.add_foreign(domaindn, "S-1-5-18", "System")
236     ldb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
237
238     # some well known sids
239     ldb.setup_name_mapping(domaindn, "S-1-5-7", nobody)
240     ldb.setup_name_mapping(domaindn, "S-1-1-0", nogroup)
241     ldb.setup_name_mapping(domaindn, "S-1-5-2", nogroup)
242     ldb.setup_name_mapping(domaindn, "S-1-5-18", root)
243     ldb.setup_name_mapping(domaindn, "S-1-5-11", users)
244     ldb.setup_name_mapping(domaindn, "S-1-5-32-544", wheel)
245     ldb.setup_name_mapping(domaindn, "S-1-5-32-545", users)
246     ldb.setup_name_mapping(domaindn, "S-1-5-32-546", nogroup)
247     ldb.setup_name_mapping(domaindn, "S-1-5-32-551", backup)
248
249     # and some well known domain rids
250     ldb.setup_name_mapping(domaindn, sid + "-500", root)
251     ldb.setup_name_mapping(domaindn, sid + "-518", wheel)
252     ldb.setup_name_mapping(domaindn, sid + "-519", wheel)
253     ldb.setup_name_mapping(domaindn, sid + "-512", wheel)
254     ldb.setup_name_mapping(domaindn, sid + "-513", users)
255     ldb.setup_name_mapping(domaindn, sid + "-520", wheel)
256
257
258 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info, 
259                            credentials, configdn, schemadn, domaindn, 
260                            hostname, netbiosname, dnsdomain, realm, 
261                            rootdn, serverrole, ldap_backend=None, 
262                            ldap_backend_type=None, erase=False):
263     """Setup the partitions for the SAM database. 
264     
265     Alternatively, provision() may call this, and then populate the database.
266     
267     :param erase: Remove the existing data present in the database.
268     :param
269      
270     :note: This will wipe the Sam Database!
271     
272     :note: This function always removes the local SAM LDB file. The erase 
273     parameter controls whether to erase the existing data, which 
274     may not be stored locally but in LDAP.
275     """
276     assert session_info is not None
277
278     if os.path.exists(samdb_path):
279         os.unlink(samdb_path)
280
281     # Also wipes the database
282     samdb = SamDB(samdb_path, session_info=session_info, 
283                   credentials=credentials, lp=lp)
284
285     #Add modules to the list to activate them by default
286     #beware often order is important
287     #
288     # Some Known ordering constraints:
289     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
290     # - objectclass must be before password_hash, because password_hash checks
291     #   that the objectclass is of type person (filled in by objectclass
292     #   module when expanding the objectclass list)
293     # - partition must be last
294     # - each partition has its own module list then
295     modules_list = ["rootdse",
296                     "paged_results",
297                     "ranged_results",
298                     "anr",
299                     "server_sort",
300                     "extended_dn",
301                     "asq",
302                     "samldb",
303                     "rdn_name",
304                     "objectclass",
305                     "kludge_acl",
306                     "operational"]
307     tdb_modules_list = [
308                     "subtree_rename",
309                     "subtree_delete",
310                     "linked_attributes"]
311     modules_list2 = ["show_deleted",
312                     "partition"]
313  
314     domaindn_ldb = "users.ldb"
315     if ldap_backend is not None:
316         domaindn_ldb = ldap_backend
317     configdn_ldb = "configuration.ldb"
318     if ldap_backend is not None:
319         configdn_ldb = ldap_backend
320     schema_ldb = "schema.ldb"
321     if ldap_backend is not None:
322         schema_ldb = ldap_backend
323         
324     if ldap_backend_type == "fedora-ds":
325         backend_modules = ["nsuniqueid","paged_searches"]
326     elif ldap_backend_type == "openldap":
327         backend_modules = ["normalise","entryuuid","paged_searches"]
328     elif serverrole == "domain controller":
329         backend_modules = ["repl_meta_data"]
330     else:
331         backend_modules = ["objectguid"]
332         
333     setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
334         "SCHEMADN": schemadn, 
335         "SCHEMADN_LDB": "schema.ldb",
336         "SCHEMADN_MOD2": ",objectguid",
337         "CONFIGDN": configdn,
338         "CONFIGDN_LDB": "configuration.ldb",
339         "DOMAINDN": domaindn,
340         "DOMAINDN_LDB": "users.ldb",
341         "SCHEMADN_MOD": "schema_fsmo,instancetype",
342         "CONFIGDN_MOD": "naming_fsmo,instancetype",
343         "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
344         "MODULES_LIST": ",".join(modules_list),
345         "TDB_MODULES_LIST": ","+",".join(tdb_modules_list),
346         "MODULES_LIST2": ",".join(modules_list2),
347         "BACKEND_MOD": ",".join(backend_modules),
348         })
349
350     samdb = SamDB(samdb_path, session_info=session_info, 
351                   credentials=credentials, lp=lp)
352
353     samdb.transaction_start()
354     try:
355         message("Setting up sam.ldb attributes")
356         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
357
358         message("Setting up sam.ldb rootDSE")
359         setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname, 
360                             dnsdomain, realm, rootdn, configdn, netbiosname)
361
362         if erase:
363             message("Erasing data from partitions")
364             samdb.erase_partitions()
365
366     except:
367         samdb.transaction_cancel()
368         raise
369
370     samdb.transaction_commit()
371     
372     return samdb
373
374
375 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, 
376                         netbiosname, domainsid, keytab_path, samdb_url, 
377                         dns_keytab_path, dnspass, machinepass):
378     """Add DC-specific bits to a secrets database.
379     
380     :param secretsdb: Ldb Handle to the secrets database
381     :param setup_path: Setup path function
382     :param machinepass: Machine password
383     """
384     setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { 
385             "MACHINEPASS_B64": b64encode(machinepass),
386             "DOMAIN": domain,
387             "REALM": realm,
388             "DNSDOMAIN": dnsdomain,
389             "DOMAINSID": str(domainsid),
390             "SECRETS_KEYTAB": keytab_path,
391             "NETBIOSNAME": netbiosname,
392             "SAM_LDB": samdb_url,
393             "DNS_KEYTAB": dns_keytab_path,
394             "DNSPASS_B64": b64encode(dnspass),
395             })
396
397
398 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
399     """Setup the secrets database.
400
401     :param path: Path to the secrets database.
402     :param setup_path: Get the path to a setup file.
403     :param session_info: Session info.
404     :param credentials: Credentials
405     :param lp: Loadparm context
406     :return: LDB handle for the created secrets database
407     """
408     if os.path.exists(path):
409         os.unlink(path)
410     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
411                       lp=lp)
412     secrets_ldb.erase()
413     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
414     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
415                       lp=lp)
416     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
417     return secrets_ldb
418
419
420 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
421     """Setup the templates database.
422
423     :param path: Path to the database.
424     :param setup_path: Function for obtaining the path to setup files.
425     :param session_info: Session info
426     :param credentials: Credentials
427     :param lp: Loadparm context
428     """
429     templates_ldb = SamDB(path, session_info=session_info,
430                           credentials=credentials, lp=lp)
431     templates_ldb.erase()
432     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
433
434
435 def setup_registry(path, setup_path, session_info, credentials, lp):
436     """Setup the registry.
437     
438     :param path: Path to the registry database
439     :param setup_path: Function that returns the path to a setup.
440     :param session_info: Session information
441     :param credentials: Credentials
442     :param lp: Loadparm context
443     """
444     reg = registry.Registry()
445     hive = registry.open_ldb(path, session_info=session_info, 
446                          credentials=credentials, lp_ctx=lp)
447     reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
448     provision_reg = setup_path("provision.reg")
449     assert os.path.exists(provision_reg)
450     reg.diff_apply(provision_reg)
451
452
453 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname, 
454                         dnsdomain, realm, rootdn, configdn, netbiosname):
455     """Setup the SamDB rootdse.
456
457     :param samdb: Sam Database handle
458     :param setup_path: Obtain setup path
459     ...
460     """
461     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
462         "SCHEMADN": schemadn, 
463         "NETBIOSNAME": netbiosname,
464         "DNSDOMAIN": dnsdomain,
465         "DEFAULTSITE": DEFAULTSITE,
466         "REALM": realm,
467         "DNSNAME": "%s.%s" % (hostname, dnsdomain),
468         "DOMAINDN": domaindn,
469         "ROOTDN": rootdn,
470         "CONFIGDN": configdn,
471         "VERSION": samba.version(),
472         })
473         
474
475 def setup_self_join(samdb, configdn, schemadn, domaindn, 
476                     netbiosname, hostname, dnsdomain, machinepass, dnspass, 
477                     realm, domainname, domainsid, invocationid, setup_path,
478                     policyguid, hostguid=None):
479     """Join a host to its own domain."""
480     if hostguid is not None:
481         hostguid_add = "objectGUID: %s" % hostguid
482     else:
483         hostguid_add = ""
484
485     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
486               "CONFIGDN": configdn, 
487               "SCHEMADN": schemadn,
488               "DOMAINDN": domaindn,
489               "INVOCATIONID": invocationid,
490               "NETBIOSNAME": netbiosname,
491               "DEFAULTSITE": DEFAULTSITE,
492               "DNSNAME": "%s.%s" % (hostname, dnsdomain),
493               "MACHINEPASS_B64": b64encode(machinepass),
494               "DNSPASS_B64": b64encode(dnspass),
495               "REALM": realm,
496               "DOMAIN": domainname,
497               "HOSTGUID_ADD": hostguid_add,
498               "DNSDOMAIN": dnsdomain})
499     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
500               "POLICYGUID": policyguid,
501               "DNSDOMAIN": dnsdomain,
502               "DOMAINSID": str(domainsid),
503               "DOMAINDN": domaindn})
504
505
506 def setup_samdb(path, setup_path, session_info, credentials, lp, 
507                 schemadn, configdn, domaindn, dnsdomain, realm, 
508                 netbiosname, message, hostname, rootdn, erase, 
509                 domainsid, aci, domainguid, policyguid, 
510                 domainname, fill, adminpass, krbtgtpass, 
511                 machinepass, hostguid, invocationid, dnspass,
512                 serverrole, ldap_backend=None, ldap_backend_type=None):
513     """Setup a complete SAM Database.
514     
515     """
516
517     # Also wipes the database
518     setup_samdb_partitions(path, setup_path, schemadn=schemadn, configdn=configdn, 
519                            domaindn=domaindn, message=message, lp=lp,
520                            credentials=credentials, session_info=session_info,
521                            hostname=hostname, netbiosname=netbiosname, 
522                            dnsdomain=dnsdomain, realm=realm, rootdn=rootdn,
523                            ldap_backend=ldap_backend, serverrole=serverrole,
524                            ldap_backend_type=ldap_backend_type, erase=erase)
525
526     samdb = SamDB(path, session_info=session_info, 
527                   credentials=credentials, lp=lp)
528
529     if fill == FILL_DRS:
530        # We want to finish here, but setup the index before we do so
531         message("Setting up sam.ldb index")
532         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
533         return samdb
534
535     message("Pre-loading the Samba 4 and AD schema")
536     samdb = SamDB(path, session_info=session_info, 
537                   credentials=credentials, lp=lp)
538     samdb.set_domain_sid(domainsid)
539     if lp.get("server role") == "domain controller":
540         samdb.set_invocation_id(invocationid)
541
542     load_schema(setup_path, samdb, schemadn, netbiosname, configdn)
543
544     samdb.transaction_start()
545         
546     try:
547         message("Adding DomainDN: %s (permitted to fail)" % domaindn)
548         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
549             "DOMAINDN": domaindn,
550             "ACI": aci,
551             })
552
553         message("Modifying DomainDN: " + domaindn + "")
554         if domainguid is not None:
555             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
556         else:
557             domainguid_mod = ""
558
559         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
560             "LDAPTIME": timestring(int(time.time())),
561             "DOMAINSID": str(domainsid),
562             "SCHEMADN": schemadn, 
563             "NETBIOSNAME": netbiosname,
564             "DEFAULTSITE": DEFAULTSITE,
565             "CONFIGDN": configdn,
566             "POLICYGUID": policyguid,
567             "DOMAINDN": domaindn,
568             "DOMAINGUID_MOD": domainguid_mod,
569             })
570
571         message("Adding configuration container (permitted to fail)")
572         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
573             "CONFIGDN": configdn, 
574             "ACI": aci,
575             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
576             })
577         message("Modifying configuration container")
578         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
579             "CONFIGDN": configdn, 
580             "SCHEMADN": schemadn,
581             })
582
583         message("Adding schema container (permitted to fail)")
584         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
585             "SCHEMADN": schemadn,
586             "ACI": aci,
587             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
588             })
589         message("Modifying schema container")
590         setup_modify_ldif(samdb, 
591             setup_path("provision_schema_basedn_modify.ldif"), {
592             "SCHEMADN": schemadn,
593             "NETBIOSNAME": netbiosname,
594             "DEFAULTSITE": DEFAULTSITE,
595             "CONFIGDN": configdn,
596             })
597
598         message("Setting up sam.ldb Samba4 schema")
599         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
600                        {"SCHEMADN": schemadn })
601         message("Setting up sam.ldb AD schema")
602         setup_add_ldif(samdb, setup_path("schema.ldif"), 
603                        {"SCHEMADN": schemadn})
604
605         message("Setting up sam.ldb configuration data")
606         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
607             "CONFIGDN": configdn,
608             "NETBIOSNAME": netbiosname,
609             "DEFAULTSITE": DEFAULTSITE,
610             "DNSDOMAIN": dnsdomain,
611             "DOMAIN": domainname,
612             "SCHEMADN": schemadn,
613             "DOMAINDN": domaindn,
614             })
615
616         message("Setting up display specifiers")
617         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
618                        {"CONFIGDN": configdn})
619
620         message("Adding users container (permitted to fail)")
621         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
622             "DOMAINDN": domaindn})
623         message("Modifying users container")
624         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
625             "DOMAINDN": domaindn})
626         message("Adding computers container (permitted to fail)")
627         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
628             "DOMAINDN": domaindn})
629         message("Modifying computers container")
630         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
631             "DOMAINDN": domaindn})
632         message("Setting up sam.ldb data")
633         setup_add_ldif(samdb, setup_path("provision.ldif"), {
634             "DOMAINDN": domaindn,
635             "NETBIOSNAME": netbiosname,
636             "DEFAULTSITE": DEFAULTSITE,
637             "CONFIGDN": configdn,
638             })
639
640         if fill == FILL_FULL:
641             message("Setting up sam.ldb users and groups")
642             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
643                 "DOMAINDN": domaindn,
644                 "DOMAINSID": str(domainsid),
645                 "CONFIGDN": configdn,
646                 "ADMINPASS_B64": b64encode(adminpass),
647                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
648                 })
649
650             if lp.get("server role") == "domain controller":
651                 message("Setting up self join")
652                 setup_self_join(samdb, configdn=configdn, schemadn=schemadn, 
653                                 domaindn=domaindn, invocationid=invocationid, 
654                                 dnspass=dnspass, netbiosname=netbiosname, 
655                                 dnsdomain=dnsdomain, realm=realm, 
656                                 machinepass=machinepass, domainname=domainname, 
657                                 domainsid=domainsid, policyguid=policyguid,
658                                 hostname=hostname, hostguid=hostguid, 
659                                 setup_path=setup_path)
660
661     #We want to setup the index last, as adds are faster unindexed
662         message("Setting up sam.ldb index")
663         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
664     except:
665         samdb.transaction_cancel()
666         raise
667
668     samdb.transaction_commit()
669     return samdb
670
671 FILL_FULL = "FULL"
672 FILL_NT4SYNC = "NT4SYNC"
673 FILL_DRS = "DRS"
674
675 def provision(lp, setup_dir, message, paths, session_info, 
676               credentials, ldapbackend, samdb_fill=FILL_FULL, realm=None, rootdn=None,
677               domain=None, hostname=None, hostip=None, domainsid=None, 
678               hostguid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
679               policyguid=None, invocationid=None, machinepass=None, 
680               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
681               wheel=None, backup=None, aci=None, serverrole=None, erase=False,
682               ldap_backend=None, ldap_backend_type=None):
683     """Provision samba4
684     
685     :note: caution, this wipes all existing data!
686     """
687
688     def setup_path(file):
689         return os.path.join(setup_dir, file)
690
691     if domainsid is None:
692         domainsid = security.random_sid()
693     if policyguid is None:
694         policyguid = uuid.random()
695     if adminpass is None:
696         adminpass = misc.random_password(12)
697     if krbtgtpass is None:
698         krbtgtpass = misc.random_password(12)
699     if machinepass is None:
700         machinepass  = misc.random_password(12)
701     if dnspass is None:
702         dnspass = misc.random_password(12)
703     if root is None:
704         root = findnss(pwd.getpwnam, "root")[0]
705     if nobody is None:
706         nobody = findnss(pwd.getpwnam, "nobody")[0]
707     if nogroup is None:
708         nogroup = findnss(grp.getgrnam, "nogroup", "nobody")[0]
709     if users is None:
710         users = findnss(grp.getgrnam, "users", "guest", "other", "unknown", 
711                         "usr")[0]
712     if wheel is None:
713         wheel = findnss(grp.getgrnam, "wheel", "root", "staff", "adm")[0]
714     if backup is None:
715         backup = findnss(grp.getgrnam, "backup", "wheel", "root", "staff")[0]
716     if aci is None:
717         aci = "# no aci for local ldb"
718     if serverrole is None:
719         serverrole = lp.get("server role")
720     if invocationid is None and serverrole == "domain controller":
721         invocationid = uuid.random()
722
723     if realm is None:
724         realm = lp.get("realm")
725
726     if lp.get("realm").upper() != realm.upper():
727         raise Exception("realm '%s' in smb.conf must match chosen realm '%s'" %
728                 (lp.get("realm"), realm))
729
730     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
731     
732     if ldap_backend == "ldapi":
733         # provision-backend will set this path suggested slapd command line / fedorads.inf
734         ldap_backend = "ldapi://" % urllib.quote(os.path.join(lp.get("private dir"), "ldap", "ldapi"), safe="")
735
736     assert realm is not None
737     realm = realm.upper()
738
739     if hostname is None:
740         hostname = gethostname().split(".")[0].lower()
741
742     if hostip is None:
743         hostip = gethostbyname(hostname)
744
745     netbiosname = hostname.upper()
746     if not valid_netbios_name(netbiosname):
747         raise InvalidNetbiosName(netbiosname)
748
749     dnsdomain    = realm.lower()
750     if serverrole == "domain controller":
751         domaindn     = "DC=" + dnsdomain.replace(".", ",DC=")
752         if domain is None:
753             domain = lp.get("workgroup")
754     
755         if lp.get("workgroup").upper() != domain.upper():
756             raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
757                 lp.get("workgroup"), domain)
758
759         assert domain is not None
760         domain = domain.upper()
761         if not valid_netbios_name(domain):
762             raise InvalidNetbiosName(domain)
763
764     else:
765         domaindn = "CN=" + netbiosname
766         domain = netbiosname
767         
768     if rootdn is None:
769        rootdn       = domaindn
770        
771     configdn     = "CN=Configuration," + rootdn
772     schemadn     = "CN=Schema," + configdn
773
774     message("set DOMAIN SID: %s" % str(domainsid))
775     message("Provisioning for %s in realm %s" % (domain, realm))
776     message("Using administrator password: %s" % adminpass)
777
778     assert paths.smbconf is not None
779
780     # only install a new smb.conf if there isn't one there already
781     if not os.path.exists(paths.smbconf):
782         message("Setting up smb.conf")
783         if serverrole == "domain controller":
784             smbconfsuffix = "dc"
785         elif serverrole == "member":
786             smbconfsuffix = "member"
787         else:
788             assert "Invalid server role setting: %s" % serverrole
789         setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
790                    paths.smbconf, {
791             "HOSTNAME": hostname,
792             "DOMAIN_CONF": domain,
793             "REALM_CONF": realm,
794             "SERVERROLE": serverrole,
795             "NETLOGONPATH": paths.netlogon,
796             "SYSVOLPATH": paths.sysvol,
797             })
798         lp.reload()
799
800     # only install a new shares config db if there is none
801     if not os.path.exists(paths.shareconf):
802         message("Setting up share.ldb")
803         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
804                         credentials=credentials, lp=lp)
805         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
806
807      
808     message("Setting up secrets.ldb")
809     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
810                                   session_info=session_info, 
811                                   credentials=credentials, lp=lp)
812
813     message("Setting up the registry")
814     setup_registry(paths.hklm, setup_path, session_info, 
815                    credentials=credentials, lp=lp)
816
817     message("Setting up templates db")
818     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
819                       credentials=credentials, lp=lp)
820
821     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
822                         credentials=credentials, lp=lp, schemadn=schemadn, 
823                         configdn=configdn, domaindn=domaindn,
824                         dnsdomain=dnsdomain, netbiosname=netbiosname, 
825                         realm=realm, message=message, hostname=hostname, 
826                         rootdn=rootdn, erase=erase, domainsid=domainsid, 
827                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
828                         domainname=domain, fill=samdb_fill, 
829                         adminpass=adminpass, krbtgtpass=krbtgtpass,
830                         hostguid=hostguid, invocationid=invocationid, 
831                         machinepass=machinepass, dnspass=dnspass,
832                         serverrole=serverrole, ldap_backend=ldap_backend, 
833                         ldap_backend_type=ldap_backend_type)
834
835     if lp.get("server role") == "domain controller":
836        policy_path = os.path.join(paths.sysvol, dnsdomain, "Policies", 
837                                   "{" + policyguid + "}")
838        os.makedirs(policy_path, 0755)
839        os.makedirs(os.path.join(policy_path, "Machine"), 0755)
840        os.makedirs(os.path.join(policy_path, "User"), 0755)
841        if not os.path.isdir(paths.netlogon):
842             os.makedirs(paths.netlogon, 0755)
843        secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
844                          credentials=credentials, lp=lp)
845        secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=realm,
846                            netbiosname=netbiosname, domainsid=domainsid, 
847                            keytab_path=paths.keytab, samdb_url=paths.samdb, 
848                            dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
849                            machinepass=machinepass, dnsdomain=dnsdomain)
850
851     if samdb_fill == FILL_FULL:
852         setup_name_mappings(samdb, str(domainsid), domaindn, root=root, 
853                             nobody=nobody, nogroup=nogroup, wheel=wheel, 
854                             users=users, backup=backup)
855    
856         message("Setting up sam.ldb rootDSE marking as synchronized")
857         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
858
859     message("Setting up phpLDAPadmin configuration")
860     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
861                                ldapi_url)
862
863     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
864
865     if lp.get("server role") == "domain controller":
866         samdb = SamDB(paths.samdb, session_info=session_info, 
867                       credentials=credentials, lp=lp)
868
869         domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
870         assert isinstance(domainguid, str)
871         hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
872                 expression="(&(objectClass=computer)(cn=%s))" % hostname,
873                 scope=SCOPE_SUBTREE)
874         assert isinstance(hostguid, str)
875
876         message("Setting up DNS zone: %s" % dnsdomain)
877         create_zone_file(paths.dns, setup_path, samdb, 
878                       hostname=hostname, hostip=hostip, dnsdomain=dnsdomain,
879                       domaindn=domaindn, dnspass=dnspass, realm=realm, 
880                       domainguid=domainguid, hostguid=hostguid)
881         message("Please install the zone located in %s into your DNS server" % paths.dns)
882
883     return domaindn
884
885
886 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
887     """Create a PHP LDAP admin configuration file.
888
889     :param path: Path to write the configuration to.
890     :param setup_path: Function to generate setup paths.
891     """
892     setup_file(setup_path("phpldapadmin-config.php"), path, 
893             {"S4_LDAPI_URI": ldapi_uri})
894
895
896 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn, 
897                   hostip, hostname, dnspass, realm, domainguid, hostguid):
898     """Write out a DNS zone file, from the info in the current database.
899     
900     :param path: Path of the new file.
901     :param setup_path": Setup path function.
902     :param samdb: SamDB object
903     :param dnsdomain: DNS Domain name
904     :param domaindn: DN of the Domain
905     :param hostip: Local IP
906     :param hostname: Local hostname
907     :param dnspass: Password for DNS
908     :param realm: Realm name
909     :param domainguid: GUID of the domain.
910     :param hostguid: GUID of the host.
911     """
912     assert isinstance(domainguid, str)
913
914     setup_file(setup_path("provision.zone"), path, {
915             "DNSPASS_B64": b64encode(dnspass),
916             "HOSTNAME": hostname,
917             "DNSDOMAIN": dnsdomain,
918             "REALM": realm,
919             "HOSTIP": hostip,
920             "DOMAINGUID": domainguid,
921             "DATESTRING": time.strftime("%Y%m%d%H"),
922             "DEFAULTSITE": DEFAULTSITE,
923             "HOSTGUID": hostguid,
924         })
925
926
927 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn):
928     """Load schema.
929     
930     :param samdb: Load a schema into a SamDB.
931     :param setup_path: Setup path function.
932     :param schemadn: DN of the schema
933     :param netbiosname: NetBIOS name of the host.
934     :param configdn: DN of the configuration
935     """
936     schema_data = open(setup_path("schema.ldif"), 'r').read()
937     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
938     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
939     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
940     head_data = substitute_var(head_data, {
941                     "SCHEMADN": schemadn,
942                     "NETBIOSNAME": netbiosname,
943                     "CONFIGDN": configdn,
944                     "DEFAULTSITE": DEFAULTSITE
945     })
946     samdb.attach_schema_from_ldif(head_data, schema_data)
947