9034140195b0dabd692b65d8757e8e8260e56530
[obnox/samba/samba-obnox.git] / source4 / scripting / python / samba / upgradehelpers.py
1 #!/usr/bin/env python
2 #
3 # Helpers for provision stuff
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
5 #
6 # Based on provision a Samba4 server by
7 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
8 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
9 #
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23
24 """Helpers used for upgrading between different database formats."""
25
26 import os
27 import re
28 import shutil
29 import samba
30
31 from samba import Ldb, version, ntacls
32 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
33 import ldb
34 from samba.provision import (provision_paths_from_lp,
35                             getpolicypath, set_gpos_acl, create_gpo_struct,
36                             FILL_FULL, provision, ProvisioningError,
37                             setsysvolacl, secretsdb_self_join)
38 from samba.dcerpc import xattr
39 from samba.dcerpc.misc import SEC_CHAN_BDC
40 from samba.samdb import SamDB
41
42 # All the ldb related to registry are commented because the path for them is
43 # relative in the provisionPath object
44 # And so opening them create a file in the current directory which is not what
45 # we want
46 # I still keep them commented because I plan soon to make more cleaner
47 ERROR =     -1
48 SIMPLE =     0x00
49 CHANGE =     0x01
50 CHANGESD =     0x02
51 GUESS =     0x04
52 PROVISION =    0x08
53 CHANGEALL =    0xff
54
55 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
56     "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
57     "objectCategory", "distinguishedName", "nTMixedDomain",
58     "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
59     "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
60     "ntPwdHistory", "unicodePwd","dBCSPwd", "supplementalCredentials",
61     "gPCUserExtensionNames", "gPCMachineExtensionNames","maxPwdAge", "secret",
62     "possibleInferiors", "privilege", "sAMAccountType"])
63
64
65 class ProvisionLDB(object):
66
67     def __init__(self):
68         self.sam = None
69         self.secrets = None
70         self.idmap = None
71         self.privilege = None
72         self.hkcr = None
73         self.hkcu = None
74         self.hku = None
75         self.hklm = None
76
77     def startTransactions(self):
78         self.sam.transaction_start()
79         self.secrets.transaction_start()
80         self.idmap.transaction_start()
81         self.privilege.transaction_start()
82 # TO BE DONE
83 #        self.hkcr.transaction_start()
84 #        self.hkcu.transaction_start()
85 #        self.hku.transaction_start()
86 #        self.hklm.transaction_start()
87
88     def groupedRollback(self):
89         ok = True
90         try:
91             self.sam.transaction_cancel()
92         except Exception:
93             ok = False
94
95         try:
96             self.secrets.transaction_cancel()
97         except Exception:
98             ok = False
99
100         try:
101             self.idmap.transaction_cancel()
102         except Exception:
103             ok = False
104
105         try:
106             self.privilege.transaction_cancel()
107         except Exception:
108             ok = False
109
110         return ok
111 # TO BE DONE
112 #        self.hkcr.transaction_cancel()
113 #        self.hkcu.transaction_cancel()
114 #        self.hku.transaction_cancel()
115 #        self.hklm.transaction_cancel()
116
117     def groupedCommit(self):
118         try:
119             self.sam.transaction_prepare_commit()
120             self.secrets.transaction_prepare_commit()
121             self.idmap.transaction_prepare_commit()
122             self.privilege.transaction_prepare_commit()
123         except Exception:
124             return self.groupedRollback()
125 # TO BE DONE
126 #        self.hkcr.transaction_prepare_commit()
127 #        self.hkcu.transaction_prepare_commit()
128 #        self.hku.transaction_prepare_commit()
129 #        self.hklm.transaction_prepare_commit()
130         try:
131             self.sam.transaction_commit()
132             self.secrets.transaction_commit()
133             self.idmap.transaction_commit()
134             self.privilege.transaction_commit()
135         except Exception:
136             return self.groupedRollback()
137
138 # TO BE DONE
139 #        self.hkcr.transaction_commit()
140 #        self.hkcu.transaction_commit()
141 #        self.hku.transaction_commit()
142 #        self.hklm.transaction_commit()
143         return True
144
145 def get_ldbs(paths, creds, session, lp):
146     """Return LDB object mapped on most important databases
147
148     :param paths: An object holding the different importants paths for provision object
149     :param creds: Credential used for openning LDB files
150     :param session: Session to use for openning LDB files
151     :param lp: A loadparam object
152     :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
153
154     ldbs = ProvisionLDB()
155
156     ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
157     ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
158     ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
159     ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
160 #    ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
161 #    ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
162 #    ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
163 #    ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
164
165     return ldbs
166
167
168 def usn_in_range(usn, range):
169     """Check if the usn is in one of the range provided.
170     To do so, the value is checked to be between the lower bound and
171     higher bound of a range
172
173     :param usn: A integer value corresponding to the usn that we want to update
174     :param range: A list of integer representing ranges, lower bounds are in
175                   the even indices, higher in odd indices
176     :return: True if the usn is in one of the range, False otherwise
177     """
178
179     idx = 0
180     cont = True
181     ok = False
182     while cont:
183         if idx ==  len(range):
184             cont = False
185             continue
186         if usn < int(range[idx]):
187             if idx %2 == 1:
188                 ok = True
189             cont = False
190         if usn == int(range[idx]):
191             cont = False
192             ok = True
193         idx = idx + 1
194     return ok
195
196
197 def get_paths(param, targetdir=None, smbconf=None):
198     """Get paths to important provision objects (smb.conf, ldb files, ...)
199
200     :param param: Param object
201     :param targetdir: Directory where the provision is (or will be) stored
202     :param smbconf: Path to the smb.conf file
203     :return: A list with the path of important provision objects"""
204     if targetdir is not None:
205         etcdir = os.path.join(targetdir, "etc")
206         if not os.path.exists(etcdir):
207             os.makedirs(etcdir)
208         smbconf = os.path.join(etcdir, "smb.conf")
209     if smbconf is None:
210         smbconf = param.default_path()
211
212     if not os.path.exists(smbconf):
213         raise ProvisioningError("Unable to find smb.conf")
214
215     lp = param.LoadParm()
216     lp.load(smbconf)
217     paths = provision_paths_from_lp(lp, lp.get("realm"))
218     return paths
219
220 def update_policyids(names, samdb):
221     """Update policy ids that could have changed after sam update
222
223     :param names: List of key provision parameters
224     :param samdb: An Ldb object conntected with the sam DB
225     """
226     # policy guid
227     res = samdb.search(expression="(displayName=Default Domain Policy)",
228                         base="CN=Policies,CN=System," + str(names.rootdn),
229                         scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
230     names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
231     # dc policy guid
232     res2 = samdb.search(expression="(displayName=Default Domain Controllers"
233                                    " Policy)",
234                             base="CN=Policies,CN=System," + str(names.rootdn),
235                             scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
236     if len(res2) == 1:
237         names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
238     else:
239         names.policyid_dc = None
240
241
242 def newprovision(names, creds, session, smbconf, provdir, logger):
243     """Create a new provision.
244
245     This provision will be the reference for knowing what has changed in the
246     since the latest upgrade in the current provision
247
248     :param names: List of provision parameters
249     :param creds: Credentials for the authentification
250     :param session: Session object
251     :param smbconf: Path to the smb.conf file
252     :param provdir: Directory where the provision will be stored
253     :param logger: A Logger
254     """
255     if os.path.isdir(provdir):
256         shutil.rmtree(provdir)
257     os.mkdir(provdir)
258     logger.info("Provision stored in %s", provdir)
259     provision(logger, session, creds, smbconf=smbconf,
260             targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
261             domain=names.domain, domainguid=names.domainguid,
262             domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
263             policyguid=names.policyid, policyguid_dc=names.policyid_dc,
264             hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
265             invocationid=names.invocation, adminpass=names.adminpass,
266             krbtgtpass=None, machinepass=None, dnspass=None, root=None,
267             nobody=None, wheel=None, users=None,
268             serverrole="domain controller", ldap_backend_extra_port=None,
269             backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
270             slapd_path=None, setup_ds_path=None, nosync=None,
271             dom_for_fun_level=names.domainlevel,
272             ldap_dryrun_mode=None, useeadb=True)
273
274
275 def dn_sort(x, y):
276     """Sorts two DNs in the lexicographical order it and put higher level DN
277     before.
278
279     So given the dns cn=bar,cn=foo and cn=foo the later will be return as
280     smaller
281
282     :param x: First object to compare
283     :param y: Second object to compare
284     """
285     p = re.compile(r'(?<!\\), ?')
286     tab1 = p.split(str(x))
287     tab2 = p.split(str(y))
288     minimum = min(len(tab1), len(tab2))
289     len1 = len(tab1)-1
290     len2 = len(tab2)-1
291     # Note: python range go up to upper limit but do not include it
292     for i in range(0, minimum):
293         ret = cmp(tab1[len1-i], tab2[len2-i])
294         if ret != 0:
295             return ret
296         else:
297             if i == minimum-1:
298                 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
299                 if len1 > len2:
300                     return 1
301                 else:
302                     return -1
303     return ret
304
305
306 def identic_rename(ldbobj, dn):
307     """Perform a back and forth rename to trigger renaming on attribute that
308     can't be directly modified.
309
310     :param lbdobj: An Ldb Object
311     :param dn: DN of the object to manipulate
312     """
313     (before, after) = str(dn).split('=', 1)
314     # we need to use relax to avoid the subtree_rename constraints
315     ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
316     ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
317
318
319 def chunck_acl(acl):
320     """Return separate ACE of an ACL
321
322     :param acl: A string representing the ACL
323     :return: A hash with different parts
324     """
325
326     p = re.compile(r'(\w+)?(\(.*?\))')
327     tab = p.findall(acl)
328
329     hash = {}
330     hash["aces"] = []
331     for e in tab:
332         if len(e[0]) > 0:
333             hash["flags"] = e[0]
334         hash["aces"].append(e[1])
335
336     return hash
337
338
339 def chunck_sddl(sddl):
340     """ Return separate parts of the SDDL (owner, group, ...)
341
342     :param sddl: An string containing the SDDL to chunk
343     :return: A hash with the different chunk
344     """
345
346     p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
347     tab = p.findall(sddl)
348
349     hash = {}
350     for e in tab:
351         if e[0] == "O:":
352             hash["owner"] = e[1]
353         if e[0] == "G:":
354             hash["group"] = e[1]
355         if e[0] == "D:":
356             hash["dacl"] = e[1]
357         if e[0] == "S:":
358             hash["sacl"] = e[1]
359
360     return hash
361
362
363 def get_diff_sddls(refsddl, cursddl, checkSacl = True):
364     """Get the difference between 2 sddl
365
366     This function split the textual representation of ACL into smaller
367     chunck in order to not to report a simple permutation as a difference
368
369     :param refsddl: First sddl to compare
370     :param cursddl: Second sddl to compare
371     :param checkSacl: If false we skip the sacl checks
372     :return: A string that explain difference between sddls
373     """
374
375     txt = ""
376     hash_cur = chunck_sddl(cursddl)
377     hash_ref = chunck_sddl(refsddl)
378
379     if not hash_cur.has_key("owner"):
380         txt = "\tNo owner in current SD"
381     elif hash_cur["owner"] != hash_ref["owner"]:
382         txt = "\tOwner mismatch: %s (in ref) %s" \
383               "(in current)\n" % (hash_ref["owner"], hash_cur["owner"])
384
385     if not hash_cur.has_key("group"):
386         txt = "%s\tNo group in current SD" % txt
387     elif hash_cur["group"] != hash_ref["group"]:
388         txt = "%s\tGroup mismatch: %s (in ref) %s" \
389               "(in current)\n" % (txt, hash_ref["group"], hash_cur["group"])
390
391     parts = [ "dacl" ]
392     if checkSacl:
393         parts.append("sacl")
394     for part in parts:
395         if hash_cur.has_key(part) and hash_ref.has_key(part):
396
397             # both are present, check if they contain the same ACE
398             h_cur = set()
399             h_ref = set()
400             c_cur = chunck_acl(hash_cur[part])
401             c_ref = chunck_acl(hash_ref[part])
402
403             for elem in c_cur["aces"]:
404                 h_cur.add(elem)
405
406             for elem in c_ref["aces"]:
407                 h_ref.add(elem)
408
409             for k in set(h_ref):
410                 if k in h_cur:
411                     h_cur.remove(k)
412                     h_ref.remove(k)
413
414             if len(h_cur) + len(h_ref) > 0:
415                 txt = "%s\tPart %s is different between reference" \
416                       " and current here is the detail:\n" % (txt, part)
417
418                 for item in h_cur:
419                     txt = "%s\t\t%s ACE is not present in the" \
420                           " reference\n" % (txt, item)
421
422                 for item in h_ref:
423                     txt = "%s\t\t%s ACE is not present in the" \
424                           " current\n" % (txt, item)
425
426         elif hash_cur.has_key(part) and not hash_ref.has_key(part):
427             txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
428         elif not hash_cur.has_key(part) and hash_ref.has_key(part):
429             txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
430
431     return txt
432
433
434 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
435     """Update secrets.ldb
436
437     :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
438         of the reference provision
439     :param secrets_ldb: An LDB object that is connected to the secrets.ldb
440         of the updated provision
441     """
442
443     messagefunc(SIMPLE, "Update of secrets.ldb")
444     reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
445     current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
446     assert reference, "Reference modules list can not be empty"
447     if len(current) == 0:
448         # No modules present
449         delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
450         delta.dn = reference[0].dn
451         secrets_ldb.add(reference[0])
452     else:
453         delta = secrets_ldb.msg_diff(current[0], reference[0])
454         delta.dn = current[0].dn
455         secrets_ldb.modify(delta)
456
457     reference = newsecrets_ldb.search(expression="objectClass=top", base="",
458                                         scope=SCOPE_SUBTREE, attrs=["dn"])
459     current = secrets_ldb.search(expression="objectClass=top", base="",
460                                         scope=SCOPE_SUBTREE, attrs=["dn"])
461     hash_new = {}
462     hash = {}
463     listMissing = []
464     listPresent = []
465
466     empty = ldb.Message()
467     for i in range(0, len(reference)):
468         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
469
470     # Create a hash for speeding the search of existing object in the
471     # current provision
472     for i in range(0, len(current)):
473         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
474
475     for k in hash_new.keys():
476         if not hash.has_key(k):
477             listMissing.append(hash_new[k])
478         else:
479             listPresent.append(hash_new[k])
480
481     for entry in listMissing:
482         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
483                                             base="", scope=SCOPE_SUBTREE)
484         current = secrets_ldb.search(expression="dn=%s" % entry,
485                                             base="", scope=SCOPE_SUBTREE)
486         delta = secrets_ldb.msg_diff(empty, reference[0])
487         for att in hashAttrNotCopied:
488             delta.remove(att)
489         messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
490                     reference[0].dn)
491         for att in delta:
492             messagefunc(CHANGE, " Adding attribute %s" % att)
493         delta.dn = reference[0].dn
494         secrets_ldb.add(delta)
495
496     for entry in listPresent:
497         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
498                                             base="", scope=SCOPE_SUBTREE)
499         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
500                                             scope=SCOPE_SUBTREE)
501         delta = secrets_ldb.msg_diff(current[0], reference[0])
502         for att in hashAttrNotCopied:
503             delta.remove(att)
504         for att in delta:
505             if att == "name":
506                 messagefunc(CHANGE, "Found attribute name on  %s,"
507                                     " must rename the DN" % (current[0].dn))
508                 identic_rename(secrets_ldb, reference[0].dn)
509             else:
510                 delta.remove(att)
511
512     for entry in listPresent:
513         reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
514                                             scope=SCOPE_SUBTREE)
515         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
516                                             scope=SCOPE_SUBTREE)
517         delta = secrets_ldb.msg_diff(current[0], reference[0])
518         for att in hashAttrNotCopied:
519             delta.remove(att)
520         for att in delta:
521             if att == "msDS-KeyVersionNumber":
522                 delta.remove(att)
523             if att != "dn":
524                 messagefunc(CHANGE,
525                             "Adding/Changing attribute %s to %s" %
526                             (att, current[0].dn))
527
528         delta.dn = current[0].dn
529         secrets_ldb.modify(delta)
530
531     res2 = secrets_ldb.search(expression="(samaccountname=dns)",
532                                 scope=SCOPE_SUBTREE, attrs=["dn"])
533
534     if (len(res2) == 1):
535             messagefunc(SIMPLE, "Remove old dns account")
536             secrets_ldb.delete(res2[0]["dn"])
537
538
539 def getOEMInfo(samdb, rootdn):
540     """Return OEM Information on the top level Samba4 use to store version
541     info in this field
542
543     :param samdb: An LDB object connect to sam.ldb
544     :param rootdn: Root DN of the domain
545     :return: The content of the field oEMInformation (if any)
546     """
547     res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
548                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
549     if len(res) > 0 and res[0].get("oEMInformation"):
550         info = res[0]["oEMInformation"]
551         return info
552     else:
553         return ""
554
555
556 def updateOEMInfo(samdb, rootdn):
557     """Update the OEMinfo field to add information about upgrade
558
559     :param samdb: an LDB object connected to the sam DB
560     :param rootdn: The string representation of the root DN of
561         the provision (ie. DC=...,DC=...)
562     """
563     res = samdb.search(expression="(objectClass=*)", base=rootdn,
564                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
565     if len(res) > 0:
566         if res[0].get("oEMInformation"):
567             info = str(res[0]["oEMInformation"])
568         else:
569             info = ""
570         info = "%s, upgrade to %s" % (info, version)
571         delta = ldb.Message()
572         delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
573         delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
574                                                         "oEMInformation" )
575         samdb.modify(delta)
576
577 def update_gpo(paths, samdb, names, lp, message, force=0):
578     """Create missing GPO file object if needed
579
580     Set ACL correctly also.
581     Check ACLs for sysvol/netlogon dirs also
582     """
583     resetacls = False
584     try:
585         ntacls.checkset_backend(lp, None, None)
586         eadbname = lp.get("posix:eadb")
587         if eadbname is not None and eadbname != "":
588             try:
589                 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
590                                 paths.sysvol, xattr.XATTR_NTACL_NAME)
591             except Exception:
592                 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
593                                 xattr.XATTR_NTACL_NAME)
594         else:
595             attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
596                                 xattr.XATTR_NTACL_NAME)
597     except Exception:
598        resetacls = True
599
600     if force:
601         resetacls = True
602
603     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
604     if not os.path.isdir(dir):
605         create_gpo_struct(dir)
606
607     if names.policyid_dc is None:
608         raise ProvisioningError("Policy ID for Domain controller is missing")
609     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
610     if not os.path.isdir(dir):
611         create_gpo_struct(dir)
612
613     def acl_error(e):
614         if os.geteuid() == 0:
615             message(ERROR, "Unable to set ACLs on policies related objects: %s" % e)
616         else:
617             message(ERROR, "Unable to set ACLs on policies related objects. "
618                     "ACLs must be set as root if file system ACLs "
619                     "(rather than posix:eadb) are used.")
620
621     # We always reinforce acls on GPO folder because they have to be in sync
622     # with the one in DS
623     try:
624         set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
625             names.domaindn, samdb, lp)
626     except TypeError, e:
627         acl_error(e)
628
629     if resetacls:
630        try:
631             setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
632                         names.domainsid, names.dnsdomain, names.domaindn, lp)
633        except TypeError, e:
634            acl_error(e)
635
636
637 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
638     """For a given hash associating dn and a number, this function will
639     update the replPropertyMetaData of each dn in the hash, so that the
640     calculated value of the msDs-KeyVersionNumber is equal or superior to the
641     one associated to the given dn.
642
643     :param samdb: An SamDB object pointing to the sam
644     :param rootdn: The base DN where we want to start
645     :param hashDns: A hash with dn as key and number representing the
646                  minimum value of msDs-KeyVersionNumber that we want to
647                  have
648     """
649     entry = samdb.search(expression='(objectClass=user)',
650                          base=ldb.Dn(samdb,str(rootdn)),
651                          scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
652                          controls=["search_options:1:2"])
653     done = 0
654     hashDone = {}
655     if len(entry) == 0:
656         raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
657     else:
658         for e in entry:
659             if hashDns.has_key(str(e.dn).lower()):
660                 val = e.get("msDs-KeyVersionNumber")
661                 if not val:
662                     val = "0"
663                 version = int(str(hashDns[str(e.dn).lower()]))
664                 if int(str(val)) < version:
665                     done = done + 1
666                     samdb.set_attribute_replmetadata_version(str(e.dn),
667                                                               "unicodePwd",
668                                                               version, True)
669 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
670     """Update the provision container db: sam.ldb
671     This function is aimed for alpha9 and newer;
672
673     :param refsampath: Path to the samdb in the reference provision
674     :param sampath: Path to the samdb in the upgraded provision
675     :param creds: Credential used for openning LDB files
676     :param session: Session to use for openning LDB files
677     :param lp: A loadparam object
678     :return: A msg_diff object with the difference between the @ATTRIBUTES
679              of the current provision and the reference provision
680     """
681
682     message(SIMPLE,
683             "Update base samdb by searching difference with reference one")
684     refsam = Ldb(refsampath, session_info=session, credentials=creds,
685                     lp=lp, options=["modules:"])
686     sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
687                 options=["modules:"])
688
689     empty = ldb.Message()
690     deltaattr = None
691     reference = refsam.search(expression="")
692
693     for refentry in reference:
694         entry = sam.search(expression="dn=%s" % refentry["dn"],
695                             scope=SCOPE_SUBTREE)
696         if not len(entry):
697             delta = sam.msg_diff(empty, refentry)
698             message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
699             if str(refentry.dn) == "@PROVISION" and\
700                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
701                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
702             delta.dn = refentry.dn
703             sam.add(delta)
704         else:
705             delta = sam.msg_diff(entry[0], refentry)
706             if str(refentry.dn) == "@ATTRIBUTES":
707                 deltaattr = sam.msg_diff(refentry, entry[0])
708             if str(refentry.dn) == "@PROVISION" and\
709                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
710                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
711             if len(delta.items()) > 1:
712                 delta.dn = refentry.dn
713                 sam.modify(delta)
714
715     return deltaattr
716
717
718 def construct_existor_expr(attrs):
719     """Construct a exists or LDAP search expression.
720
721     :param attrs: List of attribute on which we want to create the search
722         expression.
723     :return: A string representing the expression, if attrs is empty an
724         empty string is returned
725     """
726     expr = ""
727     if len(attrs) > 0:
728         expr = "(|"
729         for att in attrs:
730             expr = "%s(%s=*)"%(expr,att)
731         expr = "%s)"%expr
732     return expr
733
734 def update_machine_account_password(samdb, secrets_ldb, names):
735     """Update (change) the password of the current DC both in the SAM db and in
736        secret one
737
738     :param samdb: An LDB object related to the sam.ldb file of a given provision
739     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
740                         provision
741     :param names: List of key provision parameters"""
742
743     expression = "samAccountName=%s$" % names.netbiosname
744     secrets_msg = secrets_ldb.search(expression=expression,
745                                         attrs=["secureChannelType"])
746     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
747         res = samdb.search(expression=expression, attrs=[])
748         assert(len(res) == 1)
749
750         msg = ldb.Message(res[0].dn)
751         machinepass = samba.generate_random_password(128, 255)
752         mputf16 = machinepass.encode('utf-16-le')
753         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
754                                                 ldb.FLAG_MOD_REPLACE,
755                                                 "clearTextPassword")
756         samdb.modify(msg)
757
758         res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
759                      attrs=["msDs-keyVersionNumber"])
760         assert(len(res) == 1)
761         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
762         secChanType = int(secrets_msg[0]["secureChannelType"][0])
763
764         secretsdb_self_join(secrets_ldb, domain=names.domain,
765                     realm=names.realm,
766                     domainsid=names.domainsid,
767                     dnsdomain=names.dnsdomain,
768                     netbiosname=names.netbiosname,
769                     machinepass=machinepass,
770                     key_version_number=kvno,
771                     secure_channel_type=secChanType)
772     else:
773         raise ProvisioningError("Unable to find a Secure Channel"
774                                 "of type SEC_CHAN_BDC")
775
776 def update_dns_account_password(samdb, secrets_ldb, names):
777     """Update (change) the password of the dns both in the SAM db and in
778        secret one
779
780     :param samdb: An LDB object related to the sam.ldb file of a given provision
781     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
782                         provision
783     :param names: List of key provision parameters"""
784
785     expression = "samAccountName=dns-%s" % names.netbiosname
786     secrets_msg = secrets_ldb.search(expression=expression)
787     if len(secrets_msg) == 1:
788         res = samdb.search(expression=expression, attrs=[])
789         assert(len(res) == 1)
790
791         msg = ldb.Message(res[0].dn)
792         machinepass = samba.generate_random_password(128, 255)
793         mputf16 = machinepass.encode('utf-16-le')
794         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
795                                                 ldb.FLAG_MOD_REPLACE,
796                                                 "clearTextPassword")
797
798         samdb.modify(msg)
799
800         res = samdb.search(expression=expression,
801                      attrs=["msDs-keyVersionNumber"])
802         assert(len(res) == 1)
803         kvno = str(res[0]["msDs-keyVersionNumber"])
804
805         msg = ldb.Message(secrets_msg[0].dn)
806         msg["secret"] = ldb.MessageElement(machinepass,
807                                                 ldb.FLAG_MOD_REPLACE,
808                                                 "secret")
809         msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
810                                                 ldb.FLAG_MOD_REPLACE,
811                                                 "msDS-KeyVersionNumber")
812
813         secrets_ldb.modify(msg)
814     else:
815         raise ProvisioningError("Unable to find an object"
816                                 " with %s" % expression )
817
818 def search_constructed_attrs_stored(samdb, rootdn, attrs):
819     """Search a given sam DB for calculated attributes that are
820     still stored in the db.
821
822     :param samdb: An LDB object pointing to the sam
823     :param rootdn: The base DN where the search should start
824     :param attrs: A list of attributes to be searched
825     :return: A hash with attributes as key and an array of
826              array. Each array contains the dn and the associated
827              values for this attribute as they are stored in the
828              sam."""
829
830     hashAtt = {}
831     expr = construct_existor_expr(attrs)
832     if expr == "":
833         return hashAtt
834     entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
835                          scope=SCOPE_SUBTREE, attrs=attrs,
836                          controls=["search_options:1:2","bypassoperational:0"])
837     if len(entry) == 0:
838         # Nothing anymore
839         return hashAtt
840
841     for ent in entry:
842         for att in attrs:
843             if ent.get(att):
844                 if hashAtt.has_key(att):
845                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
846                 else:
847                     hashAtt[att] = {}
848                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
849
850     return hashAtt
851
852 def int64range2str(value):
853     """Display the int64 range stored in value as xxx-yyy
854
855     :param value: The int64 range
856     :return: A string of the representation of the range
857     """
858
859     lvalue = long(value)
860     str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
861     return str