3 # Helpers for provision stuff
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
6 # Based on provision a Samba4 server by
7 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
8 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
31 from samba import Ldb, version, ntacls
32 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
33 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
35 from samba.provision import (ProvisionNames, provision_paths_from_lp,
36 getpolicypath, set_gpo_acl, create_gpo_struct,
37 FILL_FULL, provision, ProvisioningError,
39 from samba.dcerpc import misc, security, xattr
40 from samba.ndr import ndr_unpack
42 # All the ldb related to registry are commented because the path for them is relative
43 # in the provisionPath object
44 # And so opening them create a file in the current directory which is not what we want
45 # I still keep them commented because I plan soon to make more cleaner
54 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1,
55 "objectGUID": 1, "uSNCreated": 1,
56 "replPropertyMetaData": 1, "uSNChanged": 1,
57 "parentGUID": 1, "objectCategory": 1,
58 "distinguishedName": 1, "nTMixedDomain": 1,
59 "showInAdvancedViewOnly": 1, "instanceType": 1,
60 "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
61 "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1,
62 "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
63 "supplementalCredentials":1, "gPCUserExtensionNames":1,
64 "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
65 "possibleInferiors":1, "privilege":1,
68 class ProvisionLDB(object):
79 def startTransactions(self):
80 self.sam.transaction_start()
81 self.secrets.transaction_start()
82 self.idmap.transaction_start()
83 self.privilege.transaction_start()
84 # self.hkcr.transaction_start()
85 # self.hkcu.transaction_start()
86 # self.hku.transaction_start()
87 # self.hklm.transaction_start()
89 def groupedRollback(self):
90 self.sam.transaction_cancel()
91 self.secrets.transaction_cancel()
92 self.idmap.transaction_cancel()
93 self.privilege.transaction_cancel()
94 # self.hkcr.transaction_cancel()
95 # self.hkcu.transaction_cancel()
96 # self.hku.transaction_cancel()
97 # self.hklm.transaction_cancel()
99 def groupedCommit(self):
100 self.sam.transaction_prepare_commit()
101 self.secrets.transaction_prepare_commit()
102 self.idmap.transaction_prepare_commit()
103 self.privilege.transaction_prepare_commit()
104 # self.hkcr.transaction_prepare_commit()
105 # self.hkcu.transaction_prepare_commit()
106 # self.hku.transaction_prepare_commit()
107 # self.hklm.transaction_prepare_commit()
109 self.sam.transaction_commit()
110 self.secrets.transaction_commit()
111 self.idmap.transaction_commit()
112 self.privilege.transaction_commit()
113 # self.hkcr.transaction_commit()
114 # self.hkcu.transaction_commit()
115 # self.hku.transaction_commit()
116 # self.hklm.transaction_commit()
118 def get_ldbs(paths, creds, session, lp):
119 """Return LDB object mapped on most important databases
121 :param paths: An object holding the different importants paths for provision object
122 :param creds: Credential used for openning LDB files
123 :param session: Session to use for openning LDB files
124 :param lp: A loadparam object
125 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
127 ldbs = ProvisionLDB()
129 ldbs.sam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
130 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
131 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
132 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
133 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
134 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
135 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
136 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
140 def usn_in_range(usn, range):
141 """Check if the usn is in one of the range provided.
142 To do so, the value is checked to be between the lower bound and
143 higher bound of a range
145 :param usn: A integer value corresponding to the usn that we want to update
146 :param range: A list of integer representing ranges, lower bounds are in
147 the even indices, higher in odd indices
148 :return: 1 if the usn is in one of the range, 0 otherwise"""
154 if idx == len(range):
157 if usn < int(range[idx]):
161 if usn == int(range[idx]):
167 def get_paths(param, targetdir=None, smbconf=None):
168 """Get paths to important provision objects (smb.conf, ldb files, ...)
170 :param param: Param object
171 :param targetdir: Directory where the provision is (or will be) stored
172 :param smbconf: Path to the smb.conf file
173 :return: A list with the path of important provision objects"""
174 if targetdir is not None:
175 etcdir = os.path.join(targetdir, "etc")
176 if not os.path.exists(etcdir):
178 smbconf = os.path.join(etcdir, "smb.conf")
180 smbconf = param.default_path()
182 if not os.path.exists(smbconf):
183 raise ProvisioningError("Unable to find smb.conf")
185 lp = param.LoadParm()
187 paths = provision_paths_from_lp(lp, lp.get("realm"))
191 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
192 """Get key provision parameters (realm, domain, ...) from a given provision
194 :param samdb: An LDB object connected to the sam.ldb file
195 :param secretsdb: An LDB object connected to the secrets.ldb file
196 :param idmapdb: An LDB object connected to the idmap.ldb file
197 :param paths: A list of path to provision object
198 :param smbconf: Path to the smb.conf file
199 :param lp: A LoadParm object
200 :return: A list of key provision parameters"""
202 names = ProvisionNames()
203 names.adminpass = None
205 # NT domain, kerberos realm, root dn, domain dn, domain dns name
206 names.domain = string.upper(lp.get("workgroup"))
207 names.realm = lp.get("realm")
208 basedn = "DC=" + names.realm.replace(".",",DC=")
209 names.dnsdomain = names.realm.lower()
210 names.realm = string.upper(names.realm)
212 # Get the netbiosname first (could be obtained from smb.conf in theory)
213 res = secretsdb.search(expression="(flatname=%s)" % \
214 names.domain,base="CN=Primary Domains",
215 scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
216 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
218 names.smbconf = smbconf
220 # That's a bit simplistic but it's ok as long as we have only 3
222 current = samdb.search(expression="(objectClass=*)",
223 base="", scope=SCOPE_BASE,
224 attrs=["defaultNamingContext", "schemaNamingContext",
225 "configurationNamingContext","rootDomainNamingContext"])
227 names.configdn = current[0]["configurationNamingContext"]
228 configdn = str(names.configdn)
229 names.schemadn = current[0]["schemaNamingContext"]
230 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
231 current[0]["defaultNamingContext"][0]))):
232 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
233 "is not the same ..." % (paths.samdb,
234 str(current[0]["defaultNamingContext"][0]),
235 paths.smbconf, basedn)))
237 names.domaindn=current[0]["defaultNamingContext"]
238 names.rootdn=current[0]["rootDomainNamingContext"]
240 res3 = samdb.search(expression="(objectClass=*)",
241 base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
242 names.sitename = str(res3[0]["cn"])
244 # dns hostname and server dn
245 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
246 base="OU=Domain Controllers,%s" % basedn,
247 scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
248 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
250 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
251 attrs=[], base=configdn)
252 names.serverdn = server_res[0].dn
254 # invocation id/objectguid
255 res5 = samdb.search(expression="(objectClass=*)",
256 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
257 attrs=["invocationID", "objectGUID"])
258 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
259 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
262 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
263 scope=SCOPE_BASE, attrs=["objectGUID",
264 "objectSid","msDS-Behavior-Version" ])
265 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
266 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
267 if res6[0].get("msDS-Behavior-Version") == None or \
268 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
269 names.domainlevel = DS_DOMAIN_FUNCTION_2000
271 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
274 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
275 base="CN=Policies,CN=System," + basedn,
276 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
277 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
279 res8 = samdb.search(expression="(displayName=Default Domain Controllers" \
281 base="CN=Policies,CN=System," + basedn,
282 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
284 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
286 names.policyid_dc = None
287 res9 = idmapdb.search(expression="(cn=%s)" % \
288 (security.SID_BUILTIN_ADMINISTRATORS),
291 names.wheel_gid = res9[0]["xidNumber"]
293 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
297 def newprovision(names, setup_dir, creds, session, smbconf, provdir, logger):
298 """Create a new provision.
300 This provision will be the reference for knowing what has changed in the
301 since the latest upgrade in the current provision
303 :param names: List of provision parameters
304 :param setup_dis: Directory where the setup files are stored
305 :param creds: Credentials for the authentification
306 :param session: Session object
307 :param smbconf: Path to the smb.conf file
308 :param provdir: Directory where the provision will be stored
309 :param logger: A `Logger`
311 if os.path.isdir(provdir):
312 shutil.rmtree(provdir)
313 os.chdir(os.path.join(setup_dir,".."))
315 logger.info("Provision stored in %s", provdir)
316 provision(setup_dir, logger, session, creds, smbconf=smbconf,
317 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
318 domain=names.domain, domainguid=names.domainguid,
319 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
320 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
321 hostname=names.netbiosname, hostip=None, hostip6=None,
322 invocationid=names.invocation, adminpass=names.adminpass,
323 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
324 nobody=None, wheel=None, users=None,
325 serverrole="domain controller", ldap_backend_extra_port=None,
326 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
327 slapd_path=None, setup_ds_path=None, nosync=None,
328 dom_for_fun_level=names.domainlevel,
329 ldap_dryrun_mode=None, useeadb=True)
333 """Sorts two DNs in the lexicographical order it and put higher level DN
336 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
339 :param x: First object to compare
340 :param y: Second object to compare
342 p = re.compile(r'(?<!\\), ?')
343 tab1 = p.split(str(x))
344 tab2 = p.split(str(y))
345 minimum = min(len(tab1), len(tab2))
348 # Note: python range go up to upper limit but do not include it
349 for i in range(0, minimum):
350 ret = cmp(tab1[len1-i], tab2[len2-i])
355 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
362 def identic_rename(ldbobj, dn):
363 """Perform a back and forth rename to trigger renaming on attribute that
364 can't be directly modified.
366 :param lbdobj: An Ldb Object
367 :param dn: DN of the object to manipulate """
368 (before, sep, after)=str(dn).partition('=')
369 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)))
370 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn)
373 """Return separate ACE of an ACL
375 :param acl: A string representing the ACL
376 :return: A hash with different parts
379 p = re.compile(r'(\w+)?(\(.*?\))')
387 hash["aces"].append(e[1])
392 def chunck_sddl(sddl):
393 """ Return separate parts of the SDDL (owner, group, ...)
395 :param sddl: An string containing the SDDL to chunk
396 :return: A hash with the different chunk
399 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
400 tab = p.findall(sddl)
415 def get_diff_sddls(refsddl, cursddl):
416 """Get the difference between 2 sddl
417 This function split the textual representation of ACL into smaller
418 chunck in order to not to report a simple permutation as a difference
420 :param refsddl: First sddl to compare
421 :param cursddl: Second sddl to compare
422 :return: A string that explain difference between sddls"""
425 hash_new = chunck_sddl(cursddl)
426 hash_ref = chunck_sddl(refsddl)
428 if hash_new["owner"] != hash_ref["owner"]:
429 txt = "\tOwner mismatch: %s (in ref) %s" \
430 "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
432 if hash_new["group"] != hash_ref["group"]:
433 txt = "%s\tGroup mismatch: %s (in ref) %s" \
434 "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
436 for part in ["dacl", "sacl"]:
437 if hash_new.has_key(part) and hash_ref.has_key(part):
439 # both are present, check if they contain the same ACE
442 c_new = chunck_acl(hash_new[part])
443 c_ref = chunck_acl(hash_ref[part])
445 for elem in c_new["aces"]:
448 for elem in c_ref["aces"]:
451 for k in h_ref.keys():
456 if len(h_new.keys()) + len(h_ref.keys()) > 0:
457 txt = "%s\tPart %s is different between reference" \
458 " and current here is the detail:\n" % (txt, part)
460 for item in h_new.keys():
461 txt = "%s\t\t%s ACE is not present in the" \
462 " reference\n" % (txt, item)
464 for item in h_ref.keys():
465 txt = "%s\t\t%s ACE is not present in the" \
466 " current\n" % (txt, item)
468 elif hash_new.has_key(part) and not hash_ref.has_key(part):
469 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
470 elif not hash_new.has_key(part) and hash_ref.has_key(part):
471 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
476 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
477 """Update secrets.ldb
479 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
480 of the reference provision
481 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
482 of the updated provision
485 messagefunc(SIMPLE, "update secrets.ldb")
486 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
488 current = secrets_ldb.search(expression="dn=@MODULES", base="",
490 assert reference, "Reference modules list can not be empty"
491 if len(current) == 0:
493 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
494 delta.dn = reference[0].dn
495 secrets_ldb.add(reference[0])
497 delta = secrets_ldb.msg_diff(current[0], reference[0])
498 delta.dn = current[0].dn
499 secrets_ldb.modify(delta)
501 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
502 scope=SCOPE_SUBTREE, attrs=["dn"])
503 current = secrets_ldb.search(expression="objectClass=top", base="",
504 scope=SCOPE_SUBTREE, attrs=["dn"])
510 empty = ldb.Message()
511 for i in range(0, len(reference)):
512 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
514 # Create a hash for speeding the search of existing object in the
516 for i in range(0, len(current)):
517 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
519 for k in hash_new.keys():
520 if not hash.has_key(k):
521 listMissing.append(hash_new[k])
523 listPresent.append(hash_new[k])
525 for entry in listMissing:
526 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
527 base="", scope=SCOPE_SUBTREE)
528 current = secrets_ldb.search(expression="dn=%s" % entry,
529 base="", scope=SCOPE_SUBTREE)
530 delta = secrets_ldb.msg_diff(empty, reference[0])
531 for att in hashAttrNotCopied.keys():
533 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" % \
536 messagefunc(CHANGE, " Adding attribute %s" % att)
537 delta.dn = reference[0].dn
538 secrets_ldb.add(delta)
540 for entry in listPresent:
541 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
542 base="", scope=SCOPE_SUBTREE)
543 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
545 delta = secrets_ldb.msg_diff(current[0], reference[0])
546 for att in hashAttrNotCopied.keys():
550 messagefunc(CHANGE, "Found attribute name on %s," \
551 " must rename the DN" % (current[0].dn))
552 identic_rename(secrets_ldb, reference[0].dn)
556 for entry in listPresent:
557 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
559 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
561 delta = secrets_ldb.msg_diff(current[0], reference[0])
562 for att in hashAttrNotCopied.keys():
567 "Adding/Changing attribute %s to %s" % \
568 (att, current[0].dn))
570 delta.dn = current[0].dn
571 secrets_ldb.modify(delta)
573 def getOEMInfo(samdb, rootdn):
574 """Return OEM Information on the top level
575 Samba4 use to store version info in this field
577 :param samdb: An LDB object connect to sam.ldb
578 :param rootdn: Root DN of the domain
579 :return: The content of the field oEMInformation (if any)"""
580 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
581 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
583 info = res[0]["oEMInformation"]
588 def updateOEMInfo(samdb, rootdn):
589 """Update the OEMinfo field to add information about upgrade
590 :param samdb: an LDB object connected to the sam DB
591 :param rootdn: The string representation of the root DN of
592 the provision (ie. DC=...,DC=...)
594 res = samdb.search(expression="(objectClass=*)", base=rootdn,
595 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
597 info = res[0]["oEMInformation"]
598 info = "%s, upgrade to %s" % (info, version)
599 delta = ldb.Message()
600 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
601 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
605 def update_gpo(paths, samdb, names, lp, message, force=0):
606 """Create missing GPO file object if needed
608 Set ACL correctly also.
609 Check ACLs for sysvol/netlogon dirs also
613 ntacls.checkset_backend(lp, None, None)
614 eadbname = lp.get("posix:eadb")
615 if eadbname is not None and eadbname != "":
617 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
618 paths.sysvol, xattr.XATTR_NTACL_NAME)
620 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
621 xattr.XATTR_NTACL_NAME)
623 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
624 xattr.XATTR_NTACL_NAME)
631 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
632 if not os.path.isdir(dir):
633 create_gpo_struct(dir)
635 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
636 if not os.path.isdir(dir):
637 create_gpo_struct(dir)
638 # We always reinforce acls on GPO folder because they have to be in sync
641 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
642 names.domaindn, samdb, lp)
644 message(ERROR, "Unable to set ACLs on policies related objects, if not using posix:eadb, you must be root to do it")
648 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
649 names.domainsid, names.dnsdomain, names.domaindn, lp)
651 message(ERROR, "Unable to set ACLs on sysvol share, if not using posix:eadb, you must be root to do it")
653 def delta_update_basesamdb(refsam, sam, creds, session, lp, message):
654 """Update the provision container db: sam.ldb
655 This function is aimed for alpha9 and newer;
657 :param refsam: Path to the samdb in the reference provision
658 :param sam: Path to the samdb in the upgraded provision
659 :param creds: Credential used for openning LDB files
660 :param session: Session to use for openning LDB files
661 :param lp: A loadparam object"""
664 "Update base samdb by searching difference with reference one")
665 refsam = Ldb(refsam, session_info=session, credentials=creds,
666 lp=lp, options=["modules:"])
667 sam = Ldb(sam, session_info=session, credentials=creds, lp=lp,
668 options=["modules:"])
670 empty = ldb.Message()
672 reference = refsam.search(expression="")
674 for refentry in reference:
675 entry = sam.search(expression="dn=%s" % refentry["dn"],
678 delta = sam.msg_diff(empty, refentry)
679 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
680 if str(refentry.dn) == "@PROVISION" and\
681 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
682 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
683 delta.dn = refentry.dn
686 delta = sam.msg_diff(entry[0], refentry)
687 if str(refentry.dn) == "@PROVISION" and\
688 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
689 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
690 if len(delta.items()) > 1:
691 delta.dn = refentry.dn
695 def construct_existor_expr(attrs):
696 """Construct a exists or LDAP search expression.
699 :param attrs: List of attribute on which we want to create the search
701 :return: A string representing the expression, if attrs is empty an
702 empty string is returned"""
707 expr = "%s(%s=*)"%(expr,att)
711 def search_constructed_attrs_stored(samdb, rootdn, attrs):
712 """Search a given sam DB for calculated attributes that are
713 still stored in the db.
715 :param samdb: An LDB object pointing to the sam
716 :param rootdn: The base DN where the search should start
717 :param attrs: A list of attributes to be searched
718 :return: A hash with attributes as key and an array of
719 array. Each array contains the dn and the associated
720 values for this attribute as they are stored in the
724 expr = construct_existor_expr(attrs)
727 entry = samdb.search(expression=expr, base=ldb.Dn(samdb,str(rootdn)),
728 scope=SCOPE_SUBTREE, attrs=attrs,
729 controls=["search_options:1:2","bypassoperational:0"])
737 if hashAtt.has_key(att):
738 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
741 hashAtt[att][str(ent.dn).lower()] = str(ent[att])