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, set_gpos_acl, create_gpo_struct,
37 provision, ProvisioningError,
38 setsysvolacl, secretsdb_self_join)
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"))
215 def update_policyids(names, samdb):
216 """Update policy ids that could have changed after sam update
218 :param names: List of key provision parameters
219 :param samdb: An Ldb object conntected with the sam DB
222 res = samdb.search(expression="(displayName=Default Domain Policy)",
223 base="CN=Policies,CN=System," + str(names.rootdn),
224 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
225 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
227 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
229 base="CN=Policies,CN=System," + str(names.rootdn),
230 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
232 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
234 names.policyid_dc = None
237 def newprovision(names, session, smbconf, provdir, logger, base_schema=None):
238 """Create a new provision.
240 This provision will be the reference for knowing what has changed in the
241 since the latest upgrade in the current provision
243 :param names: List of provision parameters
244 :param creds: Credentials for the authentification
245 :param session: Session object
246 :param smbconf: Path to the smb.conf file
247 :param provdir: Directory where the provision will be stored
248 :param logger: A Logger
250 if os.path.isdir(provdir):
251 shutil.rmtree(provdir)
253 logger.info("Provision stored in %s", provdir)
254 return provision(logger, session, smbconf=smbconf,
255 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
256 domain=names.domain, domainguid=names.domainguid,
257 domainsid=names.domainsid, ntdsguid=names.ntdsguid,
258 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
259 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
260 invocationid=names.invocation, adminpass=names.adminpass,
261 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
262 nobody=None, users=None,
263 serverrole="domain controller",
264 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
266 dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
267 useeadb=True, use_ntvfs=True, base_schema=base_schema)
271 """Sorts two DNs in the lexicographical order it and put higher level DN
274 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
277 :param x: First object to compare
278 :param y: Second object to compare
280 p = re.compile(r'(?<!\\), ?')
281 tab1 = p.split(str(x))
282 tab2 = p.split(str(y))
283 minimum = min(len(tab1), len(tab2))
286 # Note: python range go up to upper limit but do not include it
287 for i in range(0, minimum):
288 ret = cmp_fn(tab1[len1-i], tab2[len2-i])
293 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
301 def identic_rename(ldbobj, dn):
302 """Perform a back and forth rename to trigger renaming on attribute that
303 can't be directly modified.
305 :param lbdobj: An Ldb Object
306 :param dn: DN of the object to manipulate
308 (before, after) = str(dn).split('=', 1)
309 # we need to use relax to avoid the subtree_rename constraints
310 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
311 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
314 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
315 """Update secrets.ldb
317 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
318 of the reference provision
319 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
320 of the updated provision
323 messagefunc(SIMPLE, "Update of secrets.ldb")
324 reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
325 current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
326 assert reference, "Reference modules list can not be empty"
327 if len(current) == 0:
329 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
330 delta.dn = reference[0].dn
331 secrets_ldb.add(reference[0])
333 delta = secrets_ldb.msg_diff(current[0], reference[0])
334 delta.dn = current[0].dn
335 secrets_ldb.modify(delta)
337 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
338 scope=SCOPE_SUBTREE, attrs=["dn"])
339 current = secrets_ldb.search(expression="objectClass=top", base="",
340 scope=SCOPE_SUBTREE, attrs=["dn"])
346 empty = ldb.Message()
347 for i in range(0, len(reference)):
348 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
350 # Create a hash for speeding the search of existing object in the
352 for i in range(0, len(current)):
353 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
355 for k in hash_new.keys():
357 listMissing.append(hash_new[k])
359 listPresent.append(hash_new[k])
361 for entry in listMissing:
362 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
363 base="", scope=SCOPE_SUBTREE)
364 current = secrets_ldb.search(expression="distinguishedName=%s" % entry,
365 base="", scope=SCOPE_SUBTREE)
366 delta = secrets_ldb.msg_diff(empty, reference[0])
367 for att in hashAttrNotCopied:
369 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
372 messagefunc(CHANGE, " Adding attribute %s" % att)
373 delta.dn = reference[0].dn
374 secrets_ldb.add(delta)
376 for entry in listPresent:
377 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
378 base="", scope=SCOPE_SUBTREE)
379 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
381 delta = secrets_ldb.msg_diff(current[0], reference[0])
382 for att in hashAttrNotCopied:
386 messagefunc(CHANGE, "Found attribute name on %s,"
387 " must rename the DN" % (current[0].dn))
388 identic_rename(secrets_ldb, reference[0].dn)
392 for entry in listPresent:
393 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
395 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
397 delta = secrets_ldb.msg_diff(current[0], reference[0])
398 for att in hashAttrNotCopied:
401 if att == "msDS-KeyVersionNumber":
405 "Adding/Changing attribute %s to %s" %
406 (att, current[0].dn))
408 delta.dn = current[0].dn
409 secrets_ldb.modify(delta)
411 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
412 scope=SCOPE_SUBTREE, attrs=["dn"])
415 messagefunc(SIMPLE, "Remove old dns account")
416 secrets_ldb.delete(res2[0]["dn"])
419 def getOEMInfo(samdb, rootdn):
420 """Return OEM Information on the top level Samba4 use to store version
423 :param samdb: An LDB object connect to sam.ldb
424 :param rootdn: Root DN of the domain
425 :return: The content of the field oEMInformation (if any)
427 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
428 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
429 if len(res) > 0 and res[0].get("oEMInformation"):
430 info = res[0]["oEMInformation"]
436 def updateOEMInfo(samdb, rootdn):
437 """Update the OEMinfo field to add information about upgrade
439 :param samdb: an LDB object connected to the sam DB
440 :param rootdn: The string representation of the root DN of
441 the provision (ie. DC=...,DC=...)
443 res = samdb.search(expression="(objectClass=*)", base=rootdn,
444 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
446 if res[0].get("oEMInformation"):
447 info = str(res[0]["oEMInformation"])
450 info = "%s, upgrade to %s" % (info, version)
451 delta = ldb.Message()
452 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
453 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)
470 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
471 """For a given hash associating dn and a number, this function will
472 update the replPropertyMetaData of each dn in the hash, so that the
473 calculated value of the msDs-KeyVersionNumber is equal or superior to the
474 one associated to the given dn.
476 :param samdb: An SamDB object pointing to the sam
477 :param rootdn: The base DN where we want to start
478 :param hashDns: A hash with dn as key and number representing the
479 minimum value of msDs-KeyVersionNumber that we want to
482 entry = samdb.search(expression='(objectClass=user)',
483 base=ldb.Dn(samdb,str(rootdn)),
484 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
485 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),
502 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
503 """Update the provision container db: sam.ldb
504 This function is aimed for alpha9 and newer;
506 :param refsampath: Path to the samdb in the reference provision
507 :param sampath: Path to the samdb in the upgraded provision
508 :param creds: Credential used for openning LDB files
509 :param session: Session to use for openning LDB files
510 :param lp: A loadparam object
511 :return: A msg_diff object with the difference between the @ATTRIBUTES
512 of the current provision and the reference provision
516 "Update base samdb by searching difference with reference one")
517 refsam = Ldb(refsampath, session_info=session, credentials=creds,
518 lp=lp, options=["modules:"])
519 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
520 options=["modules:"])
522 empty = ldb.Message()
524 reference = refsam.search(expression="")
526 for refentry in reference:
527 entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
530 delta = sam.msg_diff(empty, refentry)
531 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
532 if str(refentry.dn) == "@PROVISION" and\
533 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
534 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
535 delta.dn = refentry.dn
538 delta = sam.msg_diff(entry[0], refentry)
539 if str(refentry.dn) == "@ATTRIBUTES":
540 deltaattr = sam.msg_diff(refentry, entry[0])
541 if str(refentry.dn) == "@PROVISION" and\
542 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
543 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
544 if len(delta.items()) > 1:
545 delta.dn = refentry.dn
551 def construct_existor_expr(attrs):
552 """Construct a exists or LDAP search expression.
554 :param attrs: List of attribute on which we want to create the search
556 :return: A string representing the expression, if attrs is empty an
557 empty string is returned
563 expr = "%s(%s=*)"%(expr,att)
567 def update_machine_account_password(samdb, secrets_ldb, names):
568 """Update (change) the password of the current DC both in the SAM db and in
571 :param samdb: An LDB object related to the sam.ldb file of a given provision
572 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
574 :param names: List of key provision parameters"""
576 expression = "samAccountName=%s$" % names.netbiosname
577 secrets_msg = secrets_ldb.search(expression=expression,
578 attrs=["secureChannelType"])
579 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
580 res = samdb.search(expression=expression, attrs=[])
581 assert(len(res) == 1)
583 msg = ldb.Message(res[0].dn)
584 machinepass = samba.generate_random_machine_password(128, 255)
585 mputf16 = machinepass.encode('utf-16-le')
586 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
587 ldb.FLAG_MOD_REPLACE,
591 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
592 attrs=["msDs-keyVersionNumber"])
593 assert(len(res) == 1)
594 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
595 secChanType = int(secrets_msg[0]["secureChannelType"][0])
597 secretsdb_self_join(secrets_ldb, domain=names.domain,
599 domainsid=names.domainsid,
600 dnsdomain=names.dnsdomain,
601 netbiosname=names.netbiosname,
602 machinepass=machinepass,
603 key_version_number=kvno,
604 secure_channel_type=secChanType)
606 raise ProvisioningError("Unable to find a Secure Channel"
607 "of type SEC_CHAN_BDC")
609 def update_dns_account_password(samdb, secrets_ldb, names):
610 """Update (change) the password of the dns both in the SAM db and in
613 :param samdb: An LDB object related to the sam.ldb file of a given provision
614 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
616 :param names: List of key provision parameters"""
618 expression = "samAccountName=dns-%s" % names.netbiosname
619 secrets_msg = secrets_ldb.search(expression=expression)
620 if len(secrets_msg) == 1:
621 res = samdb.search(expression=expression, attrs=[])
622 assert(len(res) == 1)
624 msg = ldb.Message(res[0].dn)
625 machinepass = samba.generate_random_password(128, 255)
626 mputf16 = machinepass.encode('utf-16-le')
627 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
628 ldb.FLAG_MOD_REPLACE,
633 res = samdb.search(expression=expression,
634 attrs=["msDs-keyVersionNumber"])
635 assert(len(res) == 1)
636 kvno = str(res[0]["msDs-keyVersionNumber"])
638 msg = ldb.Message(secrets_msg[0].dn)
639 msg["secret"] = ldb.MessageElement(machinepass,
640 ldb.FLAG_MOD_REPLACE,
642 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
643 ldb.FLAG_MOD_REPLACE,
644 "msDS-KeyVersionNumber")
646 secrets_ldb.modify(msg)
648 def update_krbtgt_account_password(samdb):
649 """Update (change) the password of the krbtgt account
651 :param samdb: An LDB object related to the sam.ldb file of a given provision"""
653 expression = "samAccountName=krbtgt"
654 res = samdb.search(expression=expression, attrs=[])
655 assert(len(res) == 1)
657 msg = ldb.Message(res[0].dn)
658 machinepass = samba.generate_random_machine_password(128, 255)
659 mputf16 = machinepass.encode('utf-16-le')
660 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
661 ldb.FLAG_MOD_REPLACE,
666 def search_constructed_attrs_stored(samdb, rootdn, attrs):
667 """Search a given sam DB for calculated attributes that are
668 still stored in the db.
670 :param samdb: An LDB object pointing to the sam
671 :param rootdn: The base DN where the search should start
672 :param attrs: A list of attributes to be searched
673 :return: A hash with attributes as key and an array of
674 array. Each array contains the dn and the associated
675 values for this attribute as they are stored in the
679 expr = construct_existor_expr(attrs)
682 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
683 scope=SCOPE_SUBTREE, attrs=attrs,
684 controls=["search_options:1:2","bypassoperational:0"])
693 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
696 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
700 def findprovisionrange(samdb, basedn):
701 """ Find ranges of usn grouped by invocation id and then by timestamp
704 :param samdb: An LDB object pointing to the samdb
705 :param basedn: The DN of the forest
707 :return: A two level dictionary with invoication id as the
708 first level, timestamp as the second one and then
709 max, min, and number as subkeys, representing respectivily
710 the maximum usn for the range, the minimum usn and the number
711 of object with usn in this range.
716 res = samdb.search(base=basedn, expression="objectClass=*",
717 scope=ldb.SCOPE_SUBTREE,
718 attrs=["replPropertyMetaData"],
719 controls=["search_options:1:2"])
723 obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
724 str(e["replPropertyMetaData"])).ctr
727 # like a timestamp but with the resolution of 1 minute
728 minutestamp =_glue.nttime2unix(o.originating_change_time) // 60
729 hash_ts = hash_id.get(str(o.originating_invocation_id))
733 ob["min"] = o.originating_usn
734 ob["max"] = o.originating_usn
736 ob["list"] = [str(e.dn)]
739 ob = hash_ts.get(minutestamp)
742 ob["min"] = o.originating_usn
743 ob["max"] = o.originating_usn
745 ob["list"] = [str(e.dn)]
747 if ob["min"] > o.originating_usn:
748 ob["min"] = o.originating_usn
749 if ob["max"] < o.originating_usn:
750 ob["max"] = o.originating_usn
751 if not (str(e.dn) in ob["list"]):
752 ob["num"] = ob["num"] + 1
753 ob["list"].append(str(e.dn))
754 hash_ts[minutestamp] = ob
755 hash_id[str(o.originating_invocation_id)] = hash_ts
757 return (hash_id, nb_obj)
759 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
760 """ print the differents ranges passed as parameter
762 :param dic: A dictionnary as returned by findprovisionrange
763 :param limit_print: minimum number of object in a range in order to print it
764 :param dest: Destination directory
765 :param samdb_path: Path to the sam.ldb file
766 :param invoicationid: Invocation ID for the current provision
773 sorted_keys.extend(hash_ts.keys())
777 for k in sorted_keys:
779 if obj["num"] > limit_print:
780 dt = _glue.nttime2string(_glue.unix2nttime(k*60))
781 print("%s # of modification: %d \tmin: %d max: %d" % (dt, obj["num"],
784 if hash_ts[k]["num"] > 600:
785 kept_record.append(k)
787 # Let's try to concatenate consecutive block if they are in the almost same minutestamp
788 for i in range(0, len(kept_record)):
790 key1 = kept_record[i]
791 key2 = kept_record[i-1]
793 # previous record is just 1 minute away from current
794 if int(hash_ts[key1]["min"]) == int(hash_ts[key2]["max"]) + 1:
795 # Copy the highest USN in the previous record
796 # and mark the current as skipped
797 hash_ts[key2]["max"] = hash_ts[key1]["max"]
798 hash_ts[key1]["skipped"] = True
800 for k in kept_record:
802 if obj.get("skipped") is None:
803 ldif = "%slastProvisionUSN: %d-%d;%s\n" % (ldif, obj["min"],
807 file = tempfile.mktemp(dir=dest, prefix="usnprov", suffix=".ldif")
809 print("To track the USNs modified/created by provision and upgrade proivsion,")
810 print(" the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif)
811 print("We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file)
812 print("You can load this file like this: ldbadd -H %s %s\n"%(str(samdb_path),file))
813 ldif = "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocationid, ldif)
814 open(file,'w').write(ldif)
816 def int64range2str(value):
817 """Display the int64 range stored in value as xxx-yyy
819 :param value: The int64 range
820 :return: A string of the representation of the range
824 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)