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()
85 # self.hkcr.transaction_start()
86 # self.hkcu.transaction_start()
87 # self.hku.transaction_start()
88 # self.hklm.transaction_start()
90 def groupedRollback(self):
93 self.sam.transaction_cancel()
98 self.secrets.transaction_cancel()
103 self.idmap.transaction_cancel()
108 self.privilege.transaction_cancel()
114 # self.hkcr.transaction_cancel()
115 # self.hkcu.transaction_cancel()
116 # self.hku.transaction_cancel()
117 # self.hklm.transaction_cancel()
119 def groupedCommit(self):
121 self.sam.transaction_prepare_commit()
122 self.secrets.transaction_prepare_commit()
123 self.idmap.transaction_prepare_commit()
124 self.privilege.transaction_prepare_commit()
126 return self.groupedRollback()
128 # self.hkcr.transaction_prepare_commit()
129 # self.hkcu.transaction_prepare_commit()
130 # self.hku.transaction_prepare_commit()
131 # self.hklm.transaction_prepare_commit()
133 self.sam.transaction_commit()
134 self.secrets.transaction_commit()
135 self.idmap.transaction_commit()
136 self.privilege.transaction_commit()
138 return self.groupedRollback()
141 # self.hkcr.transaction_commit()
142 # self.hkcu.transaction_commit()
143 # self.hku.transaction_commit()
144 # self.hklm.transaction_commit()
147 def get_ldbs(paths, creds, session, lp):
148 """Return LDB object mapped on most important databases
150 :param paths: An object holding the different importants paths for provision object
151 :param creds: Credential used for openning LDB files
152 :param session: Session to use for openning LDB files
153 :param lp: A loadparam object
154 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
156 ldbs = ProvisionLDB()
158 ldbs.sam = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
159 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
160 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
161 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
162 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
163 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
164 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
165 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
169 def usn_in_range(usn, range):
170 """Check if the usn is in one of the range provided.
171 To do so, the value is checked to be between the lower bound and
172 higher bound of a range
174 :param usn: A integer value corresponding to the usn that we want to update
175 :param range: A list of integer representing ranges, lower bounds are in
176 the even indices, higher in odd indices
177 :return: 1 if the usn is in one of the range, 0 otherwise"""
183 if idx == len(range):
186 if usn < int(range[idx]):
190 if usn == int(range[idx]):
196 def get_paths(param, targetdir=None, smbconf=None):
197 """Get paths to important provision objects (smb.conf, ldb files, ...)
199 :param param: Param object
200 :param targetdir: Directory where the provision is (or will be) stored
201 :param smbconf: Path to the smb.conf file
202 :return: A list with the path of important provision objects"""
203 if targetdir is not None:
204 etcdir = os.path.join(targetdir, "etc")
205 if not os.path.exists(etcdir):
207 smbconf = os.path.join(etcdir, "smb.conf")
209 smbconf = param.default_path()
211 if not os.path.exists(smbconf):
212 raise ProvisioningError("Unable to find smb.conf")
214 lp = param.LoadParm()
216 paths = provision_paths_from_lp(lp, lp.get("realm"))
219 def update_policyids(names, samdb):
220 """Update policy ids that could have changed after sam update
222 :param names: List of key provision parameters
223 :param samdb: An Ldb object conntected with the sam DB
226 res = samdb.search(expression="(displayName=Default Domain Policy)",
227 base="CN=Policies,CN=System," + str(names.rootdn),
228 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
229 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
231 res2 = samdb.search(expression="(displayName=Default Domain Controllers" \
233 base="CN=Policies,CN=System," + str(names.rootdn),
234 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
236 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
238 names.policyid_dc = None
240 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
241 """Get key provision parameters (realm, domain, ...) from a given provision
243 :param samdb: An LDB object connected to the sam.ldb file
244 :param secretsdb: An LDB object connected to the secrets.ldb file
245 :param idmapdb: An LDB object connected to the idmap.ldb file
246 :param paths: A list of path to provision object
247 :param smbconf: Path to the smb.conf file
248 :param lp: A LoadParm object
249 :return: A list of key provision parameters"""
251 names = ProvisionNames()
252 names.adminpass = None
254 # NT domain, kerberos realm, root dn, domain dn, domain dns name
255 names.domain = string.upper(lp.get("workgroup"))
256 names.realm = lp.get("realm")
257 basedn = "DC=" + names.realm.replace(".",",DC=")
258 names.dnsdomain = names.realm.lower()
259 names.realm = string.upper(names.realm)
261 # Get the netbiosname first (could be obtained from smb.conf in theory)
262 res = secretsdb.search(expression="(flatname=%s)" % \
263 names.domain,base="CN=Primary Domains",
264 scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
265 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
267 names.smbconf = smbconf
269 # That's a bit simplistic but it's ok as long as we have only 3
271 current = samdb.search(expression="(objectClass=*)",
272 base="", scope=SCOPE_BASE,
273 attrs=["defaultNamingContext", "schemaNamingContext",
274 "configurationNamingContext","rootDomainNamingContext"])
276 names.configdn = current[0]["configurationNamingContext"]
277 configdn = str(names.configdn)
278 names.schemadn = current[0]["schemaNamingContext"]
279 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
280 current[0]["defaultNamingContext"][0]))):
281 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
282 "is not the same ..." % (paths.samdb,
283 str(current[0]["defaultNamingContext"][0]),
284 paths.smbconf, basedn)))
286 names.domaindn=current[0]["defaultNamingContext"]
287 names.rootdn=current[0]["rootDomainNamingContext"]
289 res3 = samdb.search(expression="(objectClass=*)",
290 base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
291 names.sitename = str(res3[0]["cn"])
293 # dns hostname and server dn
294 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
295 base="OU=Domain Controllers,%s" % basedn,
296 scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
297 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
299 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
300 attrs=[], base=configdn)
301 names.serverdn = server_res[0].dn
303 # invocation id/objectguid
304 res5 = samdb.search(expression="(objectClass=*)",
305 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
306 attrs=["invocationID", "objectGUID"])
307 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
308 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
311 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
312 scope=SCOPE_BASE, attrs=["objectGUID",
313 "objectSid","msDS-Behavior-Version" ])
314 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
315 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
316 if res6[0].get("msDS-Behavior-Version") == None or \
317 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
318 names.domainlevel = DS_DOMAIN_FUNCTION_2000
320 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
323 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
324 base="CN=Policies,CN=System," + basedn,
325 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
326 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
328 res8 = samdb.search(expression="(displayName=Default Domain Controllers" \
330 base="CN=Policies,CN=System," + basedn,
331 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
333 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
335 names.policyid_dc = None
336 res9 = idmapdb.search(expression="(cn=%s)" % \
337 (security.SID_BUILTIN_ADMINISTRATORS),
340 names.wheel_gid = res9[0]["xidNumber"]
342 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
346 def newprovision(names, setup_dir, creds, session, smbconf, provdir, logger):
347 """Create a new provision.
349 This provision will be the reference for knowing what has changed in the
350 since the latest upgrade in the current provision
352 :param names: List of provision parameters
353 :param setup_dis: Directory where the setup files are stored
354 :param creds: Credentials for the authentification
355 :param session: Session object
356 :param smbconf: Path to the smb.conf file
357 :param provdir: Directory where the provision will be stored
358 :param logger: A `Logger`
360 if os.path.isdir(provdir):
361 shutil.rmtree(provdir)
362 os.chdir(os.path.join(setup_dir,".."))
364 logger.info("Provision stored in %s", provdir)
365 provision(setup_dir, logger, session, creds, smbconf=smbconf,
366 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
367 domain=names.domain, domainguid=names.domainguid,
368 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
369 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
370 hostname=names.netbiosname, hostip=None, hostip6=None,
371 invocationid=names.invocation, adminpass=names.adminpass,
372 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
373 nobody=None, wheel=None, users=None,
374 serverrole="domain controller", ldap_backend_extra_port=None,
375 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
376 slapd_path=None, setup_ds_path=None, nosync=None,
377 dom_for_fun_level=names.domainlevel,
378 ldap_dryrun_mode=None, useeadb=True)
382 """Sorts two DNs in the lexicographical order it and put higher level DN
385 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
388 :param x: First object to compare
389 :param y: Second object to compare
391 p = re.compile(r'(?<!\\), ?')
392 tab1 = p.split(str(x))
393 tab2 = p.split(str(y))
394 minimum = min(len(tab1), len(tab2))
397 # Note: python range go up to upper limit but do not include it
398 for i in range(0, minimum):
399 ret = cmp(tab1[len1-i], tab2[len2-i])
404 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
411 def identic_rename(ldbobj, dn):
412 """Perform a back and forth rename to trigger renaming on attribute that
413 can't be directly modified.
415 :param lbdobj: An Ldb Object
416 :param dn: DN of the object to manipulate """
417 (before, sep, after)=str(dn).partition('=')
418 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)))
419 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn)
422 """Return separate ACE of an ACL
424 :param acl: A string representing the ACL
425 :return: A hash with different parts
428 p = re.compile(r'(\w+)?(\(.*?\))')
436 hash["aces"].append(e[1])
441 def chunck_sddl(sddl):
442 """ Return separate parts of the SDDL (owner, group, ...)
444 :param sddl: An string containing the SDDL to chunk
445 :return: A hash with the different chunk
448 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
449 tab = p.findall(sddl)
464 def get_diff_sddls(refsddl, cursddl):
465 """Get the difference between 2 sddl
466 This function split the textual representation of ACL into smaller
467 chunck in order to not to report a simple permutation as a difference
469 :param refsddl: First sddl to compare
470 :param cursddl: Second sddl to compare
471 :return: A string that explain difference between sddls"""
474 hash_new = chunck_sddl(cursddl)
475 hash_ref = chunck_sddl(refsddl)
477 if hash_new["owner"] != hash_ref["owner"]:
478 txt = "\tOwner mismatch: %s (in ref) %s" \
479 "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
481 if hash_new["group"] != hash_ref["group"]:
482 txt = "%s\tGroup mismatch: %s (in ref) %s" \
483 "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
485 for part in ["dacl", "sacl"]:
486 if hash_new.has_key(part) and hash_ref.has_key(part):
488 # both are present, check if they contain the same ACE
491 c_new = chunck_acl(hash_new[part])
492 c_ref = chunck_acl(hash_ref[part])
494 for elem in c_new["aces"]:
497 for elem in c_ref["aces"]:
500 for k in h_ref.keys():
505 if len(h_new.keys()) + len(h_ref.keys()) > 0:
506 txt = "%s\tPart %s is different between reference" \
507 " and current here is the detail:\n" % (txt, part)
509 for item in h_new.keys():
510 txt = "%s\t\t%s ACE is not present in the" \
511 " reference\n" % (txt, item)
513 for item in h_ref.keys():
514 txt = "%s\t\t%s ACE is not present in the" \
515 " current\n" % (txt, item)
517 elif hash_new.has_key(part) and not hash_ref.has_key(part):
518 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
519 elif not hash_new.has_key(part) and hash_ref.has_key(part):
520 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
525 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
526 """Update secrets.ldb
528 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
529 of the reference provision
530 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
531 of the updated provision
534 messagefunc(SIMPLE, "update secrets.ldb")
535 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
537 current = secrets_ldb.search(expression="dn=@MODULES", base="",
539 assert reference, "Reference modules list can not be empty"
540 if len(current) == 0:
542 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
543 delta.dn = reference[0].dn
544 secrets_ldb.add(reference[0])
546 delta = secrets_ldb.msg_diff(current[0], reference[0])
547 delta.dn = current[0].dn
548 secrets_ldb.modify(delta)
550 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
551 scope=SCOPE_SUBTREE, attrs=["dn"])
552 current = secrets_ldb.search(expression="objectClass=top", base="",
553 scope=SCOPE_SUBTREE, attrs=["dn"])
559 empty = ldb.Message()
560 for i in range(0, len(reference)):
561 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
563 # Create a hash for speeding the search of existing object in the
565 for i in range(0, len(current)):
566 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
568 for k in hash_new.keys():
569 if not hash.has_key(k):
570 listMissing.append(hash_new[k])
572 listPresent.append(hash_new[k])
574 for entry in listMissing:
575 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
576 base="", scope=SCOPE_SUBTREE)
577 current = secrets_ldb.search(expression="dn=%s" % entry,
578 base="", scope=SCOPE_SUBTREE)
579 delta = secrets_ldb.msg_diff(empty, reference[0])
580 for att in hashAttrNotCopied.keys():
582 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" % \
585 messagefunc(CHANGE, " Adding attribute %s" % att)
586 delta.dn = reference[0].dn
587 secrets_ldb.add(delta)
589 for entry in listPresent:
590 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
591 base="", scope=SCOPE_SUBTREE)
592 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
594 delta = secrets_ldb.msg_diff(current[0], reference[0])
595 for att in hashAttrNotCopied.keys():
599 messagefunc(CHANGE, "Found attribute name on %s," \
600 " must rename the DN" % (current[0].dn))
601 identic_rename(secrets_ldb, reference[0].dn)
605 for entry in listPresent:
606 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
608 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
610 delta = secrets_ldb.msg_diff(current[0], reference[0])
611 for att in hashAttrNotCopied.keys():
614 if att == "msDS-KeyVersionNumber":
618 "Adding/Changing attribute %s to %s" % \
619 (att, current[0].dn))
621 delta.dn = current[0].dn
622 secrets_ldb.modify(delta)
624 def getOEMInfo(samdb, rootdn):
625 """Return OEM Information on the top level
626 Samba4 use to store version info in this field
628 :param samdb: An LDB object connect to sam.ldb
629 :param rootdn: Root DN of the domain
630 :return: The content of the field oEMInformation (if any)"""
631 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
632 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
634 info = res[0]["oEMInformation"]
639 def updateOEMInfo(samdb, rootdn):
640 """Update the OEMinfo field to add information about upgrade
641 :param samdb: an LDB object connected to the sam DB
642 :param rootdn: The string representation of the root DN of
643 the provision (ie. DC=...,DC=...)
645 res = samdb.search(expression="(objectClass=*)", base=rootdn,
646 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
648 info = res[0]["oEMInformation"]
649 info = "%s, upgrade to %s" % (info, version)
650 delta = ldb.Message()
651 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
652 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
656 def update_gpo(paths, samdb, names, lp, message, force=0):
657 """Create missing GPO file object if needed
659 Set ACL correctly also.
660 Check ACLs for sysvol/netlogon dirs also
664 ntacls.checkset_backend(lp, None, None)
665 eadbname = lp.get("posix:eadb")
666 if eadbname is not None and eadbname != "":
668 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
669 paths.sysvol, xattr.XATTR_NTACL_NAME)
671 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
672 xattr.XATTR_NTACL_NAME)
674 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
675 xattr.XATTR_NTACL_NAME)
682 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
683 if not os.path.isdir(dir):
684 create_gpo_struct(dir)
686 if names.policyid_dc == None:
687 raise ProvisioningError("Policy ID for Domain controller is missing")
688 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
689 if not os.path.isdir(dir):
690 create_gpo_struct(dir)
691 # We always reinforce acls on GPO folder because they have to be in sync
694 set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
695 names.domaindn, samdb, lp)
697 message(ERROR, "Unable to set ACLs on policies related objects, if not using posix:eadb, you must be root to do it")
701 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
702 names.domainsid, names.dnsdomain, names.domaindn, lp)
704 message(ERROR, "Unable to set ACLs on sysvol share, if not using posix:eadb, you must be root to do it")
706 def delta_update_basesamdb(refsam, sam, creds, session, lp, message):
707 """Update the provision container db: sam.ldb
708 This function is aimed for alpha9 and newer;
710 :param refsam: Path to the samdb in the reference provision
711 :param sam: Path to the samdb in the upgraded provision
712 :param creds: Credential used for openning LDB files
713 :param session: Session to use for openning LDB files
714 :param lp: A loadparam object"""
717 "Update base samdb by searching difference with reference one")
718 refsam = Ldb(refsam, session_info=session, credentials=creds,
719 lp=lp, options=["modules:"])
720 sam = Ldb(sam, session_info=session, credentials=creds, lp=lp,
721 options=["modules:"])
723 empty = ldb.Message()
725 reference = refsam.search(expression="")
727 for refentry in reference:
728 entry = sam.search(expression="dn=%s" % refentry["dn"],
731 delta = sam.msg_diff(empty, refentry)
732 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
733 if str(refentry.dn) == "@PROVISION" and\
734 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
735 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
736 delta.dn = refentry.dn
739 delta = sam.msg_diff(entry[0], refentry)
740 if str(refentry.dn) == "@PROVISION" and\
741 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
742 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
743 if len(delta.items()) > 1:
744 delta.dn = refentry.dn
748 def construct_existor_expr(attrs):
749 """Construct a exists or LDAP search expression.
752 :param attrs: List of attribute on which we want to create the search
754 :return: A string representing the expression, if attrs is empty an
755 empty string is returned"""
760 expr = "%s(%s=*)"%(expr,att)
764 def search_constructed_attrs_stored(samdb, rootdn, attrs):
765 """Search a given sam DB for calculated attributes that are
766 still stored in the db.
768 :param samdb: An LDB object pointing to the sam
769 :param rootdn: The base DN where the search should start
770 :param attrs: A list of attributes to be searched
771 :return: A hash with attributes as key and an array of
772 array. Each array contains the dn and the associated
773 values for this attribute as they are stored in the
777 expr = construct_existor_expr(attrs)
780 entry = samdb.search(expression=expr, base=ldb.Dn(samdb,str(rootdn)),
781 scope=SCOPE_SUBTREE, attrs=attrs,
782 controls=["search_options:1:2","bypassoperational:0"])
790 if hashAtt.has_key(att):
791 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
794 hashAtt[att][str(ent.dn).lower()] = str(ent[att])