Formatting fixes.
[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, 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
699 FILL_FULL = "FULL"
700 FILL_NT4SYNC = "NT4SYNC"
701 FILL_DRS = "DRS"
702
703 def provision(lp, setup_dir, message, paths, session_info, 
704               credentials, samdb_fill=FILL_FULL, realm=None, rootdn=None,
705               domain=None, hostname=None, hostip=None, domainsid=None, 
706               hostguid=None, adminpass=None, krbtgtpass=None, domainguid=None, 
707               policyguid=None, invocationid=None, machinepass=None, 
708               dnspass=None, root=None, nobody=None, nogroup=None, users=None, 
709               wheel=None, backup=None, aci=None, serverrole=None, erase=False,
710               ldap_backend=None, ldap_backend_type=None, sitename=DEFAULTSITE):
711     """Provision samba4
712     
713     :note: caution, this wipes all existing data!
714     """
715
716     def setup_path(file):
717         return os.path.join(setup_dir, file)
718
719     if domainsid is None:
720         domainsid = security.random_sid()
721     if policyguid is None:
722         policyguid = uuid.random()
723     if adminpass is None:
724         adminpass = misc.random_password(12)
725     if krbtgtpass is None:
726         krbtgtpass = misc.random_password(12)
727     if machinepass is None:
728         machinepass  = misc.random_password(12)
729     if dnspass is None:
730         dnspass = misc.random_password(12)
731     if root is None:
732         root = findnss(pwd.getpwnam, ["root"])[0]
733     if nobody is None:
734         nobody = findnss(pwd.getpwnam, ["nobody"])[0]
735     if nogroup is None:
736         nogroup = findnss(grp.getgrnam, ["nogroup", "nobody"])[0]
737     if users is None:
738         users = findnss(grp.getgrnam, ["users", "guest", "other", "unknown", 
739                         "usr"])[0]
740     if wheel is None:
741         wheel = findnss(grp.getgrnam, ["wheel", "root", "staff", "adm"])[0]
742     if backup is None:
743         backup = findnss(grp.getgrnam, ["backup", "wheel", "root", "staff"])[0]
744     if aci is None:
745         aci = "# no aci for local ldb"
746     if serverrole is None:
747         serverrole = lp.get("server role")
748     assert serverrole in ("domain controller", "member server")
749     if invocationid is None and serverrole == "domain controller":
750         invocationid = uuid.random()
751
752     if realm is None:
753         realm = lp.get("realm")
754
755     if lp.get("realm").upper() != realm.upper():
756         raise Exception("realm '%s' in smb.conf must match chosen realm '%s'" %
757                 (lp.get("realm"), realm))
758
759     ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
760     
761     if ldap_backend == "ldapi":
762         # provision-backend will set this path suggested slapd command line / fedorads.inf
763         ldap_backend = "ldapi://" % urllib.quote(os.path.join(lp.get("private dir"), "ldap", "ldapi"), safe="")
764
765     assert realm is not None
766     realm = realm.upper()
767
768     if hostname is None:
769         hostname = gethostname().split(".")[0].lower()
770
771     if hostip is None:
772         hostip = gethostbyname(hostname)
773
774     netbiosname = hostname.upper()
775     if not valid_netbios_name(netbiosname):
776         raise InvalidNetbiosName(netbiosname)
777
778     dnsdomain = realm.lower()
779     if serverrole == "domain controller":
780         domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
781         if domain is None:
782             domain = lp.get("workgroup")
783     
784         if lp.get("workgroup").upper() != domain.upper():
785             raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
786                 lp.get("workgroup"), domain)
787
788         assert domain is not None
789         domain = domain.upper()
790         if not valid_netbios_name(domain):
791             raise InvalidNetbiosName(domain)
792     else:
793         domaindn = "CN=" + netbiosname
794         domain = netbiosname
795     
796     if rootdn is None:
797        rootdn = domaindn
798        
799     configdn = "CN=Configuration," + rootdn
800     schemadn = "CN=Schema," + configdn
801
802     message("set DOMAIN SID: %s" % str(domainsid))
803     message("Provisioning for %s in realm %s" % (domain, realm))
804     message("Using administrator password: %s" % adminpass)
805
806     assert paths.smbconf is not None
807
808     # only install a new smb.conf if there isn't one there already
809     if not os.path.exists(paths.smbconf):
810         message("Setting up smb.conf")
811         if serverrole == "domain controller":
812             smbconfsuffix = "dc"
813         elif serverrole == "member":
814             smbconfsuffix = "member"
815         setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), 
816                    paths.smbconf, {
817             "HOSTNAME": hostname,
818             "DOMAIN_CONF": domain,
819             "REALM_CONF": realm,
820             "SERVERROLE": serverrole,
821             "NETLOGONPATH": paths.netlogon,
822             "SYSVOLPATH": paths.sysvol,
823             })
824         lp.load(paths.smbconf)
825
826     # only install a new shares config db if there is none
827     if not os.path.exists(paths.shareconf):
828         message("Setting up share.ldb")
829         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
830                         credentials=credentials, lp=lp)
831         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
832
833      
834     message("Setting up secrets.ldb")
835     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
836                                   session_info=session_info, 
837                                   credentials=credentials, lp=lp)
838
839     message("Setting up the registry")
840     setup_registry(paths.hklm, setup_path, session_info, 
841                    credentials=credentials, lp=lp)
842
843     message("Setting up templates db")
844     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
845                       credentials=credentials, lp=lp)
846
847     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, 
848                         credentials=credentials, lp=lp, schemadn=schemadn, 
849                         configdn=configdn, domaindn=domaindn,
850                         dnsdomain=dnsdomain, netbiosname=netbiosname, 
851                         realm=realm, message=message, hostname=hostname, 
852                         rootdn=rootdn, erase=erase, domainsid=domainsid, 
853                         aci=aci, domainguid=domainguid, policyguid=policyguid, 
854                         domainname=domain, fill=samdb_fill, 
855                         adminpass=adminpass, krbtgtpass=krbtgtpass,
856                         hostguid=hostguid, invocationid=invocationid, 
857                         machinepass=machinepass, dnspass=dnspass,
858                         serverrole=serverrole, ldap_backend=ldap_backend, 
859                         ldap_backend_type=ldap_backend_type, sitename=sitename)
860
861     if lp.get("server role") == "domain controller":
862        policy_path = os.path.join(paths.sysvol, dnsdomain, "Policies", 
863                                   "{" + policyguid + "}")
864        os.makedirs(policy_path, 0755)
865        os.makedirs(os.path.join(policy_path, "Machine"), 0755)
866        os.makedirs(os.path.join(policy_path, "User"), 0755)
867        if not os.path.isdir(paths.netlogon):
868             os.makedirs(paths.netlogon, 0755)
869        secrets_ldb = Ldb(paths.secrets, session_info=session_info, 
870                          credentials=credentials, lp=lp)
871        secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=realm,
872                            netbiosname=netbiosname, domainsid=domainsid, 
873                            keytab_path=paths.keytab, samdb_url=paths.samdb, 
874                            dns_keytab_path=paths.dns_keytab, dnspass=dnspass, 
875                            machinepass=machinepass, dnsdomain=dnsdomain)
876
877     if samdb_fill == FILL_FULL:
878         setup_name_mappings(samdb, str(domainsid), domaindn, root=root, 
879                             nobody=nobody, nogroup=nogroup, wheel=wheel, 
880                             users=users, backup=backup)
881    
882         message("Setting up sam.ldb rootDSE marking as synchronized")
883         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
884
885     message("Setting up phpLDAPadmin configuration")
886     create_phpldapadmin_config(paths.phpldapadminconfig, setup_path, 
887                                ldapi_url)
888
889     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
890
891     if lp.get("server role") == "domain controller":
892         samdb = SamDB(paths.samdb, session_info=session_info, 
893                       credentials=credentials, lp=lp)
894
895         domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
896         assert isinstance(domainguid, str)
897         hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
898                 expression="(&(objectClass=computer)(cn=%s))" % hostname,
899                 scope=SCOPE_SUBTREE)
900         assert isinstance(hostguid, str)
901
902         message("Setting up DNS zone: %s" % dnsdomain)
903         create_zone_file(paths.dns, setup_path, samdb, 
904                       hostname=hostname, hostip=hostip, dnsdomain=dnsdomain,
905                       domaindn=domaindn, dnspass=dnspass, realm=realm, 
906                       domainguid=domainguid, hostguid=hostguid)
907         message("Please install the zone located in %s into your DNS server" % paths.dns)
908
909     return domaindn
910
911
912 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
913     """Create a PHP LDAP admin configuration file.
914
915     :param path: Path to write the configuration to.
916     :param setup_path: Function to generate setup paths.
917     """
918     setup_file(setup_path("phpldapadmin-config.php"), path, 
919             {"S4_LDAPI_URI": ldapi_uri})
920
921
922 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn, 
923                   hostip, hostname, dnspass, realm, domainguid, hostguid):
924     """Write out a DNS zone file, from the info in the current database.
925     
926     :param path: Path of the new file.
927     :param setup_path": Setup path function.
928     :param samdb: SamDB object
929     :param dnsdomain: DNS Domain name
930     :param domaindn: DN of the Domain
931     :param hostip: Local IP
932     :param hostname: Local hostname
933     :param dnspass: Password for DNS
934     :param realm: Realm name
935     :param domainguid: GUID of the domain.
936     :param hostguid: GUID of the host.
937     """
938     assert isinstance(domainguid, str)
939
940     setup_file(setup_path("provision.zone"), path, {
941             "DNSPASS_B64": b64encode(dnspass),
942             "HOSTNAME": hostname,
943             "DNSDOMAIN": dnsdomain,
944             "REALM": realm,
945             "HOSTIP": hostip,
946             "DOMAINGUID": domainguid,
947             "DATESTRING": time.strftime("%Y%m%d%H"),
948             "DEFAULTSITE": DEFAULTSITE,
949             "HOSTGUID": hostguid,
950         })
951
952
953 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
954     """Load schema for the SamDB.
955     
956     :param samdb: Load a schema into a SamDB.
957     :param setup_path: Setup path function.
958     :param schemadn: DN of the schema
959     :param netbiosname: NetBIOS name of the host.
960     :param configdn: DN of the configuration
961     """
962     schema_data = open(setup_path("schema.ldif"), 'r').read()
963     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
964     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
965     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
966     head_data = substitute_var(head_data, {
967                     "SCHEMADN": schemadn,
968                     "NETBIOSNAME": netbiosname,
969                     "CONFIGDN": configdn,
970                     "DEFAULTSITE":sitename 
971     })
972     samdb.attach_schema_from_ldif(head_data, schema_data)
973