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