s4:kdc: adjust formatting of samba_kdc_update_pac() documentation
[Samba.git] / python / samba / upgradehelpers.py
blob6920cea6583d4b5b6a3a816f9b3dd17796445bb1
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 """Helpers used for upgrading between different database formats."""
24 import os
25 import re
26 import shutil
27 import samba
29 from samba.common import cmp
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, create_gpo_struct,
35 provision, ProvisioningError,
36 secretsdb_self_join)
37 from samba.provision.common import FILL_FULL
38 from samba.dcerpc import drsblobs
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 opening LDB files
136 :param session: Session to use for opening 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
214 def update_policyids(names, samdb):
215 """Update policy ids that could have changed after sam update
217 :param names: List of key provision parameters
218 :param samdb: An Ldb object conntected with the sam DB
220 # policy guid
221 res = samdb.search(expression="(displayName=Default Domain Policy)",
222 base="CN=Policies,CN=System," + str(names.rootdn),
223 scope=SCOPE_ONELEVEL, attrs=["cn", "displayName"])
224 names.policyid = str(res[0]["cn"]).replace("{", "").replace("}", "")
225 # dc policy guid
226 res2 = samdb.search(expression="(displayName=Default Domain Controllers"
227 " Policy)",
228 base="CN=Policies,CN=System," + str(names.rootdn),
229 scope=SCOPE_ONELEVEL, attrs=["cn", "displayName"])
230 if len(res2) == 1:
231 names.policyid_dc = str(res2[0]["cn"]).replace("{", "").replace("}", "")
232 else:
233 names.policyid_dc = None
236 def newprovision(names, session, smbconf, provdir, logger, base_schema=None, adprep_level=None):
237 """Create a new provision.
239 This provision will be the reference for knowing what has changed in the
240 since the latest upgrade in the current provision
242 :param names: List of provision parameters
243 :param creds: Credentials for the authentication
244 :param session: Session object
245 :param smbconf: Path to the smb.conf file
246 :param provdir: Directory where the provision will be stored
247 :param logger: A Logger
249 if os.path.isdir(provdir):
250 shutil.rmtree(provdir)
251 os.mkdir(provdir)
252 logger.info("Provision stored in %s", provdir)
253 return provision(logger, session, smbconf=smbconf,
254 targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
255 domain=names.domain, domainguid=names.domainguid,
256 domainsid=names.domainsid, ntdsguid=names.ntdsguid,
257 policyguid=names.policyid, policyguid_dc=names.policyid_dc,
258 hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
259 invocationid=names.invocation, adminpass=names.adminpass,
260 krbtgtpass=None, machinepass=None, dnspass=None, root=None,
261 nobody=None, users=None,
262 serverrole="domain controller",
263 dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
264 useeadb=True, use_ntvfs=True, base_schema=base_schema,
265 adprep_level=adprep_level)
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 for i in range(0, minimum):
286 ret = cmp(tab1[len1 - i], tab2[len2 - i])
287 if ret != 0:
288 return ret
289 else:
290 if i == minimum - 1:
291 assert len1 != len2, "PB PB PB" + " ".join(tab1) + " / " + " ".join(tab2)
292 if len1 > len2:
293 return 1
294 else:
295 return -1
296 return ret
299 def identic_rename(ldbobj, dn):
300 """Perform a back and forth rename to trigger renaming on attribute that
301 can't be directly modified.
303 :param lbdobj: An Ldb Object
304 :param dn: DN of the object to manipulate
306 (before, after) = str(dn).split('=', 1)
307 # we need to use relax to avoid the subtree_rename constraints
308 ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
309 ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
312 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
313 """Update secrets.ldb
315 :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
316 of the reference provision
317 :param secrets_ldb: An LDB object that is connected to the secrets.ldb
318 of the updated provision
321 messagefunc(SIMPLE, "Update of secrets.ldb")
322 reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
323 current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
324 assert reference, "Reference modules list can not be empty"
325 if len(current) == 0:
326 # No modules present
327 delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
328 delta.dn = reference[0].dn
329 secrets_ldb.add(reference[0])
330 else:
331 delta = secrets_ldb.msg_diff(current[0], reference[0])
332 delta.dn = current[0].dn
333 secrets_ldb.modify(delta)
335 reference = newsecrets_ldb.search(expression="objectClass=top", base="",
336 scope=SCOPE_SUBTREE, attrs=["dn"])
337 current = secrets_ldb.search(expression="objectClass=top", base="",
338 scope=SCOPE_SUBTREE, attrs=["dn"])
339 hash_new = {}
340 hash = {}
341 listMissing = []
342 listPresent = []
344 empty = ldb.Message()
345 for i in range(0, len(reference)):
346 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
348 # Create a hash for speeding the search of existing object in the
349 # current provision
350 for i in range(0, len(current)):
351 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
353 for k in hash_new.keys():
354 if k not in hash:
355 listMissing.append(hash_new[k])
356 else:
357 listPresent.append(hash_new[k])
359 for entry in listMissing:
360 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
361 base="", scope=SCOPE_SUBTREE)
362 delta = secrets_ldb.msg_diff(empty, reference[0])
363 for att in hashAttrNotCopied:
364 delta.remove(att)
365 messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
366 reference[0].dn)
367 for att in delta:
368 messagefunc(CHANGE, " Adding attribute %s" % att)
369 delta.dn = reference[0].dn
370 secrets_ldb.add(delta)
372 for entry in listPresent:
373 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
374 base="", scope=SCOPE_SUBTREE)
375 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
376 scope=SCOPE_SUBTREE)
377 delta = secrets_ldb.msg_diff(current[0], reference[0])
378 for att in hashAttrNotCopied:
379 delta.remove(att)
380 for att in delta:
381 if att == "name":
382 messagefunc(CHANGE, "Found attribute name on %s,"
383 " must rename the DN" % (current[0].dn))
384 identic_rename(secrets_ldb, reference[0].dn)
385 else:
386 delta.remove(att)
388 for entry in listPresent:
389 reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
390 scope=SCOPE_SUBTREE)
391 current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
392 scope=SCOPE_SUBTREE)
393 delta = secrets_ldb.msg_diff(current[0], reference[0])
394 for att in hashAttrNotCopied:
395 delta.remove(att)
396 for att in delta:
397 if att == "msDS-KeyVersionNumber":
398 delta.remove(att)
399 if att != "dn":
400 messagefunc(CHANGE,
401 "Adding/Changing attribute %s to %s" %
402 (att, current[0].dn))
404 delta.dn = current[0].dn
405 secrets_ldb.modify(delta)
407 res2 = secrets_ldb.search(expression="(samaccountname=dns)",
408 scope=SCOPE_SUBTREE, attrs=["dn"])
410 if len(res2) == 1:
411 messagefunc(SIMPLE, "Remove old dns account")
412 secrets_ldb.delete(res2[0]["dn"])
415 def getOEMInfo(samdb, rootdn):
416 """Return OEM Information on the top level Samba4 use to store version
417 info in this field
419 :param samdb: An LDB object connect to sam.ldb
420 :param rootdn: Root DN of the domain
421 :return: The content of the field oEMInformation (if any)
423 res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
424 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
425 if len(res) > 0 and res[0].get("oEMInformation"):
426 info = res[0]["oEMInformation"]
427 return info
428 else:
429 return ""
432 def updateOEMInfo(samdb, rootdn):
433 """Update the OEMinfo field to add information about upgrade
435 :param samdb: an LDB object connected to the sam DB
436 :param rootdn: The string representation of the root DN of
437 the provision (ie. DC=...,DC=...)
439 res = samdb.search(expression="(objectClass=*)", base=rootdn,
440 scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
441 if len(res) > 0:
442 if res[0].get("oEMInformation"):
443 info = str(res[0]["oEMInformation"])
444 else:
445 info = ""
446 info = "%s, upgrade to %s" % (info, version)
447 delta = ldb.Message()
448 delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
449 delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
450 "oEMInformation")
451 samdb.modify(delta)
454 def update_gpo(paths, samdb, names, lp, message):
455 """Create missing GPO file object if needed
457 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
458 if not os.path.isdir(dir):
459 create_gpo_struct(dir)
461 if names.policyid_dc is None:
462 raise ProvisioningError("Policy ID for Domain controller is missing")
463 dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
464 if not os.path.isdir(dir):
465 create_gpo_struct(dir)
468 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
469 """For a given hash associating dn and a number, this function will
470 update the replPropertyMetaData of each dn in the hash, so that the
471 calculated value of the msDs-KeyVersionNumber is equal or superior to the
472 one associated to the given dn.
474 :param samdb: An SamDB object pointing to the sam
475 :param rootdn: The base DN where we want to start
476 :param hashDns: A hash with dn as key and number representing the
477 minimum value of msDs-KeyVersionNumber that we want to
478 have
480 entry = samdb.search(expression='(objectClass=user)',
481 base=ldb.Dn(samdb, str(rootdn)),
482 scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
483 controls=["search_options:1:2"])
484 done = 0
485 if len(entry) == 0:
486 raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
487 else:
488 for e in entry:
489 if str(e.dn).lower() in hashDns:
490 val = e.get("msDs-KeyVersionNumber")
491 if not val:
492 val = "0"
493 version = int(str(hashDns[str(e.dn).lower()]))
494 if int(str(val)) < version:
495 done = done + 1
496 samdb.set_attribute_replmetadata_version(str(e.dn),
497 "unicodePwd",
498 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 opening LDB files
508 :param session: Session to use for opening 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
567 def update_machine_account_password(samdb, secrets_ldb, names):
568 """Update (change) the password of the current DC both in the SAM db and in
569 secret one
571 :param samdb: An LDB object related to the sam.ldb file of a given provision
572 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
573 provision
574 :param names: List of key provision parameters"""
576 expression = "samAccountName=%s$" % names.netbiosname
577 secrets_msg = secrets_ldb.search(expression=expression,
578 attrs=["secureChannelType"])
579 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
580 res = samdb.search(expression=expression, attrs=[])
581 assert(len(res) == 1)
583 msg = ldb.Message(res[0].dn)
584 machinepass = samba.generate_random_machine_password(120, 120)
585 mputf16 = machinepass.encode('utf-16-le')
586 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
587 ldb.FLAG_MOD_REPLACE,
588 "clearTextPassword")
589 samdb.modify(msg)
591 res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
592 attrs=["msDs-keyVersionNumber"])
593 assert(len(res) == 1)
594 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
595 secChanType = int(secrets_msg[0]["secureChannelType"][0])
597 secretsdb_self_join(secrets_ldb, domain=names.domain,
598 realm=names.realm,
599 domainsid=names.domainsid,
600 dnsdomain=names.dnsdomain,
601 netbiosname=names.netbiosname,
602 machinepass=machinepass,
603 key_version_number=kvno,
604 secure_channel_type=secChanType)
605 else:
606 raise ProvisioningError("Unable to find a Secure Channel"
607 "of type SEC_CHAN_BDC")
610 def update_dns_account_password(samdb, secrets_ldb, names):
611 """Update (change) the password of the dns both in the SAM db and in
612 secret one
614 :param samdb: An LDB object related to the sam.ldb file of a given provision
615 :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
616 provision
617 :param names: List of key provision parameters"""
619 expression = "samAccountName=dns-%s" % names.netbiosname
620 secrets_msg = secrets_ldb.search(expression=expression)
621 if len(secrets_msg) == 1:
622 res = samdb.search(expression=expression, attrs=[])
623 assert(len(res) == 1)
625 msg = ldb.Message(res[0].dn)
626 machinepass = samba.generate_random_password(128, 255)
627 mputf16 = machinepass.encode('utf-16-le')
628 msg["clearTextPassword"] = ldb.MessageElement(mputf16,
629 ldb.FLAG_MOD_REPLACE,
630 "clearTextPassword")
632 samdb.modify(msg)
634 res = samdb.search(expression=expression,
635 attrs=["msDs-keyVersionNumber"])
636 assert(len(res) == 1)
637 kvno = str(res[0]["msDs-keyVersionNumber"])
639 msg = ldb.Message(secrets_msg[0].dn)
640 msg["secret"] = ldb.MessageElement(machinepass,
641 ldb.FLAG_MOD_REPLACE,
642 "secret")
643 msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
644 ldb.FLAG_MOD_REPLACE,
645 "msDS-KeyVersionNumber")
647 secrets_ldb.modify(msg)
650 def update_krbtgt_account_password(samdb):
651 """Update (change) the password of the krbtgt account
653 :param samdb: An LDB object related to the sam.ldb file of a given provision"""
655 expression = "samAccountName=krbtgt"
656 res = samdb.search(expression=expression, attrs=[])
657 assert(len(res) == 1)
659 msg = ldb.Message(res[0].dn)
660 # Note that the machinepass value is ignored
661 # as the backend (password_hash.c) will generate its
662 # own random values for the krbtgt keys
663 krbtgtpass = samba.generate_random_machine_password(128, 255)
664 kputf16 = krbtgtpass.encode('utf-16-le')
665 msg["clearTextPassword"] = ldb.MessageElement(kputf16,
666 ldb.FLAG_MOD_REPLACE,
667 "clearTextPassword")
669 samdb.modify(msg)
672 def search_constructed_attrs_stored(samdb, rootdn, attrs):
673 """Search a given sam DB for calculated attributes that are
674 still stored in the db.
676 :param samdb: An LDB object pointing to the sam
677 :param rootdn: The base DN where the search should start
678 :param attrs: A list of attributes to be searched
679 :return: A hash with attributes as key and an array of
680 array. Each array contains the dn and the associated
681 values for this attribute as they are stored in the
682 sam."""
684 hashAtt = {}
685 expr = construct_existor_expr(attrs)
686 if expr == "":
687 return hashAtt
688 entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
689 scope=SCOPE_SUBTREE, attrs=attrs,
690 controls=["search_options:1:2", "bypassoperational:0"])
691 if len(entry) == 0:
692 # Nothing anymore
693 return hashAtt
695 for ent in entry:
696 for att in attrs:
697 if ent.get(att):
698 if att in hashAtt:
699 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
700 else:
701 hashAtt[att] = {}
702 hashAtt[att][str(ent.dn).lower()] = str(ent[att])
704 return hashAtt
707 def findprovisionrange(samdb, basedn):
708 """ Find ranges of usn grouped by invocation id and then by timestamp
709 rouned at 1 minute
711 :param samdb: An LDB object pointing to the samdb
712 :param basedn: The DN of the forest
714 :return: A two level dictionary with invoication id as the
715 first level, timestamp as the second one and then
716 max, min, and number as subkeys, representing respectivily
717 the maximum usn for the range, the minimum usn and the number
718 of object with usn in this range.
720 nb_obj = 0
721 hash_id = {}
723 res = samdb.search(base=basedn, expression="objectClass=*",
724 scope=ldb.SCOPE_SUBTREE,
725 attrs=["replPropertyMetaData"],
726 controls=["search_options:1:2"])
728 for e in res:
729 nb_obj = nb_obj + 1
730 obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
731 str(e["replPropertyMetaData"])).ctr
733 for o in obj.array:
734 # like a timestamp but with the resolution of 1 minute
735 minutestamp = _glue.nttime2unix(o.originating_change_time) // 60
736 hash_ts = hash_id.get(str(o.originating_invocation_id))
738 if hash_ts is None:
739 ob = {}
740 ob["min"] = o.originating_usn
741 ob["max"] = o.originating_usn
742 ob["num"] = 1
743 ob["list"] = [str(e.dn)]
744 hash_ts = {}
745 else:
746 ob = hash_ts.get(minutestamp)
747 if ob is None:
748 ob = {}
749 ob["min"] = o.originating_usn
750 ob["max"] = o.originating_usn
751 ob["num"] = 1
752 ob["list"] = [str(e.dn)]
753 else:
754 if ob["min"] > o.originating_usn:
755 ob["min"] = o.originating_usn
756 if ob["max"] < o.originating_usn:
757 ob["max"] = o.originating_usn
758 if not (str(e.dn) in ob["list"]):
759 ob["num"] = ob["num"] + 1
760 ob["list"].append(str(e.dn))
761 hash_ts[minutestamp] = ob
762 hash_id[str(o.originating_invocation_id)] = hash_ts
764 return (hash_id, nb_obj)
767 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
768 """ print the different ranges passed as parameter
770 :param dic: A dictionary as returned by findprovisionrange
771 :param limit_print: minimum number of object in a range in order to print it
772 :param dest: Destination directory
773 :param samdb_path: Path to the sam.ldb file
774 :param invoicationid: Invocation ID for the current provision
776 ldif = ""
778 for id in dic:
779 hash_ts = dic[id]
780 sorted_keys = []
781 sorted_keys.extend(hash_ts.keys())
782 sorted_keys.sort()
784 kept_record = []
785 for k in sorted_keys:
786 obj = hash_ts[k]
787 if obj["num"] > limit_print:
788 dt = _glue.nttime2string(_glue.unix2nttime(k * 60))
789 print("%s # of modification: %d \tmin: %d max: %d" % (dt, obj["num"],
790 obj["min"],
791 obj["max"]))
792 if hash_ts[k]["num"] > 600:
793 kept_record.append(k)
795 # Let's try to concatenate consecutive block if they are in the almost same minutestamp
796 for i in range(0, len(kept_record)):
797 if i != 0:
798 key1 = kept_record[i]
799 key2 = kept_record[i - 1]
800 if key1 - key2 == 1:
801 # previous record is just 1 minute away from current
802 if int(hash_ts[key1]["min"]) == int(hash_ts[key2]["max"]) + 1:
803 # Copy the highest USN in the previous record
804 # and mark the current as skipped
805 hash_ts[key2]["max"] = hash_ts[key1]["max"]
806 hash_ts[key1]["skipped"] = True
808 for k in kept_record:
809 obj = hash_ts[k]
810 if obj.get("skipped") is None:
811 ldif = "%slastProvisionUSN: %d-%d;%s\n" % (ldif, obj["min"],
812 obj["max"], id)
814 if ldif != "":
815 fd, file = tempfile.mkstemp(dir=dest, prefix="usnprov", suffix=".ldif")
816 print()
817 print("To track the USNs modified/created by provision and upgrade proivsion,")
818 print(" the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif)
819 print("We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file)
820 print("You can load this file like this: ldbadd -H %s %s\n" %(str(samdb_path), file))
821 ldif = "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocationid, ldif)
822 os.write(fd, ldif)
823 os.close(fd)
826 def int64range2str(value):
827 """Display the int64 range stored in value as xxx-yyy
829 :param value: The int64 range
830 :return: A string of the representation of the range
832 lvalue = int(value)
833 str = "%d-%d" % (lvalue &0xFFFFFFFF, lvalue >>32)
834 return str