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 """Helpers used for upgrading between different database formats."""
30 from samba import Ldb, version, ntacls
31 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
33 from samba.provision import (provision_paths_from_lp,
34 getpolicypath, set_gpos_acl, create_gpo_struct,
35 provision, ProvisioningError,
36 setsysvolacl, secretsdb_self_join)
37 from samba.provision.common import FILL_FULL
38 from samba.dcerpc import xattr, drsblobs, security
39 from samba.dcerpc.misc import SEC_CHAN_BDC
40 from samba.ndr import ndr_unpack
41 from samba.samdb import SamDB
42 from samba import _glue
45 # All the ldb related to registry are commented because the path for them is
46 # relative in the provisionPath object
47 # And so opening them create a file in the current directory which is not what
49 # I still keep them commented because I plan soon to make more cleaner
58 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
59 "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
60 "objectCategory", "distinguishedName", "nTMixedDomain",
61 "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
62 "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
63 "ntPwdHistory", "unicodePwd","dBCSPwd", "supplementalCredentials",
64 "gPCUserExtensionNames", "gPCMachineExtensionNames","maxPwdAge", "secret",
65 "possibleInferiors", "privilege", "sAMAccountType"])
68 class ProvisionLDB(object):
81 return (self.sam, self.secrets, self.idmap, self.privilege)
83 def startTransactions(self):
85 db.transaction_start()
87 # self.hkcr.transaction_start()
88 # self.hkcu.transaction_start()
89 # self.hku.transaction_start()
90 # self.hklm.transaction_start()
92 def groupedRollback(self):
96 db.transaction_cancel()
101 # self.hkcr.transaction_cancel()
102 # self.hkcu.transaction_cancel()
103 # self.hku.transaction_cancel()
104 # self.hklm.transaction_cancel()
106 def groupedCommit(self):
108 for db in self.dbs():
109 db.transaction_prepare_commit()
111 return self.groupedRollback()
113 # self.hkcr.transaction_prepare_commit()
114 # self.hkcu.transaction_prepare_commit()
115 # self.hku.transaction_prepare_commit()
116 # self.hklm.transaction_prepare_commit()
118 for db in self.dbs():
119 db.transaction_commit()
121 return self.groupedRollback()
124 # self.hkcr.transaction_commit()
125 # self.hkcu.transaction_commit()
126 # self.hku.transaction_commit()
127 # self.hklm.transaction_commit()
131 def get_ldbs(paths, creds, session, lp):
132 """Return LDB object mapped on most important databases
134 :param paths: An object holding the different importants paths for provision object
135 :param creds: Credential used for openning LDB files
136 :param session: Session to use for openning LDB files
137 :param lp: A loadparam object
138 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
140 ldbs = ProvisionLDB()
142 ldbs.sam = SamDB(paths.samdb,
143 session_info=session,
146 options=["modules:samba_dsdb"],
148 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
149 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
150 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
151 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
152 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
153 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
154 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
159 def usn_in_range(usn, range):
160 """Check if the usn is in one of the range provided.
161 To do so, the value is checked to be between the lower bound and
162 higher bound of a range
164 :param usn: A integer value corresponding to the usn that we want to update
165 :param range: A list of integer representing ranges, lower bounds are in
166 the even indices, higher in odd indices
167 :return: True if the usn is in one of the range, False otherwise
174 if idx == len(range):
177 if usn < int(range[idx]):
181 if usn == int(range[idx]):
188 def get_paths(param, targetdir=None, smbconf=None):
189 """Get paths to important provision objects (smb.conf, ldb files, ...)
191 :param param: Param object
192 :param targetdir: Directory where the provision is (or will be) stored
193 :param smbconf: Path to the smb.conf file
194 :return: A list with the path of important provision objects"""
195 if targetdir is not None:
196 if not os.path.exists(targetdir):
198 etcdir = os.path.join(targetdir, "etc")
199 if not os.path.exists(etcdir):
201 smbconf = os.path.join(etcdir, "smb.conf")
203 smbconf = param.default_path()
205 if not os.path.exists(smbconf):
206 raise ProvisioningError("Unable to find smb.conf at %s" % smbconf)
208 lp = param.LoadParm()
210 paths = provision_paths_from_lp(lp, lp.get("realm"))
213 def update_policyids(names, samdb):
214 """Update policy ids that could have changed after sam update
216 :param names: List of key provision parameters
217 :param samdb: An Ldb object conntected with the sam DB
220 res = samdb.search(expression="(displayName=Default Domain Policy)",
221 base="CN=Policies,CN=System," + str(names.rootdn),
222 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
223 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
225 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
227 base="CN=Policies,CN=System," + str(names.rootdn),
228 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
230 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
232 names.policyid_dc = None
235 def newprovision(names, session, smbconf, provdir, logger, base_schema=None):
236 """Create a new provision.
238 This provision will be the reference for knowing what has changed in the
239 since the latest upgrade in the current provision
241 :param names: List of provision parameters
242 :param creds: Credentials for the authentification
243 :param session: Session object
244 :param smbconf: Path to the smb.conf file
245 :param provdir: Directory where the provision will be stored
246 :param logger: A Logger
248 if os.path.isdir(provdir):
249 shutil.rmtree(provdir)
251 logger.info("Provision stored in %s", provdir)
252 return provision(logger, session, smbconf=smbconf,
253 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
254 domain=names.domain, domainguid=names.domainguid,
255 domainsid=names.domainsid, ntdsguid=names.ntdsguid,
256 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
257 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
258 invocationid=names.invocation, adminpass=names.adminpass,
259 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
260 nobody=None, users=None,
261 serverrole="domain controller",
262 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
264 dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
265 useeadb=True, use_ntvfs=True, base_schema=base_schema)
269 """Sorts two DNs in the lexicographical order it and put higher level DN
272 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
275 :param x: First object to compare
276 :param y: Second object to compare
278 p = re.compile(r'(?<!\\), ?')
279 tab1 = p.split(str(x))
280 tab2 = p.split(str(y))
281 minimum = min(len(tab1), len(tab2))
284 # Note: python range go up to upper limit but do not include it
285 for i in range(0, minimum):
286 ret = cmp(tab1[len1-i], tab2[len2-i])
291 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
299 def identic_rename(ldbobj, dn):
300 """Perform a back and forth rename to trigger renaming on attribute that
301 can't be directly modified.
303 :param lbdobj: An Ldb Object
304 :param dn: DN of the object to manipulate
306 (before, after) = str(dn).split('=', 1)
307 # we need to use relax to avoid the subtree_rename constraints
308 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
309 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
312 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
313 """Update secrets.ldb
315 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
316 of the reference provision
317 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
318 of the updated provision
321 messagefunc(SIMPLE, "Update of secrets.ldb")
322 reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
323 current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
324 assert reference, "Reference modules list can not be empty"
325 if len(current) == 0:
327 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
328 delta.dn = reference[0].dn
329 secrets_ldb.add(reference[0])
331 delta = secrets_ldb.msg_diff(current[0], reference[0])
332 delta.dn = current[0].dn
333 secrets_ldb.modify(delta)
335 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
336 scope=SCOPE_SUBTREE, attrs=["dn"])
337 current = secrets_ldb.search(expression="objectClass=top", base="",
338 scope=SCOPE_SUBTREE, attrs=["dn"])
344 empty = ldb.Message()
345 for i in range(0, len(reference)):
346 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
348 # Create a hash for speeding the search of existing object in the
350 for i in range(0, len(current)):
351 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
353 for k in hash_new.keys():
354 if not hash.has_key(k):
355 listMissing.append(hash_new[k])
357 listPresent.append(hash_new[k])
359 for entry in listMissing:
360 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
361 base="", scope=SCOPE_SUBTREE)
362 current = secrets_ldb.search(expression="distinguishedName=%s" % entry,
363 base="", scope=SCOPE_SUBTREE)
364 delta = secrets_ldb.msg_diff(empty, reference[0])
365 for att in hashAttrNotCopied:
367 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
370 messagefunc(CHANGE, " Adding attribute %s" % att)
371 delta.dn = reference[0].dn
372 secrets_ldb.add(delta)
374 for entry in listPresent:
375 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
376 base="", scope=SCOPE_SUBTREE)
377 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
379 delta = secrets_ldb.msg_diff(current[0], reference[0])
380 for att in hashAttrNotCopied:
384 messagefunc(CHANGE, "Found attribute name on %s,"
385 " must rename the DN" % (current[0].dn))
386 identic_rename(secrets_ldb, reference[0].dn)
390 for entry in listPresent:
391 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
393 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
395 delta = secrets_ldb.msg_diff(current[0], reference[0])
396 for att in hashAttrNotCopied:
399 if att == "msDS-KeyVersionNumber":
403 "Adding/Changing attribute %s to %s" %
404 (att, current[0].dn))
406 delta.dn = current[0].dn
407 secrets_ldb.modify(delta)
409 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
410 scope=SCOPE_SUBTREE, attrs=["dn"])
413 messagefunc(SIMPLE, "Remove old dns account")
414 secrets_ldb.delete(res2[0]["dn"])
417 def getOEMInfo(samdb, rootdn):
418 """Return OEM Information on the top level Samba4 use to store version
421 :param samdb: An LDB object connect to sam.ldb
422 :param rootdn: Root DN of the domain
423 :return: The content of the field oEMInformation (if any)
425 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
426 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
427 if len(res) > 0 and res[0].get("oEMInformation"):
428 info = res[0]["oEMInformation"]
434 def updateOEMInfo(samdb, rootdn):
435 """Update the OEMinfo field to add information about upgrade
437 :param samdb: an LDB object connected to the sam DB
438 :param rootdn: The string representation of the root DN of
439 the provision (ie. DC=...,DC=...)
441 res = samdb.search(expression="(objectClass=*)", base=rootdn,
442 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
444 if res[0].get("oEMInformation"):
445 info = str(res[0]["oEMInformation"])
448 info = "%s, upgrade to %s" % (info, version)
449 delta = ldb.Message()
450 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
451 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
455 def update_gpo(paths, samdb, names, lp, message):
456 """Create missing GPO file object if needed
458 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
459 if not os.path.isdir(dir):
460 create_gpo_struct(dir)
462 if names.policyid_dc is None:
463 raise ProvisioningError("Policy ID for Domain controller is missing")
464 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
465 if not os.path.isdir(dir):
466 create_gpo_struct(dir)
468 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
469 """For a given hash associating dn and a number, this function will
470 update the replPropertyMetaData of each dn in the hash, so that the
471 calculated value of the msDs-KeyVersionNumber is equal or superior to the
472 one associated to the given dn.
474 :param samdb: An SamDB object pointing to the sam
475 :param rootdn: The base DN where we want to start
476 :param hashDns: A hash with dn as key and number representing the
477 minimum value of msDs-KeyVersionNumber that we want to
480 entry = samdb.search(expression='(objectClass=user)',
481 base=ldb.Dn(samdb,str(rootdn)),
482 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
483 controls=["search_options:1:2"])
487 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
490 if hashDns.has_key(str(e.dn).lower()):
491 val = e.get("msDs-KeyVersionNumber")
494 version = int(str(hashDns[str(e.dn).lower()]))
495 if int(str(val)) < version:
497 samdb.set_attribute_replmetadata_version(str(e.dn),
500 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
501 """Update the provision container db: sam.ldb
502 This function is aimed for alpha9 and newer;
504 :param refsampath: Path to the samdb in the reference provision
505 :param sampath: Path to the samdb in the upgraded provision
506 :param creds: Credential used for openning LDB files
507 :param session: Session to use for openning LDB files
508 :param lp: A loadparam object
509 :return: A msg_diff object with the difference between the @ATTRIBUTES
510 of the current provision and the reference provision
514 "Update base samdb by searching difference with reference one")
515 refsam = Ldb(refsampath, session_info=session, credentials=creds,
516 lp=lp, options=["modules:"])
517 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
518 options=["modules:"])
520 empty = ldb.Message()
522 reference = refsam.search(expression="")
524 for refentry in reference:
525 entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
528 delta = sam.msg_diff(empty, refentry)
529 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
530 if str(refentry.dn) == "@PROVISION" and\
531 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
532 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
533 delta.dn = refentry.dn
536 delta = sam.msg_diff(entry[0], refentry)
537 if str(refentry.dn) == "@ATTRIBUTES":
538 deltaattr = sam.msg_diff(refentry, entry[0])
539 if str(refentry.dn) == "@PROVISION" and\
540 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
541 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
542 if len(delta.items()) > 1:
543 delta.dn = refentry.dn
549 def construct_existor_expr(attrs):
550 """Construct a exists or LDAP search expression.
552 :param attrs: List of attribute on which we want to create the search
554 :return: A string representing the expression, if attrs is empty an
555 empty string is returned
561 expr = "%s(%s=*)"%(expr,att)
565 def update_machine_account_password(samdb, secrets_ldb, names):
566 """Update (change) the password of the current DC both in the SAM db and in
569 :param samdb: An LDB object related to the sam.ldb file of a given provision
570 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
572 :param names: List of key provision parameters"""
574 expression = "samAccountName=%s$" % names.netbiosname
575 secrets_msg = secrets_ldb.search(expression=expression,
576 attrs=["secureChannelType"])
577 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
578 res = samdb.search(expression=expression, attrs=[])
579 assert(len(res) == 1)
581 msg = ldb.Message(res[0].dn)
582 machinepass = samba.generate_random_machine_password(128, 255)
583 mputf16 = machinepass.encode('utf-16-le')
584 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
585 ldb.FLAG_MOD_REPLACE,
589 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
590 attrs=["msDs-keyVersionNumber"])
591 assert(len(res) == 1)
592 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
593 secChanType = int(secrets_msg[0]["secureChannelType"][0])
595 secretsdb_self_join(secrets_ldb, domain=names.domain,
597 domainsid=names.domainsid,
598 dnsdomain=names.dnsdomain,
599 netbiosname=names.netbiosname,
600 machinepass=machinepass,
601 key_version_number=kvno,
602 secure_channel_type=secChanType)
604 raise ProvisioningError("Unable to find a Secure Channel"
605 "of type SEC_CHAN_BDC")
607 def update_dns_account_password(samdb, secrets_ldb, names):
608 """Update (change) the password of the dns both in the SAM db and in
611 :param samdb: An LDB object related to the sam.ldb file of a given provision
612 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
614 :param names: List of key provision parameters"""
616 expression = "samAccountName=dns-%s" % names.netbiosname
617 secrets_msg = secrets_ldb.search(expression=expression)
618 if len(secrets_msg) == 1:
619 res = samdb.search(expression=expression, attrs=[])
620 assert(len(res) == 1)
622 msg = ldb.Message(res[0].dn)
623 machinepass = samba.generate_random_password(128, 255)
624 mputf16 = machinepass.encode('utf-16-le')
625 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
626 ldb.FLAG_MOD_REPLACE,
631 res = samdb.search(expression=expression,
632 attrs=["msDs-keyVersionNumber"])
633 assert(len(res) == 1)
634 kvno = str(res[0]["msDs-keyVersionNumber"])
636 msg = ldb.Message(secrets_msg[0].dn)
637 msg["secret"] = ldb.MessageElement(machinepass,
638 ldb.FLAG_MOD_REPLACE,
640 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
641 ldb.FLAG_MOD_REPLACE,
642 "msDS-KeyVersionNumber")
644 secrets_ldb.modify(msg)
646 def update_krbtgt_account_password(samdb, names):
647 """Update (change) the password of the krbtgt account
649 :param samdb: An LDB object related to the sam.ldb file of a given provision
650 :param names: List of key provision parameters"""
652 expression = "samAccountName=krbtgt"
653 res = samdb.search(expression=expression, attrs=[])
654 assert(len(res) == 1)
656 msg = ldb.Message(res[0].dn)
657 machinepass = samba.generate_random_machine_password(128, 255)
658 mputf16 = machinepass.encode('utf-16-le')
659 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
660 ldb.FLAG_MOD_REPLACE,
665 def search_constructed_attrs_stored(samdb, rootdn, attrs):
666 """Search a given sam DB for calculated attributes that are
667 still stored in the db.
669 :param samdb: An LDB object pointing to the sam
670 :param rootdn: The base DN where the search should start
671 :param attrs: A list of attributes to be searched
672 :return: A hash with attributes as key and an array of
673 array. Each array contains the dn and the associated
674 values for this attribute as they are stored in the
678 expr = construct_existor_expr(attrs)
681 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
682 scope=SCOPE_SUBTREE, attrs=attrs,
683 controls=["search_options:1:2","bypassoperational:0"])
691 if hashAtt.has_key(att):
692 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
695 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
699 def findprovisionrange(samdb, basedn):
700 """ Find ranges of usn grouped by invocation id and then by timestamp
703 :param samdb: An LDB object pointing to the samdb
704 :param basedn: The DN of the forest
706 :return: A two level dictionary with invoication id as the
707 first level, timestamp as the second one and then
708 max, min, and number as subkeys, representing respectivily
709 the maximum usn for the range, the minimum usn and the number
710 of object with usn in this range.
715 res = samdb.search(base=basedn, expression="objectClass=*",
716 scope=ldb.SCOPE_SUBTREE,
717 attrs=["replPropertyMetaData"],
718 controls=["search_options:1:2"])
722 obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
723 str(e["replPropertyMetaData"])).ctr
726 # like a timestamp but with the resolution of 1 minute
727 minutestamp =_glue.nttime2unix(o.originating_change_time)/60
728 hash_ts = hash_id.get(str(o.originating_invocation_id))
732 ob["min"] = o.originating_usn
733 ob["max"] = o.originating_usn
735 ob["list"] = [str(e.dn)]
738 ob = hash_ts.get(minutestamp)
741 ob["min"] = o.originating_usn
742 ob["max"] = o.originating_usn
744 ob["list"] = [str(e.dn)]
746 if ob["min"] > o.originating_usn:
747 ob["min"] = o.originating_usn
748 if ob["max"] < o.originating_usn:
749 ob["max"] = o.originating_usn
750 if not (str(e.dn) in ob["list"]):
751 ob["num"] = ob["num"] + 1
752 ob["list"].append(str(e.dn))
753 hash_ts[minutestamp] = ob
754 hash_id[str(o.originating_invocation_id)] = hash_ts
756 return (hash_id, nb_obj)
758 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
759 """ print the differents ranges passed as parameter
761 :param dic: A dictionnary as returned by findprovisionrange
762 :param limit_print: minimum number of object in a range in order to print it
763 :param dest: Destination directory
764 :param samdb_path: Path to the sam.ldb file
765 :param invoicationid: Invocation ID for the current provision
772 sorted_keys.extend(hash_ts.keys())
776 for k in sorted_keys:
778 if obj["num"] > limit_print:
779 dt = _glue.nttime2string(_glue.unix2nttime(k*60))
780 print("%s # of modification: %d \tmin: %d max: %d" % (dt , obj["num"],
783 if hash_ts[k]["num"] > 600:
784 kept_record.append(k)
786 # Let's try to concatenate consecutive block if they are in the almost same minutestamp
787 for i in range(0, len(kept_record)):
789 key1 = kept_record[i]
790 key2 = kept_record[i-1]
792 # previous record is just 1 minute away from current
793 if int(hash_ts[key1]["min"]) == int(hash_ts[key2]["max"]) + 1:
794 # Copy the highest USN in the previous record
795 # and mark the current as skipped
796 hash_ts[key2]["max"] = hash_ts[key1]["max"]
797 hash_ts[key1]["skipped"] = True
799 for k in kept_record:
801 if obj.get("skipped") is None:
802 ldif = "%slastProvisionUSN: %d-%d;%s\n" % (ldif, obj["min"],
806 file = tempfile.mktemp(dir=dest, prefix="usnprov", suffix=".ldif")
808 print("To track the USNs modified/created by provision and upgrade proivsion,")
809 print(" the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif)
810 print("We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file)
811 print("You can load this file like this: ldbadd -H %s %s\n"%(str(samdb_path),file))
812 ldif = "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocationid, ldif)
813 open(file,'w').write(ldif)
815 def int64range2str(value):
816 """Display the int64 range stored in value as xxx-yyy
818 :param value: The int64 range
819 :return: A string of the representation of the range
823 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)