Use struct for provision parameters since there are so many of them.
[samba.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
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, sitename, 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                             sitename)
383
384         if erase:
385             message("Erasing data from partitions")
386             samdb.erase_partitions()
387
388     except:
389         samdb.transaction_cancel()
390         raise
391
392     samdb.transaction_commit()
393     
394     return samdb
395
396
397 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain, 
398                         netbiosname, domainsid, keytab_path, samdb_url, 
399                         dns_keytab_path, dnspass, machinepass):
400     """Add DC-specific bits to a secrets database.
401     
402     :param secretsdb: Ldb Handle to the secrets database
403     :param setup_path: Setup path function
404     :param machinepass: Machine password
405     """
406     setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), { 
407             "MACHINEPASS_B64": b64encode(machinepass),
408             "DOMAIN": domain,
409             "REALM": realm,
410             "DNSDOMAIN": dnsdomain,
411             "DOMAINSID": str(domainsid),
412             "SECRETS_KEYTAB": keytab_path,
413             "NETBIOSNAME": netbiosname,
414             "SAM_LDB": samdb_url,
415             "DNS_KEYTAB": dns_keytab_path,
416             "DNSPASS_B64": b64encode(dnspass),
417             })
418
419
420 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
421     """Setup the secrets database.
422
423     :param path: Path to the secrets database.
424     :param setup_path: Get the path to a setup file.
425     :param session_info: Session info.
426     :param credentials: Credentials
427     :param lp: Loadparm context
428     :return: LDB handle for the created secrets database
429     """
430     if os.path.exists(path):
431         os.unlink(path)
432     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
433                       lp=lp)
434     secrets_ldb.erase()
435     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
436     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
437                       lp=lp)
438     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
439     return secrets_ldb
440
441
442 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
443     """Setup the templates database.
444
445     :param path: Path to the database.
446     :param setup_path: Function for obtaining the path to setup files.
447     :param session_info: Session info
448     :param credentials: Credentials
449     :param lp: Loadparm context
450     """
451     templates_ldb = SamDB(path, session_info=session_info,
452                           credentials=credentials, lp=lp)
453     templates_ldb.erase()
454     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
455
456
457 def setup_registry(path, setup_path, session_info, credentials, lp):
458     """Setup the registry.
459     
460     :param path: Path to the registry database
461     :param setup_path: Function that returns the path to a setup.
462     :param session_info: Session information
463     :param credentials: Credentials
464     :param lp: Loadparm context
465     """
466     reg = registry.Registry()
467     hive = registry.open_ldb(path, session_info=session_info, 
468                          credentials=credentials, lp_ctx=lp)
469     reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
470     provision_reg = setup_path("provision.reg")
471     assert os.path.exists(provision_reg)
472     reg.diff_apply(provision_reg)
473
474
475 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname, 
476                         dnsdomain, realm, rootdn, configdn, netbiosname,
477                         sitename):
478     """Setup the SamDB rootdse.
479
480     :param samdb: Sam Database handle
481     :param setup_path: Obtain setup path
482     """
483     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
484         "SCHEMADN": schemadn, 
485         "NETBIOSNAME": netbiosname,
486         "DNSDOMAIN": dnsdomain,
487         "DEFAULTSITE": sitename,
488         "REALM": realm,
489         "DNSNAME": "%s.%s" % (hostname, dnsdomain),
490         "DOMAINDN": domaindn,
491         "ROOTDN": rootdn,
492         "CONFIGDN": configdn,
493         "VERSION": samba.version(),
494         })
495         
496
497 def setup_self_join(samdb, configdn, schemadn, domaindn, 
498                     netbiosname, hostname, dnsdomain, machinepass, dnspass, 
499                     realm, domainname, domainsid, invocationid, setup_path,
500                     policyguid, sitename, hostguid=None):
501     """Join a host to its own domain."""
502     if hostguid is not None:
503         hostguid_add = "objectGUID: %s" % hostguid
504     else:
505         hostguid_add = ""
506
507     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
508               "CONFIGDN": configdn, 
509               "SCHEMADN": schemadn,
510               "DOMAINDN": domaindn,
511               "INVOCATIONID": invocationid,
512               "NETBIOSNAME": netbiosname,
513               "DEFAULTSITE": sitename,
514               "DNSNAME": "%s.%s" % (hostname, dnsdomain),
515               "MACHINEPASS_B64": b64encode(machinepass),
516               "DNSPASS_B64": b64encode(dnspass),
517               "REALM": realm,
518               "DOMAIN": domainname,
519               "HOSTGUID_ADD": hostguid_add,
520               "DNSDOMAIN": dnsdomain})
521     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
522               "POLICYGUID": policyguid,
523               "DNSDOMAIN": dnsdomain,
524               "DOMAINSID": str(domainsid),
525               "DOMAINDN": domaindn})
526
527
528 def setup_samdb(path, setup_path, session_info, credentials, lp, 
529                 schemadn, configdn, domaindn, dnsdomain, realm, 
530                 netbiosname, message, hostname, rootdn, erase, 
531                 domainsid, aci, domainguid, policyguid, 
532                 domainname, fill, adminpass, krbtgtpass, 
533                 machinepass, hostguid, invocationid, dnspass,
534                 serverrole, sitename, ldap_backend=None, 
535                 ldap_backend_type=None):
536     """Setup a complete SAM Database.
537     
538     :note: This will wipe the main SAM database file!
539     """
540
541     assert serverrole in ("domain controller", "member server")
542
543     # Also wipes the database
544     setup_samdb_partitions(path, setup_path, schemadn=schemadn, configdn=configdn, 
545                            domaindn=domaindn, message=message, lp=lp,
546                            credentials=credentials, session_info=session_info,
547                            hostname=hostname, netbiosname=netbiosname, 
548                            dnsdomain=dnsdomain, realm=realm, rootdn=rootdn,
549                            ldap_backend=ldap_backend, serverrole=serverrole,
550                            ldap_backend_type=ldap_backend_type, erase=erase,
551                            sitename=sitename)
552
553     samdb = SamDB(path, session_info=session_info, 
554                   credentials=credentials, lp=lp)
555
556     if fill == FILL_DRS:
557        # We want to finish here, but setup the index before we do so
558         message("Setting up sam.ldb index")
559         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
560         return samdb
561
562     message("Pre-loading the Samba 4 and AD schema")
563     samdb = SamDB(path, session_info=session_info, 
564                   credentials=credentials, lp=lp)
565     samdb.set_domain_sid(domainsid)
566     if lp.get("server role") == "domain controller":
567         samdb.set_invocation_id(invocationid)
568
569     load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename)
570
571     samdb.transaction_start()
572         
573     try:
574         message("Adding DomainDN: %s (permitted to fail)" % domaindn)
575         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
576             "DOMAINDN": domaindn,
577             "ACI": aci,
578             })
579
580         message("Modifying DomainDN: " + domaindn + "")
581         if domainguid is not None:
582             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
583         else:
584             domainguid_mod = ""
585
586         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
587             "LDAPTIME": timestring(int(time.time())),
588             "DOMAINSID": str(domainsid),
589             "SCHEMADN": schemadn, 
590             "NETBIOSNAME": netbiosname,
591             "DEFAULTSITE": sitename,
592             "CONFIGDN": configdn,
593             "POLICYGUID": policyguid,
594             "DOMAINDN": domaindn,
595             "DOMAINGUID_MOD": domainguid_mod,
596             })
597
598         message("Adding configuration container (permitted to fail)")
599         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
600             "CONFIGDN": configdn, 
601             "ACI": aci,
602             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
603             })
604         message("Modifying configuration container")
605         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
606             "CONFIGDN": configdn, 
607             "SCHEMADN": schemadn,
608             })
609
610         message("Adding schema container (permitted to fail)")
611         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
612             "SCHEMADN": schemadn,
613             "ACI": aci,
614             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
615             })
616         message("Modifying schema container")
617         setup_modify_ldif(samdb, 
618             setup_path("provision_schema_basedn_modify.ldif"), {
619             "SCHEMADN": schemadn,
620             "NETBIOSNAME": netbiosname,
621             "DEFAULTSITE": sitename,
622             "CONFIGDN": configdn,
623             })
624
625         message("Setting up sam.ldb Samba4 schema")
626         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
627                        {"SCHEMADN": schemadn })
628         message("Setting up sam.ldb AD schema")
629         setup_add_ldif(samdb, setup_path("schema.ldif"), 
630                        {"SCHEMADN": schemadn})
631
632         message("Setting up sam.ldb configuration data")
633         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
634             "CONFIGDN": configdn,
635             "NETBIOSNAME": netbiosname,
636             "DEFAULTSITE": sitename,
637             "DNSDOMAIN": dnsdomain,
638             "DOMAIN": domainname,
639             "SCHEMADN": schemadn,
640             "DOMAINDN": domaindn,
641             })
642
643         message("Setting up display specifiers")
644         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
645                        {"CONFIGDN": configdn})
646
647         message("Adding users container (permitted to fail)")
648         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
649             "DOMAINDN": domaindn})
650         message("Modifying users container")
651         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
652             "DOMAINDN": domaindn})
653         message("Adding computers container (permitted to fail)")
654         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
655             "DOMAINDN": domaindn})
656         message("Modifying computers container")
657         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
658             "DOMAINDN": domaindn})
659         message("Setting up sam.ldb data")
660         setup_add_ldif(samdb, setup_path("provision.ldif"), {
661             "DOMAINDN": domaindn,
662             "NETBIOSNAME": netbiosname,
663             "DEFAULTSITE": sitename,
664             "CONFIGDN": configdn,
665             })
666
667         if fill == FILL_FULL:
668             message("Setting up sam.ldb users and groups")
669             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
670                 "DOMAINDN": domaindn,
671                 "DOMAINSID": str(domainsid),
672                 "CONFIGDN": configdn,
673                 "ADMINPASS_B64": b64encode(adminpass),
674                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
675                 })
676
677             if lp.get("server role") == "domain controller":
678                 message("Setting up self join")
679                 setup_self_join(samdb, configdn=configdn, schemadn=schemadn, 
680                                 domaindn=domaindn, invocationid=invocationid, 
681                                 dnspass=dnspass, netbiosname=netbiosname, 
682                                 dnsdomain=dnsdomain, realm=realm, 
683                                 machinepass=machinepass, domainname=domainname, 
684                                 domainsid=domainsid, policyguid=policyguid,
685                                 hostname=hostname, hostguid=hostguid, 
686                                 setup_path=setup_path, sitename=sitename)
687
688     #We want to setup the index last, as adds are faster unindexed
689         message("Setting up sam.ldb index")
690         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
691     except:
692         samdb.transaction_cancel()
693         raise
694
695     samdb.transaction_commit()
696     return samdb
697
698 FILL_FULL = "FULL"
699 FILL_NT4SYNC = "NT4SYNC"
700 FILL_DRS = "DRS"
701
702 def provision(lp, setup_dir, message, paths, session_info, 
703               credentials, samdb_fill=FILL_FULL, realm=None, rootdn=None,
704               domain=None, hostname=None, hostip=None, domainsid=None, 
705               hostguid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
706               policyguid=None, invocationid=None, machinepass=None, 
707               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
708               wheel=None, backup=None, aci=None, serverrole=None, erase=False,
709               ldap_backend=None, ldap_backend_type=None, sitename=DEFAULTSITE):
710     """Provision samba4
711     
712     :note: caution, this wipes all existing data!
713     """
714
715     def setup_path(file):
716         return os.path.join(setup_dir, file)
717
718     if domainsid is None:
719         domainsid = security.random_sid()
720     if policyguid is None:
721         policyguid = uuid.random()
722     if adminpass is None:
723         adminpass = misc.random_password(12)
724     if krbtgtpass is None:
725         krbtgtpass = misc.random_password(12)
726     if machinepass is None:
727         machinepass  = misc.random_password(12)
728     if dnspass is None:
729         dnspass = misc.random_password(12)
730     if root is None:
731         root = findnss(pwd.getpwnam, ["root"])[0]
732     if nobody is None:
733         nobody = findnss(pwd.getpwnam, ["nobody"])[0]
734     if nogroup is None:
735         nogroup = findnss(grp.getgrnam, ["nogroup", "nobody"])[0]
736     if users is None:
737         users = findnss(grp.getgrnam, ["users", "guest", "other", "unknown", 
738                         "usr"])[0]
739     if wheel is None:
740         wheel = findnss(grp.getgrnam, ["wheel", "root", "staff", "adm"])[0]
741     if backup is None:
742         backup = findnss(grp.getgrnam, ["backup", "wheel", "root", "staff"])[0]
743     if aci is None:
744         aci = "# no aci for local ldb"
745     if serverrole is None:
746         serverrole = lp.get("server role")
747     assert serverrole in ("domain controller", "member server")
748     if invocationid is None and serverrole == "domain controller":
749         invocationid = uuid.random()
750
751     if realm is None:
752         realm = lp.get("realm")
753
754     if lp.get("realm").upper() != realm.upper():
755         raise Exception("realm '%s' in smb.conf must match chosen realm '%s'" %
756                 (lp.get("realm"), realm))
757
758     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
759     
760     if ldap_backend == "ldapi":
761         # provision-backend will set this path suggested slapd command line / fedorads.inf
762         ldap_backend = "ldapi://" % urllib.quote(os.path.join(lp.get("private dir"), "ldap", "ldapi"), safe="")
763
764     assert realm is not None
765     realm = realm.upper()
766
767     if hostname is None:
768         hostname = gethostname().split(".")[0].lower()
769
770     if hostip is None:
771         hostip = gethostbyname(hostname)
772
773     netbiosname = hostname.upper()
774     if not valid_netbios_name(netbiosname):
775         raise InvalidNetbiosName(netbiosname)
776
777     dnsdomain = realm.lower()
778     if serverrole == "domain controller":
779         domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
780         if domain is None:
781             domain = lp.get("workgroup")
782     
783         if lp.get("workgroup").upper() != domain.upper():
784             raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
785                 lp.get("workgroup"), domain)
786
787         assert domain is not None
788         domain = domain.upper()
789         if not valid_netbios_name(domain):
790             raise InvalidNetbiosName(domain)
791     else:
792         domaindn = "CN=" + netbiosname
793         domain = netbiosname
794     
795     if rootdn is None:
796        rootdn = domaindn
797        
798     configdn = "CN=Configuration," + rootdn
799     schemadn = "CN=Schema," + configdn
800
801     message("set DOMAIN SID: %s" % str(domainsid))
802     message("Provisioning for %s in realm %s" % (domain, realm))
803     message("Using administrator password: %s" % adminpass)
804
805     assert paths.smbconf is not None
806
807     # only install a new smb.conf if there isn't one there already
808     if not os.path.exists(paths.smbconf):
809         message("Setting up smb.conf")
810         if serverrole == "domain controller":
811             smbconfsuffix = "dc"
812         elif serverrole == "member":
813             smbconfsuffix = "member"
814         setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
815                    paths.smbconf, {
816             "HOSTNAME": hostname,
817             "DOMAIN_CONF": domain,
818             "REALM_CONF": realm,
819             "SERVERROLE": serverrole,
820             "NETLOGONPATH": paths.netlogon,
821             "SYSVOLPATH": paths.sysvol,
822             })
823         lp.load(paths.smbconf)
824
825     # only install a new shares config db if there is none
826     if not os.path.exists(paths.shareconf):
827         message("Setting up share.ldb")
828         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
829                         credentials=credentials, lp=lp)
830         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
831
832      
833     message("Setting up secrets.ldb")
834     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
835                                   session_info=session_info, 
836                                   credentials=credentials, lp=lp)
837
838     message("Setting up the registry")
839     setup_registry(paths.hklm, setup_path, session_info, 
840                    credentials=credentials, lp=lp)
841
842     message("Setting up templates db")
843     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
844                       credentials=credentials, lp=lp)
845
846     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
847                         credentials=credentials, lp=lp, schemadn=schemadn, 
848                         configdn=configdn, domaindn=domaindn,
849                         dnsdomain=dnsdomain, netbiosname=netbiosname, 
850                         realm=realm, message=message, hostname=hostname, 
851                         rootdn=rootdn, erase=erase, domainsid=domainsid, 
852                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
853                         domainname=domain, fill=samdb_fill, 
854                         adminpass=adminpass, krbtgtpass=krbtgtpass,
855                         hostguid=hostguid, invocationid=invocationid, 
856                         machinepass=machinepass, dnspass=dnspass,
857                         serverrole=serverrole, ldap_backend=ldap_backend, 
858                         ldap_backend_type=ldap_backend_type, sitename=sitename)
859
860     if lp.get("server role") == "domain controller":
861        policy_path = os.path.join(paths.sysvol, dnsdomain, "Policies", 
862                                   "{" + policyguid + "}")
863        os.makedirs(policy_path, 0755)
864        os.makedirs(os.path.join(policy_path, "Machine"), 0755)
865        os.makedirs(os.path.join(policy_path, "User"), 0755)
866        if not os.path.isdir(paths.netlogon):
867             os.makedirs(paths.netlogon, 0755)
868        secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
869                          credentials=credentials, lp=lp)
870        secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=realm,
871                            netbiosname=netbiosname, domainsid=domainsid, 
872                            keytab_path=paths.keytab, samdb_url=paths.samdb, 
873                            dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
874                            machinepass=machinepass, dnsdomain=dnsdomain)
875
876     if samdb_fill == FILL_FULL:
877         setup_name_mappings(samdb, str(domainsid), domaindn, root=root, 
878                             nobody=nobody, nogroup=nogroup, wheel=wheel, 
879                             users=users, backup=backup)
880    
881         message("Setting up sam.ldb rootDSE marking as synchronized")
882         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
883
884     message("Setting up phpLDAPadmin configuration")
885     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
886                                ldapi_url)
887
888     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
889
890     if lp.get("server role") == "domain controller":
891         samdb = SamDB(paths.samdb, session_info=session_info, 
892                       credentials=credentials, lp=lp)
893
894         domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
895         assert isinstance(domainguid, str)
896         hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
897                 expression="(&(objectClass=computer)(cn=%s))" % hostname,
898                 scope=SCOPE_SUBTREE)
899         assert isinstance(hostguid, str)
900
901         message("Setting up DNS zone: %s" % dnsdomain)
902         create_zone_file(paths.dns, setup_path, samdb, 
903                       hostname=hostname, hostip=hostip, dnsdomain=dnsdomain,
904                       domaindn=domaindn, dnspass=dnspass, realm=realm, 
905                       domainguid=domainguid, hostguid=hostguid)
906         message("Please install the zone located in %s into your DNS server" % paths.dns)
907
908     return domaindn
909
910
911 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
912     """Create a PHP LDAP admin configuration file.
913
914     :param path: Path to write the configuration to.
915     :param setup_path: Function to generate setup paths.
916     """
917     setup_file(setup_path("phpldapadmin-config.php"), path, 
918             {"S4_LDAPI_URI": ldapi_uri})
919
920
921 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn, 
922                   hostip, hostname, dnspass, realm, domainguid, hostguid):
923     """Write out a DNS zone file, from the info in the current database.
924     
925     :param path: Path of the new file.
926     :param setup_path": Setup path function.
927     :param samdb: SamDB object
928     :param dnsdomain: DNS Domain name
929     :param domaindn: DN of the Domain
930     :param hostip: Local IP
931     :param hostname: Local hostname
932     :param dnspass: Password for DNS
933     :param realm: Realm name
934     :param domainguid: GUID of the domain.
935     :param hostguid: GUID of the host.
936     """
937     assert isinstance(domainguid, str)
938
939     setup_file(setup_path("provision.zone"), path, {
940             "DNSPASS_B64": b64encode(dnspass),
941             "HOSTNAME": hostname,
942             "DNSDOMAIN": dnsdomain,
943             "REALM": realm,
944             "HOSTIP": hostip,
945             "DOMAINGUID": domainguid,
946             "DATESTRING": time.strftime("%Y%m%d%H"),
947             "DEFAULTSITE": DEFAULTSITE,
948             "HOSTGUID": hostguid,
949         })
950
951
952 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
953     """Load schema for the SamDB.
954     
955     :param samdb: Load a schema into a SamDB.
956     :param setup_path: Setup path function.
957     :param schemadn: DN of the schema
958     :param netbiosname: NetBIOS name of the host.
959     :param configdn: DN of the configuration
960     """
961     schema_data = open(setup_path("schema.ldif"), 'r').read()
962     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
963     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
964     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
965     head_data = substitute_var(head_data, {
966                     "SCHEMADN": schemadn,
967                     "NETBIOSNAME": netbiosname,
968                     "CONFIGDN": configdn,
969                     "DEFAULTSITE":sitename 
970     })
971     samdb.attach_schema_from_ldif(head_data, schema_data)
972