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