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