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 cmp = lambda x
, y
: (x
> y
) - (x
< y
) # cmp is removed in py3
286 for i
in range(0, minimum
):
287 ret
= cmp(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
,
456 def update_gpo(paths
, samdb
, names
, lp
, message
):
457 """Create missing GPO file object if needed
459 dir = getpolicypath(paths
.sysvol
, names
.dnsdomain
, names
.policyid
)
460 if not os
.path
.isdir(dir):
461 create_gpo_struct(dir)
463 if names
.policyid_dc
is None:
464 raise ProvisioningError("Policy ID for Domain controller is missing")
465 dir = getpolicypath(paths
.sysvol
, names
.dnsdomain
, names
.policyid_dc
)
466 if not os
.path
.isdir(dir):
467 create_gpo_struct(dir)
469 def increment_calculated_keyversion_number(samdb
, rootdn
, hashDns
):
470 """For a given hash associating dn and a number, this function will
471 update the replPropertyMetaData of each dn in the hash, so that the
472 calculated value of the msDs-KeyVersionNumber is equal or superior to the
473 one associated to the given dn.
475 :param samdb: An SamDB object pointing to the sam
476 :param rootdn: The base DN where we want to start
477 :param hashDns: A hash with dn as key and number representing the
478 minimum value of msDs-KeyVersionNumber that we want to
481 entry
= samdb
.search(expression
='(objectClass=user)',
482 base
=ldb
.Dn(samdb
,str(rootdn
)),
483 scope
=SCOPE_SUBTREE
, attrs
=["msDs-KeyVersionNumber"],
484 controls
=["search_options:1:2"])
488 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
491 if hashDns
.has_key(str(e
.dn
).lower()):
492 val
= e
.get("msDs-KeyVersionNumber")
495 version
= int(str(hashDns
[str(e
.dn
).lower()]))
496 if int(str(val
)) < version
:
498 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 openning LDB files
508 :param session: Session to use for openning 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
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
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
)
566 def update_machine_account_password(samdb
, secrets_ldb
, names
):
567 """Update (change) the password of the current DC both in the SAM db and in
570 :param samdb: An LDB object related to the sam.ldb file of a given provision
571 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
573 :param names: List of key provision parameters"""
575 expression
= "samAccountName=%s$" % names
.netbiosname
576 secrets_msg
= secrets_ldb
.search(expression
=expression
,
577 attrs
=["secureChannelType"])
578 if int(secrets_msg
[0]["secureChannelType"][0]) == SEC_CHAN_BDC
:
579 res
= samdb
.search(expression
=expression
, attrs
=[])
580 assert(len(res
) == 1)
582 msg
= ldb
.Message(res
[0].dn
)
583 machinepass
= samba
.generate_random_machine_password(128, 255)
584 mputf16
= machinepass
.encode('utf-16-le')
585 msg
["clearTextPassword"] = ldb
.MessageElement(mputf16
,
586 ldb
.FLAG_MOD_REPLACE
,
590 res
= samdb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
),
591 attrs
=["msDs-keyVersionNumber"])
592 assert(len(res
) == 1)
593 kvno
= int(str(res
[0]["msDs-keyVersionNumber"]))
594 secChanType
= int(secrets_msg
[0]["secureChannelType"][0])
596 secretsdb_self_join(secrets_ldb
, domain
=names
.domain
,
598 domainsid
=names
.domainsid
,
599 dnsdomain
=names
.dnsdomain
,
600 netbiosname
=names
.netbiosname
,
601 machinepass
=machinepass
,
602 key_version_number
=kvno
,
603 secure_channel_type
=secChanType
)
605 raise ProvisioningError("Unable to find a Secure Channel"
606 "of type SEC_CHAN_BDC")
608 def update_dns_account_password(samdb
, secrets_ldb
, names
):
609 """Update (change) the password of the dns both in the SAM db and in
612 :param samdb: An LDB object related to the sam.ldb file of a given provision
613 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
615 :param names: List of key provision parameters"""
617 expression
= "samAccountName=dns-%s" % names
.netbiosname
618 secrets_msg
= secrets_ldb
.search(expression
=expression
)
619 if len(secrets_msg
) == 1:
620 res
= samdb
.search(expression
=expression
, attrs
=[])
621 assert(len(res
) == 1)
623 msg
= ldb
.Message(res
[0].dn
)
624 machinepass
= samba
.generate_random_password(128, 255)
625 mputf16
= machinepass
.encode('utf-16-le')
626 msg
["clearTextPassword"] = ldb
.MessageElement(mputf16
,
627 ldb
.FLAG_MOD_REPLACE
,
632 res
= samdb
.search(expression
=expression
,
633 attrs
=["msDs-keyVersionNumber"])
634 assert(len(res
) == 1)
635 kvno
= str(res
[0]["msDs-keyVersionNumber"])
637 msg
= ldb
.Message(secrets_msg
[0].dn
)
638 msg
["secret"] = ldb
.MessageElement(machinepass
,
639 ldb
.FLAG_MOD_REPLACE
,
641 msg
["msDS-KeyVersionNumber"] = ldb
.MessageElement(kvno
,
642 ldb
.FLAG_MOD_REPLACE
,
643 "msDS-KeyVersionNumber")
645 secrets_ldb
.modify(msg
)
647 def update_krbtgt_account_password(samdb
, names
):
648 """Update (change) the password of the krbtgt account
650 :param samdb: An LDB object related to the sam.ldb file of a given provision
651 :param names: List of key provision parameters"""
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"])
692 if hashAtt
.has_key(att
):
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)