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