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