s4-upgradeprovisision: fix bug 8063, old SD can miss some componenent (group, owner...
[amitay/samba.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):
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     :return: A string that explain difference between sddls
372     """
373
374     txt = ""
375     hash_cur = chunck_sddl(cursddl)
376     hash_ref = chunck_sddl(refsddl)
377
378     if not hash_cur.has_key("owner"):
379         txt = "\tNo owner in current SD"
380     elif hash_cur["owner"] != hash_ref["owner"]:
381         txt = "\tOwner mismatch: %s (in ref) %s" \
382               "(in current)\n" % (hash_ref["owner"], hash_cur["owner"])
383
384     if not hash_cur.has_key("group"):
385         txt = "%s\tNo group in current SD" % txt
386     elif hash_cur["group"] != hash_ref["group"]:
387         txt = "%s\tGroup mismatch: %s (in ref) %s" \
388               "(in current)\n" % (txt, hash_ref["group"], hash_cur["group"])
389
390     for part in ["dacl", "sacl"]:
391         if hash_cur.has_key(part) and hash_ref.has_key(part):
392
393             # both are present, check if they contain the same ACE
394             h_cur = set()
395             h_ref = set()
396             c_cur = chunck_acl(hash_cur[part])
397             c_ref = chunck_acl(hash_ref[part])
398
399             for elem in c_cur["aces"]:
400                 h_cur.add(elem)
401
402             for elem in c_ref["aces"]:
403                 h_ref.add(elem)
404
405             for k in set(h_ref):
406                 if k in h_cur:
407                     h_cur.remove(k)
408                     h_ref.remove(k)
409
410             if len(h_cur) + len(h_ref) > 0:
411                 txt = "%s\tPart %s is different between reference" \
412                       " and current here is the detail:\n" % (txt, part)
413
414                 for item in h_cur:
415                     txt = "%s\t\t%s ACE is not present in the" \
416                           " reference\n" % (txt, item)
417
418                 for item in h_ref:
419                     txt = "%s\t\t%s ACE is not present in the" \
420                           " current\n" % (txt, item)
421
422         elif hash_cur.has_key(part) and not hash_ref.has_key(part):
423             txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
424         elif not hash_cur.has_key(part) and hash_ref.has_key(part):
425             txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
426
427     return txt
428
429
430 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
431     """Update secrets.ldb
432
433     :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
434         of the reference provision
435     :param secrets_ldb: An LDB object that is connected to the secrets.ldb
436         of the updated provision
437     """
438
439     messagefunc(SIMPLE, "update secrets.ldb")
440     reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
441                                         scope=SCOPE_SUBTREE)
442     current = secrets_ldb.search(expression="dn=@MODULES", base="",
443                                         scope=SCOPE_SUBTREE)
444     assert reference, "Reference modules list can not be empty"
445     if len(current) == 0:
446         # No modules present
447         delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
448         delta.dn = reference[0].dn
449         secrets_ldb.add(reference[0])
450     else:
451         delta = secrets_ldb.msg_diff(current[0], reference[0])
452         delta.dn = current[0].dn
453         secrets_ldb.modify(delta)
454
455     reference = newsecrets_ldb.search(expression="objectClass=top", base="",
456                                         scope=SCOPE_SUBTREE, attrs=["dn"])
457     current = secrets_ldb.search(expression="objectClass=top", base="",
458                                         scope=SCOPE_SUBTREE, attrs=["dn"])
459     hash_new = {}
460     hash = {}
461     listMissing = []
462     listPresent = []
463
464     empty = ldb.Message()
465     for i in range(0, len(reference)):
466         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
467
468     # Create a hash for speeding the search of existing object in the
469     # current provision
470     for i in range(0, len(current)):
471         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
472
473     for k in hash_new.keys():
474         if not hash.has_key(k):
475             listMissing.append(hash_new[k])
476         else:
477             listPresent.append(hash_new[k])
478
479     for entry in listMissing:
480         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
481                                             base="", scope=SCOPE_SUBTREE)
482         current = secrets_ldb.search(expression="dn=%s" % entry,
483                                             base="", scope=SCOPE_SUBTREE)
484         delta = secrets_ldb.msg_diff(empty, reference[0])
485         for att in hashAttrNotCopied:
486             delta.remove(att)
487         messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
488                     reference[0].dn)
489         for att in delta:
490             messagefunc(CHANGE, " Adding attribute %s" % att)
491         delta.dn = reference[0].dn
492         secrets_ldb.add(delta)
493
494     for entry in listPresent:
495         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
496                                             base="", scope=SCOPE_SUBTREE)
497         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
498                                             scope=SCOPE_SUBTREE)
499         delta = secrets_ldb.msg_diff(current[0], reference[0])
500         for att in hashAttrNotCopied:
501             delta.remove(att)
502         for att in delta:
503             if att == "name":
504                 messagefunc(CHANGE, "Found attribute name on  %s,"
505                                     " must rename the DN" % (current[0].dn))
506                 identic_rename(secrets_ldb, reference[0].dn)
507             else:
508                 delta.remove(att)
509
510     for entry in listPresent:
511         reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
512                                             scope=SCOPE_SUBTREE)
513         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
514                                             scope=SCOPE_SUBTREE)
515         delta = secrets_ldb.msg_diff(current[0], reference[0])
516         for att in hashAttrNotCopied:
517             delta.remove(att)
518         for att in delta:
519             if att == "msDS-KeyVersionNumber":
520                 delta.remove(att)
521             if att != "dn":
522                 messagefunc(CHANGE,
523                             "Adding/Changing attribute %s to %s" %
524                             (att, current[0].dn))
525
526         delta.dn = current[0].dn
527         secrets_ldb.modify(delta)
528
529     res2 = secrets_ldb.search(expression="(samaccountname=dns)",
530                                 scope=SCOPE_SUBTREE, attrs=["dn"])
531
532     if (len(res2) == 1):
533             messagefunc(SIMPLE, "Remove old dns account")
534             secrets_ldb.delete(res2[0]["dn"])
535
536
537 def getOEMInfo(samdb, rootdn):
538     """Return OEM Information on the top level Samba4 use to store version
539     info in this field
540
541     :param samdb: An LDB object connect to sam.ldb
542     :param rootdn: Root DN of the domain
543     :return: The content of the field oEMInformation (if any)
544     """
545     res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
546                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
547     if len(res) > 0:
548         info = res[0]["oEMInformation"]
549         return info
550     else:
551         return ""
552
553
554 def updateOEMInfo(samdb, rootdn):
555     """Update the OEMinfo field to add information about upgrade
556
557     :param samdb: an LDB object connected to the sam DB
558     :param rootdn: The string representation of the root DN of
559         the provision (ie. DC=...,DC=...)
560     """
561     res = samdb.search(expression="(objectClass=*)", base=rootdn,
562                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
563     if len(res) > 0:
564         info = res[0]["oEMInformation"]
565         info = "%s, upgrade to %s" % (info, version)
566         delta = ldb.Message()
567         delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
568         delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
569                                                         "oEMInformation" )
570         samdb.modify(delta)
571
572 def update_gpo(paths, samdb, names, lp, message, force=0):
573     """Create missing GPO file object if needed
574
575     Set ACL correctly also.
576     Check ACLs for sysvol/netlogon dirs also
577     """
578     resetacls = False
579     try:
580         ntacls.checkset_backend(lp, None, None)
581         eadbname = lp.get("posix:eadb")
582         if eadbname is not None and eadbname != "":
583             try:
584                 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
585                                 paths.sysvol, xattr.XATTR_NTACL_NAME)
586             except Exception:
587                 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
588                                 xattr.XATTR_NTACL_NAME)
589         else:
590             attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
591                                 xattr.XATTR_NTACL_NAME)
592     except Exception:
593        resetacls = True
594
595     if force:
596         resetacls = True
597
598     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
599     if not os.path.isdir(dir):
600         create_gpo_struct(dir)
601
602     if names.policyid_dc is None:
603         raise ProvisioningError("Policy ID for Domain controller is missing")
604     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
605     if not os.path.isdir(dir):
606         create_gpo_struct(dir)
607     # We always reinforce acls on GPO folder because they have to be in sync
608     # with the one in DS
609     try:
610         set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
611             names.domaindn, samdb, lp)
612     except TypeError, e:
613         message(ERROR, "Unable to set ACLs on policies related objects,"
614                        " if not using posix:eadb, you must be root to do it")
615
616     if resetacls:
617        try:
618             setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
619                         names.domainsid, names.dnsdomain, names.domaindn, lp)
620        except TypeError, e:
621             message(ERROR, "Unable to set ACLs on sysvol share, if not using"
622                            "posix:eadb, you must be root to do it")
623
624 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
625     """For a given hash associating dn and a number, this function will
626     update the replPropertyMetaData of each dn in the hash, so that the
627     calculated value of the msDs-KeyVersionNumber is equal or superior to the
628     one associated to the given dn.
629
630     :param samdb: An SamDB object pointing to the sam
631     :param rootdn: The base DN where we want to start
632     :param hashDns: A hash with dn as key and number representing the
633                  minimum value of msDs-KeyVersionNumber that we want to
634                  have
635     """
636     entry = samdb.search(expression='(objectClass=user)',
637                          base=ldb.Dn(samdb,str(rootdn)),
638                          scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
639                          controls=["search_options:1:2"])
640     done = 0
641     hashDone = {}
642     if len(entry) == 0:
643         raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
644     else:
645         for e in entry:
646             if hashDns.has_key(str(e.dn).lower()):
647                 val = e.get("msDs-KeyVersionNumber")
648                 if not val:
649                     val = "0"
650                 version = int(str(hashDns[str(e.dn).lower()]))
651                 if int(str(val)) < version:
652                     done = done + 1
653                     samdb.set_attribute_replmetadata_version(str(e.dn),
654                                                               "unicodePwd",
655                                                               version, True)
656 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
657     """Update the provision container db: sam.ldb
658     This function is aimed for alpha9 and newer;
659
660     :param refsampath: Path to the samdb in the reference provision
661     :param sampath: Path to the samdb in the upgraded provision
662     :param creds: Credential used for openning LDB files
663     :param session: Session to use for openning LDB files
664     :param lp: A loadparam object
665     :return: A msg_diff object with the difference between the @ATTRIBUTES
666              of the current provision and the reference provision
667     """
668
669     message(SIMPLE,
670             "Update base samdb by searching difference with reference one")
671     refsam = Ldb(refsampath, session_info=session, credentials=creds,
672                     lp=lp, options=["modules:"])
673     sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
674                 options=["modules:"])
675
676     empty = ldb.Message()
677     deltaattr = None
678     reference = refsam.search(expression="")
679
680     for refentry in reference:
681         entry = sam.search(expression="dn=%s" % refentry["dn"],
682                             scope=SCOPE_SUBTREE)
683         if not len(entry):
684             delta = sam.msg_diff(empty, refentry)
685             message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
686             if str(refentry.dn) == "@PROVISION" and\
687                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
688                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
689             delta.dn = refentry.dn
690             sam.add(delta)
691         else:
692             delta = sam.msg_diff(entry[0], refentry)
693             if str(refentry.dn) == "@ATTRIBUTES":
694                 deltaattr = sam.msg_diff(refentry, entry[0])
695             if str(refentry.dn) == "@PROVISION" and\
696                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
697                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
698             if len(delta.items()) > 1:
699                 delta.dn = refentry.dn
700                 sam.modify(delta)
701
702     return deltaattr
703
704
705 def construct_existor_expr(attrs):
706     """Construct a exists or LDAP search expression.
707
708     :param attrs: List of attribute on which we want to create the search
709         expression.
710     :return: A string representing the expression, if attrs is empty an
711         empty string is returned
712     """
713     expr = ""
714     if len(attrs) > 0:
715         expr = "(|"
716         for att in attrs:
717             expr = "%s(%s=*)"%(expr,att)
718         expr = "%s)"%expr
719     return expr
720
721 def update_machine_account_password(samdb, secrets_ldb, names):
722     """Update (change) the password of the current DC both in the SAM db and in
723        secret one
724
725     :param samdb: An LDB object related to the sam.ldb file of a given provision
726     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
727                         provision
728     :param names: List of key provision parameters"""
729
730     expression = "samAccountName=%s$" % names.netbiosname
731     secrets_msg = secrets_ldb.search(expression=expression,
732                                         attrs=["secureChannelType"])
733     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
734         res = samdb.search(expression=expression, attrs=[])
735         assert(len(res) == 1)
736
737         msg = ldb.Message(res[0].dn)
738         machinepass = samba.generate_random_password(128, 255)
739         mputf16 = machinepass.encode('utf-16-le')
740         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
741                                                 ldb.FLAG_MOD_REPLACE,
742                                                 "clearTextPassword")
743         samdb.modify(msg)
744
745         res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
746                      attrs=["msDs-keyVersionNumber"])
747         assert(len(res) == 1)
748         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
749         secChanType = int(secrets_msg[0]["secureChannelType"][0])
750
751         secretsdb_self_join(secrets_ldb, domain=names.domain,
752                     realm=names.realm,
753                     domainsid=names.domainsid,
754                     dnsdomain=names.dnsdomain,
755                     netbiosname=names.netbiosname,
756                     machinepass=machinepass,
757                     key_version_number=kvno,
758                     secure_channel_type=secChanType)
759     else:
760         raise ProvisioningError("Unable to find a Secure Channel"
761                                 "of type SEC_CHAN_BDC")
762
763 def update_dns_account_password(samdb, secrets_ldb, names):
764     """Update (change) the password of the dns both in the SAM db and in
765        secret one
766
767     :param samdb: An LDB object related to the sam.ldb file of a given provision
768     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
769                         provision
770     :param names: List of key provision parameters"""
771
772     expression = "samAccountName=dns-%s" % names.netbiosname
773     secrets_msg = secrets_ldb.search(expression=expression)
774     if len(secrets_msg) == 1:
775         res = samdb.search(expression=expression, attrs=[])
776         assert(len(res) == 1)
777
778         msg = ldb.Message(res[0].dn)
779         machinepass = samba.generate_random_password(128, 255)
780         mputf16 = machinepass.encode('utf-16-le')
781         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
782                                                 ldb.FLAG_MOD_REPLACE,
783                                                 "clearTextPassword")
784
785         samdb.modify(msg)
786
787         res = samdb.search(expression=expression,
788                      attrs=["msDs-keyVersionNumber"])
789         assert(len(res) == 1)
790         kvno = str(res[0]["msDs-keyVersionNumber"])
791
792         msg = ldb.Message(secrets_msg[0].dn)
793         msg["secret"] = ldb.MessageElement(machinepass,
794                                                 ldb.FLAG_MOD_REPLACE,
795                                                 "secret")
796         msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
797                                                 ldb.FLAG_MOD_REPLACE,
798                                                 "msDS-KeyVersionNumber")
799
800         secrets_ldb.modify(msg)
801     else:
802         raise ProvisioningError("Unable to find an object"
803                                 " with %s" % expression )
804
805 def search_constructed_attrs_stored(samdb, rootdn, attrs):
806     """Search a given sam DB for calculated attributes that are
807     still stored in the db.
808
809     :param samdb: An LDB object pointing to the sam
810     :param rootdn: The base DN where the search should start
811     :param attrs: A list of attributes to be searched
812     :return: A hash with attributes as key and an array of
813              array. Each array contains the dn and the associated
814              values for this attribute as they are stored in the
815              sam."""
816
817     hashAtt = {}
818     expr = construct_existor_expr(attrs)
819     if expr == "":
820         return hashAtt
821     entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
822                          scope=SCOPE_SUBTREE, attrs=attrs,
823                          controls=["search_options:1:2","bypassoperational:0"])
824     if len(entry) == 0:
825         # Nothing anymore
826         return hashAtt
827
828     for ent in entry:
829         for att in attrs:
830             if ent.get(att):
831                 if hashAtt.has_key(att):
832                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
833                 else:
834                     hashAtt[att] = {}
835                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
836
837     return hashAtt
838
839 def int64range2str(value):
840     """Display the int64 range stored in value as xxx-yyy
841
842     :param value: The int64 range
843     :return: A string of the representation of the range
844     """
845
846     lvalue = long(value)
847     str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
848     return str