py:dcerpc/raw_testcase: let do_single_request() check stub length against alloc_hint
[Samba.git] / python / samba / upgradehelpers.py
blob40120bd7190c2998a6a6725595f92b74eb24909a
1 # Helpers for provision stuff
2 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2012
4 # Based on provision a Samba4 server by
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from __future__ import print_function
23 from __future__ import division
24 """Helpers used for upgrading between different database formats."""
26 import os
27 import re
28 import shutil
29 import samba
31 from samba.compat import cmp_fn
32 from samba import Ldb, version, ntacls
33 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
34 import ldb
35 from samba.provision import (provision_paths_from_lp,
36 getpolicypath, create_gpo_struct,
37 provision, ProvisioningError,
38 secretsdb_self_join)
39 from samba.provision.common import FILL_FULL
40 from samba.dcerpc import xattr, drsblobs, security
41 from samba.dcerpc.misc import SEC_CHAN_BDC
42 from samba.ndr import ndr_unpack
43 from samba.samdb import SamDB
44 from samba import _glue
45 import tempfile
47 # All the ldb related to registry are commented because the path for them is
48 # relative in the provisionPath object
49 # And so opening them create a file in the current directory which is not what
50 # we want
51 # I still keep them commented because I plan soon to make more cleaner
52 ERROR = -1
53 SIMPLE = 0x00
54 CHANGE = 0x01
55 CHANGESD = 0x02
56 GUESS = 0x04
57 PROVISION = 0x08
58 CHANGEALL = 0xff
60 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
61 "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
62 "objectCategory", "distinguishedName", "nTMixedDomain",
63 "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
64 "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
65 "ntPwdHistory", "unicodePwd", "dBCSPwd", "supplementalCredentials",
66 "gPCUserExtensionNames", "gPCMachineExtensionNames", "maxPwdAge", "secret",
67 "possibleInferiors", "privilege", "sAMAccountType"])
70 class ProvisionLDB(object):
72 def __init__(self):
73 self.sam = None
74 self.secrets = None
75 self.idmap = None
76 self.privilege = None
77 self.hkcr = None
78 self.hkcu = None
79 self.hku = None
80 self.hklm = None
82 def dbs(self):
83 return (self.sam, self.secrets, self.idmap, self.privilege)
85 def startTransactions(self):
86 for db in self.dbs():
87 db.transaction_start()
88 # TO BE DONE
89 # self.hkcr.transaction_start()
90 # self.hkcu.transaction_start()
91 # self.hku.transaction_start()
92 # self.hklm.transaction_start()
94 def groupedRollback(self):
95 ok = True
96 for db in self.dbs():
97 try:
98 db.transaction_cancel()
99 except Exception:
100 ok = False
101 return ok
102 # TO BE DONE
103 # self.hkcr.transaction_cancel()
104 # self.hkcu.transaction_cancel()
105 # self.hku.transaction_cancel()
106 # self.hklm.transaction_cancel()
108 def groupedCommit(self):
109 try:
110 for db in self.dbs():
111 db.transaction_prepare_commit()
112 except Exception:
113 return self.groupedRollback()
114 # TO BE DONE
115 # self.hkcr.transaction_prepare_commit()
116 # self.hkcu.transaction_prepare_commit()
117 # self.hku.transaction_prepare_commit()
118 # self.hklm.transaction_prepare_commit()
119 try:
120 for db in self.dbs():
121 db.transaction_commit()
122 except Exception:
123 return self.groupedRollback()
125 # TO BE DONE
126 # self.hkcr.transaction_commit()
127 # self.hkcu.transaction_commit()
128 # self.hku.transaction_commit()
129 # self.hklm.transaction_commit()
130 return True
133 def get_ldbs(paths, creds, session, lp):
134 """Return LDB object mapped on most important databases
136 :param paths: An object holding the different importants paths for provision object
137 :param creds: Credential used for openning LDB files
138 :param session: Session to use for openning LDB files
139 :param lp: A loadparam object
140 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
142 ldbs = ProvisionLDB()
144 ldbs.sam = SamDB(paths.samdb,
145 session_info=session,
146 credentials=creds,
147 lp=lp,
148 options=["modules:samba_dsdb"],
149 flags=0)
150 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
151 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
152 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
153 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
154 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
155 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
156 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
158 return ldbs
161 def usn_in_range(usn, range):
162 """Check if the usn is in one of the range provided.
163 To do so, the value is checked to be between the lower bound and
164 higher bound of a range
166 :param usn: A integer value corresponding to the usn that we want to update
167 :param range: A list of integer representing ranges, lower bounds are in
168 the even indices, higher in odd indices
169 :return: True if the usn is in one of the range, False otherwise
172 idx = 0
173 cont = True
174 ok = False
175 while cont:
176 if idx == len(range):
177 cont = False
178 continue
179 if usn < int(range[idx]):
180 if idx % 2 == 1:
181 ok = True
182 cont = False
183 if usn == int(range[idx]):
184 cont = False
185 ok = True
186 idx = idx + 1
187 return ok
190 def get_paths(param, targetdir=None, smbconf=None):
191 """Get paths to important provision objects (smb.conf, ldb files, ...)
193 :param param: Param object
194 :param targetdir: Directory where the provision is (or will be) stored
195 :param smbconf: Path to the smb.conf file
196 :return: A list with the path of important provision objects"""
197 if targetdir is not None:
198 if not os.path.exists(targetdir):
199 os.mkdir(targetdir)
200 etcdir = os.path.join(targetdir, "etc")
201 if not os.path.exists(etcdir):
202 os.makedirs(etcdir)
203 smbconf = os.path.join(etcdir, "smb.conf")
204 if smbconf is None:
205 smbconf = param.default_path()
207 if not os.path.exists(smbconf):
208 raise ProvisioningError("Unable to find smb.conf at %s" % smbconf)
210 lp = param.LoadParm()
211 lp.load(smbconf)
212 paths = provision_paths_from_lp(lp, lp.get("realm"))
213 return paths
216 def update_policyids(names, samdb):
217 """Update policy ids that could have changed after sam update
219 :param names: List of key provision parameters
220 :param samdb: An Ldb object conntected with the sam DB
222 # policy guid
223 res = samdb.search(expression="(displayName=Default Domain Policy)",
224 base="CN=Policies,CN=System," + str(names.rootdn),
225 scope=SCOPE_ONELEVEL, attrs=["cn", "displayName"])
226 names.policyid = str(res[0]["cn"]).replace("{", "").replace("}", "")
227 # dc policy guid
228 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
229 " Policy)",
230 base="CN=Policies,CN=System," + str(names.rootdn),
231 scope=SCOPE_ONELEVEL, attrs=["cn", "displayName"])
232 if len(res2) == 1:
233 names.policyid_dc = str(res2[0]["cn"]).replace("{", "").replace("}", "")
234 else:
235 names.policyid_dc = None
238 def newprovision(names, session, smbconf, provdir, logger, base_schema=None):
239 """Create a new provision.
241 This provision will be the reference for knowing what has changed in the
242 since the latest upgrade in the current provision
244 :param names: List of provision parameters
245 :param creds: Credentials for the authentification
246 :param session: Session object
247 :param smbconf: Path to the smb.conf file
248 :param provdir: Directory where the provision will be stored
249 :param logger: A Logger
251 if os.path.isdir(provdir):
252 shutil.rmtree(provdir)
253 os.mkdir(provdir)
254 logger.info("Provision stored in %s", provdir)
255 return provision(logger, session, smbconf=smbconf,
256 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
257 domain=names.domain, domainguid=names.domainguid,
258 domainsid=names.domainsid, ntdsguid=names.ntdsguid,
259 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
260 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
261 invocationid=names.invocation, adminpass=names.adminpass,
262 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
263 nobody=None, users=None,
264 serverrole="domain controller",
265 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
266 slapd_path=None,
267 dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
268 useeadb=True, use_ntvfs=True, base_schema=base_schema)
271 def dn_sort(x, y):
272 """Sorts two DNs in the lexicographical order it and put higher level DN
273 before.
275 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
276 smaller
278 :param x: First object to compare
279 :param y: Second object to compare
281 p = re.compile(r'(?<!\\), ?')
282 tab1 = p.split(str(x))
283 tab2 = p.split(str(y))
284 minimum = min(len(tab1), len(tab2))
285 len1 = len(tab1) - 1
286 len2 = len(tab2) - 1
287 # Note: python range go up to upper limit but do not include it
288 for i in range(0, minimum):
289 ret = cmp_fn(tab1[len1 - i], tab2[len2 - i])
290 if ret != 0:
291 return ret
292 else:
293 if i == minimum - 1:
294 assert len1 != len2, "PB PB PB" + " ".join(tab1) + " / " + " ".join(tab2)
295 if len1 > len2:
296 return 1
297 else:
298 return -1
299 return ret
302 def identic_rename(ldbobj, dn):
303 """Perform a back and forth rename to trigger renaming on attribute that
304 can't be directly modified.
306 :param lbdobj: An Ldb Object
307 :param dn: DN of the object to manipulate
309 (before, after) = str(dn).split('=', 1)
310 # we need to use relax to avoid the subtree_rename constraints
311 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
312 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
315 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
316 """Update secrets.ldb
318 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
319 of the reference provision
320 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
321 of the updated provision
324 messagefunc(SIMPLE, "Update of secrets.ldb")
325 reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
326 current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
327 assert reference, "Reference modules list can not be empty"
328 if len(current) == 0:
329 # No modules present
330 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
331 delta.dn = reference[0].dn
332 secrets_ldb.add(reference[0])
333 else:
334 delta = secrets_ldb.msg_diff(current[0], reference[0])
335 delta.dn = current[0].dn
336 secrets_ldb.modify(delta)
338 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
339 scope=SCOPE_SUBTREE, attrs=["dn"])
340 current = secrets_ldb.search(expression="objectClass=top", base="",
341 scope=SCOPE_SUBTREE, attrs=["dn"])
342 hash_new = {}
343 hash = {}
344 listMissing = []
345 listPresent = []
347 empty = ldb.Message()
348 for i in range(0, len(reference)):
349 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
351 # Create a hash for speeding the search of existing object in the
352 # current provision
353 for i in range(0, len(current)):
354 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
356 for k in hash_new.keys():
357 if k not in hash:
358 listMissing.append(hash_new[k])
359 else:
360 listPresent.append(hash_new[k])
362 for entry in listMissing:
363 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
364 base="", scope=SCOPE_SUBTREE)
365 current = secrets_ldb.search(expression="distinguishedName=%s" % entry,
366 base="", scope=SCOPE_SUBTREE)
367 delta = secrets_ldb.msg_diff(empty, reference[0])
368 for att in hashAttrNotCopied:
369 delta.remove(att)
370 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
371 reference[0].dn)
372 for att in delta:
373 messagefunc(CHANGE, " Adding attribute %s" % att)
374 delta.dn = reference[0].dn
375 secrets_ldb.add(delta)
377 for entry in listPresent:
378 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
379 base="", scope=SCOPE_SUBTREE)
380 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
381 scope=SCOPE_SUBTREE)
382 delta = secrets_ldb.msg_diff(current[0], reference[0])
383 for att in hashAttrNotCopied:
384 delta.remove(att)
385 for att in delta:
386 if att == "name":
387 messagefunc(CHANGE, "Found attribute name on %s,"
388 " must rename the DN" % (current[0].dn))
389 identic_rename(secrets_ldb, reference[0].dn)
390 else:
391 delta.remove(att)
393 for entry in listPresent:
394 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
395 scope=SCOPE_SUBTREE)
396 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
397 scope=SCOPE_SUBTREE)
398 delta = secrets_ldb.msg_diff(current[0], reference[0])
399 for att in hashAttrNotCopied:
400 delta.remove(att)
401 for att in delta:
402 if att == "msDS-KeyVersionNumber":
403 delta.remove(att)
404 if att != "dn":
405 messagefunc(CHANGE,
406 "Adding/Changing attribute %s to %s" %
407 (att, current[0].dn))
409 delta.dn = current[0].dn
410 secrets_ldb.modify(delta)
412 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
413 scope=SCOPE_SUBTREE, attrs=["dn"])
415 if len(res2) == 1:
416 messagefunc(SIMPLE, "Remove old dns account")
417 secrets_ldb.delete(res2[0]["dn"])
420 def getOEMInfo(samdb, rootdn):
421 """Return OEM Information on the top level Samba4 use to store version
422 info in this field
424 :param samdb: An LDB object connect to sam.ldb
425 :param rootdn: Root DN of the domain
426 :return: The content of the field oEMInformation (if any)
428 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
429 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
430 if len(res) > 0 and res[0].get("oEMInformation"):
431 info = res[0]["oEMInformation"]
432 return info
433 else:
434 return ""
437 def updateOEMInfo(samdb, rootdn):
438 """Update the OEMinfo field to add information about upgrade
440 :param samdb: an LDB object connected to the sam DB
441 :param rootdn: The string representation of the root DN of
442 the provision (ie. DC=...,DC=...)
444 res = samdb.search(expression="(objectClass=*)", base=rootdn,
445 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
446 if len(res) > 0:
447 if res[0].get("oEMInformation"):
448 info = str(res[0]["oEMInformation"])
449 else:
450 info = ""
451 info = "%s, upgrade to %s" % (info, version)
452 delta = ldb.Message()
453 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
454 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
455 "oEMInformation")
456 samdb.modify(delta)
459 def update_gpo(paths, samdb, names, lp, message):
460 """Create missing GPO file object if needed
462 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
463 if not os.path.isdir(dir):
464 create_gpo_struct(dir)
466 if names.policyid_dc is None:
467 raise ProvisioningError("Policy ID for Domain controller is missing")
468 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
469 if not os.path.isdir(dir):
470 create_gpo_struct(dir)
473 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
474 """For a given hash associating dn and a number, this function will
475 update the replPropertyMetaData of each dn in the hash, so that the
476 calculated value of the msDs-KeyVersionNumber is equal or superior to the
477 one associated to the given dn.
479 :param samdb: An SamDB object pointing to the sam
480 :param rootdn: The base DN where we want to start
481 :param hashDns: A hash with dn as key and number representing the
482 minimum value of msDs-KeyVersionNumber that we want to
483 have
485 entry = samdb.search(expression='(objectClass=user)',
486 base=ldb.Dn(samdb, str(rootdn)),
487 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
488 controls=["search_options:1:2"])
489 done = 0
490 if len(entry) == 0:
491 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
492 else:
493 for e in entry:
494 if str(e.dn).lower() in hashDns:
495 val = e.get("msDs-KeyVersionNumber")
496 if not val:
497 val = "0"
498 version = int(str(hashDns[str(e.dn).lower()]))
499 if int(str(val)) < version:
500 done = done + 1
501 samdb.set_attribute_replmetadata_version(str(e.dn),
502 "unicodePwd",
503 version, True)
506 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
507 """Update the provision container db: sam.ldb
508 This function is aimed for alpha9 and newer;
510 :param refsampath: Path to the samdb in the reference provision
511 :param sampath: Path to the samdb in the upgraded provision
512 :param creds: Credential used for openning LDB files
513 :param session: Session to use for openning LDB files
514 :param lp: A loadparam object
515 :return: A msg_diff object with the difference between the @ATTRIBUTES
516 of the current provision and the reference provision
519 message(SIMPLE,
520 "Update base samdb by searching difference with reference one")
521 refsam = Ldb(refsampath, session_info=session, credentials=creds,
522 lp=lp, options=["modules:"])
523 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
524 options=["modules:"])
526 empty = ldb.Message()
527 deltaattr = None
528 reference = refsam.search(expression="")
530 for refentry in reference:
531 entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
532 scope=SCOPE_SUBTREE)
533 if not len(entry):
534 delta = sam.msg_diff(empty, refentry)
535 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
536 if str(refentry.dn) == "@PROVISION" and\
537 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
538 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
539 delta.dn = refentry.dn
540 sam.add(delta)
541 else:
542 delta = sam.msg_diff(entry[0], refentry)
543 if str(refentry.dn) == "@ATTRIBUTES":
544 deltaattr = sam.msg_diff(refentry, entry[0])
545 if str(refentry.dn) == "@PROVISION" and\
546 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
547 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
548 if len(delta.items()) > 1:
549 delta.dn = refentry.dn
550 sam.modify(delta)
552 return deltaattr
555 def construct_existor_expr(attrs):
556 """Construct a exists or LDAP search expression.
558 :param attrs: List of attribute on which we want to create the search
559 expression.
560 :return: A string representing the expression, if attrs is empty an
561 empty string is returned
563 expr = ""
564 if len(attrs) > 0:
565 expr = "(|"
566 for att in attrs:
567 expr = "%s(%s=*)" %(expr, att)
568 expr = "%s)" %expr
569 return expr
572 def update_machine_account_password(samdb, secrets_ldb, names):
573 """Update (change) the password of the current DC both in the SAM db and in
574 secret one
576 :param samdb: An LDB object related to the sam.ldb file of a given provision
577 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
578 provision
579 :param names: List of key provision parameters"""
581 expression = "samAccountName=%s$" % names.netbiosname
582 secrets_msg = secrets_ldb.search(expression=expression,
583 attrs=["secureChannelType"])
584 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
585 res = samdb.search(expression=expression, attrs=[])
586 assert(len(res) == 1)
588 msg = ldb.Message(res[0].dn)
589 machinepass = samba.generate_random_machine_password(128, 255)
590 mputf16 = machinepass.encode('utf-16-le')
591 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
592 ldb.FLAG_MOD_REPLACE,
593 "clearTextPassword")
594 samdb.modify(msg)
596 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
597 attrs=["msDs-keyVersionNumber"])
598 assert(len(res) == 1)
599 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
600 secChanType = int(secrets_msg[0]["secureChannelType"][0])
602 secretsdb_self_join(secrets_ldb, domain=names.domain,
603 realm=names.realm,
604 domainsid=names.domainsid,
605 dnsdomain=names.dnsdomain,
606 netbiosname=names.netbiosname,
607 machinepass=machinepass,
608 key_version_number=kvno,
609 secure_channel_type=secChanType)
610 else:
611 raise ProvisioningError("Unable to find a Secure Channel"
612 "of type SEC_CHAN_BDC")
615 def update_dns_account_password(samdb, secrets_ldb, names):
616 """Update (change) the password of the dns both in the SAM db and in
617 secret one
619 :param samdb: An LDB object related to the sam.ldb file of a given provision
620 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
621 provision
622 :param names: List of key provision parameters"""
624 expression = "samAccountName=dns-%s" % names.netbiosname
625 secrets_msg = secrets_ldb.search(expression=expression)
626 if len(secrets_msg) == 1:
627 res = samdb.search(expression=expression, attrs=[])
628 assert(len(res) == 1)
630 msg = ldb.Message(res[0].dn)
631 machinepass = samba.generate_random_password(128, 255)
632 mputf16 = machinepass.encode('utf-16-le')
633 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
634 ldb.FLAG_MOD_REPLACE,
635 "clearTextPassword")
637 samdb.modify(msg)
639 res = samdb.search(expression=expression,
640 attrs=["msDs-keyVersionNumber"])
641 assert(len(res) == 1)
642 kvno = str(res[0]["msDs-keyVersionNumber"])
644 msg = ldb.Message(secrets_msg[0].dn)
645 msg["secret"] = ldb.MessageElement(machinepass,
646 ldb.FLAG_MOD_REPLACE,
647 "secret")
648 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
649 ldb.FLAG_MOD_REPLACE,
650 "msDS-KeyVersionNumber")
652 secrets_ldb.modify(msg)
655 def update_krbtgt_account_password(samdb):
656 """Update (change) the password of the krbtgt account
658 :param samdb: An LDB object related to the sam.ldb file of a given provision"""
660 expression = "samAccountName=krbtgt"
661 res = samdb.search(expression=expression, attrs=[])
662 assert(len(res) == 1)
664 msg = ldb.Message(res[0].dn)
665 machinepass = samba.generate_random_machine_password(128, 255)
666 mputf16 = machinepass.encode('utf-16-le')
667 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
668 ldb.FLAG_MOD_REPLACE,
669 "clearTextPassword")
671 samdb.modify(msg)
674 def search_constructed_attrs_stored(samdb, rootdn, attrs):
675 """Search a given sam DB for calculated attributes that are
676 still stored in the db.
678 :param samdb: An LDB object pointing to the sam
679 :param rootdn: The base DN where the search should start
680 :param attrs: A list of attributes to be searched
681 :return: A hash with attributes as key and an array of
682 array. Each array contains the dn and the associated
683 values for this attribute as they are stored in the
684 sam."""
686 hashAtt = {}
687 expr = construct_existor_expr(attrs)
688 if expr == "":
689 return hashAtt
690 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
691 scope=SCOPE_SUBTREE, attrs=attrs,
692 controls=["search_options:1:2", "bypassoperational:0"])
693 if len(entry) == 0:
694 # Nothing anymore
695 return hashAtt
697 for ent in entry:
698 for att in attrs:
699 if ent.get(att):
700 if att in hashAtt:
701 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
702 else:
703 hashAtt[att] = {}
704 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
706 return hashAtt
709 def findprovisionrange(samdb, basedn):
710 """ Find ranges of usn grouped by invocation id and then by timestamp
711 rouned at 1 minute
713 :param samdb: An LDB object pointing to the samdb
714 :param basedn: The DN of the forest
716 :return: A two level dictionary with invoication id as the
717 first level, timestamp as the second one and then
718 max, min, and number as subkeys, representing respectivily
719 the maximum usn for the range, the minimum usn and the number
720 of object with usn in this range.
722 nb_obj = 0
723 hash_id = {}
725 res = samdb.search(base=basedn, expression="objectClass=*",
726 scope=ldb.SCOPE_SUBTREE,
727 attrs=["replPropertyMetaData"],
728 controls=["search_options:1:2"])
730 for e in res:
731 nb_obj = nb_obj + 1
732 obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
733 str(e["replPropertyMetaData"])).ctr
735 for o in obj.array:
736 # like a timestamp but with the resolution of 1 minute
737 minutestamp = _glue.nttime2unix(o.originating_change_time) // 60
738 hash_ts = hash_id.get(str(o.originating_invocation_id))
740 if hash_ts is None:
741 ob = {}
742 ob["min"] = o.originating_usn
743 ob["max"] = o.originating_usn
744 ob["num"] = 1
745 ob["list"] = [str(e.dn)]
746 hash_ts = {}
747 else:
748 ob = hash_ts.get(minutestamp)
749 if ob is None:
750 ob = {}
751 ob["min"] = o.originating_usn
752 ob["max"] = o.originating_usn
753 ob["num"] = 1
754 ob["list"] = [str(e.dn)]
755 else:
756 if ob["min"] > o.originating_usn:
757 ob["min"] = o.originating_usn
758 if ob["max"] < o.originating_usn:
759 ob["max"] = o.originating_usn
760 if not (str(e.dn) in ob["list"]):
761 ob["num"] = ob["num"] + 1
762 ob["list"].append(str(e.dn))
763 hash_ts[minutestamp] = ob
764 hash_id[str(o.originating_invocation_id)] = hash_ts
766 return (hash_id, nb_obj)
769 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
770 """ print the differents ranges passed as parameter
772 :param dic: A dictionnary as returned by findprovisionrange
773 :param limit_print: minimum number of object in a range in order to print it
774 :param dest: Destination directory
775 :param samdb_path: Path to the sam.ldb file
776 :param invoicationid: Invocation ID for the current provision
778 ldif = ""
780 for id in dic:
781 hash_ts = dic[id]
782 sorted_keys = []
783 sorted_keys.extend(hash_ts.keys())
784 sorted_keys.sort()
786 kept_record = []
787 for k in sorted_keys:
788 obj = hash_ts[k]
789 if obj["num"] > limit_print:
790 dt = _glue.nttime2string(_glue.unix2nttime(k * 60))
791 print("%s # of modification: %d \tmin: %d max: %d" % (dt, obj["num"],
792 obj["min"],
793 obj["max"]))
794 if hash_ts[k]["num"] > 600:
795 kept_record.append(k)
797 # Let's try to concatenate consecutive block if they are in the almost same minutestamp
798 for i in range(0, len(kept_record)):
799 if i != 0:
800 key1 = kept_record[i]
801 key2 = kept_record[i - 1]
802 if key1 - key2 == 1:
803 # previous record is just 1 minute away from current
804 if int(hash_ts[key1]["min"]) == int(hash_ts[key2]["max"]) + 1:
805 # Copy the highest USN in the previous record
806 # and mark the current as skipped
807 hash_ts[key2]["max"] = hash_ts[key1]["max"]
808 hash_ts[key1]["skipped"] = True
810 for k in kept_record:
811 obj = hash_ts[k]
812 if obj.get("skipped") is None:
813 ldif = "%slastProvisionUSN: %d-%d;%s\n" % (ldif, obj["min"],
814 obj["max"], id)
816 if ldif != "":
817 fd, file = tempfile.mkstemp(dir=dest, prefix="usnprov", suffix=".ldif")
818 print()
819 print("To track the USNs modified/created by provision and upgrade proivsion,")
820 print(" the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif)
821 print("We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file)
822 print("You can load this file like this: ldbadd -H %s %s\n" %(str(samdb_path), file))
823 ldif = "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocationid, ldif)
824 os.write(fd, ldif)
825 os.close(fd)
828 def int64range2str(value):
829 """Display the int64 range stored in value as xxx-yyy
831 :param value: The int64 range
832 :return: A string of the representation of the range
834 lvalue = int(value)
835 str = "%d-%d" % (lvalue &0xFFFFFFFF, lvalue >>32)
836 return str