1607cb343bf6d3397e3ff5db587d7d98113e7b80
[samba.git] / source4 / scripting / python / samba / provision.py
1 #
2 #    backend code for provisioning a Samba4 server
3 #    Released under the GNU GPL v3 or later
4 #    Copyright Jelmer Vernooij 2007
5 #
6 # Based on the original in EJS:
7 #    Copyright Andrew Tridgell 2005
8 #
9
10 from base64 import b64encode
11 import os
12 import pwd
13 import grp
14 import time
15 import uuid, misc
16 from socket import gethostname, gethostbyname
17 import param
18 import registry
19 import samba
20 from samba import Ldb, substitute_var, valid_netbios_name
21 from samba.samdb import SamDB
22 import security
23 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
24         LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
25
26 """Functions for setting up a Samba configuration."""
27
28 DEFAULTSITE = "Default-First-Site-Name"
29
30 class InvalidNetbiosName(Exception):
31     def __init__(self, name):
32         super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
33
34
35 class ProvisionPaths:
36     def __init__(self):
37         self.smbconf = None
38         self.shareconf = None
39         self.hklm = None
40         self.hkcu = None
41         self.hkcr = None
42         self.hku = None
43         self.hkpd = None
44         self.hkpt = None
45         self.samdb = None
46         self.secrets = None
47         self.keytab = None
48         self.dns_keytab = None
49         self.dns = None
50         self.winsdb = None
51         self.ldap_basedn_ldif = None
52         self.ldap_config_basedn_ldif = None
53         self.ldap_schema_basedn_ldif = None
54
55
56 def install_ok(lp, session_info, credentials):
57     """Check whether the current install seems ok.
58     
59     :param lp: Loadparm context
60     :param session_info: Session information
61     :param credentials: Credentials
62     """
63     if lp.get("realm") == "":
64         return False
65     ldb = Ldb(lp.get("sam database"), session_info=session_info, 
66             credentials=credentials, lp=lp)
67     if len(ldb.search("(cn=Administrator)")) != 1:
68         return False
69     return True
70
71
72 def findnss(nssfn, *names):
73     """Find a user or group from a list of possibilities."""
74     for name in names:
75         try:
76             return nssfn(name)
77         except KeyError:
78             pass
79     raise Exception("Unable to find user/group for %s" % arguments[1])
80
81
82 def open_ldb(session_info, credentials, lp, dbname):
83     """Open a LDB, thrashing it if it is corrupt.
84
85     :param session_info: auth session information
86     :param credentials: credentials
87     :param lp: Loadparm context
88     :param dbname: Path of the database to open.
89     :return: a Ldb object
90     """
91     assert session_info is not None
92     try:
93         return Ldb(dbname, session_info=session_info, credentials=credentials, 
94                    lp=lp)
95     except LdbError, e:
96         print e
97         os.unlink(dbname)
98         return Ldb(dbname, session_info=session_info, credentials=credentials,
99                    lp=lp)
100
101
102 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
103     """Setup a ldb in the private dir.
104     
105     :param ldb: LDB file to import data into
106     :param ldif_path: Path of the LDIF file to load
107     :param subst_vars: Optional variables to subsitute in LDIF.
108     """
109     assert isinstance(ldif_path, str)
110
111     data = open(ldif_path, 'r').read()
112     if subst_vars is not None:
113         data = substitute_var(data, subst_vars)
114
115     assert "${" not in data
116
117     ldb.add_ldif(data)
118
119
120 def setup_modify_ldif(ldb, ldif_path, substvars=None):
121     """Modify a ldb in the private dir.
122     
123     :param ldb: LDB object.
124     :param ldif_path: LDIF file path.
125     :param substvars: Optional dictionary with substitution variables.
126     """
127     data = open(ldif_path, 'r').read()
128     if substvars is not None:
129         data = substitute_var(data, substvars)
130
131     assert "${" not in data
132
133     ldb.modify_ldif(data)
134
135
136 def setup_ldb(ldb, ldif_path, subst_vars):
137     assert ldb is not None
138     ldb.transaction_start()
139     try:
140         setup_add_ldif(ldb, ldif_path, subst_vars)
141     except:
142         ldb.transaction_cancel()
143         raise
144     ldb.transaction_commit()
145
146
147 def setup_file(template, fname, substvars):
148     """Setup a file in the private dir.
149
150     :param template: Path of the template file.
151     :param fname: Path of the file to create.
152     :param substvars: Substitution variables.
153     """
154     f = fname
155
156     if os.path.exists(f):
157         os.unlink(f)
158
159     data = open(template, 'r').read()
160     if substvars:
161         data = substitute_var(data, substvars)
162     assert not "${" in data
163
164     open(f, 'w').write(data)
165
166
167 def provision_paths_from_lp(lp, dnsdomain):
168     """Set the default paths for provisioning.
169
170     :param lp: Loadparm context.
171     :param dnsdomain: DNS Domain name
172     """
173     paths = ProvisionPaths()
174     private_dir = lp.get("private dir")
175     paths.shareconf = os.path.join(private_dir, "share.ldb")
176     paths.samdb = os.path.join(private_dir, lp.get("sam database") or "samdb.ldb")
177     paths.secrets = os.path.join(private_dir, lp.get("secrets database") or "secrets.ldb")
178     paths.templates = os.path.join(private_dir, "templates.ldb")
179     paths.keytab = os.path.join(private_dir, "secrets.keytab")
180     paths.dns_keytab = os.path.join(private_dir, "dns.keytab")
181     paths.dns = os.path.join(private_dir, dnsdomain + ".zone")
182     paths.winsdb = os.path.join(private_dir, "wins.ldb")
183     paths.ldap_basedn_ldif = os.path.join(private_dir, 
184                                           dnsdomain + ".ldif")
185     paths.ldap_config_basedn_ldif = os.path.join(private_dir, 
186                                              dnsdomain + "-config.ldif")
187     paths.ldap_schema_basedn_ldif = os.path.join(private_dir, 
188                                               dnsdomain + "-schema.ldif")
189     paths.s4_ldapi_path = os.path.join(private_dir, "ldapi")
190     paths.phpldapadminconfig = os.path.join(private_dir, 
191                                             "phpldapadmin-config.php")
192     paths.hklm = os.path.join(private_dir, "hklm.ldb")
193     paths.sysvol = lp.get("sysvol", "path")
194     if paths.sysvol is None:
195         paths.sysvol = os.path.join(lp.get("lock dir"), "sysvol")
196
197     paths.netlogon = lp.get("netlogon", "path")
198     if paths.netlogon is None:
199         paths.netlogon = os.path.join(os.path.join(paths.sysvol, "scripts"))
200
201     return paths
202
203
204 def setup_name_mappings(ldb, sid, domaindn, root, nobody, nogroup, users, 
205                         wheel, backup):
206     """setup reasonable name mappings for sam names to unix names.
207     
208     :param ldb: SamDB object.
209     :param sid: The domain sid.
210     :param domaindn: The domain DN.
211     :param root: Name of the UNIX root user.
212     :param nobody: Name of the UNIX nobody user.
213     :param nogroup: Name of the unix nobody group.
214     :param users: Name of the unix users group.
215     :param wheel: Name of the wheel group (users that can become root).
216     :param backup: Name of the backup group."""
217     # add some foreign sids if they are not present already
218     ldb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
219     ldb.add_foreign(domaindn, "S-1-1-0", "World")
220     ldb.add_foreign(domaindn, "S-1-5-2", "Network")
221     ldb.add_foreign(domaindn, "S-1-5-18", "System")
222     ldb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
223
224     # some well known sids
225     ldb.setup_name_mapping(domaindn, "S-1-5-7", nobody)
226     ldb.setup_name_mapping(domaindn, "S-1-1-0", nogroup)
227     ldb.setup_name_mapping(domaindn, "S-1-5-2", nogroup)
228     ldb.setup_name_mapping(domaindn, "S-1-5-18", root)
229     ldb.setup_name_mapping(domaindn, "S-1-5-11", users)
230     ldb.setup_name_mapping(domaindn, "S-1-5-32-544", wheel)
231     ldb.setup_name_mapping(domaindn, "S-1-5-32-545", users)
232     ldb.setup_name_mapping(domaindn, "S-1-5-32-546", nogroup)
233     ldb.setup_name_mapping(domaindn, "S-1-5-32-551", backup)
234
235     # and some well known domain rids
236     ldb.setup_name_mapping(domaindn, sid + "-500", root)
237     ldb.setup_name_mapping(domaindn, sid + "-518", wheel)
238     ldb.setup_name_mapping(domaindn, sid + "-519", wheel)
239     ldb.setup_name_mapping(domaindn, sid + "-512", wheel)
240     ldb.setup_name_mapping(domaindn, sid + "-513", users)
241     ldb.setup_name_mapping(domaindn, sid + "-520", wheel)
242
243
244 def provision_become_dc(setup_dir, message, paths, lp, session_info, 
245                         credentials):
246     assert session_info is not None
247     erase = False
248
249     def setup_path(file):
250         return os.path.join(setup_dir, file)
251     os.path.unlink(paths.samdb)
252
253     message("Setting up templates db")
254     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
255                       credentials=credentials, lp=lp)
256
257     # Also wipes the database
258     message("Setting up sam.ldb")
259     samdb = SamDB(paths.samdb, session_info=session_info, 
260                   credentials=credentials, lp=lp)
261
262     message("Setting up sam.ldb partitions")
263     setup_samdb_partitions(samdb, setup_path, schemadn, configdn, domaindn)
264
265     samdb = SamDB(paths.samdb, session_info=session_info, 
266                   credentials=credentials, lp=lp)
267
268     ldb.transaction_start()
269     try:
270         message("Setting up sam.ldb attributes")
271         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
272
273         message("Setting up sam.ldb rootDSE")
274         setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, 
275                             hostname, dnsdomain, realm, rootdn, configdn, 
276                             netbiosname)
277
278         if erase:
279             message("Erasing data from partitions")
280             samdb.erase_partitions()
281
282         message("Setting up sam.ldb indexes")
283         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
284     except:
285         samdb.transaction_cancel()
286         raise
287
288     samdb.transaction_commit()
289
290     message("Setting up %s" % paths.secrets)
291     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, session_info, 
292                                   credentials, lp)
293     setup_ldb(secrets_ldb, setup_path("secrets_dc.ldif"), 
294               { "MACHINEPASS_B64": b64encode(machinepass) })
295
296
297 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
298     if os.path.exists(path):
299         os.unlink(path)
300     secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials, lp=lp)
301     secrets_ldb.erase()
302     secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
303     secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
304     return secrets_ldb
305
306
307 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
308     templates_ldb = SamDB(path, session_info=session_info,
309                         credentials=credentials, lp=lp)
310     templates_ldb.erase()
311     templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
312
313
314 def setup_registry(path, setup_path, session_info, credentials, lp):
315     reg = registry.Registry()
316     hive = registry.open_ldb(path, session_info=session_info, 
317                          credentials=credentials, lp_ctx=lp)
318     reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
319     provision_reg = setup_path("provision.reg")
320     assert os.path.exists(provision_reg)
321     reg.diff_apply(provision_reg)
322
323
324 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname, 
325                         dnsdomain, realm, rootdn, configdn, netbiosname):
326     setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
327         "SCHEMADN": schemadn, 
328         "NETBIOSNAME": netbiosname,
329         "DNSDOMAIN": dnsdomain,
330         "DEFAULTSITE": DEFAULTSITE,
331         "REALM": realm,
332         "DNSNAME": "%s.%s" % (hostname, dnsdomain),
333         "DOMAINDN": domaindn,
334         "ROOTDN": rootdn,
335         "CONFIGDN": configdn,
336         "VERSION": samba.version(),
337         })
338
339
340 def setup_samdb_partitions(samdb, setup_path, schemadn, configdn, domaindn):
341     #Add modules to the list to activate them by default
342     #beware often order is important
343     #
344     # Some Known ordering constraints:
345     # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
346     # - objectclass must be before password_hash, because password_hash checks
347     #   that the objectclass is of type person (filled in by objectclass
348     #   module when expanding the objectclass list)
349     # - partition must be last
350     # - each partition has its own module list then
351     modules_list = ["rootdse",
352                     "paged_results",
353                     "ranged_results",
354                     "anr",
355                     "server_sort",
356                     "extended_dn",
357                     "asq",
358                     "samldb",
359                     "rdn_name",
360                     "objectclass",
361                     "kludge_acl",
362                     "operational"]
363     tdb_modules_list = [
364                     "subtree_rename",
365                     "subtree_delete",
366                     "linked_attributes"]
367     modules_list2 = ["show_deleted",
368                     "partition"]
369  
370     setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
371         "SCHEMADN": schemadn, 
372         "SCHEMADN_LDB": "schema.ldb",
373         "SCHEMADN_MOD2": ",objectguid",
374         "CONFIGDN": configdn,
375         "CONFIGDN_LDB": "configuration.ldb",
376         "DOMAINDN": domaindn,
377         "DOMAINDN_LDB": "users.ldb",
378         "SCHEMADN_MOD": "schema_fsmo",
379         "CONFIGDN_MOD": "naming_fsmo",
380         "CONFIGDN_MOD2": ",objectguid",
381         "DOMAINDN_MOD": "pdc_fsmo,password_hash",
382         "DOMAINDN_MOD2": ",objectguid",
383         "MODULES_LIST": ",".join(modules_list),
384         "TDB_MODULES_LIST": ","+",".join(tdb_modules_list),
385         "MODULES_LIST2": ",".join(modules_list2),
386         })
387
388
389 def setup_self_join(samdb, configdn, schemadn, domaindn, 
390                     netbiosname, hostname, dnsdomain, machinepass, dnspass, 
391                     realm, domainname, domainsid, invocationid, setup_path,
392                     policyguid, hostguid=None):
393     if hostguid is not None:
394         hostguid_add = "objectGUID: %s" % hostguid
395     else:
396         hostguid_add = ""
397
398     setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), { 
399               "CONFIGDN": configdn, 
400               "SCHEMADN": schemadn,
401               "DOMAINDN": domaindn,
402               "INVOCATIONID": invocationid,
403               "NETBIOSNAME": netbiosname,
404               "DEFAULTSITE": DEFAULTSITE,
405               "DNSNAME": "%s.%s" % (hostname, dnsdomain),
406               "MACHINEPASS_B64": b64encode(machinepass),
407               "DNSPASS_B64": b64encode(dnspass),
408               "REALM": realm,
409               "DOMAIN": domainname,
410               "HOSTGUID_ADD": hostguid_add,
411               "DNSDOMAIN": dnsdomain})
412     setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), { 
413               "POLICYGUID": policyguid,
414               "DNSDOMAIN": dnsdomain,
415               "DOMAINSID": str(domainsid),
416               "DOMAINDN": domaindn})
417
418
419 def setup_samdb(path, setup_path, session_info, credentials, lp, 
420                 schemadn, configdn, domaindn, dnsdomain, realm, 
421                 netbiosname, message, hostname, rootdn, erase, 
422                 domainsid, aci, rdn_dc, domainguid, policyguid, 
423                 domainname, blank, adminpass, krbtgtpass, 
424                 machinepass, hostguid, invocationid, dnspass):
425     # Also wipes the database
426     message("Setting up sam.ldb")
427     samdb = SamDB(path, session_info=session_info, 
428                   credentials=credentials, lp=lp)
429
430     message("Setting up sam.ldb partitions")
431     setup_samdb_partitions(samdb, setup_path, schemadn, configdn, domaindn)
432
433     samdb = SamDB(path, session_info=session_info, 
434                   credentials=credentials, lp=lp)
435
436     samdb.transaction_start()
437     try:
438         message("Setting up sam.ldb attributes")
439         samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
440
441         message("Setting up sam.ldb rootDSE")
442         setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, 
443                             hostname, dnsdomain, realm, rootdn, configdn, 
444                             netbiosname)
445
446         if erase:
447             message("Erasing data from partitions")
448             samdb.erase_partitions()
449     except:
450         samdb.transaction_cancel()
451         raise
452
453     samdb.transaction_commit()
454
455     message("Pre-loading the Samba 4 and AD schema")
456     samdb = SamDB(path, session_info=session_info, 
457                   credentials=credentials, lp=lp)
458     samdb.set_domain_sid(domainsid)
459     load_schema(setup_path, samdb, schemadn, netbiosname, configdn)
460
461     samdb.transaction_start()
462         
463     try:
464         message("Adding DomainDN: %s (permitted to fail)" % domaindn)
465         setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
466             "DOMAINDN": domaindn,
467             "ACI": aci,
468             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
469             "RDN_DC": rdn_dc,
470             })
471
472         message("Modifying DomainDN: " + domaindn + "")
473         if domainguid is not None:
474             domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
475         else:
476             domainguid_mod = ""
477
478         setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
479             "RDN_DC": rdn_dc,
480             "LDAPTIME": timestring(int(time.time())),
481             "DOMAINSID": str(domainsid),
482             "SCHEMADN": schemadn, 
483             "NETBIOSNAME": netbiosname,
484             "DEFAULTSITE": DEFAULTSITE,
485             "CONFIGDN": configdn,
486             "POLICYGUID": policyguid,
487             "DOMAINDN": domaindn,
488             "DOMAINGUID_MOD": domainguid_mod,
489             })
490
491         message("Adding configuration container (permitted to fail)")
492         setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
493             "CONFIGDN": configdn, 
494             "ACI": aci,
495             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
496             })
497         message("Modifying configuration container")
498         setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
499             "CONFIGDN": configdn, 
500             "SCHEMADN": schemadn,
501             })
502
503         message("Adding schema container (permitted to fail)")
504         setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
505             "SCHEMADN": schemadn,
506             "ACI": aci,
507             "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
508             })
509         message("Modifying schema container")
510         setup_modify_ldif(samdb, setup_path("provision_schema_basedn_modify.ldif"), {
511             "SCHEMADN": schemadn,
512             "NETBIOSNAME": netbiosname,
513             "DEFAULTSITE": DEFAULTSITE,
514             "CONFIGDN": configdn,
515             })
516
517         message("Setting up sam.ldb Samba4 schema")
518         setup_add_ldif(samdb, setup_path("schema_samba4.ldif"), 
519                        {"SCHEMADN": schemadn })
520         message("Setting up sam.ldb AD schema")
521         setup_add_ldif(samdb, setup_path("schema.ldif"), 
522                        {"SCHEMADN": schemadn})
523
524         message("Setting up sam.ldb configuration data")
525         setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
526             "CONFIGDN": configdn,
527             "NETBIOSNAME": netbiosname,
528             "DEFAULTSITE": DEFAULTSITE,
529             "DNSDOMAIN": dnsdomain,
530             "DOMAIN": domainname,
531             "SCHEMADN": schemadn,
532             "DOMAINDN": domaindn,
533             })
534
535         message("Setting up display specifiers")
536         setup_add_ldif(samdb, setup_path("display_specifiers.ldif"), 
537                        {"CONFIGDN": configdn})
538
539         message("Adding users container (permitted to fail)")
540         setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
541             "DOMAINDN": domaindn})
542         message("Modifying users container")
543         setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
544             "DOMAINDN": domaindn})
545         message("Adding computers container (permitted to fail)")
546         setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
547             "DOMAINDN": domaindn})
548         message("Modifying computers container")
549         setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
550             "DOMAINDN": domaindn})
551         message("Setting up sam.ldb data")
552         setup_add_ldif(samdb, setup_path("provision.ldif"), {
553             "DOMAINDN": domaindn,
554             "NETBIOSNAME": netbiosname,
555             "DEFAULTSITE": DEFAULTSITE,
556             "CONFIGDN": configdn,
557             })
558
559         if not blank:
560             message("Setting up sam.ldb users and groups")
561             setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
562                 "DOMAINDN": domaindn,
563                 "DOMAINSID": str(domainsid),
564                 "CONFIGDN": configdn,
565                 "ADMINPASS_B64": b64encode(adminpass),
566                 "KRBTGTPASS_B64": b64encode(krbtgtpass),
567                 })
568
569             if lp.get("server role") == "domain controller":
570                 message("Setting up self join")
571                 setup_self_join(samdb, configdn=configdn, schemadn=schemadn, domaindn=domaindn, 
572                                 invocationid=invocationid, dnspass=dnspass, netbiosname=netbiosname,
573                                 dnsdomain=dnsdomain, realm=realm, machinepass=machinepass, 
574                                 domainname=domainname, domainsid=domainsid, policyguid=policyguid,
575                                 hostname=hostname, hostguid=hostguid, setup_path=setup_path)
576
577         message("Setting up sam.ldb index")
578         samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
579
580         message("Setting up sam.ldb rootDSE marking as synchronized")
581         setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
582     except:
583         samdb.transaction_cancel()
584         raise
585
586     samdb.transaction_commit()
587     return samdb
588
589
590 def provision(lp, setup_dir, message, blank, paths, session_info, 
591               credentials, ldapbackend, realm=None, domain=None, hostname=None, 
592               hostip=None, domainsid=None, hostguid=None, adminpass=None, 
593               krbtgtpass=None, domainguid=None, policyguid=None, 
594               invocationid=None, machinepass=None, dnspass=None, root=None,
595               nobody=None, nogroup=None, users=None, wheel=None, backup=None, 
596               aci=None, serverrole=None):
597     """Provision samba4
598     
599     :note: caution, this wipes all existing data!
600     """
601
602     def setup_path(file):
603         return os.path.join(setup_dir, file)
604
605     erase = False
606
607     if domainsid is None:
608         domainsid = security.random_sid()
609     if policyguid is None:
610         policyguid = uuid.random()
611     if invocationid is None:
612         invocationid = uuid.random()
613     if adminpass is None:
614         adminpass = misc.random_password(12)
615     if krbtgtpass is None:
616         krbtgtpass = misc.random_password(12)
617     if machinepass is None:
618         machinepass  = misc.random_password(12)
619     if dnspass is None:
620         dnspass = misc.random_password(12)
621     if root is None:
622         root = findnss(pwd.getpwnam, "root")[4]
623     if nobody is None:
624         nobody = findnss(pwd.getpwnam, "nobody")[4]
625     if nogroup is None:
626         nogroup = findnss(grp.getgrnam, "nogroup", "nobody")[2]
627     if users is None:
628         users = findnss(grp.getgrnam, "users", "guest", "other", "unknown", 
629                         "usr")[2]
630     if wheel is None:
631         wheel = findnss(grp.getgrnam, "wheel", "root", "staff", "adm")[2]
632     if backup is None:
633         backup = findnss(grp.getgrnam, "backup", "wheel", "root", "staff")[2]
634     if aci is None:
635         aci = "# no aci for local ldb"
636     if serverrole is None:
637         serverrole = lp.get("server role")
638
639     if realm is None:
640         realm = lp.get("realm")
641     else:
642         if lp.get("realm").upper() != realm.upper():
643             raise Exception("realm '%s' in smb.conf must match chosen realm '%s'\n" %
644                 (lp.get("realm"), realm))
645
646     assert realm is not None
647     realm = realm.upper()
648
649     if domain is None:
650         domain = lp.get("workgroup")
651     else:
652         if lp.get("workgroup").upper() != domain.upper():
653             raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'\n",
654                 lp.get("workgroup"), domain)
655
656     assert domain is not None
657     domain = domain.upper()
658     if not valid_netbios_name(domain):
659         raise InvalidNetbiosName(domain)
660
661     if hostname is None:
662         hostname = gethostname().split(".")[0].lower()
663
664     if hostip is None:
665         hostip = gethostbyname(hostname)
666
667     netbiosname = hostname.upper()
668     if not valid_netbios_name(netbiosname):
669         raise InvalidNetbiosName(netbiosname)
670
671     dnsdomain    = realm.lower()
672     domaindn     = "DC=" + dnsdomain.replace(".", ",DC=")
673     rootdn       = domaindn
674     configdn     = "CN=Configuration," + rootdn
675     schemadn     = "CN=Schema," + configdn
676
677     rdn_dc = domaindn.split(",")[0][len("DC="):]
678
679     message("set DOMAIN SID: %s" % str(domainsid))
680     message("Provisioning for %s in realm %s" % (domain, realm))
681     message("Using administrator password: %s" % adminpass)
682
683     assert paths.smbconf is not None
684
685     # only install a new smb.conf if there isn't one there already
686     if not os.path.exists(paths.smbconf):
687         message("Setting up smb.conf")
688         if serverrole == "domain controller":
689             smbconfsuffix = "dc"
690         elif serverrole == "member":
691             smbconfsuffix = "member"
692         else:
693             assert "Invalid server role setting: %s" % serverrole
694         setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix), paths.smbconf, {
695             "HOSTNAME": hostname,
696             "DOMAIN_CONF": domain,
697             "REALM_CONF": realm,
698             "SERVERROLE": serverrole,
699             "NETLOGONPATH": paths.netlogon,
700             "SYSVOLPATH": paths.sysvol,
701             })
702         lp.reload()
703
704     # only install a new shares config db if there is none
705     if not os.path.exists(paths.shareconf):
706         message("Setting up share.ldb")
707         share_ldb = Ldb(paths.shareconf, session_info=session_info, 
708                         credentials=credentials, lp=lp)
709         share_ldb.load_ldif_file_add(setup_path("share.ldif"))
710
711     message("Setting up secrets.ldb")
712     secrets_ldb = setup_secretsdb(paths.secrets, setup_path, 
713                                   session_info=session_info, 
714                                   credentials=credentials, lp=lp)
715
716     message("Setting up the registry")
717     setup_registry(paths.hklm, setup_path, session_info, 
718                    credentials=credentials, lp=lp)
719
720     message("Setting up templates db")
721     setup_templatesdb(paths.templates, setup_path, session_info=session_info, 
722                       credentials=credentials, lp=lp)
723
724     samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info, credentials=credentials,
725                         lp=lp, schemadn=schemadn, configdn=configdn, domaindn=domaindn,
726                         dnsdomain=dnsdomain, netbiosname=netbiosname, realm=realm, message=message,
727                         hostname=hostname, rootdn=rootdn, erase=erase, domainsid=domainsid, aci=aci,
728                         rdn_dc=rdn_dc, domainguid=domainguid, policyguid=policyguid, 
729                         domainname=domain, blank=blank, adminpass=adminpass, krbtgtpass=krbtgtpass,
730                         hostguid=hostguid, invocationid=invocationid, machinepass=machinepass,
731                         dnspass=dnspass)
732
733     if lp.get("server role") == "domain controller":
734         os.makedirs(os.path.join(paths.sysvol, dnsdomain, "Policies", "{" + policyguid + "}"), 0755)
735         os.makedirs(os.path.join(paths.sysvol, dnsdomain, "Policies", "{" + policyguid + "}", "Machine"), 0755)
736         os.makedirs(os.path.join(paths.sysvol, dnsdomain, "Policies", "{" + policyguid + "}", "User"), 0755)
737         if not os.path.isdir(paths.netlogon):
738             os.makedirs(paths.netlogon, 0755)
739         secrets_ldb = Ldb(paths.secrets, session_info=session_info, credentials=credentials, lp=lp)
740         setup_ldb(secrets_ldb, setup_path("secrets_dc.ldif"), { 
741             "MACHINEPASS_B64": b64encode(machinepass),
742             "DOMAIN": domain,
743             "REALM": realm,
744             "LDAPTIME": timestring(int(time.time())),
745             "DNSDOMAIN": dnsdomain,
746             "DOMAINSID": str(domainsid),
747             "SECRETS_KEYTAB": paths.keytab,
748             "NETBIOSNAME": netbiosname,
749             "SAM_LDB": paths.samdb,
750             "DNS_KEYTAB": paths.dns_keytab,
751             "DNSPASS_B64": b64encode(dnspass),
752             })
753
754     if not blank:
755         setup_name_mappings(samdb, str(domainsid), 
756                         domaindn, root=root, nobody=nobody, 
757                         nogroup=nogroup, wheel=wheel, users=users,
758                         backup=backup)
759
760     message("Setting up phpLDAPadmin configuration")
761     create_phplpapdadmin_config(paths.phpldapadminconfig, setup_path, paths.s4_ldapi_path)
762
763     message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
764
765     if lp.get("server role") == "domain controller":
766         samdb = SamDB(paths.samdb, session_info=session_info, 
767                       credentials=credentials, lp=lp)
768
769         domainguid = samdb.searchone(domaindn, "objectGUID")
770         assert isinstance(domainguid, str)
771         hostguid = samdb.searchone(domaindn, "objectGUID",
772                 expression="(&(objectClass=computer)(cn=%s))" % hostname,
773                 scope=SCOPE_SUBTREE)
774         assert isinstance(hostguid, str)
775
776         message("Setting up DNS zone: %s" % dnsdomain)
777         create_zone_file(paths.dns, setup_path, samdb, 
778                       hostname=hostname, hostip=hostip, dnsdomain=dnsdomain,
779                       domaindn=domaindn, dnspass=dnspass, realm=realm, 
780                       domainguid=domainguid, hostguid=hostguid)
781         message("Please install the zone located in %s into your DNS server" % paths.dns)
782
783     return domaindn
784
785 def create_phplpapdadmin_config(path, setup_path, s4_ldapi_path):
786     """Create a PHP LDAP admin configuration file.
787
788     :param path: Path to write the configuration to.
789     :param setup_path: Function to generate setup paths.
790     :param s4_ldapi_path: Path to Samba 4 LDAPI socket.
791     """
792     setup_file(setup_path("phpldapadmin-config.php"), 
793                path, {"S4_LDAPI_URI": "ldapi://%s" % s4_ldapi_path.replace("/", "%2F")})
794
795
796 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn, 
797                   hostip, hostname, dnspass, realm, domainguid, hostguid):
798     """Write out a DNS zone file, from the info in the current database.
799     
800     :param path: Path of the new file.
801     :param setup_path": Setup path function.
802     :param samdb: SamDB object
803     :param dnsdomain: DNS Domain name
804     :param domaindn: DN of the Domain
805     :param hostip: Local IP
806     :param hostname: Local hostname
807     :param dnspass: Password for DNS
808     :param realm: Realm name
809     :param domainguid: GUID of the domain.
810     :param hostguid: GUID of the host.
811     """
812
813     setup_file(setup_path("provision.zone"), path, {
814             "DNSPASS_B64": b64encode(dnspass),
815             "HOSTNAME": hostname,
816             "DNSDOMAIN": dnsdomain,
817             "REALM": realm,
818             "HOSTIP": hostip,
819             "DOMAINGUID": domainguid,
820             "DATESTRING": time.strftime("%Y%m%d%H"),
821             "DEFAULTSITE": DEFAULTSITE,
822             "HOSTGUID": hostguid,
823         })
824
825
826 def provision_ldapbase(setup_dir, message, paths):
827     """Write out a DNS zone file, from the info in the current database."""
828     message("Setting up LDAP base entry: %s" % domaindn)
829     rdns = domaindn.split(",")
830
831     rdn_dc = rdns[0][len("DC="):]
832
833     def setup_path(file):
834         return os.path.join(setup_dir, file)
835
836     setup_file(setup_path("provision_basedn.ldif"), 
837            paths.ldap_basedn_ldif)
838
839     setup_file(setup_path("provision_configuration_basedn.ldif"), 
840            paths.ldap_config_basedn_ldif)
841
842     setup_file(setup_path("provision_schema_basedn.ldif"), 
843            paths.ldap_schema_basedn_ldif, {
844             "SCHEMADN": schemadn,
845             "ACI": "# no aci for local ldb",
846             "EXTENSIBLEOBJECT": "objectClass: extensibleObject"})
847
848     message("Please install the LDIF located in " + paths.ldap_basedn_ldif + ", " + paths.ldap_config_basedn_ldif + " and " + paths.ldap_schema_basedn_ldif + " into your LDAP server, and re-run with --ldap-backend=ldap://my.ldap.server")
849
850
851 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn):
852     """Load schema.
853     
854     :param samdb: Load a schema into a SamDB.
855     :param setup_path: Setup path function.
856     :param schemadn: DN of the schema
857     :param netbiosname: NetBIOS name of the host.
858     :param configdn: DN of the configuration
859     """
860     schema_data = open(setup_path("schema.ldif"), 'r').read()
861     schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
862     schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
863     head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
864     head_data = substitute_var(head_data, {
865                     "SCHEMADN": schemadn,
866                     "NETBIOSNAME": netbiosname,
867                     "CONFIGDN": configdn,
868                     "DEFAULTSITE": DEFAULTSITE})
869     samdb.attach_schema_from_ldif(head_data, schema_data)
870