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