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