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