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/>.
24 """Helers used for upgrading between different database formats."""
32 from samba import Ldb, version, ntacls
33 from samba.dsdb import DS_DOMAIN_FUNCTION_2000
34 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
36 from samba.provision import (ProvisionNames, provision_paths_from_lp,
37 getpolicypath, set_gpos_acl, create_gpo_struct,
38 FILL_FULL, provision, ProvisioningError,
39 setsysvolacl, secretsdb_self_join)
40 from samba.dcerpc import misc, security, xattr
41 from samba.dcerpc.misc import SEC_CHAN_BDC
42 from samba.ndr import ndr_unpack
43 from samba.samdb import SamDB
45 # All the ldb related to registry are commented because the path for them is
46 # relative in the provisionPath object
47 # And so opening them create a file in the current directory which is not what
49 # I still keep them commented because I plan soon to make more cleaner
58 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
59 "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
60 "objectCategory", "distinguishedName", "nTMixedDomain",
61 "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
62 "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
63 "ntPwdHistory", "unicodePwd","dBCSPwd", "supplementalCredentials",
64 "gPCUserExtensionNames", "gPCMachineExtensionNames","maxPwdAge", "secret",
65 "possibleInferiors", "privilege", "sAMAccountType"])
68 class ProvisionLDB(object):
80 def startTransactions(self):
81 self.sam.transaction_start()
82 self.secrets.transaction_start()
83 self.idmap.transaction_start()
84 self.privilege.transaction_start()
86 # self.hkcr.transaction_start()
87 # self.hkcu.transaction_start()
88 # self.hku.transaction_start()
89 # self.hklm.transaction_start()
91 def groupedRollback(self):
94 self.sam.transaction_cancel()
99 self.secrets.transaction_cancel()
104 self.idmap.transaction_cancel()
109 self.privilege.transaction_cancel()
115 # self.hkcr.transaction_cancel()
116 # self.hkcu.transaction_cancel()
117 # self.hku.transaction_cancel()
118 # self.hklm.transaction_cancel()
120 def groupedCommit(self):
122 self.sam.transaction_prepare_commit()
123 self.secrets.transaction_prepare_commit()
124 self.idmap.transaction_prepare_commit()
125 self.privilege.transaction_prepare_commit()
127 return self.groupedRollback()
129 # self.hkcr.transaction_prepare_commit()
130 # self.hkcu.transaction_prepare_commit()
131 # self.hku.transaction_prepare_commit()
132 # self.hklm.transaction_prepare_commit()
134 self.sam.transaction_commit()
135 self.secrets.transaction_commit()
136 self.idmap.transaction_commit()
137 self.privilege.transaction_commit()
139 return self.groupedRollback()
142 # self.hkcr.transaction_commit()
143 # self.hkcu.transaction_commit()
144 # self.hku.transaction_commit()
145 # self.hklm.transaction_commit()
148 def get_ldbs(paths, creds, session, lp):
149 """Return LDB object mapped on most important databases
151 :param paths: An object holding the different importants paths for provision object
152 :param creds: Credential used for openning LDB files
153 :param session: Session to use for openning LDB files
154 :param lp: A loadparam object
155 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
157 ldbs = ProvisionLDB()
159 ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
160 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
161 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
162 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
163 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
164 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
165 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
166 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
171 def usn_in_range(usn, range):
172 """Check if the usn is in one of the range provided.
173 To do so, the value is checked to be between the lower bound and
174 higher bound of a range
176 :param usn: A integer value corresponding to the usn that we want to update
177 :param range: A list of integer representing ranges, lower bounds are in
178 the even indices, higher in odd indices
179 :return: True if the usn is in one of the range, False otherwise
186 if idx == len(range):
189 if usn < int(range[idx]):
193 if usn == int(range[idx]):
200 def get_paths(param, targetdir=None, smbconf=None):
201 """Get paths to important provision objects (smb.conf, ldb files, ...)
203 :param param: Param object
204 :param targetdir: Directory where the provision is (or will be) stored
205 :param smbconf: Path to the smb.conf file
206 :return: A list with the path of important provision objects"""
207 if targetdir is not None:
208 etcdir = os.path.join(targetdir, "etc")
209 if not os.path.exists(etcdir):
211 smbconf = os.path.join(etcdir, "smb.conf")
213 smbconf = param.default_path()
215 if not os.path.exists(smbconf):
216 raise ProvisioningError("Unable to find smb.conf")
218 lp = param.LoadParm()
220 paths = provision_paths_from_lp(lp, lp.get("realm"))
223 def update_policyids(names, samdb):
224 """Update policy ids that could have changed after sam update
226 :param names: List of key provision parameters
227 :param samdb: An Ldb object conntected with the sam DB
230 res = samdb.search(expression="(displayName=Default Domain Policy)",
231 base="CN=Policies,CN=System," + str(names.rootdn),
232 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
233 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
235 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
237 base="CN=Policies,CN=System," + str(names.rootdn),
238 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
240 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
242 names.policyid_dc = None
245 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
246 """Get key provision parameters (realm, domain, ...) from a given provision
248 :param samdb: An LDB object connected to the sam.ldb file
249 :param secretsdb: An LDB object connected to the secrets.ldb file
250 :param idmapdb: An LDB object connected to the idmap.ldb file
251 :param paths: A list of path to provision object
252 :param smbconf: Path to the smb.conf file
253 :param lp: A LoadParm object
254 :return: A list of key provision parameters
256 names = ProvisionNames()
257 names.adminpass = None
259 # NT domain, kerberos realm, root dn, domain dn, domain dns name
260 names.domain = string.upper(lp.get("workgroup"))
261 names.realm = lp.get("realm")
262 basedn = "DC=" + names.realm.replace(".",",DC=")
263 names.dnsdomain = names.realm.lower()
264 names.realm = string.upper(names.realm)
266 # Get the netbiosname first (could be obtained from smb.conf in theory)
267 res = secretsdb.search(expression="(flatname=%s)" %
268 names.domain,base="CN=Primary Domains",
269 scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
270 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
272 names.smbconf = smbconf
274 # That's a bit simplistic but it's ok as long as we have only 3
276 current = samdb.search(expression="(objectClass=*)",
277 base="", scope=SCOPE_BASE,
278 attrs=["defaultNamingContext", "schemaNamingContext",
279 "configurationNamingContext","rootDomainNamingContext"])
281 names.configdn = current[0]["configurationNamingContext"]
282 configdn = str(names.configdn)
283 names.schemadn = current[0]["schemaNamingContext"]
284 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
285 current[0]["defaultNamingContext"][0]))):
286 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
287 "is not the same ..." % (paths.samdb,
288 str(current[0]["defaultNamingContext"][0]),
289 paths.smbconf, basedn)))
291 names.domaindn=current[0]["defaultNamingContext"]
292 names.rootdn=current[0]["rootDomainNamingContext"]
294 res3 = samdb.search(expression="(objectClass=*)",
295 base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
296 names.sitename = str(res3[0]["cn"])
298 # dns hostname and server dn
299 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
300 base="OU=Domain Controllers,%s" % basedn,
301 scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
302 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
304 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
305 attrs=[], base=configdn)
306 names.serverdn = server_res[0].dn
308 # invocation id/objectguid
309 res5 = samdb.search(expression="(objectClass=*)",
310 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
311 attrs=["invocationID", "objectGUID"])
312 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
313 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
316 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
317 scope=SCOPE_BASE, attrs=["objectGUID",
318 "objectSid","msDS-Behavior-Version" ])
319 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
320 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
321 if res6[0].get("msDS-Behavior-Version") is None or \
322 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
323 names.domainlevel = DS_DOMAIN_FUNCTION_2000
325 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
328 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
329 base="CN=Policies,CN=System," + basedn,
330 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
331 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
333 res8 = samdb.search(expression="(displayName=Default Domain Controllers"
335 base="CN=Policies,CN=System," + basedn,
336 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
338 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
340 names.policyid_dc = None
341 res9 = idmapdb.search(expression="(cn=%s)" %
342 (security.SID_BUILTIN_ADMINISTRATORS),
345 names.wheel_gid = res9[0]["xidNumber"]
347 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
351 def newprovision(names, setup_dir, creds, session, smbconf, provdir, logger):
352 """Create a new provision.
354 This provision will be the reference for knowing what has changed in the
355 since the latest upgrade in the current provision
357 :param names: List of provision parameters
358 :param setup_dir: Directory where the setup files are stored
359 :param creds: Credentials for the authentification
360 :param session: Session object
361 :param smbconf: Path to the smb.conf file
362 :param provdir: Directory where the provision will be stored
363 :param logger: A Logger
365 if os.path.isdir(provdir):
366 shutil.rmtree(provdir)
368 logger.info("Provision stored in %s", provdir)
369 provision(setup_dir, logger, session, creds, smbconf=smbconf,
370 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
371 domain=names.domain, domainguid=names.domainguid,
372 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
373 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
374 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
375 invocationid=names.invocation, adminpass=names.adminpass,
376 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
377 nobody=None, wheel=None, users=None,
378 serverrole="domain controller", ldap_backend_extra_port=None,
379 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
380 slapd_path=None, setup_ds_path=None, nosync=None,
381 dom_for_fun_level=names.domainlevel,
382 ldap_dryrun_mode=None, useeadb=True)
386 """Sorts two DNs in the lexicographical order it and put higher level DN
389 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
392 :param x: First object to compare
393 :param y: Second object to compare
395 p = re.compile(r'(?<!\\), ?')
396 tab1 = p.split(str(x))
397 tab2 = p.split(str(y))
398 minimum = min(len(tab1), len(tab2))
401 # Note: python range go up to upper limit but do not include it
402 for i in range(0, minimum):
403 ret = cmp(tab1[len1-i], tab2[len2-i])
408 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
416 def identic_rename(ldbobj, dn):
417 """Perform a back and forth rename to trigger renaming on attribute that
418 can't be directly modified.
420 :param lbdobj: An Ldb Object
421 :param dn: DN of the object to manipulate
423 (before, after) = str(dn).split('=', 1)
424 # we need to use relax to avoid the subtree_rename constraints
425 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
426 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
430 """Return separate ACE of an ACL
432 :param acl: A string representing the ACL
433 :return: A hash with different parts
436 p = re.compile(r'(\w+)?(\(.*?\))')
444 hash["aces"].append(e[1])
449 def chunck_sddl(sddl):
450 """ Return separate parts of the SDDL (owner, group, ...)
452 :param sddl: An string containing the SDDL to chunk
453 :return: A hash with the different chunk
456 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
457 tab = p.findall(sddl)
473 def get_diff_sddls(refsddl, cursddl):
474 """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
485 hash_new = chunck_sddl(cursddl)
486 hash_ref = chunck_sddl(refsddl)
488 if hash_new["owner"] != hash_ref["owner"]:
489 txt = "\tOwner mismatch: %s (in ref) %s" \
490 "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
492 if hash_new["group"] != hash_ref["group"]:
493 txt = "%s\tGroup mismatch: %s (in ref) %s" \
494 "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
496 for part in ["dacl", "sacl"]:
497 if hash_new.has_key(part) and hash_ref.has_key(part):
499 # both are present, check if they contain the same ACE
502 c_new = chunck_acl(hash_new[part])
503 c_ref = chunck_acl(hash_ref[part])
505 for elem in c_new["aces"]:
508 for elem in c_ref["aces"]:
516 if len(h_new) + len(h_ref) > 0:
517 txt = "%s\tPart %s is different between reference" \
518 " and current here is the detail:\n" % (txt, part)
521 txt = "%s\t\t%s ACE is not present in the" \
522 " reference\n" % (txt, item)
525 txt = "%s\t\t%s ACE is not present in the" \
526 " current\n" % (txt, item)
528 elif hash_new.has_key(part) and not hash_ref.has_key(part):
529 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
530 elif not hash_new.has_key(part) and hash_ref.has_key(part):
531 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
536 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
537 """Update secrets.ldb
539 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
540 of the reference provision
541 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
542 of the updated provision
545 messagefunc(SIMPLE, "update secrets.ldb")
546 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
548 current = secrets_ldb.search(expression="dn=@MODULES", base="",
550 assert reference, "Reference modules list can not be empty"
551 if len(current) == 0:
553 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
554 delta.dn = reference[0].dn
555 secrets_ldb.add(reference[0])
557 delta = secrets_ldb.msg_diff(current[0], reference[0])
558 delta.dn = current[0].dn
559 secrets_ldb.modify(delta)
561 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
562 scope=SCOPE_SUBTREE, attrs=["dn"])
563 current = secrets_ldb.search(expression="objectClass=top", base="",
564 scope=SCOPE_SUBTREE, attrs=["dn"])
570 empty = ldb.Message()
571 for i in range(0, len(reference)):
572 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
574 # Create a hash for speeding the search of existing object in the
576 for i in range(0, len(current)):
577 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
579 for k in hash_new.keys():
580 if not hash.has_key(k):
581 listMissing.append(hash_new[k])
583 listPresent.append(hash_new[k])
585 for entry in listMissing:
586 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
587 base="", scope=SCOPE_SUBTREE)
588 current = secrets_ldb.search(expression="dn=%s" % entry,
589 base="", scope=SCOPE_SUBTREE)
590 delta = secrets_ldb.msg_diff(empty, reference[0])
591 for att in hashAttrNotCopied:
593 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
596 messagefunc(CHANGE, " Adding attribute %s" % att)
597 delta.dn = reference[0].dn
598 secrets_ldb.add(delta)
600 for entry in listPresent:
601 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
602 base="", scope=SCOPE_SUBTREE)
603 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
605 delta = secrets_ldb.msg_diff(current[0], reference[0])
606 for att in hashAttrNotCopied:
610 messagefunc(CHANGE, "Found attribute name on %s,"
611 " must rename the DN" % (current[0].dn))
612 identic_rename(secrets_ldb, reference[0].dn)
616 for entry in listPresent:
617 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
619 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
621 delta = secrets_ldb.msg_diff(current[0], reference[0])
622 for att in hashAttrNotCopied:
625 if att == "msDS-KeyVersionNumber":
629 "Adding/Changing attribute %s to %s" %
630 (att, current[0].dn))
632 delta.dn = current[0].dn
633 secrets_ldb.modify(delta)
635 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
636 scope=SCOPE_SUBTREE, attrs=["dn"])
639 messagefunc(SIMPLE, "Remove old dns account")
640 secrets_ldb.delete(res2[0]["dn"])
643 def getOEMInfo(samdb, rootdn):
644 """Return OEM Information on the top level Samba4 use to store version
647 :param samdb: An LDB object connect to sam.ldb
648 :param rootdn: Root DN of the domain
649 :return: The content of the field oEMInformation (if any)
651 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
652 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
654 info = res[0]["oEMInformation"]
660 def updateOEMInfo(samdb, rootdn):
661 """Update the OEMinfo field to add information about upgrade
663 :param samdb: an LDB object connected to the sam DB
664 :param rootdn: The string representation of the root DN of
665 the provision (ie. DC=...,DC=...)
667 res = samdb.search(expression="(objectClass=*)", base=rootdn,
668 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
670 info = res[0]["oEMInformation"]
671 info = "%s, upgrade to %s" % (info, version)
672 delta = ldb.Message()
673 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
674 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
678 def update_gpo(paths, samdb, names, lp, message, force=0):
679 """Create missing GPO file object if needed
681 Set ACL correctly also.
682 Check ACLs for sysvol/netlogon dirs also
686 ntacls.checkset_backend(lp, None, None)
687 eadbname = lp.get("posix:eadb")
688 if eadbname is not None and eadbname != "":
690 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
691 paths.sysvol, xattr.XATTR_NTACL_NAME)
693 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
694 xattr.XATTR_NTACL_NAME)
696 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
697 xattr.XATTR_NTACL_NAME)
704 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
705 if not os.path.isdir(dir):
706 create_gpo_struct(dir)
708 if names.policyid_dc is None:
709 raise ProvisioningError("Policy ID for Domain controller is missing")
710 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
711 if not os.path.isdir(dir):
712 create_gpo_struct(dir)
713 # We always reinforce acls on GPO folder because they have to be in sync
716 set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
717 names.domaindn, samdb, lp)
719 message(ERROR, "Unable to set ACLs on policies related objects,"
720 " if not using posix:eadb, you must be root to do it")
724 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
725 names.domainsid, names.dnsdomain, names.domaindn, lp)
727 message(ERROR, "Unable to set ACLs on sysvol share, if not using"
728 "posix:eadb, you must be root to do it")
730 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
731 """For a given hash associating dn and a number, this function will
732 update the replPropertyMetaData of each dn in the hash, so that the
733 calculated value of the msDs-KeyVersionNumber is equal or superior to the
734 one associated to the given dn.
736 :param samdb: An SamDB object pointing to the sam
737 :param rootdn: The base DN where we want to start
738 :param hashDns: A hash with dn as key and number representing the
739 minimum value of msDs-KeyVersionNumber that we want to
742 entry = samdb.search(expression='(objectClass=user)',
743 base=ldb.Dn(samdb,str(rootdn)),
744 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
745 controls=["search_options:1:2"])
749 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
752 if hashDns.has_key(str(e.dn).lower()):
753 val = e.get("msDs-KeyVersionNumber")
756 version = int(str(hashDns[str(e.dn).lower()]))
757 if int(str(val)) < version:
759 samdb.set_attribute_replmetadata_version(str(e.dn),
762 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
763 """Update the provision container db: sam.ldb
764 This function is aimed for alpha9 and newer;
766 :param refsampath: Path to the samdb in the reference provision
767 :param sampath: Path to the samdb in the upgraded provision
768 :param creds: Credential used for openning LDB files
769 :param session: Session to use for openning LDB files
770 :param lp: A loadparam object
771 :return: A msg_diff object with the difference between the @ATTRIBUTES
772 of the current provision and the reference provision
776 "Update base samdb by searching difference with reference one")
777 refsam = Ldb(refsampath, session_info=session, credentials=creds,
778 lp=lp, options=["modules:"])
779 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
780 options=["modules:"])
782 empty = ldb.Message()
784 reference = refsam.search(expression="")
786 for refentry in reference:
787 entry = sam.search(expression="dn=%s" % refentry["dn"],
790 delta = sam.msg_diff(empty, refentry)
791 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
792 if str(refentry.dn) == "@PROVISION" and\
793 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
794 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
795 delta.dn = refentry.dn
798 delta = sam.msg_diff(entry[0], refentry)
799 if str(refentry.dn) == "@ATTRIBUTES":
800 deltaattr = sam.msg_diff(refentry, entry[0])
801 if str(refentry.dn) == "@PROVISION" and\
802 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
803 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
804 if len(delta.items()) > 1:
805 delta.dn = refentry.dn
811 def construct_existor_expr(attrs):
812 """Construct a exists or LDAP search expression.
814 :param attrs: List of attribute on which we want to create the search
816 :return: A string representing the expression, if attrs is empty an
817 empty string is returned
823 expr = "%s(%s=*)"%(expr,att)
827 def update_machine_account_password(samdb, secrets_ldb, names):
828 """Update (change) the password of the current DC both in the SAM db and in
831 :param samdb: An LDB object related to the sam.ldb file of a given provision
832 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
834 :param names: List of key provision parameters"""
836 expression = "samAccountName=%s$" % names.netbiosname
837 secrets_msg = secrets_ldb.search(expression=expression,
838 attrs=["secureChannelType"])
839 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
840 res = samdb.search(expression=expression, attrs=[])
841 assert(len(res) == 1)
843 msg = ldb.Message(res[0].dn)
844 machinepass = samba.generate_random_password(128, 255)
845 mputf16 = machinepass.encode('utf-16-le')
846 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
847 ldb.FLAG_MOD_REPLACE,
851 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
852 attrs=["msDs-keyVersionNumber"])
853 assert(len(res) == 1)
854 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
855 secChanType = int(secrets_msg[0]["secureChannelType"][0])
857 secretsdb_self_join(secrets_ldb, domain=names.domain,
859 domainsid=names.domainsid,
860 dnsdomain=names.dnsdomain,
861 netbiosname=names.netbiosname,
862 machinepass=machinepass,
863 key_version_number=kvno,
864 secure_channel_type=secChanType)
866 raise ProvisioningError("Unable to find a Secure Channel"
867 "of type SEC_CHAN_BDC")
869 def update_dns_account_password(samdb, secrets_ldb, names):
870 """Update (change) the password of the dns both in the SAM db and in
873 :param samdb: An LDB object related to the sam.ldb file of a given provision
874 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
876 :param names: List of key provision parameters"""
878 expression = "samAccountName=dns-%s" % names.netbiosname
879 secrets_msg = secrets_ldb.search(expression=expression)
880 if len(secrets_msg) == 1:
881 res = samdb.search(expression=expression, attrs=[])
882 assert(len(res) == 1)
884 msg = ldb.Message(res[0].dn)
885 machinepass = samba.generate_random_password(128, 255)
886 mputf16 = machinepass.encode('utf-16-le')
887 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
888 ldb.FLAG_MOD_REPLACE,
893 res = samdb.search(expression=expression,
894 attrs=["msDs-keyVersionNumber"])
895 assert(len(res) == 1)
896 kvno = str(res[0]["msDs-keyVersionNumber"])
898 msg = ldb.Message(secrets_msg[0].dn)
899 msg["secret"] = ldb.MessageElement(machinepass,
900 ldb.FLAG_MOD_REPLACE,
902 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
903 ldb.FLAG_MOD_REPLACE,
904 "msDS-KeyVersionNumber")
906 secrets_ldb.modify(msg)
908 raise ProvisioningError("Unable to find an object"
909 " with %s" % expression )
911 def search_constructed_attrs_stored(samdb, rootdn, attrs):
912 """Search a given sam DB for calculated attributes that are
913 still stored in the db.
915 :param samdb: An LDB object pointing to the sam
916 :param rootdn: The base DN where the search should start
917 :param attrs: A list of attributes to be searched
918 :return: A hash with attributes as key and an array of
919 array. Each array contains the dn and the associated
920 values for this attribute as they are stored in the
924 expr = construct_existor_expr(attrs)
927 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
928 scope=SCOPE_SUBTREE, attrs=attrs,
929 controls=["search_options:1:2","bypassoperational:0"])
937 if hashAtt.has_key(att):
938 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
941 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
945 def int64range2str(value):
946 """Display the int64 range stored in value as xxx-yyy
948 :param value: The int64 range
949 :return: A string of the representation of the range
953 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)