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