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