s3:smb2_server: correctly maintain request counters for compound requests
[Samba.git] / python / samba / upgradehelpers.py
blobe219602e0c789d0fdc444a78aa2bc43c8ee2ebcc
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 """Helpers used for upgrading between different database formats."""
25 import os
26 import re
27 import shutil
28 import samba
30 from samba import Ldb, version, ntacls
31 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
32 import ldb
33 from samba.provision import (provision_paths_from_lp,
34 getpolicypath, set_gpos_acl, create_gpo_struct,
35 provision, ProvisioningError,
36 setsysvolacl, secretsdb_self_join)
37 from samba.provision.common import FILL_FULL
38 from samba.dcerpc import xattr, drsblobs, security
39 from samba.dcerpc.misc import SEC_CHAN_BDC
40 from samba.ndr import ndr_unpack
41 from samba.samdb import SamDB
42 from samba import _glue
43 import tempfile
45 # All the ldb related to registry are commented because the path for them is
46 # relative in the provisionPath object
47 # And so opening them create a file in the current directory which is not what
48 # we want
49 # I still keep them commented because I plan soon to make more cleaner
50 ERROR = -1
51 SIMPLE = 0x00
52 CHANGE = 0x01
53 CHANGESD = 0x02
54 GUESS = 0x04
55 PROVISION = 0x08
56 CHANGEALL = 0xff
58 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
59 "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
60 "objectCategory", "distinguishedName", "nTMixedDomain",
61 "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
62 "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
63 "ntPwdHistory", "unicodePwd","dBCSPwd", "supplementalCredentials",
64 "gPCUserExtensionNames", "gPCMachineExtensionNames","maxPwdAge", "secret",
65 "possibleInferiors", "privilege", "sAMAccountType"])
68 class ProvisionLDB(object):
70 def __init__(self):
71 self.sam = None
72 self.secrets = None
73 self.idmap = None
74 self.privilege = None
75 self.hkcr = None
76 self.hkcu = None
77 self.hku = None
78 self.hklm = None
80 def dbs(self):
81 return (self.sam, self.secrets, self.idmap, self.privilege)
83 def startTransactions(self):
84 for db in self.dbs():
85 db.transaction_start()
86 # TO BE DONE
87 # self.hkcr.transaction_start()
88 # self.hkcu.transaction_start()
89 # self.hku.transaction_start()
90 # self.hklm.transaction_start()
92 def groupedRollback(self):
93 ok = True
94 for db in self.dbs():
95 try:
96 db.transaction_cancel()
97 except Exception:
98 ok = False
99 return ok
100 # TO BE DONE
101 # self.hkcr.transaction_cancel()
102 # self.hkcu.transaction_cancel()
103 # self.hku.transaction_cancel()
104 # self.hklm.transaction_cancel()
106 def groupedCommit(self):
107 try:
108 for db in self.dbs():
109 db.transaction_prepare_commit()
110 except Exception:
111 return self.groupedRollback()
112 # TO BE DONE
113 # self.hkcr.transaction_prepare_commit()
114 # self.hkcu.transaction_prepare_commit()
115 # self.hku.transaction_prepare_commit()
116 # self.hklm.transaction_prepare_commit()
117 try:
118 for db in self.dbs():
119 db.transaction_commit()
120 except Exception:
121 return self.groupedRollback()
123 # TO BE DONE
124 # self.hkcr.transaction_commit()
125 # self.hkcu.transaction_commit()
126 # self.hku.transaction_commit()
127 # self.hklm.transaction_commit()
128 return True
131 def get_ldbs(paths, creds, session, lp):
132 """Return LDB object mapped on most important databases
134 :param paths: An object holding the different importants paths for provision object
135 :param creds: Credential used for openning LDB files
136 :param session: Session to use for openning LDB files
137 :param lp: A loadparam object
138 :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
140 ldbs = ProvisionLDB()
142 ldbs.sam = SamDB(paths.samdb,
143 session_info=session,
144 credentials=creds,
145 lp=lp,
146 options=["modules:samba_dsdb"],
147 flags=0)
148 ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
149 ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
150 ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
151 # ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
152 # ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
153 # ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
154 # ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
156 return ldbs
159 def usn_in_range(usn, range):
160 """Check if the usn is in one of the range provided.
161 To do so, the value is checked to be between the lower bound and
162 higher bound of a range
164 :param usn: A integer value corresponding to the usn that we want to update
165 :param range: A list of integer representing ranges, lower bounds are in
166 the even indices, higher in odd indices
167 :return: True if the usn is in one of the range, False otherwise
170 idx = 0
171 cont = True
172 ok = False
173 while cont:
174 if idx == len(range):
175 cont = False
176 continue
177 if usn < int(range[idx]):
178 if idx %2 == 1:
179 ok = True
180 cont = False
181 if usn == int(range[idx]):
182 cont = False
183 ok = True
184 idx = idx + 1
185 return ok
188 def get_paths(param, targetdir=None, smbconf=None):
189 """Get paths to important provision objects (smb.conf, ldb files, ...)
191 :param param: Param object
192 :param targetdir: Directory where the provision is (or will be) stored
193 :param smbconf: Path to the smb.conf file
194 :return: A list with the path of important provision objects"""
195 if targetdir is not None:
196 if not os.path.exists(targetdir):
197 os.mkdir(targetdir)
198 etcdir = os.path.join(targetdir, "etc")
199 if not os.path.exists(etcdir):
200 os.makedirs(etcdir)
201 smbconf = os.path.join(etcdir, "smb.conf")
202 if smbconf is None:
203 smbconf = param.default_path()
205 if not os.path.exists(smbconf):
206 raise ProvisioningError("Unable to find smb.conf at %s" % smbconf)
208 lp = param.LoadParm()
209 lp.load(smbconf)
210 paths = provision_paths_from_lp(lp, lp.get("realm"))
211 return paths
213 def update_policyids(names, samdb):
214 """Update policy ids that could have changed after sam update
216 :param names: List of key provision parameters
217 :param samdb: An Ldb object conntected with the sam DB
219 # policy guid
220 res = samdb.search(expression="(displayName=Default Domain Policy)",
221 base="CN=Policies,CN=System," + str(names.rootdn),
222 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
223 names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
224 # dc policy guid
225 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
226 " Policy)",
227 base="CN=Policies,CN=System," + str(names.rootdn),
228 scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
229 if len(res2) == 1:
230 names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
231 else:
232 names.policyid_dc = None
235 def newprovision(names, session, smbconf, provdir, logger, base_schema=None):
236 """Create a new provision.
238 This provision will be the reference for knowing what has changed in the
239 since the latest upgrade in the current provision
241 :param names: List of provision parameters
242 :param creds: Credentials for the authentification
243 :param session: Session object
244 :param smbconf: Path to the smb.conf file
245 :param provdir: Directory where the provision will be stored
246 :param logger: A Logger
248 if os.path.isdir(provdir):
249 shutil.rmtree(provdir)
250 os.mkdir(provdir)
251 logger.info("Provision stored in %s", provdir)
252 return provision(logger, session, smbconf=smbconf,
253 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
254 domain=names.domain, domainguid=names.domainguid,
255 domainsid=names.domainsid, ntdsguid=names.ntdsguid,
256 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
257 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
258 invocationid=names.invocation, adminpass=names.adminpass,
259 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
260 nobody=None, users=None,
261 serverrole="domain controller",
262 backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
263 slapd_path=None,
264 dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
265 useeadb=True, use_ntvfs=True, base_schema=base_schema)
268 def dn_sort(x, y):
269 """Sorts two DNs in the lexicographical order it and put higher level DN
270 before.
272 So given the dns cn=bar,cn=foo and cn=foo the later will be return as
273 smaller
275 :param x: First object to compare
276 :param y: Second object to compare
278 p = re.compile(r'(?<!\\), ?')
279 tab1 = p.split(str(x))
280 tab2 = p.split(str(y))
281 minimum = min(len(tab1), len(tab2))
282 len1 = len(tab1)-1
283 len2 = len(tab2)-1
284 # Note: python range go up to upper limit but do not include it
285 cmp = lambda x, y: (x > y) - (x < y) # cmp is removed in py3
286 for i in range(0, minimum):
287 ret = cmp(tab1[len1-i], tab2[len2-i])
288 if ret != 0:
289 return ret
290 else:
291 if i == minimum-1:
292 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
293 if len1 > len2:
294 return 1
295 else:
296 return -1
297 return ret
300 def identic_rename(ldbobj, dn):
301 """Perform a back and forth rename to trigger renaming on attribute that
302 can't be directly modified.
304 :param lbdobj: An Ldb Object
305 :param dn: DN of the object to manipulate
307 (before, after) = str(dn).split('=', 1)
308 # we need to use relax to avoid the subtree_rename constraints
309 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
310 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
313 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
314 """Update secrets.ldb
316 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
317 of the reference provision
318 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
319 of the updated provision
322 messagefunc(SIMPLE, "Update of secrets.ldb")
323 reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
324 current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
325 assert reference, "Reference modules list can not be empty"
326 if len(current) == 0:
327 # No modules present
328 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
329 delta.dn = reference[0].dn
330 secrets_ldb.add(reference[0])
331 else:
332 delta = secrets_ldb.msg_diff(current[0], reference[0])
333 delta.dn = current[0].dn
334 secrets_ldb.modify(delta)
336 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
337 scope=SCOPE_SUBTREE, attrs=["dn"])
338 current = secrets_ldb.search(expression="objectClass=top", base="",
339 scope=SCOPE_SUBTREE, attrs=["dn"])
340 hash_new = {}
341 hash = {}
342 listMissing = []
343 listPresent = []
345 empty = ldb.Message()
346 for i in range(0, len(reference)):
347 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
349 # Create a hash for speeding the search of existing object in the
350 # current provision
351 for i in range(0, len(current)):
352 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
354 for k in hash_new.keys():
355 if k not in hash:
356 listMissing.append(hash_new[k])
357 else:
358 listPresent.append(hash_new[k])
360 for entry in listMissing:
361 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
362 base="", scope=SCOPE_SUBTREE)
363 current = secrets_ldb.search(expression="distinguishedName=%s" % entry,
364 base="", scope=SCOPE_SUBTREE)
365 delta = secrets_ldb.msg_diff(empty, reference[0])
366 for att in hashAttrNotCopied:
367 delta.remove(att)
368 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
369 reference[0].dn)
370 for att in delta:
371 messagefunc(CHANGE, " Adding attribute %s" % att)
372 delta.dn = reference[0].dn
373 secrets_ldb.add(delta)
375 for entry in listPresent:
376 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
377 base="", scope=SCOPE_SUBTREE)
378 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
379 scope=SCOPE_SUBTREE)
380 delta = secrets_ldb.msg_diff(current[0], reference[0])
381 for att in hashAttrNotCopied:
382 delta.remove(att)
383 for att in delta:
384 if att == "name":
385 messagefunc(CHANGE, "Found attribute name on %s,"
386 " must rename the DN" % (current[0].dn))
387 identic_rename(secrets_ldb, reference[0].dn)
388 else:
389 delta.remove(att)
391 for entry in listPresent:
392 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
393 scope=SCOPE_SUBTREE)
394 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
395 scope=SCOPE_SUBTREE)
396 delta = secrets_ldb.msg_diff(current[0], reference[0])
397 for att in hashAttrNotCopied:
398 delta.remove(att)
399 for att in delta:
400 if att == "msDS-KeyVersionNumber":
401 delta.remove(att)
402 if att != "dn":
403 messagefunc(CHANGE,
404 "Adding/Changing attribute %s to %s" %
405 (att, current[0].dn))
407 delta.dn = current[0].dn
408 secrets_ldb.modify(delta)
410 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
411 scope=SCOPE_SUBTREE, attrs=["dn"])
413 if len(res2) == 1:
414 messagefunc(SIMPLE, "Remove old dns account")
415 secrets_ldb.delete(res2[0]["dn"])
418 def getOEMInfo(samdb, rootdn):
419 """Return OEM Information on the top level Samba4 use to store version
420 info in this field
422 :param samdb: An LDB object connect to sam.ldb
423 :param rootdn: Root DN of the domain
424 :return: The content of the field oEMInformation (if any)
426 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
427 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
428 if len(res) > 0 and res[0].get("oEMInformation"):
429 info = res[0]["oEMInformation"]
430 return info
431 else:
432 return ""
435 def updateOEMInfo(samdb, rootdn):
436 """Update the OEMinfo field to add information about upgrade
438 :param samdb: an LDB object connected to the sam DB
439 :param rootdn: The string representation of the root DN of
440 the provision (ie. DC=...,DC=...)
442 res = samdb.search(expression="(objectClass=*)", base=rootdn,
443 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
444 if len(res) > 0:
445 if res[0].get("oEMInformation"):
446 info = str(res[0]["oEMInformation"])
447 else:
448 info = ""
449 info = "%s, upgrade to %s" % (info, version)
450 delta = ldb.Message()
451 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
452 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
453 "oEMInformation" )
454 samdb.modify(delta)
456 def update_gpo(paths, samdb, names, lp, message):
457 """Create missing GPO file object if needed
459 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
460 if not os.path.isdir(dir):
461 create_gpo_struct(dir)
463 if names.policyid_dc is None:
464 raise ProvisioningError("Policy ID for Domain controller is missing")
465 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
466 if not os.path.isdir(dir):
467 create_gpo_struct(dir)
469 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
470 """For a given hash associating dn and a number, this function will
471 update the replPropertyMetaData of each dn in the hash, so that the
472 calculated value of the msDs-KeyVersionNumber is equal or superior to the
473 one associated to the given dn.
475 :param samdb: An SamDB object pointing to the sam
476 :param rootdn: The base DN where we want to start
477 :param hashDns: A hash with dn as key and number representing the
478 minimum value of msDs-KeyVersionNumber that we want to
479 have
481 entry = samdb.search(expression='(objectClass=user)',
482 base=ldb.Dn(samdb,str(rootdn)),
483 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
484 controls=["search_options:1:2"])
485 done = 0
486 hashDone = {}
487 if len(entry) == 0:
488 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
489 else:
490 for e in entry:
491 if hashDns.has_key(str(e.dn).lower()):
492 val = e.get("msDs-KeyVersionNumber")
493 if not val:
494 val = "0"
495 version = int(str(hashDns[str(e.dn).lower()]))
496 if int(str(val)) < version:
497 done = done + 1
498 samdb.set_attribute_replmetadata_version(str(e.dn),
499 "unicodePwd",
500 version, True)
501 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
502 """Update the provision container db: sam.ldb
503 This function is aimed for alpha9 and newer;
505 :param refsampath: Path to the samdb in the reference provision
506 :param sampath: Path to the samdb in the upgraded provision
507 :param creds: Credential used for openning LDB files
508 :param session: Session to use for openning LDB files
509 :param lp: A loadparam object
510 :return: A msg_diff object with the difference between the @ATTRIBUTES
511 of the current provision and the reference provision
514 message(SIMPLE,
515 "Update base samdb by searching difference with reference one")
516 refsam = Ldb(refsampath, session_info=session, credentials=creds,
517 lp=lp, options=["modules:"])
518 sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
519 options=["modules:"])
521 empty = ldb.Message()
522 deltaattr = None
523 reference = refsam.search(expression="")
525 for refentry in reference:
526 entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
527 scope=SCOPE_SUBTREE)
528 if not len(entry):
529 delta = sam.msg_diff(empty, refentry)
530 message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
531 if str(refentry.dn) == "@PROVISION" and\
532 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
533 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
534 delta.dn = refentry.dn
535 sam.add(delta)
536 else:
537 delta = sam.msg_diff(entry[0], refentry)
538 if str(refentry.dn) == "@ATTRIBUTES":
539 deltaattr = sam.msg_diff(refentry, entry[0])
540 if str(refentry.dn) == "@PROVISION" and\
541 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
542 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
543 if len(delta.items()) > 1:
544 delta.dn = refentry.dn
545 sam.modify(delta)
547 return deltaattr
550 def construct_existor_expr(attrs):
551 """Construct a exists or LDAP search expression.
553 :param attrs: List of attribute on which we want to create the search
554 expression.
555 :return: A string representing the expression, if attrs is empty an
556 empty string is returned
558 expr = ""
559 if len(attrs) > 0:
560 expr = "(|"
561 for att in attrs:
562 expr = "%s(%s=*)"%(expr,att)
563 expr = "%s)"%expr
564 return expr
566 def update_machine_account_password(samdb, secrets_ldb, names):
567 """Update (change) the password of the current DC both in the SAM db and in
568 secret one
570 :param samdb: An LDB object related to the sam.ldb file of a given provision
571 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
572 provision
573 :param names: List of key provision parameters"""
575 expression = "samAccountName=%s$" % names.netbiosname
576 secrets_msg = secrets_ldb.search(expression=expression,
577 attrs=["secureChannelType"])
578 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
579 res = samdb.search(expression=expression, attrs=[])
580 assert(len(res) == 1)
582 msg = ldb.Message(res[0].dn)
583 machinepass = samba.generate_random_machine_password(128, 255)
584 mputf16 = machinepass.encode('utf-16-le')
585 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
586 ldb.FLAG_MOD_REPLACE,
587 "clearTextPassword")
588 samdb.modify(msg)
590 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
591 attrs=["msDs-keyVersionNumber"])
592 assert(len(res) == 1)
593 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
594 secChanType = int(secrets_msg[0]["secureChannelType"][0])
596 secretsdb_self_join(secrets_ldb, domain=names.domain,
597 realm=names.realm,
598 domainsid=names.domainsid,
599 dnsdomain=names.dnsdomain,
600 netbiosname=names.netbiosname,
601 machinepass=machinepass,
602 key_version_number=kvno,
603 secure_channel_type=secChanType)
604 else:
605 raise ProvisioningError("Unable to find a Secure Channel"
606 "of type SEC_CHAN_BDC")
608 def update_dns_account_password(samdb, secrets_ldb, names):
609 """Update (change) the password of the dns both in the SAM db and in
610 secret one
612 :param samdb: An LDB object related to the sam.ldb file of a given provision
613 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
614 provision
615 :param names: List of key provision parameters"""
617 expression = "samAccountName=dns-%s" % names.netbiosname
618 secrets_msg = secrets_ldb.search(expression=expression)
619 if len(secrets_msg) == 1:
620 res = samdb.search(expression=expression, attrs=[])
621 assert(len(res) == 1)
623 msg = ldb.Message(res[0].dn)
624 machinepass = samba.generate_random_password(128, 255)
625 mputf16 = machinepass.encode('utf-16-le')
626 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
627 ldb.FLAG_MOD_REPLACE,
628 "clearTextPassword")
630 samdb.modify(msg)
632 res = samdb.search(expression=expression,
633 attrs=["msDs-keyVersionNumber"])
634 assert(len(res) == 1)
635 kvno = str(res[0]["msDs-keyVersionNumber"])
637 msg = ldb.Message(secrets_msg[0].dn)
638 msg["secret"] = ldb.MessageElement(machinepass,
639 ldb.FLAG_MOD_REPLACE,
640 "secret")
641 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
642 ldb.FLAG_MOD_REPLACE,
643 "msDS-KeyVersionNumber")
645 secrets_ldb.modify(msg)
647 def update_krbtgt_account_password(samdb, names):
648 """Update (change) the password of the krbtgt account
650 :param samdb: An LDB object related to the sam.ldb file of a given provision
651 :param names: List of key provision parameters"""
653 expression = "samAccountName=krbtgt"
654 res = samdb.search(expression=expression, attrs=[])
655 assert(len(res) == 1)
657 msg = ldb.Message(res[0].dn)
658 machinepass = samba.generate_random_machine_password(128, 255)
659 mputf16 = machinepass.encode('utf-16-le')
660 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
661 ldb.FLAG_MOD_REPLACE,
662 "clearTextPassword")
664 samdb.modify(msg)
666 def search_constructed_attrs_stored(samdb, rootdn, attrs):
667 """Search a given sam DB for calculated attributes that are
668 still stored in the db.
670 :param samdb: An LDB object pointing to the sam
671 :param rootdn: The base DN where the search should start
672 :param attrs: A list of attributes to be searched
673 :return: A hash with attributes as key and an array of
674 array. Each array contains the dn and the associated
675 values for this attribute as they are stored in the
676 sam."""
678 hashAtt = {}
679 expr = construct_existor_expr(attrs)
680 if expr == "":
681 return hashAtt
682 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
683 scope=SCOPE_SUBTREE, attrs=attrs,
684 controls=["search_options:1:2","bypassoperational:0"])
685 if len(entry) == 0:
686 # Nothing anymore
687 return hashAtt
689 for ent in entry:
690 for att in attrs:
691 if ent.get(att):
692 if hashAtt.has_key(att):
693 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
694 else:
695 hashAtt[att] = {}
696 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
698 return hashAtt
700 def findprovisionrange(samdb, basedn):
701 """ Find ranges of usn grouped by invocation id and then by timestamp
702 rouned at 1 minute
704 :param samdb: An LDB object pointing to the samdb
705 :param basedn: The DN of the forest
707 :return: A two level dictionary with invoication id as the
708 first level, timestamp as the second one and then
709 max, min, and number as subkeys, representing respectivily
710 the maximum usn for the range, the minimum usn and the number
711 of object with usn in this range.
713 nb_obj = 0
714 hash_id = {}
716 res = samdb.search(base=basedn, expression="objectClass=*",
717 scope=ldb.SCOPE_SUBTREE,
718 attrs=["replPropertyMetaData"],
719 controls=["search_options:1:2"])
721 for e in res:
722 nb_obj = nb_obj + 1
723 obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
724 str(e["replPropertyMetaData"])).ctr
726 for o in obj.array:
727 # like a timestamp but with the resolution of 1 minute
728 minutestamp =_glue.nttime2unix(o.originating_change_time)/60
729 hash_ts = hash_id.get(str(o.originating_invocation_id))
731 if hash_ts is None:
732 ob = {}
733 ob["min"] = o.originating_usn
734 ob["max"] = o.originating_usn
735 ob["num"] = 1
736 ob["list"] = [str(e.dn)]
737 hash_ts = {}
738 else:
739 ob = hash_ts.get(minutestamp)
740 if ob 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 else:
747 if ob["min"] > o.originating_usn:
748 ob["min"] = o.originating_usn
749 if ob["max"] < o.originating_usn:
750 ob["max"] = o.originating_usn
751 if not (str(e.dn) in ob["list"]):
752 ob["num"] = ob["num"] + 1
753 ob["list"].append(str(e.dn))
754 hash_ts[minutestamp] = ob
755 hash_id[str(o.originating_invocation_id)] = hash_ts
757 return (hash_id, nb_obj)
759 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
760 """ print the differents ranges passed as parameter
762 :param dic: A dictionnary as returned by findprovisionrange
763 :param limit_print: minimum number of object in a range in order to print it
764 :param dest: Destination directory
765 :param samdb_path: Path to the sam.ldb file
766 :param invoicationid: Invocation ID for the current provision
768 ldif = ""
770 for id in dic:
771 hash_ts = dic[id]
772 sorted_keys = []
773 sorted_keys.extend(hash_ts.keys())
774 sorted_keys.sort()
776 kept_record = []
777 for k in sorted_keys:
778 obj = hash_ts[k]
779 if obj["num"] > limit_print:
780 dt = _glue.nttime2string(_glue.unix2nttime(k*60))
781 print("%s # of modification: %d \tmin: %d max: %d" % (dt , obj["num"],
782 obj["min"],
783 obj["max"]))
784 if hash_ts[k]["num"] > 600:
785 kept_record.append(k)
787 # Let's try to concatenate consecutive block if they are in the almost same minutestamp
788 for i in range(0, len(kept_record)):
789 if i != 0:
790 key1 = kept_record[i]
791 key2 = kept_record[i-1]
792 if key1 - key2 == 1:
793 # previous record is just 1 minute away from current
794 if int(hash_ts[key1]["min"]) == int(hash_ts[key2]["max"]) + 1:
795 # Copy the highest USN in the previous record
796 # and mark the current as skipped
797 hash_ts[key2]["max"] = hash_ts[key1]["max"]
798 hash_ts[key1]["skipped"] = True
800 for k in kept_record:
801 obj = hash_ts[k]
802 if obj.get("skipped") is None:
803 ldif = "%slastProvisionUSN: %d-%d;%s\n" % (ldif, obj["min"],
804 obj["max"], id)
806 if ldif != "":
807 file = tempfile.mktemp(dir=dest, prefix="usnprov", suffix=".ldif")
808 print()
809 print("To track the USNs modified/created by provision and upgrade proivsion,")
810 print(" the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif)
811 print("We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file)
812 print("You can load this file like this: ldbadd -H %s %s\n"%(str(samdb_path),file))
813 ldif = "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocationid, ldif)
814 open(file,'w').write(ldif)
816 def int64range2str(value):
817 """Display the int64 range stored in value as xxx-yyy
819 :param value: The int64 range
820 :return: A string of the representation of the range
823 lvalue = long(value)
824 str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
825 return str