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