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 """Helpers used for upgrading between different database formats."""
29 from samba
.common
import cmp
30 from samba
import Ldb
, version
31 from ldb
import SCOPE_SUBTREE
, SCOPE_ONELEVEL
, SCOPE_BASE
33 from samba
.provision
import (provision_paths_from_lp
,
34 getpolicypath
, create_gpo_struct
,
35 provision
, ProvisioningError
,
37 from samba
.provision
.common
import FILL_FULL
38 from samba
.dcerpc
import drsblobs
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 opening LDB files
136 :param session: Session to use for opening 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"))
214 def update_policyids(names
, samdb
):
215 """Update policy ids that could have changed after sam update
217 :param names: List of key provision parameters
218 :param samdb: An Ldb object conntected with the sam DB
221 res
= samdb
.search(expression
="(displayName=Default Domain Policy)",
222 base
="CN=Policies,CN=System," + str(names
.rootdn
),
223 scope
=SCOPE_ONELEVEL
, attrs
=["cn", "displayName"])
224 names
.policyid
= str(res
[0]["cn"]).replace("{", "").replace("}", "")
226 res2
= samdb
.search(expression
="(displayName=Default Domain Controllers"
228 base
="CN=Policies,CN=System," + str(names
.rootdn
),
229 scope
=SCOPE_ONELEVEL
, attrs
=["cn", "displayName"])
231 names
.policyid_dc
= str(res2
[0]["cn"]).replace("{", "").replace("}", "")
233 names
.policyid_dc
= None
236 def newprovision(names
, session
, smbconf
, provdir
, logger
, base_schema
=None, adprep_level
=None):
237 """Create a new provision.
239 This provision will be the reference for knowing what has changed in the
240 since the latest upgrade in the current provision
242 :param names: List of provision parameters
243 :param creds: Credentials for the authentication
244 :param session: Session object
245 :param smbconf: Path to the smb.conf file
246 :param provdir: Directory where the provision will be stored
247 :param logger: A Logger
249 if os
.path
.isdir(provdir
):
250 shutil
.rmtree(provdir
)
252 logger
.info("Provision stored in %s", provdir
)
253 return provision(logger
, session
, smbconf
=smbconf
,
254 targetdir
=provdir
, samdb_fill
=FILL_FULL
, realm
=names
.realm
,
255 domain
=names
.domain
, domainguid
=names
.domainguid
,
256 domainsid
=names
.domainsid
, ntdsguid
=names
.ntdsguid
,
257 policyguid
=names
.policyid
, policyguid_dc
=names
.policyid_dc
,
258 hostname
=names
.netbiosname
.lower(), hostip
=None, hostip6
=None,
259 invocationid
=names
.invocation
, adminpass
=names
.adminpass
,
260 krbtgtpass
=None, machinepass
=None, dnspass
=None, root
=None,
261 nobody
=None, users
=None,
262 serverrole
="domain controller",
263 dom_for_fun_level
=names
.domainlevel
, dns_backend
=names
.dns_backend
,
264 useeadb
=True, use_ntvfs
=True, base_schema
=base_schema
,
265 adprep_level
=adprep_level
)
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
.copy(delta
.ldb
)
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():
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 delta
= secrets_ldb
.msg_diff(empty
, reference
[0])
363 for att
in hashAttrNotCopied
:
365 messagefunc(CHANGE
, "Entry %s is missing from secrets.ldb" %
368 messagefunc(CHANGE
, " Adding attribute %s" % att
)
369 delta
.dn
= reference
[0].dn
.copy(delta
.ldb
)
370 secrets_ldb
.add(delta
)
372 for entry
in listPresent
:
373 reference
= newsecrets_ldb
.search(expression
="distinguishedName=%s" % entry
,
374 base
="", scope
=SCOPE_SUBTREE
)
375 current
= secrets_ldb
.search(expression
="distinguishedName=%s" % entry
, base
="",
377 delta
= secrets_ldb
.msg_diff(current
[0], reference
[0])
378 for att
in hashAttrNotCopied
:
382 messagefunc(CHANGE
, "Found attribute name on %s,"
383 " must rename the DN" % (current
[0].dn
))
384 identic_rename(secrets_ldb
, reference
[0].dn
)
388 for entry
in listPresent
:
389 reference
= newsecrets_ldb
.search(expression
="distinguishedName=%s" % entry
, base
="",
391 current
= secrets_ldb
.search(expression
="distinguishedName=%s" % entry
, base
="",
393 delta
= secrets_ldb
.msg_diff(current
[0], reference
[0])
394 for att
in hashAttrNotCopied
:
397 if att
== "msDS-KeyVersionNumber":
401 "Adding/Changing attribute %s to %s" %
402 (att
, current
[0].dn
))
404 delta
.dn
= current
[0].dn
405 secrets_ldb
.modify(delta
)
407 res2
= secrets_ldb
.search(expression
="(samaccountname=dns)",
408 scope
=SCOPE_SUBTREE
, attrs
=["dn"])
411 messagefunc(SIMPLE
, "Remove old dns account")
412 secrets_ldb
.delete(res2
[0]["dn"])
415 def getOEMInfo(samdb
, rootdn
):
416 """Return OEM Information on the top level Samba4 use to store version
419 :param samdb: An LDB object connect to sam.ldb
420 :param rootdn: Root DN of the domain
421 :return: The content of the field oEMInformation (if any)
423 res
= samdb
.search(expression
="(objectClass=*)", base
=str(rootdn
),
424 scope
=SCOPE_BASE
, attrs
=["dn", "oEMInformation"])
425 if len(res
) > 0 and res
[0].get("oEMInformation"):
426 info
= res
[0]["oEMInformation"]
432 def updateOEMInfo(samdb
, rootdn
):
433 """Update the OEMinfo field to add information about upgrade
435 :param samdb: an LDB object connected to the sam DB
436 :param rootdn: The string representation of the root DN of
437 the provision (ie. DC=...,DC=...)
439 res
= samdb
.search(expression
="(objectClass=*)", base
=rootdn
,
440 scope
=SCOPE_BASE
, attrs
=["dn", "oEMInformation"])
442 if res
[0].get("oEMInformation"):
443 info
= str(res
[0]["oEMInformation"])
446 info
= "%s, upgrade to %s" % (info
, version
)
447 delta
= ldb
.Message()
448 delta
.dn
= ldb
.Dn(samdb
, str(res
[0]["dn"]))
449 delta
["oEMInformation"] = ldb
.MessageElement(info
, ldb
.FLAG_MOD_REPLACE
,
454 def update_gpo(paths
, names
):
455 """Create missing GPO file object if needed
457 dir = getpolicypath(paths
.sysvol
, names
.dnsdomain
, names
.policyid
)
458 if not os
.path
.isdir(dir):
459 create_gpo_struct(dir)
461 if names
.policyid_dc
is None:
462 raise ProvisioningError("Policy ID for Domain controller is missing")
463 dir = getpolicypath(paths
.sysvol
, names
.dnsdomain
, names
.policyid_dc
)
464 if not os
.path
.isdir(dir):
465 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"])
486 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
489 if str(e
.dn
).lower() in hashDns
:
490 val
= e
.get("msDs-KeyVersionNumber")
493 version
= int(str(hashDns
[str(e
.dn
).lower()]))
494 if int(str(val
)) < version
:
496 samdb
.set_attribute_replmetadata_version(str(e
.dn
),
501 def delta_update_basesamdb(refsampath
, sampath
, creds
, session
, lp
, message
):
502 """Update the provision container db: sam.ldb
503 This function is aimed for alpha9 and newer;
505 :param refsampath: Path to the samdb in the reference provision
506 :param sampath: Path to the samdb in the upgraded provision
507 :param creds: Credential used for opening LDB files
508 :param session: Session to use for opening LDB files
509 :param lp: A loadparam object
510 :return: A msg_diff object with the difference between the @ATTRIBUTES
511 of the current provision and the reference provision
515 "Update base samdb by searching difference with reference one")
516 refsam
= Ldb(refsampath
, session_info
=session
, credentials
=creds
,
517 lp
=lp
, options
=["modules:"])
518 sam
= Ldb(sampath
, session_info
=session
, credentials
=creds
, lp
=lp
,
519 options
=["modules:"])
521 empty
= ldb
.Message()
523 reference
= refsam
.search(expression
="")
525 for refentry
in reference
:
526 entry
= sam
.search(expression
="distinguishedName=%s" % refentry
["dn"],
529 delta
= sam
.msg_diff(empty
, refentry
)
530 message(CHANGE
, "Adding %s to sam db" % str(refentry
.dn
))
531 if str(refentry
.dn
) == "@PROVISION" and\
532 delta
.get(samba
.provision
.LAST_PROVISION_USN_ATTRIBUTE
):
533 delta
.remove(samba
.provision
.LAST_PROVISION_USN_ATTRIBUTE
)
534 delta
.dn
= refentry
.dn
.copy(delta
.ldb
)
537 delta
= sam
.msg_diff(entry
[0], refentry
)
538 if str(refentry
.dn
) == "@ATTRIBUTES":
539 deltaattr
= sam
.msg_diff(refentry
, entry
[0])
540 if str(refentry
.dn
) == "@PROVISION" and\
541 delta
.get(samba
.provision
.LAST_PROVISION_USN_ATTRIBUTE
):
542 delta
.remove(samba
.provision
.LAST_PROVISION_USN_ATTRIBUTE
)
543 if len(delta
.items()) > 1:
544 delta
.dn
= refentry
.dn
.copy(delta
.ldb
)
550 def construct_existor_expr(attrs
):
551 """Construct a exists or LDAP search expression.
553 :param attrs: List of attribute on which we want to create the search
555 :return: A string representing the expression, if attrs is empty an
556 empty string is returned
562 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(120, 120)
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")
610 def update_dns_account_password(samdb
, secrets_ldb
, names
):
611 """Update (change) the password of the dns both in the SAM db and in
614 :param samdb: An LDB object related to the sam.ldb file of a given provision
615 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
617 :param names: List of key provision parameters"""
619 expression
= "samAccountName=dns-%s" % names
.netbiosname
620 secrets_msg
= secrets_ldb
.search(expression
=expression
)
621 if len(secrets_msg
) == 1:
622 res
= samdb
.search(expression
=expression
, attrs
=[])
623 assert(len(res
) == 1)
625 msg
= ldb
.Message(res
[0].dn
)
626 machinepass
= samba
.generate_random_password(128, 255)
627 mputf16
= machinepass
.encode('utf-16-le')
628 msg
["clearTextPassword"] = ldb
.MessageElement(mputf16
,
629 ldb
.FLAG_MOD_REPLACE
,
634 res
= samdb
.search(expression
=expression
,
635 attrs
=["msDs-keyVersionNumber"])
636 assert(len(res
) == 1)
637 kvno
= str(res
[0]["msDs-keyVersionNumber"])
639 msg
= ldb
.Message(secrets_msg
[0].dn
)
640 msg
["secret"] = ldb
.MessageElement(machinepass
,
641 ldb
.FLAG_MOD_REPLACE
,
643 msg
["msDS-KeyVersionNumber"] = ldb
.MessageElement(kvno
,
644 ldb
.FLAG_MOD_REPLACE
,
645 "msDS-KeyVersionNumber")
647 secrets_ldb
.modify(msg
)
650 def update_krbtgt_account_password(samdb
):
651 """Update (change) the password of the krbtgt account
653 :param samdb: An LDB object related to the sam.ldb file of a given provision"""
655 expression
= "samAccountName=krbtgt"
656 res
= samdb
.search(expression
=expression
, attrs
=[])
657 assert(len(res
) == 1)
659 msg
= ldb
.Message(res
[0].dn
)
660 # Note that the machinepass value is ignored
661 # as the backend (password_hash.c) will generate its
662 # own random values for the krbtgt keys
663 krbtgtpass
= samba
.generate_random_machine_password(128, 255)
664 kputf16
= krbtgtpass
.encode('utf-16-le')
665 msg
["clearTextPassword"] = ldb
.MessageElement(kputf16
,
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 different ranges passed as parameter
770 :param dic: A dictionary 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)