s4-upgradeprovisision: fix bug 8063, old SD can miss some componenent (group, owner...
[Samba/gbeck.git] / source4 / scripting / python / samba / upgradehelpers.py
blobad5de73b5bbd5e419055ebbfd9d2402255e7e2dc
1 #!/usr/bin/env python
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 """Helpers used for upgrading between different database formats."""
26 import os
27 import re
28 import shutil
29 import samba
31 from samba import Ldb, version, ntacls
32 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
33 import ldb
34 from samba.provision import (provision_paths_from_lp,
35 getpolicypath, set_gpos_acl, create_gpo_struct,
36 FILL_FULL, provision, ProvisioningError,
37 setsysvolacl, secretsdb_self_join)
38 from samba.dcerpc import xattr
39 from samba.dcerpc.misc import SEC_CHAN_BDC
40 from samba.samdb import SamDB
42 # All the ldb related to registry are commented because the path for them is
43 # relative in the provisionPath object
44 # And so opening them create a file in the current directory which is not what
45 # we want
46 # I still keep them commented because I plan soon to make more cleaner
47 ERROR = -1
48 SIMPLE = 0x00
49 CHANGE = 0x01
50 CHANGESD = 0x02
51 GUESS = 0x04
52 PROVISION = 0x08
53 CHANGEALL = 0xff
55 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
56 "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
57 "objectCategory", "distinguishedName", "nTMixedDomain",
58 "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
59 "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
60 "ntPwdHistory", "unicodePwd","dBCSPwd", "supplementalCredentials",
61 "gPCUserExtensionNames", "gPCMachineExtensionNames","maxPwdAge", "secret",
62 "possibleInferiors", "privilege", "sAMAccountType"])
65 class ProvisionLDB(object):
67 def __init__(self):
68 self.sam = None
69 self.secrets = None
70 self.idmap = None
71 self.privilege = None
72 self.hkcr = None
73 self.hkcu = None
74 self.hku = None
75 self.hklm = None
77 def startTransactions(self):
78 self.sam.transaction_start()
79 self.secrets.transaction_start()
80 self.idmap.transaction_start()
81 self.privilege.transaction_start()
82 # TO BE DONE
83 # self.hkcr.transaction_start()
84 # self.hkcu.transaction_start()
85 # self.hku.transaction_start()
86 # self.hklm.transaction_start()
88 def groupedRollback(self):
89 ok = True
90 try:
91 self.sam.transaction_cancel()
92 except Exception:
93 ok = False
95 try:
96 self.secrets.transaction_cancel()
97 except Exception:
98 ok = False
100 try:
101 self.idmap.transaction_cancel()
102 except Exception:
103 ok = False
105 try:
106 self.privilege.transaction_cancel()
107 except Exception:
108 ok = False
110 return ok
111 # TO BE DONE
112 # self.hkcr.transaction_cancel()
113 # self.hkcu.transaction_cancel()
114 # self.hku.transaction_cancel()
115 # self.hklm.transaction_cancel()
117 def groupedCommit(self):
118 try:
119 self.sam.transaction_prepare_commit()
120 self.secrets.transaction_prepare_commit()
121 self.idmap.transaction_prepare_commit()
122 self.privilege.transaction_prepare_commit()
123 except Exception:
124 return self.groupedRollback()
125 # TO BE DONE
126 # self.hkcr.transaction_prepare_commit()
127 # self.hkcu.transaction_prepare_commit()
128 # self.hku.transaction_prepare_commit()
129 # self.hklm.transaction_prepare_commit()
130 try:
131 self.sam.transaction_commit()
132 self.secrets.transaction_commit()
133 self.idmap.transaction_commit()
134 self.privilege.transaction_commit()
135 except Exception:
136 return self.groupedRollback()
138 # TO BE DONE
139 # self.hkcr.transaction_commit()
140 # self.hkcu.transaction_commit()
141 # self.hku.transaction_commit()
142 # self.hklm.transaction_commit()
143 return True
145 def get_ldbs(paths, creds, session, lp):
146 """Return LDB object mapped on most important databases
148 :param paths: An object holding the different importants paths for provision object
149 :param creds: Credential used for openning LDB files
150 :param session: Session to use for openning LDB files
151 :param lp: A loadparam object
152 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
154 ldbs = ProvisionLDB()
156 ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
157 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
158 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
159 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
160 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
161 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
162 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
163 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
165 return ldbs
168 def usn_in_range(usn, range):
169 """Check if the usn is in one of the range provided.
170 To do so, the value is checked to be between the lower bound and
171 higher bound of a range
173 :param usn: A integer value corresponding to the usn that we want to update
174 :param range: A list of integer representing ranges, lower bounds are in
175 the even indices, higher in odd indices
176 :return: True if the usn is in one of the range, False otherwise
179 idx = 0
180 cont = True
181 ok = False
182 while cont:
183 if idx == len(range):
184 cont = False
185 continue
186 if usn < int(range[idx]):
187 if idx %2 == 1:
188 ok = True
189 cont = False
190 if usn == int(range[idx]):
191 cont = False
192 ok = True
193 idx = idx + 1
194 return ok
197 def get_paths(param, targetdir=None, smbconf=None):
198 """Get paths to important provision objects (smb.conf, ldb files, ...)
200 :param param: Param object
201 :param targetdir: Directory where the provision is (or will be) stored
202 :param smbconf: Path to the smb.conf file
203 :return: A list with the path of important provision objects"""
204 if targetdir is not None:
205 etcdir = os.path.join(targetdir, "etc")
206 if not os.path.exists(etcdir):
207 os.makedirs(etcdir)
208 smbconf = os.path.join(etcdir, "smb.conf")
209 if smbconf is None:
210 smbconf = param.default_path()
212 if not os.path.exists(smbconf):
213 raise ProvisioningError("Unable to find smb.conf")
215 lp = param.LoadParm()
216 lp.load(smbconf)
217 paths = provision_paths_from_lp(lp, lp.get("realm"))
218 return paths
220 def update_policyids(names, samdb):
221 """Update policy ids that could have changed after sam update
223 :param names: List of key provision parameters
224 :param samdb: An Ldb object conntected with the sam DB
226 # policy guid
227 res = samdb.search(expression="(displayName=Default Domain Policy)",
228 base="CN=Policies,CN=System," + str(names.rootdn),
229 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
230 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
231 # dc policy guid
232 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
233 " Policy)",
234 base="CN=Policies,CN=System," + str(names.rootdn),
235 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
236 if len(res2) == 1:
237 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
238 else:
239 names.policyid_dc = None
242 def newprovision(names, creds, session, smbconf, provdir, logger):
243 """Create a new provision.
245 This provision will be the reference for knowing what has changed in the
246 since the latest upgrade in the current provision
248 :param names: List of provision parameters
249 :param creds: Credentials for the authentification
250 :param session: Session object
251 :param smbconf: Path to the smb.conf file
252 :param provdir: Directory where the provision will be stored
253 :param logger: A Logger
255 if os.path.isdir(provdir):
256 shutil.rmtree(provdir)
257 os.mkdir(provdir)
258 logger.info("Provision stored in %s", provdir)
259 provision(logger, session, creds, smbconf=smbconf,
260 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
261 domain=names.domain, domainguid=names.domainguid,
262 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
263 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
264 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
265 invocationid=names.invocation, adminpass=names.adminpass,
266 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
267 nobody=None, wheel=None, users=None,
268 serverrole="domain controller", ldap_backend_extra_port=None,
269 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
270 slapd_path=None, setup_ds_path=None, nosync=None,
271 dom_for_fun_level=names.domainlevel,
272 ldap_dryrun_mode=None, useeadb=True)
275 def dn_sort(x, y):
276 """Sorts two DNs in the lexicographical order it and put higher level DN
277 before.
279 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
280 smaller
282 :param x: First object to compare
283 :param y: Second object to compare
285 p = re.compile(r'(?<!\\), ?')
286 tab1 = p.split(str(x))
287 tab2 = p.split(str(y))
288 minimum = min(len(tab1), len(tab2))
289 len1 = len(tab1)-1
290 len2 = len(tab2)-1
291 # Note: python range go up to upper limit but do not include it
292 for i in range(0, minimum):
293 ret = cmp(tab1[len1-i], tab2[len2-i])
294 if ret != 0:
295 return ret
296 else:
297 if i == minimum-1:
298 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
299 if len1 > len2:
300 return 1
301 else:
302 return -1
303 return ret
306 def identic_rename(ldbobj, dn):
307 """Perform a back and forth rename to trigger renaming on attribute that
308 can't be directly modified.
310 :param lbdobj: An Ldb Object
311 :param dn: DN of the object to manipulate
313 (before, after) = str(dn).split('=', 1)
314 # we need to use relax to avoid the subtree_rename constraints
315 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
316 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
319 def chunck_acl(acl):
320 """Return separate ACE of an ACL
322 :param acl: A string representing the ACL
323 :return: A hash with different parts
326 p = re.compile(r'(\w+)?(\(.*?\))')
327 tab = p.findall(acl)
329 hash = {}
330 hash["aces"] = []
331 for e in tab:
332 if len(e[0]) > 0:
333 hash["flags"] = e[0]
334 hash["aces"].append(e[1])
336 return hash
339 def chunck_sddl(sddl):
340 """ Return separate parts of the SDDL (owner, group, ...)
342 :param sddl: An string containing the SDDL to chunk
343 :return: A hash with the different chunk
346 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
347 tab = p.findall(sddl)
349 hash = {}
350 for e in tab:
351 if e[0] == "O:":
352 hash["owner"] = e[1]
353 if e[0] == "G:":
354 hash["group"] = e[1]
355 if e[0] == "D:":
356 hash["dacl"] = e[1]
357 if e[0] == "S:":
358 hash["sacl"] = e[1]
360 return hash
363 def get_diff_sddls(refsddl, cursddl):
364 """Get the difference between 2 sddl
366 This function split the textual representation of ACL into smaller
367 chunck in order to not to report a simple permutation as a difference
369 :param refsddl: First sddl to compare
370 :param cursddl: Second sddl to compare
371 :return: A string that explain difference between sddls
374 txt = ""
375 hash_cur = chunck_sddl(cursddl)
376 hash_ref = chunck_sddl(refsddl)
378 if not hash_cur.has_key("owner"):
379 txt = "\tNo owner in current SD"
380 elif hash_cur["owner"] != hash_ref["owner"]:
381 txt = "\tOwner mismatch: %s (in ref) %s" \
382 "(in current)\n" % (hash_ref["owner"], hash_cur["owner"])
384 if not hash_cur.has_key("group"):
385 txt = "%s\tNo group in current SD" % txt
386 elif hash_cur["group"] != hash_ref["group"]:
387 txt = "%s\tGroup mismatch: %s (in ref) %s" \
388 "(in current)\n" % (txt, hash_ref["group"], hash_cur["group"])
390 for part in ["dacl", "sacl"]:
391 if hash_cur.has_key(part) and hash_ref.has_key(part):
393 # both are present, check if they contain the same ACE
394 h_cur = set()
395 h_ref = set()
396 c_cur = chunck_acl(hash_cur[part])
397 c_ref = chunck_acl(hash_ref[part])
399 for elem in c_cur["aces"]:
400 h_cur.add(elem)
402 for elem in c_ref["aces"]:
403 h_ref.add(elem)
405 for k in set(h_ref):
406 if k in h_cur:
407 h_cur.remove(k)
408 h_ref.remove(k)
410 if len(h_cur) + len(h_ref) > 0:
411 txt = "%s\tPart %s is different between reference" \
412 " and current here is the detail:\n" % (txt, part)
414 for item in h_cur:
415 txt = "%s\t\t%s ACE is not present in the" \
416 " reference\n" % (txt, item)
418 for item in h_ref:
419 txt = "%s\t\t%s ACE is not present in the" \
420 " current\n" % (txt, item)
422 elif hash_cur.has_key(part) and not hash_ref.has_key(part):
423 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
424 elif not hash_cur.has_key(part) and hash_ref.has_key(part):
425 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
427 return txt
430 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
431 """Update secrets.ldb
433 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
434 of the reference provision
435 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
436 of the updated provision
439 messagefunc(SIMPLE, "update secrets.ldb")
440 reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
441 scope=SCOPE_SUBTREE)
442 current = secrets_ldb.search(expression="dn=@MODULES", base="",
443 scope=SCOPE_SUBTREE)
444 assert reference, "Reference modules list can not be empty"
445 if len(current) == 0:
446 # No modules present
447 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
448 delta.dn = reference[0].dn
449 secrets_ldb.add(reference[0])
450 else:
451 delta = secrets_ldb.msg_diff(current[0], reference[0])
452 delta.dn = current[0].dn
453 secrets_ldb.modify(delta)
455 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
456 scope=SCOPE_SUBTREE, attrs=["dn"])
457 current = secrets_ldb.search(expression="objectClass=top", base="",
458 scope=SCOPE_SUBTREE, attrs=["dn"])
459 hash_new = {}
460 hash = {}
461 listMissing = []
462 listPresent = []
464 empty = ldb.Message()
465 for i in range(0, len(reference)):
466 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
468 # Create a hash for speeding the search of existing object in the
469 # current provision
470 for i in range(0, len(current)):
471 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
473 for k in hash_new.keys():
474 if not hash.has_key(k):
475 listMissing.append(hash_new[k])
476 else:
477 listPresent.append(hash_new[k])
479 for entry in listMissing:
480 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
481 base="", scope=SCOPE_SUBTREE)
482 current = secrets_ldb.search(expression="dn=%s" % entry,
483 base="", scope=SCOPE_SUBTREE)
484 delta = secrets_ldb.msg_diff(empty, reference[0])
485 for att in hashAttrNotCopied:
486 delta.remove(att)
487 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
488 reference[0].dn)
489 for att in delta:
490 messagefunc(CHANGE, " Adding attribute %s" % att)
491 delta.dn = reference[0].dn
492 secrets_ldb.add(delta)
494 for entry in listPresent:
495 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
496 base="", scope=SCOPE_SUBTREE)
497 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
498 scope=SCOPE_SUBTREE)
499 delta = secrets_ldb.msg_diff(current[0], reference[0])
500 for att in hashAttrNotCopied:
501 delta.remove(att)
502 for att in delta:
503 if att == "name":
504 messagefunc(CHANGE, "Found attribute name on %s,"
505 " must rename the DN" % (current[0].dn))
506 identic_rename(secrets_ldb, reference[0].dn)
507 else:
508 delta.remove(att)
510 for entry in listPresent:
511 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
512 scope=SCOPE_SUBTREE)
513 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
514 scope=SCOPE_SUBTREE)
515 delta = secrets_ldb.msg_diff(current[0], reference[0])
516 for att in hashAttrNotCopied:
517 delta.remove(att)
518 for att in delta:
519 if att == "msDS-KeyVersionNumber":
520 delta.remove(att)
521 if att != "dn":
522 messagefunc(CHANGE,
523 "Adding/Changing attribute %s to %s" %
524 (att, current[0].dn))
526 delta.dn = current[0].dn
527 secrets_ldb.modify(delta)
529 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
530 scope=SCOPE_SUBTREE, attrs=["dn"])
532 if (len(res2) == 1):
533 messagefunc(SIMPLE, "Remove old dns account")
534 secrets_ldb.delete(res2[0]["dn"])
537 def getOEMInfo(samdb, rootdn):
538 """Return OEM Information on the top level Samba4 use to store version
539 info in this field
541 :param samdb: An LDB object connect to sam.ldb
542 :param rootdn: Root DN of the domain
543 :return: The content of the field oEMInformation (if any)
545 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
546 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
547 if len(res) > 0:
548 info = res[0]["oEMInformation"]
549 return info
550 else:
551 return ""
554 def updateOEMInfo(samdb, rootdn):
555 """Update the OEMinfo field to add information about upgrade
557 :param samdb: an LDB object connected to the sam DB
558 :param rootdn: The string representation of the root DN of
559 the provision (ie. DC=...,DC=...)
561 res = samdb.search(expression="(objectClass=*)", base=rootdn,
562 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
563 if len(res) > 0:
564 info = res[0]["oEMInformation"]
565 info = "%s, upgrade to %s" % (info, version)
566 delta = ldb.Message()
567 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
568 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
569 "oEMInformation" )
570 samdb.modify(delta)
572 def update_gpo(paths, samdb, names, lp, message, force=0):
573 """Create missing GPO file object if needed
575 Set ACL correctly also.
576 Check ACLs for sysvol/netlogon dirs also
578 resetacls = False
579 try:
580 ntacls.checkset_backend(lp, None, None)
581 eadbname = lp.get("posix:eadb")
582 if eadbname is not None and eadbname != "":
583 try:
584 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
585 paths.sysvol, xattr.XATTR_NTACL_NAME)
586 except Exception:
587 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
588 xattr.XATTR_NTACL_NAME)
589 else:
590 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
591 xattr.XATTR_NTACL_NAME)
592 except Exception:
593 resetacls = True
595 if force:
596 resetacls = True
598 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
599 if not os.path.isdir(dir):
600 create_gpo_struct(dir)
602 if names.policyid_dc is None:
603 raise ProvisioningError("Policy ID for Domain controller is missing")
604 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
605 if not os.path.isdir(dir):
606 create_gpo_struct(dir)
607 # We always reinforce acls on GPO folder because they have to be in sync
608 # with the one in DS
609 try:
610 set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
611 names.domaindn, samdb, lp)
612 except TypeError, e:
613 message(ERROR, "Unable to set ACLs on policies related objects,"
614 " if not using posix:eadb, you must be root to do it")
616 if resetacls:
617 try:
618 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
619 names.domainsid, names.dnsdomain, names.domaindn, lp)
620 except TypeError, e:
621 message(ERROR, "Unable to set ACLs on sysvol share, if not using"
622 "posix:eadb, you must be root to do it")
624 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
625 """For a given hash associating dn and a number, this function will
626 update the replPropertyMetaData of each dn in the hash, so that the
627 calculated value of the msDs-KeyVersionNumber is equal or superior to the
628 one associated to the given dn.
630 :param samdb: An SamDB object pointing to the sam
631 :param rootdn: The base DN where we want to start
632 :param hashDns: A hash with dn as key and number representing the
633 minimum value of msDs-KeyVersionNumber that we want to
634 have
636 entry = samdb.search(expression='(objectClass=user)',
637 base=ldb.Dn(samdb,str(rootdn)),
638 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
639 controls=["search_options:1:2"])
640 done = 0
641 hashDone = {}
642 if len(entry) == 0:
643 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
644 else:
645 for e in entry:
646 if hashDns.has_key(str(e.dn).lower()):
647 val = e.get("msDs-KeyVersionNumber")
648 if not val:
649 val = "0"
650 version = int(str(hashDns[str(e.dn).lower()]))
651 if int(str(val)) < version:
652 done = done + 1
653 samdb.set_attribute_replmetadata_version(str(e.dn),
654 "unicodePwd",
655 version, True)
656 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
657 """Update the provision container db: sam.ldb
658 This function is aimed for alpha9 and newer;
660 :param refsampath: Path to the samdb in the reference provision
661 :param sampath: Path to the samdb in the upgraded provision
662 :param creds: Credential used for openning LDB files
663 :param session: Session to use for openning LDB files
664 :param lp: A loadparam object
665 :return: A msg_diff object with the difference between the @ATTRIBUTES
666 of the current provision and the reference provision
669 message(SIMPLE,
670 "Update base samdb by searching difference with reference one")
671 refsam = Ldb(refsampath, session_info=session, credentials=creds,
672 lp=lp, options=["modules:"])
673 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
674 options=["modules:"])
676 empty = ldb.Message()
677 deltaattr = None
678 reference = refsam.search(expression="")
680 for refentry in reference:
681 entry = sam.search(expression="dn=%s" % refentry["dn"],
682 scope=SCOPE_SUBTREE)
683 if not len(entry):
684 delta = sam.msg_diff(empty, refentry)
685 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
686 if str(refentry.dn) == "@PROVISION" and\
687 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
688 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
689 delta.dn = refentry.dn
690 sam.add(delta)
691 else:
692 delta = sam.msg_diff(entry[0], refentry)
693 if str(refentry.dn) == "@ATTRIBUTES":
694 deltaattr = sam.msg_diff(refentry, entry[0])
695 if str(refentry.dn) == "@PROVISION" and\
696 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
697 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
698 if len(delta.items()) > 1:
699 delta.dn = refentry.dn
700 sam.modify(delta)
702 return deltaattr
705 def construct_existor_expr(attrs):
706 """Construct a exists or LDAP search expression.
708 :param attrs: List of attribute on which we want to create the search
709 expression.
710 :return: A string representing the expression, if attrs is empty an
711 empty string is returned
713 expr = ""
714 if len(attrs) > 0:
715 expr = "(|"
716 for att in attrs:
717 expr = "%s(%s=*)"%(expr,att)
718 expr = "%s)"%expr
719 return expr
721 def update_machine_account_password(samdb, secrets_ldb, names):
722 """Update (change) the password of the current DC both in the SAM db and in
723 secret one
725 :param samdb: An LDB object related to the sam.ldb file of a given provision
726 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
727 provision
728 :param names: List of key provision parameters"""
730 expression = "samAccountName=%s$" % names.netbiosname
731 secrets_msg = secrets_ldb.search(expression=expression,
732 attrs=["secureChannelType"])
733 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
734 res = samdb.search(expression=expression, attrs=[])
735 assert(len(res) == 1)
737 msg = ldb.Message(res[0].dn)
738 machinepass = samba.generate_random_password(128, 255)
739 mputf16 = machinepass.encode('utf-16-le')
740 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
741 ldb.FLAG_MOD_REPLACE,
742 "clearTextPassword")
743 samdb.modify(msg)
745 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
746 attrs=["msDs-keyVersionNumber"])
747 assert(len(res) == 1)
748 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
749 secChanType = int(secrets_msg[0]["secureChannelType"][0])
751 secretsdb_self_join(secrets_ldb, domain=names.domain,
752 realm=names.realm,
753 domainsid=names.domainsid,
754 dnsdomain=names.dnsdomain,
755 netbiosname=names.netbiosname,
756 machinepass=machinepass,
757 key_version_number=kvno,
758 secure_channel_type=secChanType)
759 else:
760 raise ProvisioningError("Unable to find a Secure Channel"
761 "of type SEC_CHAN_BDC")
763 def update_dns_account_password(samdb, secrets_ldb, names):
764 """Update (change) the password of the dns both in the SAM db and in
765 secret one
767 :param samdb: An LDB object related to the sam.ldb file of a given provision
768 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
769 provision
770 :param names: List of key provision parameters"""
772 expression = "samAccountName=dns-%s" % names.netbiosname
773 secrets_msg = secrets_ldb.search(expression=expression)
774 if len(secrets_msg) == 1:
775 res = samdb.search(expression=expression, attrs=[])
776 assert(len(res) == 1)
778 msg = ldb.Message(res[0].dn)
779 machinepass = samba.generate_random_password(128, 255)
780 mputf16 = machinepass.encode('utf-16-le')
781 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
782 ldb.FLAG_MOD_REPLACE,
783 "clearTextPassword")
785 samdb.modify(msg)
787 res = samdb.search(expression=expression,
788 attrs=["msDs-keyVersionNumber"])
789 assert(len(res) == 1)
790 kvno = str(res[0]["msDs-keyVersionNumber"])
792 msg = ldb.Message(secrets_msg[0].dn)
793 msg["secret"] = ldb.MessageElement(machinepass,
794 ldb.FLAG_MOD_REPLACE,
795 "secret")
796 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
797 ldb.FLAG_MOD_REPLACE,
798 "msDS-KeyVersionNumber")
800 secrets_ldb.modify(msg)
801 else:
802 raise ProvisioningError("Unable to find an object"
803 " with %s" % expression )
805 def search_constructed_attrs_stored(samdb, rootdn, attrs):
806 """Search a given sam DB for calculated attributes that are
807 still stored in the db.
809 :param samdb: An LDB object pointing to the sam
810 :param rootdn: The base DN where the search should start
811 :param attrs: A list of attributes to be searched
812 :return: A hash with attributes as key and an array of
813 array. Each array contains the dn and the associated
814 values for this attribute as they are stored in the
815 sam."""
817 hashAtt = {}
818 expr = construct_existor_expr(attrs)
819 if expr == "":
820 return hashAtt
821 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
822 scope=SCOPE_SUBTREE, attrs=attrs,
823 controls=["search_options:1:2","bypassoperational:0"])
824 if len(entry) == 0:
825 # Nothing anymore
826 return hashAtt
828 for ent in entry:
829 for att in attrs:
830 if ent.get(att):
831 if hashAtt.has_key(att):
832 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
833 else:
834 hashAtt[att] = {}
835 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
837 return hashAtt
839 def int64range2str(value):
840 """Display the int64 range stored in value as xxx-yyy
842 :param value: The int64 range
843 :return: A string of the representation of the range
846 lvalue = long(value)
847 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
848 return str