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