1 # Helpers for provision stuff
2 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2012
4 # Based on provision a Samba4 server by
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from __future__ import print_function
23 from __future__ import division
24 """Helpers used for upgrading between different database formats."""
31 from samba.compat import cmp_fn
32 from samba import Ldb, version, ntacls
33 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
35 from samba.provision import (provision_paths_from_lp,
36 getpolicypath, create_gpo_struct,
37 provision, ProvisioningError,
39 from samba.provision.common import FILL_FULL
40 from samba.dcerpc import xattr, drsblobs, security
41 from samba.dcerpc.misc import SEC_CHAN_BDC
42 from samba.ndr import ndr_unpack
43 from samba.samdb import SamDB
44 from samba import _glue
47 # All the ldb related to registry are commented because the path for them is
48 # relative in the provisionPath object
49 # And so opening them create a file in the current directory which is not what
51 # I still keep them commented because I plan soon to make more cleaner
60 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
61 "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
62 "objectCategory", "distinguishedName", "nTMixedDomain",
63 "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
64 "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
65 "ntPwdHistory", "unicodePwd", "dBCSPwd", "supplementalCredentials",
66 "gPCUserExtensionNames", "gPCMachineExtensionNames", "maxPwdAge", "secret",
67 "possibleInferiors", "privilege", "sAMAccountType"])
70 class ProvisionLDB(object):
83 return (self.sam, self.secrets, self.idmap, self.privilege)
85 def startTransactions(self):
87 db.transaction_start()
89 # self.hkcr.transaction_start()
90 # self.hkcu.transaction_start()
91 # self.hku.transaction_start()
92 # self.hklm.transaction_start()
94 def groupedRollback(self):
98 db.transaction_cancel()
103 # self.hkcr.transaction_cancel()
104 # self.hkcu.transaction_cancel()
105 # self.hku.transaction_cancel()
106 # self.hklm.transaction_cancel()
108 def groupedCommit(self):
110 for db in self.dbs():
111 db.transaction_prepare_commit()
113 return self.groupedRollback()
115 # self.hkcr.transaction_prepare_commit()
116 # self.hkcu.transaction_prepare_commit()
117 # self.hku.transaction_prepare_commit()
118 # self.hklm.transaction_prepare_commit()
120 for db in self.dbs():
121 db.transaction_commit()
123 return self.groupedRollback()
126 # self.hkcr.transaction_commit()
127 # self.hkcu.transaction_commit()
128 # self.hku.transaction_commit()
129 # self.hklm.transaction_commit()
133 def get_ldbs(paths, creds, session, lp):
134 """Return LDB object mapped on most important databases
136 :param paths: An object holding the different importants paths for provision object
137 :param creds: Credential used for openning LDB files
138 :param session: Session to use for openning LDB files
139 :param lp: A loadparam object
140 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
142 ldbs = ProvisionLDB()
144 ldbs.sam = SamDB(paths.samdb,
145 session_info=session,
148 options=["modules:samba_dsdb"],
150 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
151 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
152 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
153 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
154 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
155 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
156 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
161 def usn_in_range(usn, range):
162 """Check if the usn is in one of the range provided.
163 To do so, the value is checked to be between the lower bound and
164 higher bound of a range
166 :param usn: A integer value corresponding to the usn that we want to update
167 :param range: A list of integer representing ranges, lower bounds are in
168 the even indices, higher in odd indices
169 :return: True if the usn is in one of the range, False otherwise
176 if idx == len(range):
179 if usn < int(range[idx]):
183 if usn == int(range[idx]):
190 def get_paths(param, targetdir=None, smbconf=None):
191 """Get paths to important provision objects (smb.conf, ldb files, ...)
193 :param param: Param object
194 :param targetdir: Directory where the provision is (or will be) stored
195 :param smbconf: Path to the smb.conf file
196 :return: A list with the path of important provision objects"""
197 if targetdir is not None:
198 if not os.path.exists(targetdir):
200 etcdir = os.path.join(targetdir, "etc")
201 if not os.path.exists(etcdir):
203 smbconf = os.path.join(etcdir, "smb.conf")
205 smbconf = param.default_path()
207 if not os.path.exists(smbconf):
208 raise ProvisioningError("Unable to find smb.conf at %s" % smbconf)
210 lp = param.LoadParm()
212 paths = provision_paths_from_lp(lp, lp.get("realm"))
216 def update_policyids(names, samdb):
217 """Update policy ids that could have changed after sam update
219 :param names: List of key provision parameters
220 :param samdb: An Ldb object conntected with the sam DB
223 res = samdb.search(expression="(displayName=Default Domain Policy)",
224 base="CN=Policies,CN=System," + str(names.rootdn),
225 scope=SCOPE_ONELEVEL, attrs=["cn", "displayName"])
226 names.policyid = str(res[0]["cn"]).replace("{", "").replace("}", "")
228 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
230 base="CN=Policies,CN=System," + str(names.rootdn),
231 scope=SCOPE_ONELEVEL, attrs=["cn", "displayName"])
233 names.policyid_dc = str(res2[0]["cn"]).replace("{", "").replace("}", "")
235 names.policyid_dc = None
238 def newprovision(names, session, smbconf, provdir, logger, base_schema=None):
239 """Create a new provision.
241 This provision will be the reference for knowing what has changed in the
242 since the latest upgrade in the current provision
244 :param names: List of provision parameters
245 :param creds: Credentials for the authentification
246 :param session: Session object
247 :param smbconf: Path to the smb.conf file
248 :param provdir: Directory where the provision will be stored
249 :param logger: A Logger
251 if os.path.isdir(provdir):
252 shutil.rmtree(provdir)
254 logger.info("Provision stored in %s", provdir)
255 return provision(logger, session, smbconf=smbconf,
256 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
257 domain=names.domain, domainguid=names.domainguid,
258 domainsid=names.domainsid, ntdsguid=names.ntdsguid,
259 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
260 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
261 invocationid=names.invocation, adminpass=names.adminpass,
262 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
263 nobody=None, users=None,
264 serverrole="domain controller",
265 dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
266 useeadb=True, use_ntvfs=True, base_schema=base_schema)
270 """Sorts two DNs in the lexicographical order it and put higher level DN
273 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
276 :param x: First object to compare
277 :param y: Second object to compare
279 p = re.compile(r'(?<!\\), ?')
280 tab1 = p.split(str(x))
281 tab2 = p.split(str(y))
282 minimum = min(len(tab1), len(tab2))
285 # Note: python range go up to upper limit but do not include it
286 for i in range(0, minimum):
287 ret = cmp_fn(tab1[len1 - i], tab2[len2 - i])
292 assert len1 != len2, "PB PB PB" + " ".join(tab1) + " / " + " ".join(tab2)
300 def identic_rename(ldbobj, dn):
301 """Perform a back and forth rename to trigger renaming on attribute that
302 can't be directly modified.
304 :param lbdobj: An Ldb Object
305 :param dn: DN of the object to manipulate
307 (before, after) = str(dn).split('=', 1)
308 # we need to use relax to avoid the subtree_rename constraints
309 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
310 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
313 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
314 """Update secrets.ldb
316 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
317 of the reference provision
318 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
319 of the updated provision
322 messagefunc(SIMPLE, "Update of secrets.ldb")
323 reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
324 current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
325 assert reference, "Reference modules list can not be empty"
326 if len(current) == 0:
328 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
329 delta.dn = reference[0].dn
330 secrets_ldb.add(reference[0])
332 delta = secrets_ldb.msg_diff(current[0], reference[0])
333 delta.dn = current[0].dn
334 secrets_ldb.modify(delta)
336 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
337 scope=SCOPE_SUBTREE, attrs=["dn"])
338 current = secrets_ldb.search(expression="objectClass=top", base="",
339 scope=SCOPE_SUBTREE, attrs=["dn"])
345 empty = ldb.Message()
346 for i in range(0, len(reference)):
347 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
349 # Create a hash for speeding the search of existing object in the
351 for i in range(0, len(current)):
352 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
354 for k in hash_new.keys():
356 listMissing.append(hash_new[k])
358 listPresent.append(hash_new[k])
360 for entry in listMissing:
361 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
362 base="", scope=SCOPE_SUBTREE)
363 current = secrets_ldb.search(expression="distinguishedName=%s" % entry,
364 base="", scope=SCOPE_SUBTREE)
365 delta = secrets_ldb.msg_diff(empty, reference[0])
366 for att in hashAttrNotCopied:
368 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
371 messagefunc(CHANGE, " Adding attribute %s" % att)
372 delta.dn = reference[0].dn
373 secrets_ldb.add(delta)
375 for entry in listPresent:
376 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
377 base="", scope=SCOPE_SUBTREE)
378 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
380 delta = secrets_ldb.msg_diff(current[0], reference[0])
381 for att in hashAttrNotCopied:
385 messagefunc(CHANGE, "Found attribute name on %s,"
386 " must rename the DN" % (current[0].dn))
387 identic_rename(secrets_ldb, reference[0].dn)
391 for entry in listPresent:
392 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
394 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
396 delta = secrets_ldb.msg_diff(current[0], reference[0])
397 for att in hashAttrNotCopied:
400 if att == "msDS-KeyVersionNumber":
404 "Adding/Changing attribute %s to %s" %
405 (att, current[0].dn))
407 delta.dn = current[0].dn
408 secrets_ldb.modify(delta)
410 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
411 scope=SCOPE_SUBTREE, attrs=["dn"])
414 messagefunc(SIMPLE, "Remove old dns account")
415 secrets_ldb.delete(res2[0]["dn"])
418 def getOEMInfo(samdb, rootdn):
419 """Return OEM Information on the top level Samba4 use to store version
422 :param samdb: An LDB object connect to sam.ldb
423 :param rootdn: Root DN of the domain
424 :return: The content of the field oEMInformation (if any)
426 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
427 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
428 if len(res) > 0 and res[0].get("oEMInformation"):
429 info = res[0]["oEMInformation"]
435 def updateOEMInfo(samdb, rootdn):
436 """Update the OEMinfo field to add information about upgrade
438 :param samdb: an LDB object connected to the sam DB
439 :param rootdn: The string representation of the root DN of
440 the provision (ie. DC=...,DC=...)
442 res = samdb.search(expression="(objectClass=*)", base=rootdn,
443 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
445 if res[0].get("oEMInformation"):
446 info = str(res[0]["oEMInformation"])
449 info = "%s, upgrade to %s" % (info, version)
450 delta = ldb.Message()
451 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
452 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
457 def update_gpo(paths, samdb, names, lp, message):
458 """Create missing GPO file object if needed
460 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
461 if not os.path.isdir(dir):
462 create_gpo_struct(dir)
464 if names.policyid_dc is None:
465 raise ProvisioningError("Policy ID for Domain controller is missing")
466 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
467 if not os.path.isdir(dir):
468 create_gpo_struct(dir)
471 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
472 """For a given hash associating dn and a number, this function will
473 update the replPropertyMetaData of each dn in the hash, so that the
474 calculated value of the msDs-KeyVersionNumber is equal or superior to the
475 one associated to the given dn.
477 :param samdb: An SamDB object pointing to the sam
478 :param rootdn: The base DN where we want to start
479 :param hashDns: A hash with dn as key and number representing the
480 minimum value of msDs-KeyVersionNumber that we want to
483 entry = samdb.search(expression='(objectClass=user)',
484 base=ldb.Dn(samdb, str(rootdn)),
485 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
486 controls=["search_options:1:2"])
489 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
492 if str(e.dn).lower() in hashDns:
493 val = e.get("msDs-KeyVersionNumber")
496 version = int(str(hashDns[str(e.dn).lower()]))
497 if int(str(val)) < version:
499 samdb.set_attribute_replmetadata_version(str(e.dn),
504 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
505 """Update the provision container db: sam.ldb
506 This function is aimed for alpha9 and newer;
508 :param refsampath: Path to the samdb in the reference provision
509 :param sampath: Path to the samdb in the upgraded provision
510 :param creds: Credential used for openning LDB files
511 :param session: Session to use for openning LDB files
512 :param lp: A loadparam object
513 :return: A msg_diff object with the difference between the @ATTRIBUTES
514 of the current provision and the reference provision
518 "Update base samdb by searching difference with reference one")
519 refsam = Ldb(refsampath, session_info=session, credentials=creds,
520 lp=lp, options=["modules:"])
521 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
522 options=["modules:"])
524 empty = ldb.Message()
526 reference = refsam.search(expression="")
528 for refentry in reference:
529 entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
532 delta = sam.msg_diff(empty, refentry)
533 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
534 if str(refentry.dn) == "@PROVISION" and\
535 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
536 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
537 delta.dn = refentry.dn
540 delta = sam.msg_diff(entry[0], refentry)
541 if str(refentry.dn) == "@ATTRIBUTES":
542 deltaattr = sam.msg_diff(refentry, entry[0])
543 if str(refentry.dn) == "@PROVISION" and\
544 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
545 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
546 if len(delta.items()) > 1:
547 delta.dn = refentry.dn
553 def construct_existor_expr(attrs):
554 """Construct a exists or LDAP search expression.
556 :param attrs: List of attribute on which we want to create the search
558 :return: A string representing the expression, if attrs is empty an
559 empty string is returned
565 expr = "%s(%s=*)" %(expr, att)
570 def update_machine_account_password(samdb, secrets_ldb, names):
571 """Update (change) the password of the current DC both in the SAM db and in
574 :param samdb: An LDB object related to the sam.ldb file of a given provision
575 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
577 :param names: List of key provision parameters"""
579 expression = "samAccountName=%s$" % names.netbiosname
580 secrets_msg = secrets_ldb.search(expression=expression,
581 attrs=["secureChannelType"])
582 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
583 res = samdb.search(expression=expression, attrs=[])
584 assert(len(res) == 1)
586 msg = ldb.Message(res[0].dn)
587 machinepass = samba.generate_random_machine_password(128, 255)
588 mputf16 = machinepass.encode('utf-16-le')
589 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
590 ldb.FLAG_MOD_REPLACE,
594 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
595 attrs=["msDs-keyVersionNumber"])
596 assert(len(res) == 1)
597 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
598 secChanType = int(secrets_msg[0]["secureChannelType"][0])
600 secretsdb_self_join(secrets_ldb, domain=names.domain,
602 domainsid=names.domainsid,
603 dnsdomain=names.dnsdomain,
604 netbiosname=names.netbiosname,
605 machinepass=machinepass,
606 key_version_number=kvno,
607 secure_channel_type=secChanType)
609 raise ProvisioningError("Unable to find a Secure Channel"
610 "of type SEC_CHAN_BDC")
613 def update_dns_account_password(samdb, secrets_ldb, names):
614 """Update (change) the password of the dns both in the SAM db and in
617 :param samdb: An LDB object related to the sam.ldb file of a given provision
618 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
620 :param names: List of key provision parameters"""
622 expression = "samAccountName=dns-%s" % names.netbiosname
623 secrets_msg = secrets_ldb.search(expression=expression)
624 if len(secrets_msg) == 1:
625 res = samdb.search(expression=expression, attrs=[])
626 assert(len(res) == 1)
628 msg = ldb.Message(res[0].dn)
629 machinepass = samba.generate_random_password(128, 255)
630 mputf16 = machinepass.encode('utf-16-le')
631 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
632 ldb.FLAG_MOD_REPLACE,
637 res = samdb.search(expression=expression,
638 attrs=["msDs-keyVersionNumber"])
639 assert(len(res) == 1)
640 kvno = str(res[0]["msDs-keyVersionNumber"])
642 msg = ldb.Message(secrets_msg[0].dn)
643 msg["secret"] = ldb.MessageElement(machinepass,
644 ldb.FLAG_MOD_REPLACE,
646 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
647 ldb.FLAG_MOD_REPLACE,
648 "msDS-KeyVersionNumber")
650 secrets_ldb.modify(msg)
653 def update_krbtgt_account_password(samdb):
654 """Update (change) the password of the krbtgt account
656 :param samdb: An LDB object related to the sam.ldb file of a given provision"""
658 expression = "samAccountName=krbtgt"
659 res = samdb.search(expression=expression, attrs=[])
660 assert(len(res) == 1)
662 msg = ldb.Message(res[0].dn)
663 machinepass = samba.generate_random_machine_password(128, 255)
664 mputf16 = machinepass.encode('utf-16-le')
665 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
666 ldb.FLAG_MOD_REPLACE,
672 def search_constructed_attrs_stored(samdb, rootdn, attrs):
673 """Search a given sam DB for calculated attributes that are
674 still stored in the db.
676 :param samdb: An LDB object pointing to the sam
677 :param rootdn: The base DN where the search should start
678 :param attrs: A list of attributes to be searched
679 :return: A hash with attributes as key and an array of
680 array. Each array contains the dn and the associated
681 values for this attribute as they are stored in the
685 expr = construct_existor_expr(attrs)
688 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
689 scope=SCOPE_SUBTREE, attrs=attrs,
690 controls=["search_options:1:2", "bypassoperational:0"])
699 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
702 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
707 def findprovisionrange(samdb, basedn):
708 """ Find ranges of usn grouped by invocation id and then by timestamp
711 :param samdb: An LDB object pointing to the samdb
712 :param basedn: The DN of the forest
714 :return: A two level dictionary with invoication id as the
715 first level, timestamp as the second one and then
716 max, min, and number as subkeys, representing respectivily
717 the maximum usn for the range, the minimum usn and the number
718 of object with usn in this range.
723 res = samdb.search(base=basedn, expression="objectClass=*",
724 scope=ldb.SCOPE_SUBTREE,
725 attrs=["replPropertyMetaData"],
726 controls=["search_options:1:2"])
730 obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
731 str(e["replPropertyMetaData"])).ctr
734 # like a timestamp but with the resolution of 1 minute
735 minutestamp = _glue.nttime2unix(o.originating_change_time) // 60
736 hash_ts = hash_id.get(str(o.originating_invocation_id))
740 ob["min"] = o.originating_usn
741 ob["max"] = o.originating_usn
743 ob["list"] = [str(e.dn)]
746 ob = hash_ts.get(minutestamp)
749 ob["min"] = o.originating_usn
750 ob["max"] = o.originating_usn
752 ob["list"] = [str(e.dn)]
754 if ob["min"] > o.originating_usn:
755 ob["min"] = o.originating_usn
756 if ob["max"] < o.originating_usn:
757 ob["max"] = o.originating_usn
758 if not (str(e.dn) in ob["list"]):
759 ob["num"] = ob["num"] + 1
760 ob["list"].append(str(e.dn))
761 hash_ts[minutestamp] = ob
762 hash_id[str(o.originating_invocation_id)] = hash_ts
764 return (hash_id, nb_obj)
767 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
768 """ print the differents ranges passed as parameter
770 :param dic: A dictionnary as returned by findprovisionrange
771 :param limit_print: minimum number of object in a range in order to print it
772 :param dest: Destination directory
773 :param samdb_path: Path to the sam.ldb file
774 :param invoicationid: Invocation ID for the current provision
781 sorted_keys.extend(hash_ts.keys())
785 for k in sorted_keys:
787 if obj["num"] > limit_print:
788 dt = _glue.nttime2string(_glue.unix2nttime(k * 60))
789 print("%s # of modification: %d \tmin: %d max: %d" % (dt, obj["num"],
792 if hash_ts[k]["num"] > 600:
793 kept_record.append(k)
795 # Let's try to concatenate consecutive block if they are in the almost same minutestamp
796 for i in range(0, len(kept_record)):
798 key1 = kept_record[i]
799 key2 = kept_record[i - 1]
801 # previous record is just 1 minute away from current
802 if int(hash_ts[key1]["min"]) == int(hash_ts[key2]["max"]) + 1:
803 # Copy the highest USN in the previous record
804 # and mark the current as skipped
805 hash_ts[key2]["max"] = hash_ts[key1]["max"]
806 hash_ts[key1]["skipped"] = True
808 for k in kept_record:
810 if obj.get("skipped") is None:
811 ldif = "%slastProvisionUSN: %d-%d;%s\n" % (ldif, obj["min"],
815 fd, file = tempfile.mkstemp(dir=dest, prefix="usnprov", suffix=".ldif")
817 print("To track the USNs modified/created by provision and upgrade proivsion,")
818 print(" the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif)
819 print("We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file)
820 print("You can load this file like this: ldbadd -H %s %s\n" %(str(samdb_path), file))
821 ldif = "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocationid, ldif)
826 def int64range2str(value):
827 """Display the int64 range stored in value as xxx-yyy
829 :param value: The int64 range
830 :return: A string of the representation of the range
833 str = "%d-%d" % (lvalue &0xFFFFFFFF, lvalue >>32)