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