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