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_gpos_acl, create_gpo_struct,
37 FILL_FULL, provision, ProvisioningError,
38 setsysvolacl, secretsdb_self_join)
39 from samba.dcerpc import misc, security, xattr
40 from samba.dcerpc.misc import SEC_CHAN_BDC
41 from samba.ndr import ndr_unpack
42 from samba.samdb import SamDB
44 # All the ldb related to registry are commented because the path for them is relative
45 # in the provisionPath object
46 # And so opening them create a file in the current directory which is not what we want
47 # I still keep them commented because I plan soon to make more cleaner
56 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1,
57 "objectGUID": 1, "uSNCreated": 1,
58 "replPropertyMetaData": 1, "uSNChanged": 1,
59 "parentGUID": 1, "objectCategory": 1,
60 "distinguishedName": 1, "nTMixedDomain": 1,
61 "showInAdvancedViewOnly": 1, "instanceType": 1,
62 "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
63 "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1,
64 "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
65 "supplementalCredentials":1, "gPCUserExtensionNames":1,
66 "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
67 "possibleInferiors":1, "privilege":1,
70 class ProvisionLDB(object):
82 def startTransactions(self):
83 self.sam.transaction_start()
84 self.secrets.transaction_start()
85 self.idmap.transaction_start()
86 self.privilege.transaction_start()
88 # self.hkcr.transaction_start()
89 # self.hkcu.transaction_start()
90 # self.hku.transaction_start()
91 # self.hklm.transaction_start()
93 def groupedRollback(self):
96 self.sam.transaction_cancel()
101 self.secrets.transaction_cancel()
106 self.idmap.transaction_cancel()
111 self.privilege.transaction_cancel()
117 # self.hkcr.transaction_cancel()
118 # self.hkcu.transaction_cancel()
119 # self.hku.transaction_cancel()
120 # self.hklm.transaction_cancel()
122 def groupedCommit(self):
124 self.sam.transaction_prepare_commit()
125 self.secrets.transaction_prepare_commit()
126 self.idmap.transaction_prepare_commit()
127 self.privilege.transaction_prepare_commit()
129 return self.groupedRollback()
131 # self.hkcr.transaction_prepare_commit()
132 # self.hkcu.transaction_prepare_commit()
133 # self.hku.transaction_prepare_commit()
134 # self.hklm.transaction_prepare_commit()
136 self.sam.transaction_commit()
137 self.secrets.transaction_commit()
138 self.idmap.transaction_commit()
139 self.privilege.transaction_commit()
141 return self.groupedRollback()
144 # self.hkcr.transaction_commit()
145 # self.hkcu.transaction_commit()
146 # self.hku.transaction_commit()
147 # self.hklm.transaction_commit()
150 def get_ldbs(paths, creds, session, lp):
151 """Return LDB object mapped on most important databases
153 :param paths: An object holding the different importants paths for provision object
154 :param creds: Credential used for openning LDB files
155 :param session: Session to use for openning LDB files
156 :param lp: A loadparam object
157 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
159 ldbs = ProvisionLDB()
161 ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
162 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
163 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
164 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
165 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
166 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
167 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
168 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
173 def usn_in_range(usn, range):
174 """Check if the usn is in one of the range provided.
175 To do so, the value is checked to be between the lower bound and
176 higher bound of a range
178 :param usn: A integer value corresponding to the usn that we want to update
179 :param range: A list of integer representing ranges, lower bounds are in
180 the even indices, higher in odd indices
181 :return: True if the usn is in one of the range, False otherwise
188 if idx == len(range):
191 if usn < int(range[idx]):
195 if usn == int(range[idx]):
202 def get_paths(param, targetdir=None, smbconf=None):
203 """Get paths to important provision objects (smb.conf, ldb files, ...)
205 :param param: Param object
206 :param targetdir: Directory where the provision is (or will be) stored
207 :param smbconf: Path to the smb.conf file
208 :return: A list with the path of important provision objects"""
209 if targetdir is not None:
210 etcdir = os.path.join(targetdir, "etc")
211 if not os.path.exists(etcdir):
213 smbconf = os.path.join(etcdir, "smb.conf")
215 smbconf = param.default_path()
217 if not os.path.exists(smbconf):
218 raise ProvisioningError("Unable to find smb.conf")
220 lp = param.LoadParm()
222 paths = provision_paths_from_lp(lp, lp.get("realm"))
225 def update_policyids(names, samdb):
226 """Update policy ids that could have changed after sam update
228 :param names: List of key provision parameters
229 :param samdb: An Ldb object conntected with the sam DB
232 res = samdb.search(expression="(displayName=Default Domain Policy)",
233 base="CN=Policies,CN=System," + str(names.rootdn),
234 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
235 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
237 res2 = samdb.search(expression="(displayName=Default Domain Controllers" \
239 base="CN=Policies,CN=System," + str(names.rootdn),
240 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
242 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
244 names.policyid_dc = None
247 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
248 """Get key provision parameters (realm, domain, ...) from a given provision
250 :param samdb: An LDB object connected to the sam.ldb file
251 :param secretsdb: An LDB object connected to the secrets.ldb file
252 :param idmapdb: An LDB object connected to the idmap.ldb file
253 :param paths: A list of path to provision object
254 :param smbconf: Path to the smb.conf file
255 :param lp: A LoadParm object
256 :return: A list of key provision parameters
258 names = ProvisionNames()
259 names.adminpass = None
261 # NT domain, kerberos realm, root dn, domain dn, domain dns name
262 names.domain = string.upper(lp.get("workgroup"))
263 names.realm = lp.get("realm")
264 basedn = "DC=" + names.realm.replace(".",",DC=")
265 names.dnsdomain = names.realm.lower()
266 names.realm = string.upper(names.realm)
268 # Get the netbiosname first (could be obtained from smb.conf in theory)
269 res = secretsdb.search(expression="(flatname=%s)" % \
270 names.domain,base="CN=Primary Domains",
271 scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
272 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
274 names.smbconf = smbconf
276 # That's a bit simplistic but it's ok as long as we have only 3
278 current = samdb.search(expression="(objectClass=*)",
279 base="", scope=SCOPE_BASE,
280 attrs=["defaultNamingContext", "schemaNamingContext",
281 "configurationNamingContext","rootDomainNamingContext"])
283 names.configdn = current[0]["configurationNamingContext"]
284 configdn = str(names.configdn)
285 names.schemadn = current[0]["schemaNamingContext"]
286 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
287 current[0]["defaultNamingContext"][0]))):
288 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
289 "is not the same ..." % (paths.samdb,
290 str(current[0]["defaultNamingContext"][0]),
291 paths.smbconf, basedn)))
293 names.domaindn=current[0]["defaultNamingContext"]
294 names.rootdn=current[0]["rootDomainNamingContext"]
296 res3 = samdb.search(expression="(objectClass=*)",
297 base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
298 names.sitename = str(res3[0]["cn"])
300 # dns hostname and server dn
301 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
302 base="OU=Domain Controllers,%s" % basedn,
303 scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
304 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
306 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
307 attrs=[], base=configdn)
308 names.serverdn = server_res[0].dn
310 # invocation id/objectguid
311 res5 = samdb.search(expression="(objectClass=*)",
312 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
313 attrs=["invocationID", "objectGUID"])
314 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
315 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
318 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
319 scope=SCOPE_BASE, attrs=["objectGUID",
320 "objectSid","msDS-Behavior-Version" ])
321 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
322 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
323 if res6[0].get("msDS-Behavior-Version") is None or \
324 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
325 names.domainlevel = DS_DOMAIN_FUNCTION_2000
327 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
330 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
331 base="CN=Policies,CN=System," + basedn,
332 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
333 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
335 res8 = samdb.search(expression="(displayName=Default Domain Controllers" \
337 base="CN=Policies,CN=System," + basedn,
338 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
340 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
342 names.policyid_dc = None
343 res9 = idmapdb.search(expression="(cn=%s)" % \
344 (security.SID_BUILTIN_ADMINISTRATORS),
347 names.wheel_gid = res9[0]["xidNumber"]
349 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
353 def newprovision(names, setup_dir, creds, session, smbconf, provdir, logger):
354 """Create a new provision.
356 This provision will be the reference for knowing what has changed in the
357 since the latest upgrade in the current provision
359 :param names: List of provision parameters
360 :param setup_dis: Directory where the setup files are stored
361 :param creds: Credentials for the authentification
362 :param session: Session object
363 :param smbconf: Path to the smb.conf file
364 :param provdir: Directory where the provision will be stored
365 :param logger: A `Logger`
367 if os.path.isdir(provdir):
368 shutil.rmtree(provdir)
369 os.chdir(os.path.join(setup_dir,".."))
371 logger.info("Provision stored in %s", provdir)
372 provision(setup_dir, logger, session, creds, smbconf=smbconf,
373 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
374 domain=names.domain, domainguid=names.domainguid,
375 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
376 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
377 hostname=names.netbiosname, hostip=None, hostip6=None,
378 invocationid=names.invocation, adminpass=names.adminpass,
379 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
380 nobody=None, wheel=None, users=None,
381 serverrole="domain controller", ldap_backend_extra_port=None,
382 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
383 slapd_path=None, setup_ds_path=None, nosync=None,
384 dom_for_fun_level=names.domainlevel,
385 ldap_dryrun_mode=None, useeadb=True)
389 """Sorts two DNs in the lexicographical order it and put higher level DN
392 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
395 :param x: First object to compare
396 :param y: Second object to compare
398 p = re.compile(r'(?<!\\), ?')
399 tab1 = p.split(str(x))
400 tab2 = p.split(str(y))
401 minimum = min(len(tab1), len(tab2))
404 # Note: python range go up to upper limit but do not include it
405 for i in range(0, minimum):
406 ret = cmp(tab1[len1-i], tab2[len2-i])
411 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
419 def identic_rename(ldbobj, dn):
420 """Perform a back and forth rename to trigger renaming on attribute that
421 can't be directly modified.
423 :param lbdobj: An Ldb Object
424 :param dn: DN of the object to manipulate
426 (before, after) = str(dn).split('=', 1)
427 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)))
428 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn)
432 """Return separate ACE of an ACL
434 :param acl: A string representing the ACL
435 :return: A hash with different parts
438 p = re.compile(r'(\w+)?(\(.*?\))')
446 hash["aces"].append(e[1])
451 def chunck_sddl(sddl):
452 """ Return separate parts of the SDDL (owner, group, ...)
454 :param sddl: An string containing the SDDL to chunk
455 :return: A hash with the different chunk
458 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
459 tab = p.findall(sddl)
474 def get_diff_sddls(refsddl, cursddl):
475 """Get the difference between 2 sddl
476 This function split the textual representation of ACL into smaller
477 chunck in order to not to report a simple permutation as a difference
479 :param refsddl: First sddl to compare
480 :param cursddl: Second sddl to compare
481 :return: A string that explain difference between sddls"""
484 hash_new = chunck_sddl(cursddl)
485 hash_ref = chunck_sddl(refsddl)
487 if hash_new["owner"] != hash_ref["owner"]:
488 txt = "\tOwner mismatch: %s (in ref) %s" \
489 "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
491 if hash_new["group"] != hash_ref["group"]:
492 txt = "%s\tGroup mismatch: %s (in ref) %s" \
493 "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
495 for part in ["dacl", "sacl"]:
496 if hash_new.has_key(part) and hash_ref.has_key(part):
498 # both are present, check if they contain the same ACE
501 c_new = chunck_acl(hash_new[part])
502 c_ref = chunck_acl(hash_ref[part])
504 for elem in c_new["aces"]:
507 for elem in c_ref["aces"]:
515 if len(h_new) + len(h_ref) > 0:
516 txt = "%s\tPart %s is different between reference" \
517 " and current here is the detail:\n" % (txt, part)
520 txt = "%s\t\t%s ACE is not present in the" \
521 " reference\n" % (txt, item)
524 txt = "%s\t\t%s ACE is not present in the" \
525 " current\n" % (txt, item)
527 elif hash_new.has_key(part) and not hash_ref.has_key(part):
528 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
529 elif not hash_new.has_key(part) and hash_ref.has_key(part):
530 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
535 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
536 """Update secrets.ldb
538 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
539 of the reference provision
540 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
541 of the updated provision
544 messagefunc(SIMPLE, "update secrets.ldb")
545 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
547 current = secrets_ldb.search(expression="dn=@MODULES", base="",
549 assert reference, "Reference modules list can not be empty"
550 if len(current) == 0:
552 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
553 delta.dn = reference[0].dn
554 secrets_ldb.add(reference[0])
556 delta = secrets_ldb.msg_diff(current[0], reference[0])
557 delta.dn = current[0].dn
558 secrets_ldb.modify(delta)
560 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
561 scope=SCOPE_SUBTREE, attrs=["dn"])
562 current = secrets_ldb.search(expression="objectClass=top", base="",
563 scope=SCOPE_SUBTREE, attrs=["dn"])
569 empty = ldb.Message()
570 for i in range(0, len(reference)):
571 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
573 # Create a hash for speeding the search of existing object in the
575 for i in range(0, len(current)):
576 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
578 for k in hash_new.keys():
579 if not hash.has_key(k):
580 listMissing.append(hash_new[k])
582 listPresent.append(hash_new[k])
584 for entry in listMissing:
585 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
586 base="", scope=SCOPE_SUBTREE)
587 current = secrets_ldb.search(expression="dn=%s" % entry,
588 base="", scope=SCOPE_SUBTREE)
589 delta = secrets_ldb.msg_diff(empty, reference[0])
590 for att in hashAttrNotCopied.keys():
592 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" % \
595 messagefunc(CHANGE, " Adding attribute %s" % att)
596 delta.dn = reference[0].dn
597 secrets_ldb.add(delta)
599 for entry in listPresent:
600 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
601 base="", scope=SCOPE_SUBTREE)
602 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
604 delta = secrets_ldb.msg_diff(current[0], reference[0])
605 for att in hashAttrNotCopied.keys():
609 messagefunc(CHANGE, "Found attribute name on %s," \
610 " must rename the DN" % (current[0].dn))
611 identic_rename(secrets_ldb, reference[0].dn)
615 for entry in listPresent:
616 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
618 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
620 delta = secrets_ldb.msg_diff(current[0], reference[0])
621 for att in hashAttrNotCopied.keys():
624 if att == "msDS-KeyVersionNumber":
628 "Adding/Changing attribute %s to %s" % \
629 (att, current[0].dn))
631 delta.dn = current[0].dn
632 secrets_ldb.modify(delta)
634 def getOEMInfo(samdb, rootdn):
635 """Return OEM Information on the top level
636 Samba4 use to store version info in this field
638 :param samdb: An LDB object connect to sam.ldb
639 :param rootdn: Root DN of the domain
640 :return: The content of the field oEMInformation (if any)"""
641 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
642 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
644 info = res[0]["oEMInformation"]
649 def updateOEMInfo(samdb, rootdn):
650 """Update the OEMinfo field to add information about upgrade
651 :param samdb: an LDB object connected to the sam DB
652 :param rootdn: The string representation of the root DN of
653 the provision (ie. DC=...,DC=...)
655 res = samdb.search(expression="(objectClass=*)", base=rootdn,
656 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
658 info = res[0]["oEMInformation"]
659 info = "%s, upgrade to %s" % (info, version)
660 delta = ldb.Message()
661 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
662 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
666 def update_gpo(paths, samdb, names, lp, message, force=0):
667 """Create missing GPO file object if needed
669 Set ACL correctly also.
670 Check ACLs for sysvol/netlogon dirs also
674 ntacls.checkset_backend(lp, None, None)
675 eadbname = lp.get("posix:eadb")
676 if eadbname is not None and eadbname != "":
678 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
679 paths.sysvol, xattr.XATTR_NTACL_NAME)
681 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
682 xattr.XATTR_NTACL_NAME)
684 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
685 xattr.XATTR_NTACL_NAME)
692 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
693 if not os.path.isdir(dir):
694 create_gpo_struct(dir)
696 if names.policyid_dc is None:
697 raise ProvisioningError("Policy ID for Domain controller is missing")
698 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
699 if not os.path.isdir(dir):
700 create_gpo_struct(dir)
701 # We always reinforce acls on GPO folder because they have to be in sync
704 set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
705 names.domaindn, samdb, lp)
707 message(ERROR, "Unable to set ACLs on policies related objects,"
708 " if not using posix:eadb, you must be root to do it")
712 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
713 names.domainsid, names.dnsdomain, names.domaindn, lp)
715 message(ERROR, "Unable to set ACLs on sysvol share, if not using"
716 "posix:eadb, you must be root to do it")
718 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
719 """For a given hash associating dn and a number, this function will
720 update the replPropertyMetaData of each dn in the hash, so that the
721 calculated value of the msDs-KeyVersionNumber is equal or superior to the
722 one associated to the given dn.
724 :param samdb: An SamDB object pointing to the sam
725 :param rootdn: The base DN where we want to start
726 :param hashDns: A hash with dn as key and number representing the
727 minimum value of msDs-KeyVersionNumber that we want to
730 entry = samdb.search(expression='(objectClass=user)',
731 base=ldb.Dn(samdb,str(rootdn)),
732 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
733 controls=["search_options:1:2"])
737 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
740 if hashDns.has_key(str(e.dn).lower()):
741 val = e.get("msDs-KeyVersionNumber")
744 version = int(str(hashDns[str(e.dn).lower()]))
745 if int(str(val)) < version:
747 samdb.set_attribute_replmetadata_version(str(e.dn),
750 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
751 """Update the provision container db: sam.ldb
752 This function is aimed for alpha9 and newer;
754 :param refsampath: Path to the samdb in the reference provision
755 :param sampath: Path to the samdb in the upgraded provision
756 :param creds: Credential used for openning LDB files
757 :param session: Session to use for openning LDB files
758 :param lp: A loadparam object
759 :return: A msg_diff object with the difference between the @ATTRIBUTES
760 of the current provision and the reference provision
764 "Update base samdb by searching difference with reference one")
765 refsam = Ldb(refsampath, session_info=session, credentials=creds,
766 lp=lp, options=["modules:"])
767 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
768 options=["modules:"])
770 empty = ldb.Message()
772 reference = refsam.search(expression="")
774 for refentry in reference:
775 entry = sam.search(expression="dn=%s" % refentry["dn"],
778 delta = sam.msg_diff(empty, refentry)
779 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
780 if str(refentry.dn) == "@PROVISION" and\
781 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
782 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
783 delta.dn = refentry.dn
786 delta = sam.msg_diff(entry[0], refentry)
787 if str(refentry.dn) == "@ATTRIBUTES":
788 deltaattr = sam.msg_diff(refentry, entry[0])
789 if str(refentry.dn) == "@PROVISION" and\
790 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
791 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
792 if len(delta.items()) > 1:
793 delta.dn = refentry.dn
798 def construct_existor_expr(attrs):
799 """Construct a exists or LDAP search expression.
802 :param attrs: List of attribute on which we want to create the search
804 :return: A string representing the expression, if attrs is empty an
805 empty string is returned"""
810 expr = "%s(%s=*)"%(expr,att)
814 def update_machine_account_password(samdb, secrets_ldb, names):
815 """Update (change) the password of the current DC both in the SAM db and in
818 :param samdb: An LDB object related to the sam.ldb file of a given provision
819 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
821 :param names: List of key provision parameters"""
823 expression = "samAccountName=%s$" % names.netbiosname
824 secrets_msg = secrets_ldb.search(expression=expression,
825 attrs=["secureChannelType"])
826 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
827 res = samdb.search(expression=expression, attrs=[])
828 assert(len(res) == 1)
830 msg = ldb.Message(res[0].dn)
831 machinepass = samba.generate_random_password(128, 255)
832 msg["userPassword"] = ldb.MessageElement(machinepass,
833 ldb.FLAG_MOD_REPLACE,
837 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
838 attrs=["msDs-keyVersionNumber"])
839 assert(len(res) == 1)
840 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
841 secChanType = int(secrets_msg[0]["secureChannelType"][0])
843 secretsdb_self_join(secrets_ldb, domain=names.domain,
845 domainsid=names.domainsid,
846 dnsdomain=names.dnsdomain,
847 netbiosname=names.netbiosname,
848 machinepass=machinepass,
849 key_version_number=kvno,
850 secure_channel_type=secChanType)
852 raise ProvisioningError("Unable to find a Secure Channel"
853 "of type SEC_CHAN_BDC")
856 def search_constructed_attrs_stored(samdb, rootdn, attrs):
857 """Search a given sam DB for calculated attributes that are
858 still stored in the db.
860 :param samdb: An LDB object pointing to the sam
861 :param rootdn: The base DN where the search should start
862 :param attrs: A list of attributes to be searched
863 :return: A hash with attributes as key and an array of
864 array. Each array contains the dn and the associated
865 values for this attribute as they are stored in the
869 expr = construct_existor_expr(attrs)
872 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
873 scope=SCOPE_SUBTREE, attrs=attrs,
874 controls=["search_options:1:2","bypassoperational:0"])
882 if hashAtt.has_key(att):
883 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
886 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
890 def int64range2str(value):
891 """Display the int64 range stored in value as xxx-yyy
893 :param value: The int64 range
894 :return: A string of the representation of the range
898 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)