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
41 from samba.samdb import SamDB
43 # All the ldb related to registry are commented because the path for them is relative
44 # in the provisionPath object
45 # And so opening them create a file in the current directory which is not what we want
46 # I still keep them commented because I plan soon to make more cleaner
55 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1,
56 "objectGUID": 1, "uSNCreated": 1,
57 "replPropertyMetaData": 1, "uSNChanged": 1,
58 "parentGUID": 1, "objectCategory": 1,
59 "distinguishedName": 1, "nTMixedDomain": 1,
60 "showInAdvancedViewOnly": 1, "instanceType": 1,
61 "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
62 "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1,
63 "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
64 "supplementalCredentials":1, "gPCUserExtensionNames":1,
65 "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
66 "possibleInferiors":1, "privilege":1,
69 class ProvisionLDB(object):
81 def startTransactions(self):
82 self.sam.transaction_start()
83 self.secrets.transaction_start()
84 self.idmap.transaction_start()
85 self.privilege.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):
95 self.sam.transaction_cancel()
100 self.secrets.transaction_cancel()
105 self.idmap.transaction_cancel()
110 self.privilege.transaction_cancel()
116 # self.hkcr.transaction_cancel()
117 # self.hkcu.transaction_cancel()
118 # self.hku.transaction_cancel()
119 # self.hklm.transaction_cancel()
121 def groupedCommit(self):
123 self.sam.transaction_prepare_commit()
124 self.secrets.transaction_prepare_commit()
125 self.idmap.transaction_prepare_commit()
126 self.privilege.transaction_prepare_commit()
128 return self.groupedRollback()
130 # self.hkcr.transaction_prepare_commit()
131 # self.hkcu.transaction_prepare_commit()
132 # self.hku.transaction_prepare_commit()
133 # self.hklm.transaction_prepare_commit()
135 self.sam.transaction_commit()
136 self.secrets.transaction_commit()
137 self.idmap.transaction_commit()
138 self.privilege.transaction_commit()
140 return self.groupedRollback()
143 # self.hkcr.transaction_commit()
144 # self.hkcu.transaction_commit()
145 # self.hku.transaction_commit()
146 # self.hklm.transaction_commit()
149 def get_ldbs(paths, creds, session, lp):
150 """Return LDB object mapped on most important databases
152 :param paths: An object holding the different importants paths for provision object
153 :param creds: Credential used for openning LDB files
154 :param session: Session to use for openning LDB files
155 :param lp: A loadparam object
156 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
158 ldbs = ProvisionLDB()
160 ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
161 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
162 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
163 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
164 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
165 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
166 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
167 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
172 def usn_in_range(usn, range):
173 """Check if the usn is in one of the range provided.
174 To do so, the value is checked to be between the lower bound and
175 higher bound of a range
177 :param usn: A integer value corresponding to the usn that we want to update
178 :param range: A list of integer representing ranges, lower bounds are in
179 the even indices, higher in odd indices
180 :return: True if the usn is in one of the range, False otherwise
187 if idx == len(range):
190 if usn < int(range[idx]):
194 if usn == int(range[idx]):
201 def get_paths(param, targetdir=None, smbconf=None):
202 """Get paths to important provision objects (smb.conf, ldb files, ...)
204 :param param: Param object
205 :param targetdir: Directory where the provision is (or will be) stored
206 :param smbconf: Path to the smb.conf file
207 :return: A list with the path of important provision objects"""
208 if targetdir is not None:
209 etcdir = os.path.join(targetdir, "etc")
210 if not os.path.exists(etcdir):
212 smbconf = os.path.join(etcdir, "smb.conf")
214 smbconf = param.default_path()
216 if not os.path.exists(smbconf):
217 raise ProvisioningError("Unable to find smb.conf")
219 lp = param.LoadParm()
221 paths = provision_paths_from_lp(lp, lp.get("realm"))
224 def update_policyids(names, samdb):
225 """Update policy ids that could have changed after sam update
227 :param names: List of key provision parameters
228 :param samdb: An Ldb object conntected with the sam DB
231 res = samdb.search(expression="(displayName=Default Domain Policy)",
232 base="CN=Policies,CN=System," + str(names.rootdn),
233 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
234 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
236 res2 = samdb.search(expression="(displayName=Default Domain Controllers" \
238 base="CN=Policies,CN=System," + str(names.rootdn),
239 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
241 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
243 names.policyid_dc = None
246 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
247 """Get key provision parameters (realm, domain, ...) from a given provision
249 :param samdb: An LDB object connected to the sam.ldb file
250 :param secretsdb: An LDB object connected to the secrets.ldb file
251 :param idmapdb: An LDB object connected to the idmap.ldb file
252 :param paths: A list of path to provision object
253 :param smbconf: Path to the smb.conf file
254 :param lp: A LoadParm object
255 :return: A list of key provision parameters
257 names = ProvisionNames()
258 names.adminpass = None
260 # NT domain, kerberos realm, root dn, domain dn, domain dns name
261 names.domain = string.upper(lp.get("workgroup"))
262 names.realm = lp.get("realm")
263 basedn = "DC=" + names.realm.replace(".",",DC=")
264 names.dnsdomain = names.realm.lower()
265 names.realm = string.upper(names.realm)
267 # Get the netbiosname first (could be obtained from smb.conf in theory)
268 res = secretsdb.search(expression="(flatname=%s)" % \
269 names.domain,base="CN=Primary Domains",
270 scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
271 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
273 names.smbconf = smbconf
275 # That's a bit simplistic but it's ok as long as we have only 3
277 current = samdb.search(expression="(objectClass=*)",
278 base="", scope=SCOPE_BASE,
279 attrs=["defaultNamingContext", "schemaNamingContext",
280 "configurationNamingContext","rootDomainNamingContext"])
282 names.configdn = current[0]["configurationNamingContext"]
283 configdn = str(names.configdn)
284 names.schemadn = current[0]["schemaNamingContext"]
285 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
286 current[0]["defaultNamingContext"][0]))):
287 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
288 "is not the same ..." % (paths.samdb,
289 str(current[0]["defaultNamingContext"][0]),
290 paths.smbconf, basedn)))
292 names.domaindn=current[0]["defaultNamingContext"]
293 names.rootdn=current[0]["rootDomainNamingContext"]
295 res3 = samdb.search(expression="(objectClass=*)",
296 base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
297 names.sitename = str(res3[0]["cn"])
299 # dns hostname and server dn
300 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
301 base="OU=Domain Controllers,%s" % basedn,
302 scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
303 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
305 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
306 attrs=[], base=configdn)
307 names.serverdn = server_res[0].dn
309 # invocation id/objectguid
310 res5 = samdb.search(expression="(objectClass=*)",
311 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
312 attrs=["invocationID", "objectGUID"])
313 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
314 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
317 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
318 scope=SCOPE_BASE, attrs=["objectGUID",
319 "objectSid","msDS-Behavior-Version" ])
320 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
321 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
322 if res6[0].get("msDS-Behavior-Version") is None or \
323 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
324 names.domainlevel = DS_DOMAIN_FUNCTION_2000
326 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
329 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
330 base="CN=Policies,CN=System," + basedn,
331 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
332 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
334 res8 = samdb.search(expression="(displayName=Default Domain Controllers" \
336 base="CN=Policies,CN=System," + basedn,
337 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
339 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
341 names.policyid_dc = None
342 res9 = idmapdb.search(expression="(cn=%s)" % \
343 (security.SID_BUILTIN_ADMINISTRATORS),
346 names.wheel_gid = res9[0]["xidNumber"]
348 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
352 def newprovision(names, setup_dir, creds, session, smbconf, provdir, logger):
353 """Create a new provision.
355 This provision will be the reference for knowing what has changed in the
356 since the latest upgrade in the current provision
358 :param names: List of provision parameters
359 :param setup_dis: Directory where the setup files are stored
360 :param creds: Credentials for the authentification
361 :param session: Session object
362 :param smbconf: Path to the smb.conf file
363 :param provdir: Directory where the provision will be stored
364 :param logger: A `Logger`
366 if os.path.isdir(provdir):
367 shutil.rmtree(provdir)
368 os.chdir(os.path.join(setup_dir,".."))
370 logger.info("Provision stored in %s", provdir)
371 provision(setup_dir, logger, session, creds, smbconf=smbconf,
372 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
373 domain=names.domain, domainguid=names.domainguid,
374 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
375 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
376 hostname=names.netbiosname, hostip=None, hostip6=None,
377 invocationid=names.invocation, adminpass=names.adminpass,
378 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
379 nobody=None, wheel=None, users=None,
380 serverrole="domain controller", ldap_backend_extra_port=None,
381 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
382 slapd_path=None, setup_ds_path=None, nosync=None,
383 dom_for_fun_level=names.domainlevel,
384 ldap_dryrun_mode=None, useeadb=True)
388 """Sorts two DNs in the lexicographical order it and put higher level DN
391 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
394 :param x: First object to compare
395 :param y: Second object to compare
397 p = re.compile(r'(?<!\\), ?')
398 tab1 = p.split(str(x))
399 tab2 = p.split(str(y))
400 minimum = min(len(tab1), len(tab2))
403 # Note: python range go up to upper limit but do not include it
404 for i in range(0, minimum):
405 ret = cmp(tab1[len1-i], tab2[len2-i])
410 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
418 def identic_rename(ldbobj, dn):
419 """Perform a back and forth rename to trigger renaming on attribute that
420 can't be directly modified.
422 :param lbdobj: An Ldb Object
423 :param dn: DN of the object to manipulate
425 (before, sep, after)=str(dn).partition('=')
426 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)))
427 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn)
431 """Return separate ACE of an ACL
433 :param acl: A string representing the ACL
434 :return: A hash with different parts
437 p = re.compile(r'(\w+)?(\(.*?\))')
445 hash["aces"].append(e[1])
450 def chunck_sddl(sddl):
451 """ Return separate parts of the SDDL (owner, group, ...)
453 :param sddl: An string containing the SDDL to chunk
454 :return: A hash with the different chunk
457 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
458 tab = p.findall(sddl)
473 def get_diff_sddls(refsddl, cursddl):
474 """Get the difference between 2 sddl
475 This function split the textual representation of ACL into smaller
476 chunck in order to not to report a simple permutation as a difference
478 :param refsddl: First sddl to compare
479 :param cursddl: Second sddl to compare
480 :return: A string that explain difference between sddls"""
483 hash_new = chunck_sddl(cursddl)
484 hash_ref = chunck_sddl(refsddl)
486 if hash_new["owner"] != hash_ref["owner"]:
487 txt = "\tOwner mismatch: %s (in ref) %s" \
488 "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
490 if hash_new["group"] != hash_ref["group"]:
491 txt = "%s\tGroup mismatch: %s (in ref) %s" \
492 "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
494 for part in ["dacl", "sacl"]:
495 if hash_new.has_key(part) and hash_ref.has_key(part):
497 # both are present, check if they contain the same ACE
500 c_new = chunck_acl(hash_new[part])
501 c_ref = chunck_acl(hash_ref[part])
503 for elem in c_new["aces"]:
506 for elem in c_ref["aces"]:
514 if len(h_new) + len(h_ref) > 0:
515 txt = "%s\tPart %s is different between reference" \
516 " and current here is the detail:\n" % (txt, part)
519 txt = "%s\t\t%s ACE is not present in the" \
520 " reference\n" % (txt, item)
523 txt = "%s\t\t%s ACE is not present in the" \
524 " current\n" % (txt, item)
526 elif hash_new.has_key(part) and not hash_ref.has_key(part):
527 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
528 elif not hash_new.has_key(part) and hash_ref.has_key(part):
529 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
534 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
535 """Update secrets.ldb
537 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
538 of the reference provision
539 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
540 of the updated provision
543 messagefunc(SIMPLE, "update secrets.ldb")
544 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
546 current = secrets_ldb.search(expression="dn=@MODULES", base="",
548 assert reference, "Reference modules list can not be empty"
549 if len(current) == 0:
551 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
552 delta.dn = reference[0].dn
553 secrets_ldb.add(reference[0])
555 delta = secrets_ldb.msg_diff(current[0], reference[0])
556 delta.dn = current[0].dn
557 secrets_ldb.modify(delta)
559 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
560 scope=SCOPE_SUBTREE, attrs=["dn"])
561 current = secrets_ldb.search(expression="objectClass=top", base="",
562 scope=SCOPE_SUBTREE, attrs=["dn"])
568 empty = ldb.Message()
569 for i in range(0, len(reference)):
570 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
572 # Create a hash for speeding the search of existing object in the
574 for i in range(0, len(current)):
575 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
577 for k in hash_new.keys():
578 if not hash.has_key(k):
579 listMissing.append(hash_new[k])
581 listPresent.append(hash_new[k])
583 for entry in listMissing:
584 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
585 base="", scope=SCOPE_SUBTREE)
586 current = secrets_ldb.search(expression="dn=%s" % entry,
587 base="", scope=SCOPE_SUBTREE)
588 delta = secrets_ldb.msg_diff(empty, reference[0])
589 for att in hashAttrNotCopied.keys():
591 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" % \
594 messagefunc(CHANGE, " Adding attribute %s" % att)
595 delta.dn = reference[0].dn
596 secrets_ldb.add(delta)
598 for entry in listPresent:
599 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
600 base="", scope=SCOPE_SUBTREE)
601 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
603 delta = secrets_ldb.msg_diff(current[0], reference[0])
604 for att in hashAttrNotCopied.keys():
608 messagefunc(CHANGE, "Found attribute name on %s," \
609 " must rename the DN" % (current[0].dn))
610 identic_rename(secrets_ldb, reference[0].dn)
614 for entry in listPresent:
615 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
617 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
619 delta = secrets_ldb.msg_diff(current[0], reference[0])
620 for att in hashAttrNotCopied.keys():
623 if att == "msDS-KeyVersionNumber":
627 "Adding/Changing attribute %s to %s" % \
628 (att, current[0].dn))
630 delta.dn = current[0].dn
631 secrets_ldb.modify(delta)
633 def getOEMInfo(samdb, rootdn):
634 """Return OEM Information on the top level
635 Samba4 use to store version info in this field
637 :param samdb: An LDB object connect to sam.ldb
638 :param rootdn: Root DN of the domain
639 :return: The content of the field oEMInformation (if any)"""
640 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
641 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
643 info = res[0]["oEMInformation"]
648 def updateOEMInfo(samdb, rootdn):
649 """Update the OEMinfo field to add information about upgrade
650 :param samdb: an LDB object connected to the sam DB
651 :param rootdn: The string representation of the root DN of
652 the provision (ie. DC=...,DC=...)
654 res = samdb.search(expression="(objectClass=*)", base=rootdn,
655 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
657 info = res[0]["oEMInformation"]
658 info = "%s, upgrade to %s" % (info, version)
659 delta = ldb.Message()
660 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
661 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
665 def update_gpo(paths, samdb, names, lp, message, force=0):
666 """Create missing GPO file object if needed
668 Set ACL correctly also.
669 Check ACLs for sysvol/netlogon dirs also
673 ntacls.checkset_backend(lp, None, None)
674 eadbname = lp.get("posix:eadb")
675 if eadbname is not None and eadbname != "":
677 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
678 paths.sysvol, xattr.XATTR_NTACL_NAME)
680 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
681 xattr.XATTR_NTACL_NAME)
683 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
684 xattr.XATTR_NTACL_NAME)
691 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
692 if not os.path.isdir(dir):
693 create_gpo_struct(dir)
695 if names.policyid_dc is None:
696 raise ProvisioningError("Policy ID for Domain controller is missing")
697 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
698 if not os.path.isdir(dir):
699 create_gpo_struct(dir)
700 # We always reinforce acls on GPO folder because they have to be in sync
703 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
704 names.domaindn, samdb, lp)
706 message(ERROR, "Unable to set ACLs on policies related objects, if not using posix:eadb, you must be root to do it")
710 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
711 names.domainsid, names.dnsdomain, names.domaindn, lp)
713 message(ERROR, "Unable to set ACLs on sysvol share, if not using posix:eadb, you must be root to do it")
715 def delta_update_basesamdb(refsam, sam, creds, session, lp, message):
716 """Update the provision container db: sam.ldb
717 This function is aimed for alpha9 and newer;
719 :param refsam: Path to the samdb in the reference provision
720 :param sam: Path to the samdb in the upgraded provision
721 :param creds: Credential used for openning LDB files
722 :param session: Session to use for openning LDB files
723 :param lp: A loadparam object"""
726 "Update base samdb by searching difference with reference one")
727 refsam = Ldb(refsam, session_info=session, credentials=creds,
728 lp=lp, options=["modules:"])
729 sam = Ldb(sam, session_info=session, credentials=creds, lp=lp,
730 options=["modules:"])
732 empty = ldb.Message()
734 reference = refsam.search(expression="")
736 for refentry in reference:
737 entry = sam.search(expression="dn=%s" % refentry["dn"],
740 delta = sam.msg_diff(empty, refentry)
741 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
742 if str(refentry.dn) == "@PROVISION" and\
743 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
744 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
745 delta.dn = refentry.dn
748 delta = sam.msg_diff(entry[0], refentry)
749 if str(refentry.dn) == "@PROVISION" and\
750 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
751 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
752 if len(delta.items()) > 1:
753 delta.dn = refentry.dn
757 def construct_existor_expr(attrs):
758 """Construct a exists or LDAP search expression.
761 :param attrs: List of attribute on which we want to create the search
763 :return: A string representing the expression, if attrs is empty an
764 empty string is returned"""
769 expr = "%s(%s=*)"%(expr,att)
773 def search_constructed_attrs_stored(samdb, rootdn, attrs):
774 """Search a given sam DB for calculated attributes that are
775 still stored in the db.
777 :param samdb: An LDB object pointing to the sam
778 :param rootdn: The base DN where the search should start
779 :param attrs: A list of attributes to be searched
780 :return: A hash with attributes as key and an array of
781 array. Each array contains the dn and the associated
782 values for this attribute as they are stored in the
786 expr = construct_existor_expr(attrs)
789 entry = samdb.search(expression=expr, base=ldb.Dn(samdb,str(rootdn)),
790 scope=SCOPE_SUBTREE, attrs=attrs,
791 controls=["search_options:1:2","bypassoperational:0"])
799 if hashAtt.has_key(att):
800 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
803 hashAtt[att][str(ent.dn).lower()] = str(ent[att])