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