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