s4 provision: DNS backend should be set by caller
[Samba/gebeck_regimport.git] / source4 / scripting / python / samba / upgradehelpers.py
blob043f629ce537d5bb913f61895f0879701a36e999
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 dns_backend="BIND9_FLATFILE"
260 provision(logger, session, creds, smbconf=smbconf,
261 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
262 domain=names.domain, domainguid=names.domainguid,
263 domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
264 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
265 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
266 invocationid=names.invocation, adminpass=names.adminpass,
267 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
268 nobody=None, wheel=None, users=None,
269 serverrole="domain controller", ldap_backend_extra_port=None,
270 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
271 slapd_path=None, setup_ds_path=None, nosync=None,
272 dom_for_fun_level=names.domainlevel, dns_backend=dns_backend,
273 ldap_dryrun_mode=None, useeadb=True)
276 def dn_sort(x, y):
277 """Sorts two DNs in the lexicographical order it and put higher level DN
278 before.
280 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
281 smaller
283 :param x: First object to compare
284 :param y: Second object to compare
286 p = re.compile(r'(?<!\\), ?')
287 tab1 = p.split(str(x))
288 tab2 = p.split(str(y))
289 minimum = min(len(tab1), len(tab2))
290 len1 = len(tab1)-1
291 len2 = len(tab2)-1
292 # Note: python range go up to upper limit but do not include it
293 for i in range(0, minimum):
294 ret = cmp(tab1[len1-i], tab2[len2-i])
295 if ret != 0:
296 return ret
297 else:
298 if i == minimum-1:
299 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
300 if len1 > len2:
301 return 1
302 else:
303 return -1
304 return ret
307 def identic_rename(ldbobj, dn):
308 """Perform a back and forth rename to trigger renaming on attribute that
309 can't be directly modified.
311 :param lbdobj: An Ldb Object
312 :param dn: DN of the object to manipulate
314 (before, after) = str(dn).split('=', 1)
315 # we need to use relax to avoid the subtree_rename constraints
316 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
317 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
320 def chunck_acl(acl):
321 """Return separate ACE of an ACL
323 :param acl: A string representing the ACL
324 :return: A hash with different parts
327 p = re.compile(r'(\w+)?(\(.*?\))')
328 tab = p.findall(acl)
330 hash = {}
331 hash["aces"] = []
332 for e in tab:
333 if len(e[0]) > 0:
334 hash["flags"] = e[0]
335 hash["aces"].append(e[1])
337 return hash
340 def chunck_sddl(sddl):
341 """ Return separate parts of the SDDL (owner, group, ...)
343 :param sddl: An string containing the SDDL to chunk
344 :return: A hash with the different chunk
347 p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
348 tab = p.findall(sddl)
350 hash = {}
351 for e in tab:
352 if e[0] == "O:":
353 hash["owner"] = e[1]
354 if e[0] == "G:":
355 hash["group"] = e[1]
356 if e[0] == "D:":
357 hash["dacl"] = e[1]
358 if e[0] == "S:":
359 hash["sacl"] = e[1]
361 return hash
364 def get_diff_sddls(refsddl, cursddl, checkSacl = True):
365 """Get the difference between 2 sddl
367 This function split the textual representation of ACL into smaller
368 chunck in order to not to report a simple permutation as a difference
370 :param refsddl: First sddl to compare
371 :param cursddl: Second sddl to compare
372 :param checkSacl: If false we skip the sacl checks
373 :return: A string that explain difference between sddls
376 txt = ""
377 hash_cur = chunck_sddl(cursddl)
378 hash_ref = chunck_sddl(refsddl)
380 if not hash_cur.has_key("owner"):
381 txt = "\tNo owner in current SD"
382 elif hash_cur["owner"] != hash_ref["owner"]:
383 txt = "\tOwner mismatch: %s (in ref) %s" \
384 "(in current)\n" % (hash_ref["owner"], hash_cur["owner"])
386 if not hash_cur.has_key("group"):
387 txt = "%s\tNo group in current SD" % txt
388 elif hash_cur["group"] != hash_ref["group"]:
389 txt = "%s\tGroup mismatch: %s (in ref) %s" \
390 "(in current)\n" % (txt, hash_ref["group"], hash_cur["group"])
392 parts = [ "dacl" ]
393 if checkSacl:
394 parts.append("sacl")
395 for part in parts:
396 if hash_cur.has_key(part) and hash_ref.has_key(part):
398 # both are present, check if they contain the same ACE
399 h_cur = set()
400 h_ref = set()
401 c_cur = chunck_acl(hash_cur[part])
402 c_ref = chunck_acl(hash_ref[part])
404 for elem in c_cur["aces"]:
405 h_cur.add(elem)
407 for elem in c_ref["aces"]:
408 h_ref.add(elem)
410 for k in set(h_ref):
411 if k in h_cur:
412 h_cur.remove(k)
413 h_ref.remove(k)
415 if len(h_cur) + len(h_ref) > 0:
416 txt = "%s\tPart %s is different between reference" \
417 " and current here is the detail:\n" % (txt, part)
419 for item in h_cur:
420 txt = "%s\t\t%s ACE is not present in the" \
421 " reference\n" % (txt, item)
423 for item in h_ref:
424 txt = "%s\t\t%s ACE is not present in the" \
425 " current\n" % (txt, item)
427 elif hash_cur.has_key(part) and not hash_ref.has_key(part):
428 txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
429 elif not hash_cur.has_key(part) and hash_ref.has_key(part):
430 txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
432 return txt
435 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
436 """Update secrets.ldb
438 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
439 of the reference provision
440 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
441 of the updated provision
444 messagefunc(SIMPLE, "Update of secrets.ldb")
445 reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
446 current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
447 assert reference, "Reference modules list can not be empty"
448 if len(current) == 0:
449 # No modules present
450 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
451 delta.dn = reference[0].dn
452 secrets_ldb.add(reference[0])
453 else:
454 delta = secrets_ldb.msg_diff(current[0], reference[0])
455 delta.dn = current[0].dn
456 secrets_ldb.modify(delta)
458 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
459 scope=SCOPE_SUBTREE, attrs=["dn"])
460 current = secrets_ldb.search(expression="objectClass=top", base="",
461 scope=SCOPE_SUBTREE, attrs=["dn"])
462 hash_new = {}
463 hash = {}
464 listMissing = []
465 listPresent = []
467 empty = ldb.Message()
468 for i in range(0, len(reference)):
469 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
471 # Create a hash for speeding the search of existing object in the
472 # current provision
473 for i in range(0, len(current)):
474 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
476 for k in hash_new.keys():
477 if not hash.has_key(k):
478 listMissing.append(hash_new[k])
479 else:
480 listPresent.append(hash_new[k])
482 for entry in listMissing:
483 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
484 base="", scope=SCOPE_SUBTREE)
485 current = secrets_ldb.search(expression="dn=%s" % entry,
486 base="", scope=SCOPE_SUBTREE)
487 delta = secrets_ldb.msg_diff(empty, reference[0])
488 for att in hashAttrNotCopied:
489 delta.remove(att)
490 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
491 reference[0].dn)
492 for att in delta:
493 messagefunc(CHANGE, " Adding attribute %s" % att)
494 delta.dn = reference[0].dn
495 secrets_ldb.add(delta)
497 for entry in listPresent:
498 reference = newsecrets_ldb.search(expression="dn=%s" % entry,
499 base="", scope=SCOPE_SUBTREE)
500 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
501 scope=SCOPE_SUBTREE)
502 delta = secrets_ldb.msg_diff(current[0], reference[0])
503 for att in hashAttrNotCopied:
504 delta.remove(att)
505 for att in delta:
506 if att == "name":
507 messagefunc(CHANGE, "Found attribute name on %s,"
508 " must rename the DN" % (current[0].dn))
509 identic_rename(secrets_ldb, reference[0].dn)
510 else:
511 delta.remove(att)
513 for entry in listPresent:
514 reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
515 scope=SCOPE_SUBTREE)
516 current = secrets_ldb.search(expression="dn=%s" % entry, base="",
517 scope=SCOPE_SUBTREE)
518 delta = secrets_ldb.msg_diff(current[0], reference[0])
519 for att in hashAttrNotCopied:
520 delta.remove(att)
521 for att in delta:
522 if att == "msDS-KeyVersionNumber":
523 delta.remove(att)
524 if att != "dn":
525 messagefunc(CHANGE,
526 "Adding/Changing attribute %s to %s" %
527 (att, current[0].dn))
529 delta.dn = current[0].dn
530 secrets_ldb.modify(delta)
532 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
533 scope=SCOPE_SUBTREE, attrs=["dn"])
535 if (len(res2) == 1):
536 messagefunc(SIMPLE, "Remove old dns account")
537 secrets_ldb.delete(res2[0]["dn"])
540 def getOEMInfo(samdb, rootdn):
541 """Return OEM Information on the top level Samba4 use to store version
542 info in this field
544 :param samdb: An LDB object connect to sam.ldb
545 :param rootdn: Root DN of the domain
546 :return: The content of the field oEMInformation (if any)
548 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
549 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
550 if len(res) > 0 and res[0].get("oEMInformation"):
551 info = res[0]["oEMInformation"]
552 return info
553 else:
554 return ""
557 def updateOEMInfo(samdb, rootdn):
558 """Update the OEMinfo field to add information about upgrade
560 :param samdb: an LDB object connected to the sam DB
561 :param rootdn: The string representation of the root DN of
562 the provision (ie. DC=...,DC=...)
564 res = samdb.search(expression="(objectClass=*)", base=rootdn,
565 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
566 if len(res) > 0:
567 if res[0].get("oEMInformation"):
568 info = str(res[0]["oEMInformation"])
569 else:
570 info = ""
571 info = "%s, upgrade to %s" % (info, version)
572 delta = ldb.Message()
573 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
574 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
575 "oEMInformation" )
576 samdb.modify(delta)
578 def update_gpo(paths, samdb, names, lp, message, force=0):
579 """Create missing GPO file object if needed
581 Set ACL correctly also.
582 Check ACLs for sysvol/netlogon dirs also
584 resetacls = False
585 try:
586 ntacls.checkset_backend(lp, None, None)
587 eadbname = lp.get("posix:eadb")
588 if eadbname is not None and eadbname != "":
589 try:
590 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
591 paths.sysvol, xattr.XATTR_NTACL_NAME)
592 except Exception:
593 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
594 xattr.XATTR_NTACL_NAME)
595 else:
596 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
597 xattr.XATTR_NTACL_NAME)
598 except Exception:
599 resetacls = True
601 if force:
602 resetacls = True
604 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
605 if not os.path.isdir(dir):
606 create_gpo_struct(dir)
608 if names.policyid_dc is None:
609 raise ProvisioningError("Policy ID for Domain controller is missing")
610 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
611 if not os.path.isdir(dir):
612 create_gpo_struct(dir)
614 def acl_error(e):
615 if os.geteuid() == 0:
616 message(ERROR, "Unable to set ACLs on policies related objects: %s" % e)
617 else:
618 message(ERROR, "Unable to set ACLs on policies related objects. "
619 "ACLs must be set as root if file system ACLs "
620 "(rather than posix:eadb) are used.")
622 # We always reinforce acls on GPO folder because they have to be in sync
623 # with the one in DS
624 try:
625 set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
626 names.domaindn, samdb, lp)
627 except TypeError, e:
628 acl_error(e)
630 if resetacls:
631 try:
632 setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
633 names.domainsid, names.dnsdomain, names.domaindn, lp)
634 except TypeError, e:
635 acl_error(e)
638 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
639 """For a given hash associating dn and a number, this function will
640 update the replPropertyMetaData of each dn in the hash, so that the
641 calculated value of the msDs-KeyVersionNumber is equal or superior to the
642 one associated to the given dn.
644 :param samdb: An SamDB object pointing to the sam
645 :param rootdn: The base DN where we want to start
646 :param hashDns: A hash with dn as key and number representing the
647 minimum value of msDs-KeyVersionNumber that we want to
648 have
650 entry = samdb.search(expression='(objectClass=user)',
651 base=ldb.Dn(samdb,str(rootdn)),
652 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
653 controls=["search_options:1:2"])
654 done = 0
655 hashDone = {}
656 if len(entry) == 0:
657 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
658 else:
659 for e in entry:
660 if hashDns.has_key(str(e.dn).lower()):
661 val = e.get("msDs-KeyVersionNumber")
662 if not val:
663 val = "0"
664 version = int(str(hashDns[str(e.dn).lower()]))
665 if int(str(val)) < version:
666 done = done + 1
667 samdb.set_attribute_replmetadata_version(str(e.dn),
668 "unicodePwd",
669 version, True)
670 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
671 """Update the provision container db: sam.ldb
672 This function is aimed for alpha9 and newer;
674 :param refsampath: Path to the samdb in the reference provision
675 :param sampath: Path to the samdb in the upgraded provision
676 :param creds: Credential used for openning LDB files
677 :param session: Session to use for openning LDB files
678 :param lp: A loadparam object
679 :return: A msg_diff object with the difference between the @ATTRIBUTES
680 of the current provision and the reference provision
683 message(SIMPLE,
684 "Update base samdb by searching difference with reference one")
685 refsam = Ldb(refsampath, session_info=session, credentials=creds,
686 lp=lp, options=["modules:"])
687 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
688 options=["modules:"])
690 empty = ldb.Message()
691 deltaattr = None
692 reference = refsam.search(expression="")
694 for refentry in reference:
695 entry = sam.search(expression="dn=%s" % refentry["dn"],
696 scope=SCOPE_SUBTREE)
697 if not len(entry):
698 delta = sam.msg_diff(empty, refentry)
699 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
700 if str(refentry.dn) == "@PROVISION" and\
701 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
702 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
703 delta.dn = refentry.dn
704 sam.add(delta)
705 else:
706 delta = sam.msg_diff(entry[0], refentry)
707 if str(refentry.dn) == "@ATTRIBUTES":
708 deltaattr = sam.msg_diff(refentry, entry[0])
709 if str(refentry.dn) == "@PROVISION" and\
710 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
711 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
712 if len(delta.items()) > 1:
713 delta.dn = refentry.dn
714 sam.modify(delta)
716 return deltaattr
719 def construct_existor_expr(attrs):
720 """Construct a exists or LDAP search expression.
722 :param attrs: List of attribute on which we want to create the search
723 expression.
724 :return: A string representing the expression, if attrs is empty an
725 empty string is returned
727 expr = ""
728 if len(attrs) > 0:
729 expr = "(|"
730 for att in attrs:
731 expr = "%s(%s=*)"%(expr,att)
732 expr = "%s)"%expr
733 return expr
735 def update_machine_account_password(samdb, secrets_ldb, names):
736 """Update (change) the password of the current DC both in the SAM db and in
737 secret one
739 :param samdb: An LDB object related to the sam.ldb file of a given provision
740 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
741 provision
742 :param names: List of key provision parameters"""
744 expression = "samAccountName=%s$" % names.netbiosname
745 secrets_msg = secrets_ldb.search(expression=expression,
746 attrs=["secureChannelType"])
747 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
748 res = samdb.search(expression=expression, attrs=[])
749 assert(len(res) == 1)
751 msg = ldb.Message(res[0].dn)
752 machinepass = samba.generate_random_password(128, 255)
753 mputf16 = machinepass.encode('utf-16-le')
754 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
755 ldb.FLAG_MOD_REPLACE,
756 "clearTextPassword")
757 samdb.modify(msg)
759 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
760 attrs=["msDs-keyVersionNumber"])
761 assert(len(res) == 1)
762 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
763 secChanType = int(secrets_msg[0]["secureChannelType"][0])
765 secretsdb_self_join(secrets_ldb, domain=names.domain,
766 realm=names.realm,
767 domainsid=names.domainsid,
768 dnsdomain=names.dnsdomain,
769 netbiosname=names.netbiosname,
770 machinepass=machinepass,
771 key_version_number=kvno,
772 secure_channel_type=secChanType)
773 else:
774 raise ProvisioningError("Unable to find a Secure Channel"
775 "of type SEC_CHAN_BDC")
777 def update_dns_account_password(samdb, secrets_ldb, names):
778 """Update (change) the password of the dns both in the SAM db and in
779 secret one
781 :param samdb: An LDB object related to the sam.ldb file of a given provision
782 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
783 provision
784 :param names: List of key provision parameters"""
786 expression = "samAccountName=dns-%s" % names.netbiosname
787 secrets_msg = secrets_ldb.search(expression=expression)
788 if len(secrets_msg) == 1:
789 res = samdb.search(expression=expression, attrs=[])
790 assert(len(res) == 1)
792 msg = ldb.Message(res[0].dn)
793 machinepass = samba.generate_random_password(128, 255)
794 mputf16 = machinepass.encode('utf-16-le')
795 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
796 ldb.FLAG_MOD_REPLACE,
797 "clearTextPassword")
799 samdb.modify(msg)
801 res = samdb.search(expression=expression,
802 attrs=["msDs-keyVersionNumber"])
803 assert(len(res) == 1)
804 kvno = str(res[0]["msDs-keyVersionNumber"])
806 msg = ldb.Message(secrets_msg[0].dn)
807 msg["secret"] = ldb.MessageElement(machinepass,
808 ldb.FLAG_MOD_REPLACE,
809 "secret")
810 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
811 ldb.FLAG_MOD_REPLACE,
812 "msDS-KeyVersionNumber")
814 secrets_ldb.modify(msg)
815 else:
816 raise ProvisioningError("Unable to find an object"
817 " with %s" % expression )
819 def search_constructed_attrs_stored(samdb, rootdn, attrs):
820 """Search a given sam DB for calculated attributes that are
821 still stored in the db.
823 :param samdb: An LDB object pointing to the sam
824 :param rootdn: The base DN where the search should start
825 :param attrs: A list of attributes to be searched
826 :return: A hash with attributes as key and an array of
827 array. Each array contains the dn and the associated
828 values for this attribute as they are stored in the
829 sam."""
831 hashAtt = {}
832 expr = construct_existor_expr(attrs)
833 if expr == "":
834 return hashAtt
835 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
836 scope=SCOPE_SUBTREE, attrs=attrs,
837 controls=["search_options:1:2","bypassoperational:0"])
838 if len(entry) == 0:
839 # Nothing anymore
840 return hashAtt
842 for ent in entry:
843 for att in attrs:
844 if ent.get(att):
845 if hashAtt.has_key(att):
846 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
847 else:
848 hashAtt[att] = {}
849 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
851 return hashAtt
853 def int64range2str(value):
854 """Display the int64 range stored in value as xxx-yyy
856 :param value: The int64 range
857 :return: A string of the representation of the range
860 lvalue = long(value)
861 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
862 return str