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/>.
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 relative
46 # in the provisionPath object
47 # And so opening them create a file in the current directory which is not what we want
48 # I still keep them commented because I plan soon to make more cleaner
57 hashAttrNotCopied = { "dn": 1, "whenCreated": 1, "whenChanged": 1,
58 "objectGUID": 1, "uSNCreated": 1,
59 "replPropertyMetaData": 1, "uSNChanged": 1,
60 "parentGUID": 1, "objectCategory": 1,
61 "distinguishedName": 1, "nTMixedDomain": 1,
62 "showInAdvancedViewOnly": 1, "instanceType": 1,
63 "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
64 "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1,
65 "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
66 "supplementalCredentials":1, "gPCUserExtensionNames":1,
67 "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
68 "possibleInferiors":1, "privilege":1,
71 class ProvisionLDB(object):
83 def startTransactions(self):
84 self.sam.transaction_start()
85 self.secrets.transaction_start()
86 self.idmap.transaction_start()
87 self.privilege.transaction_start()
89 # self.hkcr.transaction_start()
90 # self.hkcu.transaction_start()
91 # self.hku.transaction_start()
92 # self.hklm.transaction_start()
94 def groupedRollback(self):
97 self.sam.transaction_cancel()
102 self.secrets.transaction_cancel()
107 self.idmap.transaction_cancel()
112 self.privilege.transaction_cancel()
118 # self.hkcr.transaction_cancel()
119 # self.hkcu.transaction_cancel()
120 # self.hku.transaction_cancel()
121 # self.hklm.transaction_cancel()
123 def groupedCommit(self):
125 self.sam.transaction_prepare_commit()
126 self.secrets.transaction_prepare_commit()
127 self.idmap.transaction_prepare_commit()
128 self.privilege.transaction_prepare_commit()
130 return self.groupedRollback()
132 # self.hkcr.transaction_prepare_commit()
133 # self.hkcu.transaction_prepare_commit()
134 # self.hku.transaction_prepare_commit()
135 # self.hklm.transaction_prepare_commit()
137 self.sam.transaction_commit()
138 self.secrets.transaction_commit()
139 self.idmap.transaction_commit()
140 self.privilege.transaction_commit()
142 return self.groupedRollback()
145 # self.hkcr.transaction_commit()
146 # self.hkcu.transaction_commit()
147 # self.hku.transaction_commit()
148 # self.hklm.transaction_commit()
151 def get_ldbs(paths, creds, session, lp):
152 """Return LDB object mapped on most important databases
154 :param paths: An object holding the different importants paths for provision object
155 :param creds: Credential used for openning LDB files
156 :param session: Session to use for openning LDB files
157 :param lp: A loadparam object
158 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
160 ldbs = ProvisionLDB()
162 ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
163 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
164 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
165 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
166 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
167 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
168 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
169 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
174 def usn_in_range(usn, range):
175 """Check if the usn is in one of the range provided.
176 To do so, the value is checked to be between the lower bound and
177 higher bound of a range
179 :param usn: A integer value corresponding to the usn that we want to update
180 :param range: A list of integer representing ranges, lower bounds are in
181 the even indices, higher in odd indices
182 :return: True if the usn is in one of the range, False otherwise
189 if idx == len(range):
192 if usn < int(range[idx]):
196 if usn == int(range[idx]):
203 def get_paths(param, targetdir=None, smbconf=None):
204 """Get paths to important provision objects (smb.conf, ldb files, ...)
206 :param param: Param object
207 :param targetdir: Directory where the provision is (or will be) stored
208 :param smbconf: Path to the smb.conf file
209 :return: A list with the path of important provision objects"""
210 if targetdir is not None:
211 etcdir = os.path.join(targetdir, "etc")
212 if not os.path.exists(etcdir):
214 smbconf = os.path.join(etcdir, "smb.conf")
216 smbconf = param.default_path()
218 if not os.path.exists(smbconf):
219 raise ProvisioningError("Unable to find smb.conf")
221 lp = param.LoadParm()
223 paths = provision_paths_from_lp(lp, lp.get("realm"))
226 def update_policyids(names, samdb):
227 """Update policy ids that could have changed after sam update
229 :param names: List of key provision parameters
230 :param samdb: An Ldb object conntected with the sam DB
233 res = samdb.search(expression="(displayName=Default Domain Policy)",
234 base="CN=Policies,CN=System," + str(names.rootdn),
235 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
236 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
238 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
240 base="CN=Policies,CN=System," + str(names.rootdn),
241 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
243 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
245 names.policyid_dc = None
248 def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
249 """Get key provision parameters (realm, domain, ...) from a given provision
251 :param samdb: An LDB object connected to the sam.ldb file
252 :param secretsdb: An LDB object connected to the secrets.ldb file
253 :param idmapdb: An LDB object connected to the idmap.ldb file
254 :param paths: A list of path to provision object
255 :param smbconf: Path to the smb.conf file
256 :param lp: A LoadParm object
257 :return: A list of key provision parameters
259 names = ProvisionNames()
260 names.adminpass = None
262 # NT domain, kerberos realm, root dn, domain dn, domain dns name
263 names.domain = string.upper(lp.get("workgroup"))
264 names.realm = lp.get("realm")
265 basedn = "DC=" + names.realm.replace(".",",DC=")
266 names.dnsdomain = names.realm.lower()
267 names.realm = string.upper(names.realm)
269 # Get the netbiosname first (could be obtained from smb.conf in theory)
270 res = secretsdb.search(expression="(flatname=%s)" %
271 names.domain,base="CN=Primary Domains",
272 scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
273 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
275 names.smbconf = smbconf
277 # That's a bit simplistic but it's ok as long as we have only 3
279 current = samdb.search(expression="(objectClass=*)",
280 base="", scope=SCOPE_BASE,
281 attrs=["defaultNamingContext", "schemaNamingContext",
282 "configurationNamingContext","rootDomainNamingContext"])
284 names.configdn = current[0]["configurationNamingContext"]
285 configdn = str(names.configdn)
286 names.schemadn = current[0]["schemaNamingContext"]
287 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
288 current[0]["defaultNamingContext"][0]))):
289 raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
290 "is not the same ..." % (paths.samdb,
291 str(current[0]["defaultNamingContext"][0]),
292 paths.smbconf, basedn)))
294 names.domaindn=current[0]["defaultNamingContext"]
295 names.rootdn=current[0]["rootDomainNamingContext"]
297 res3 = samdb.search(expression="(objectClass=*)",
298 base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
299 names.sitename = str(res3[0]["cn"])
301 # dns hostname and server dn
302 res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
303 base="OU=Domain Controllers,%s" % basedn,
304 scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
305 names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
307 server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
308 attrs=[], base=configdn)
309 names.serverdn = server_res[0].dn
311 # invocation id/objectguid
312 res5 = samdb.search(expression="(objectClass=*)",
313 base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
314 attrs=["invocationID", "objectGUID"])
315 names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
316 names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
319 res6 = samdb.search(expression="(objectClass=*)", base=basedn,
320 scope=SCOPE_BASE, attrs=["objectGUID",
321 "objectSid","msDS-Behavior-Version" ])
322 names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
323 names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
324 if res6[0].get("msDS-Behavior-Version") is None or \
325 int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
326 names.domainlevel = DS_DOMAIN_FUNCTION_2000
328 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
331 res7 = samdb.search(expression="(displayName=Default Domain Policy)",
332 base="CN=Policies,CN=System," + basedn,
333 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
334 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
336 res8 = samdb.search(expression="(displayName=Default Domain Controllers"
338 base="CN=Policies,CN=System," + basedn,
339 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
341 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
343 names.policyid_dc = None
344 res9 = idmapdb.search(expression="(cn=%s)" %
345 (security.SID_BUILTIN_ADMINISTRATORS),
348 names.wheel_gid = res9[0]["xidNumber"]
350 raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
354 def newprovision(names, setup_dir, creds, session, smbconf, provdir, logger):
355 """Create a new provision.
357 This provision will be the reference for knowing what has changed in the
358 since the latest upgrade in the current provision
360 :param names: List of provision parameters
361 :param setup_dir: Directory where the setup files are stored
362 :param creds: Credentials for the authentification
363 :param session: Session object
364 :param smbconf: Path to the smb.conf file
365 :param provdir: Directory where the provision will be stored
366 :param logger: A Logger
368 if os.path.isdir(provdir):
369 shutil.rmtree(provdir)
370 os.chdir(os.path.join(setup_dir,".."))
372 logger.info("Provision stored in %s", provdir)
373 provision(setup_dir, logger, session, creds, smbconf=smbconf,
374 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
375 domain=names.domain, domainguid=names.domainguid,
376 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
377 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
378 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
379 invocationid=names.invocation, adminpass=names.adminpass,
380 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
381 nobody=None, wheel=None, users=None,
382 serverrole="domain controller", ldap_backend_extra_port=None,
383 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
384 slapd_path=None, setup_ds_path=None, nosync=None,
385 dom_for_fun_level=names.domainlevel,
386 ldap_dryrun_mode=None, useeadb=True)
390 """Sorts two DNs in the lexicographical order it and put higher level DN
393 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
396 :param x: First object to compare
397 :param y: Second object to compare
399 p = re.compile(r'(?<!\\), ?')
400 tab1 = p.split(str(x))
401 tab2 = p.split(str(y))
402 minimum = min(len(tab1), len(tab2))
405 # Note: python range go up to upper limit but do not include it
406 for i in range(0, minimum):
407 ret = cmp(tab1[len1-i], tab2[len2-i])
412 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
420 def identic_rename(ldbobj, dn):
421 """Perform a back and forth rename to trigger renaming on attribute that
422 can't be directly modified.
424 :param lbdobj: An Ldb Object
425 :param dn: DN of the object to manipulate
427 (before, after) = str(dn).split('=', 1)
428 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)))
429 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn)
433 """Return separate ACE of an ACL
435 :param acl: A string representing the ACL
436 :return: A hash with different parts
439 p = re.compile(r'(\w+)?(\(.*?\))')
447 hash["aces"].append(e[1])
452 def chunck_sddl(sddl):
453 """ Return separate parts of the SDDL (owner, group, ...)
455 :param sddl: An string containing the SDDL to chunk
456 :return: A hash with the different chunk
459 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
460 tab = p.findall(sddl)
476 def get_diff_sddls(refsddl, cursddl):
477 """Get the difference between 2 sddl
479 This function split the textual representation of ACL into smaller
480 chunck in order to not to report a simple permutation as a difference
482 :param refsddl: First sddl to compare
483 :param cursddl: Second sddl to compare
484 :return: A string that explain difference between sddls
488 hash_new = chunck_sddl(cursddl)
489 hash_ref = chunck_sddl(refsddl)
491 if hash_new["owner"] != hash_ref["owner"]:
492 txt = "\tOwner mismatch: %s (in ref) %s" \
493 "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
495 if hash_new["group"] != hash_ref["group"]:
496 txt = "%s\tGroup mismatch: %s (in ref) %s" \
497 "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
499 for part in ["dacl", "sacl"]:
500 if hash_new.has_key(part) and hash_ref.has_key(part):
502 # both are present, check if they contain the same ACE
505 c_new = chunck_acl(hash_new[part])
506 c_ref = chunck_acl(hash_ref[part])
508 for elem in c_new["aces"]:
511 for elem in c_ref["aces"]:
519 if len(h_new) + len(h_ref) > 0:
520 txt = "%s\tPart %s is different between reference" \
521 " and current here is the detail:\n" % (txt, part)
524 txt = "%s\t\t%s ACE is not present in the" \
525 " reference\n" % (txt, item)
528 txt = "%s\t\t%s ACE is not present in the" \
529 " current\n" % (txt, item)
531 elif hash_new.has_key(part) and not hash_ref.has_key(part):
532 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
533 elif not hash_new.has_key(part) and hash_ref.has_key(part):
534 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
539 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
540 """Update secrets.ldb
542 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
543 of the reference provision
544 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
545 of the updated provision
548 messagefunc(SIMPLE, "update secrets.ldb")
549 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
551 current = secrets_ldb.search(expression="dn=@MODULES", base="",
553 assert reference, "Reference modules list can not be empty"
554 if len(current) == 0:
556 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
557 delta.dn = reference[0].dn
558 secrets_ldb.add(reference[0])
560 delta = secrets_ldb.msg_diff(current[0], reference[0])
561 delta.dn = current[0].dn
562 secrets_ldb.modify(delta)
564 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
565 scope=SCOPE_SUBTREE, attrs=["dn"])
566 current = secrets_ldb.search(expression="objectClass=top", base="",
567 scope=SCOPE_SUBTREE, attrs=["dn"])
573 empty = ldb.Message()
574 for i in range(0, len(reference)):
575 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
577 # Create a hash for speeding the search of existing object in the
579 for i in range(0, len(current)):
580 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
582 for k in hash_new.keys():
583 if not hash.has_key(k):
584 listMissing.append(hash_new[k])
586 listPresent.append(hash_new[k])
588 for entry in listMissing:
589 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
590 base="", scope=SCOPE_SUBTREE)
591 current = secrets_ldb.search(expression="dn=%s" % entry,
592 base="", scope=SCOPE_SUBTREE)
593 delta = secrets_ldb.msg_diff(empty, reference[0])
594 for att in hashAttrNotCopied.keys():
596 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
599 messagefunc(CHANGE, " Adding attribute %s" % att)
600 delta.dn = reference[0].dn
601 secrets_ldb.add(delta)
603 for entry in listPresent:
604 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
605 base="", scope=SCOPE_SUBTREE)
606 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
608 delta = secrets_ldb.msg_diff(current[0], reference[0])
609 for att in hashAttrNotCopied.keys():
613 messagefunc(CHANGE, "Found attribute name on %s,"
614 " must rename the DN" % (current[0].dn))
615 identic_rename(secrets_ldb, reference[0].dn)
619 for entry in listPresent:
620 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
622 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
624 delta = secrets_ldb.msg_diff(current[0], reference[0])
625 for att in hashAttrNotCopied.keys():
628 if att == "msDS-KeyVersionNumber":
632 "Adding/Changing attribute %s to %s" %
633 (att, current[0].dn))
635 delta.dn = current[0].dn
636 secrets_ldb.modify(delta)
638 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
639 scope=SCOPE_SUBTREE, attrs=["dn"])
642 messagefunc(SIMPLE, "Remove old dns account")
643 secrets_ldb.delete(res2[0]["dn"])
646 def getOEMInfo(samdb, rootdn):
647 """Return OEM Information on the top level Samba4 use to store version
650 :param samdb: An LDB object connect to sam.ldb
651 :param rootdn: Root DN of the domain
652 :return: The content of the field oEMInformation (if any)
654 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
655 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
657 info = res[0]["oEMInformation"]
663 def updateOEMInfo(samdb, rootdn):
664 """Update the OEMinfo field to add information about upgrade
666 :param samdb: an LDB object connected to the sam DB
667 :param rootdn: The string representation of the root DN of
668 the provision (ie. DC=...,DC=...)
670 res = samdb.search(expression="(objectClass=*)", base=rootdn,
671 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
673 info = res[0]["oEMInformation"]
674 info = "%s, upgrade to %s" % (info, version)
675 delta = ldb.Message()
676 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
677 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
681 def update_gpo(paths, samdb, names, lp, message, force=0):
682 """Create missing GPO file object if needed
684 Set ACL correctly also.
685 Check ACLs for sysvol/netlogon dirs also
689 ntacls.checkset_backend(lp, None, None)
690 eadbname = lp.get("posix:eadb")
691 if eadbname is not None and eadbname != "":
693 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
694 paths.sysvol, xattr.XATTR_NTACL_NAME)
696 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
697 xattr.XATTR_NTACL_NAME)
699 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
700 xattr.XATTR_NTACL_NAME)
707 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
708 if not os.path.isdir(dir):
709 create_gpo_struct(dir)
711 if names.policyid_dc is None:
712 raise ProvisioningError("Policy ID for Domain controller is missing")
713 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
714 if not os.path.isdir(dir):
715 create_gpo_struct(dir)
716 # We always reinforce acls on GPO folder because they have to be in sync
719 set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
720 names.domaindn, samdb, lp)
722 message(ERROR, "Unable to set ACLs on policies related objects,"
723 " if not using posix:eadb, you must be root to do it")
727 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
728 names.domainsid, names.dnsdomain, names.domaindn, lp)
730 message(ERROR, "Unable to set ACLs on sysvol share, if not using"
731 "posix:eadb, you must be root to do it")
733 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
734 """For a given hash associating dn and a number, this function will
735 update the replPropertyMetaData of each dn in the hash, so that the
736 calculated value of the msDs-KeyVersionNumber is equal or superior to the
737 one associated to the given dn.
739 :param samdb: An SamDB object pointing to the sam
740 :param rootdn: The base DN where we want to start
741 :param hashDns: A hash with dn as key and number representing the
742 minimum value of msDs-KeyVersionNumber that we want to
745 entry = samdb.search(expression='(objectClass=user)',
746 base=ldb.Dn(samdb,str(rootdn)),
747 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
748 controls=["search_options:1:2"])
752 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
755 if hashDns.has_key(str(e.dn).lower()):
756 val = e.get("msDs-KeyVersionNumber")
759 version = int(str(hashDns[str(e.dn).lower()]))
760 if int(str(val)) < version:
762 samdb.set_attribute_replmetadata_version(str(e.dn),
765 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
766 """Update the provision container db: sam.ldb
767 This function is aimed for alpha9 and newer;
769 :param refsampath: Path to the samdb in the reference provision
770 :param sampath: Path to the samdb in the upgraded provision
771 :param creds: Credential used for openning LDB files
772 :param session: Session to use for openning LDB files
773 :param lp: A loadparam object
774 :return: A msg_diff object with the difference between the @ATTRIBUTES
775 of the current provision and the reference provision
779 "Update base samdb by searching difference with reference one")
780 refsam = Ldb(refsampath, session_info=session, credentials=creds,
781 lp=lp, options=["modules:"])
782 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
783 options=["modules:"])
785 empty = ldb.Message()
787 reference = refsam.search(expression="")
789 for refentry in reference:
790 entry = sam.search(expression="dn=%s" % refentry["dn"],
793 delta = sam.msg_diff(empty, refentry)
794 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
795 if str(refentry.dn) == "@PROVISION" and\
796 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
797 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
798 delta.dn = refentry.dn
801 delta = sam.msg_diff(entry[0], refentry)
802 if str(refentry.dn) == "@ATTRIBUTES":
803 deltaattr = sam.msg_diff(refentry, entry[0])
804 if str(refentry.dn) == "@PROVISION" and\
805 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
806 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
807 if len(delta.items()) > 1:
808 delta.dn = refentry.dn
814 def construct_existor_expr(attrs):
815 """Construct a exists or LDAP search expression.
817 :param attrs: List of attribute on which we want to create the search
819 :return: A string representing the expression, if attrs is empty an
820 empty string is returned
826 expr = "%s(%s=*)"%(expr,att)
830 def update_machine_account_password(samdb, secrets_ldb, names):
831 """Update (change) the password of the current DC both in the SAM db and in
834 :param samdb: An LDB object related to the sam.ldb file of a given provision
835 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
837 :param names: List of key provision parameters"""
839 expression = "samAccountName=%s$" % names.netbiosname
840 secrets_msg = secrets_ldb.search(expression=expression,
841 attrs=["secureChannelType"])
842 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
843 res = samdb.search(expression=expression, attrs=[])
844 assert(len(res) == 1)
846 msg = ldb.Message(res[0].dn)
847 machinepass = samba.generate_random_password(128, 255)
848 mputf16 = machinepass.encode('utf-16-le')
849 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
850 ldb.FLAG_MOD_REPLACE,
854 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
855 attrs=["msDs-keyVersionNumber"])
856 assert(len(res) == 1)
857 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
858 secChanType = int(secrets_msg[0]["secureChannelType"][0])
860 secretsdb_self_join(secrets_ldb, domain=names.domain,
862 domainsid=names.domainsid,
863 dnsdomain=names.dnsdomain,
864 netbiosname=names.netbiosname,
865 machinepass=machinepass,
866 key_version_number=kvno,
867 secure_channel_type=secChanType)
869 raise ProvisioningError("Unable to find a Secure Channel"
870 "of type SEC_CHAN_BDC")
872 def update_dns_account_password(samdb, secrets_ldb, names):
873 """Update (change) the password of the dns both in the SAM db and in
876 :param samdb: An LDB object related to the sam.ldb file of a given provision
877 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
879 :param names: List of key provision parameters"""
881 expression = "samAccountName=dns-%s" % names.netbiosname
882 secrets_msg = secrets_ldb.search(expression=expression)
883 if len(secrets_msg) == 1:
884 res = samdb.search(expression=expression, attrs=[])
885 assert(len(res) == 1)
887 msg = ldb.Message(res[0].dn)
888 machinepass = samba.generate_random_password(128, 255)
889 mputf16 = machinepass.encode('utf-16-le')
890 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
891 ldb.FLAG_MOD_REPLACE,
896 res = samdb.search(expression=expression,
897 attrs=["msDs-keyVersionNumber"])
898 assert(len(res) == 1)
899 kvno = str(res[0]["msDs-keyVersionNumber"])
901 msg = ldb.Message(secrets_msg[0].dn)
902 msg["secret"] = ldb.MessageElement(machinepass,
903 ldb.FLAG_MOD_REPLACE,
905 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
906 ldb.FLAG_MOD_REPLACE,
907 "msDS-KeyVersionNumber")
909 secrets_ldb.modify(msg)
911 raise ProvisioningError("Unable to find an object"
912 " with %s" % expression )
914 def search_constructed_attrs_stored(samdb, rootdn, attrs):
915 """Search a given sam DB for calculated attributes that are
916 still stored in the db.
918 :param samdb: An LDB object pointing to the sam
919 :param rootdn: The base DN where the search should start
920 :param attrs: A list of attributes to be searched
921 :return: A hash with attributes as key and an array of
922 array. Each array contains the dn and the associated
923 values for this attribute as they are stored in the
927 expr = construct_existor_expr(attrs)
930 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
931 scope=SCOPE_SUBTREE, attrs=attrs,
932 controls=["search_options:1:2","bypassoperational:0"])
940 if hashAtt.has_key(att):
941 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
944 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
948 def int64range2str(value):
949 """Display the int64 range stored in value as xxx-yyy
951 :param value: The int64 range
952 :return: A string of the representation of the range
956 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)