libndr: Avoid assigning duplicate versions to symbols
[amitay/samba.git] / python / samba / upgradehelpers.py
1 # Helpers for provision stuff
2 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2012
3 #
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
7 #
8 #
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.
13 #
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.
18 #
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/>.
21
22 from __future__ import print_function
23 from __future__ import division
24 """Helpers used for upgrading between different database formats."""
25
26 import os
27 import re
28 import shutil
29 import samba
30
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
46
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
59
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"])
68
69
70 class ProvisionLDB(object):
71
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
81
82     def dbs(self):
83         return (self.sam, self.secrets, self.idmap, self.privilege)
84
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()
93
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()
107
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()
124
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
131
132
133 def get_ldbs(paths, creds, session, lp):
134     """Return LDB object mapped on most important databases
135
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"""
141
142     ldbs = ProvisionLDB()
143
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)
157
158     return ldbs
159
160
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
165
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
170     """
171
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
188
189
190 def get_paths(param, targetdir=None, smbconf=None):
191     """Get paths to important provision objects (smb.conf, ldb files, ...)
192
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()
206
207     if not os.path.exists(smbconf):
208         raise ProvisioningError("Unable to find smb.conf at %s" % smbconf)
209
210     lp = param.LoadParm()
211     lp.load(smbconf)
212     paths = provision_paths_from_lp(lp, lp.get("realm"))
213     return paths
214
215
216 def update_policyids(names, samdb):
217     """Update policy ids that could have changed after sam update
218
219     :param names: List of key provision parameters
220     :param samdb: An Ldb object conntected with the sam DB
221     """
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
236
237
238 def newprovision(names, session, smbconf, provdir, logger, base_schema=None):
239     """Create a new provision.
240
241     This provision will be the reference for knowing what has changed in the
242     since the latest upgrade in the current provision
243
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
250     """
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                      dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
266                      useeadb=True, use_ntvfs=True, base_schema=base_schema)
267
268
269 def dn_sort(x, y):
270     """Sorts two DNs in the lexicographical order it and put higher level DN
271     before.
272
273     So given the dns cn=bar,cn=foo and cn=foo the later will be return as
274     smaller
275
276     :param x: First object to compare
277     :param y: Second object to compare
278     """
279     p = re.compile(r'(?<!\\), ?')
280     tab1 = p.split(str(x))
281     tab2 = p.split(str(y))
282     minimum = min(len(tab1), len(tab2))
283     len1 = len(tab1) - 1
284     len2 = len(tab2) - 1
285     # Note: python range go up to upper limit but do not include it
286     for i in range(0, minimum):
287         ret = cmp_fn(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
298
299
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.
303
304     :param lbdobj: An Ldb Object
305     :param dn: DN of the object to manipulate
306     """
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"])
311
312
313 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
314     """Update secrets.ldb
315
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
320     """
321
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)
335
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 = []
344
345     empty = ldb.Message()
346     for i in range(0, len(reference)):
347         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
348
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"]
353
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])
359
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)
374
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)
390
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))
406
407         delta.dn = current[0].dn
408         secrets_ldb.modify(delta)
409
410     res2 = secrets_ldb.search(expression="(samaccountname=dns)",
411                               scope=SCOPE_SUBTREE, attrs=["dn"])
412
413     if len(res2) == 1:
414             messagefunc(SIMPLE, "Remove old dns account")
415             secrets_ldb.delete(res2[0]["dn"])
416
417
418 def getOEMInfo(samdb, rootdn):
419     """Return OEM Information on the top level Samba4 use to store version
420     info in this field
421
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)
425     """
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 ""
433
434
435 def updateOEMInfo(samdb, rootdn):
436     """Update the OEMinfo field to add information about upgrade
437
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=...)
441     """
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)
455
456
457 def update_gpo(paths, samdb, names, lp, message):
458     """Create missing GPO file object if needed
459     """
460     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
461     if not os.path.isdir(dir):
462         create_gpo_struct(dir)
463
464     if names.policyid_dc is None:
465         raise ProvisioningError("Policy ID for Domain controller is missing")
466     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
467     if not os.path.isdir(dir):
468         create_gpo_struct(dir)
469
470
471 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
472     """For a given hash associating dn and a number, this function will
473     update the replPropertyMetaData of each dn in the hash, so that the
474     calculated value of the msDs-KeyVersionNumber is equal or superior to the
475     one associated to the given dn.
476
477     :param samdb: An SamDB object pointing to the sam
478     :param rootdn: The base DN where we want to start
479     :param hashDns: A hash with dn as key and number representing the
480                  minimum value of msDs-KeyVersionNumber that we want to
481                  have
482     """
483     entry = samdb.search(expression='(objectClass=user)',
484                          base=ldb.Dn(samdb, str(rootdn)),
485                          scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
486                          controls=["search_options:1:2"])
487     done = 0
488     if len(entry) == 0:
489         raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
490     else:
491         for e in entry:
492             if str(e.dn).lower() in hashDns:
493                 val = e.get("msDs-KeyVersionNumber")
494                 if not val:
495                     val = "0"
496                 version = int(str(hashDns[str(e.dn).lower()]))
497                 if int(str(val)) < version:
498                     done = done + 1
499                     samdb.set_attribute_replmetadata_version(str(e.dn),
500                                                              "unicodePwd",
501                                                              version, True)
502
503
504 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
505     """Update the provision container db: sam.ldb
506     This function is aimed for alpha9 and newer;
507
508     :param refsampath: Path to the samdb in the reference provision
509     :param sampath: Path to the samdb in the upgraded provision
510     :param creds: Credential used for openning LDB files
511     :param session: Session to use for openning LDB files
512     :param lp: A loadparam object
513     :return: A msg_diff object with the difference between the @ATTRIBUTES
514              of the current provision and the reference provision
515     """
516
517     message(SIMPLE,
518             "Update base samdb by searching difference with reference one")
519     refsam = Ldb(refsampath, session_info=session, credentials=creds,
520                  lp=lp, options=["modules:"])
521     sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
522               options=["modules:"])
523
524     empty = ldb.Message()
525     deltaattr = None
526     reference = refsam.search(expression="")
527
528     for refentry in reference:
529         entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
530                            scope=SCOPE_SUBTREE)
531         if not len(entry):
532             delta = sam.msg_diff(empty, refentry)
533             message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
534             if str(refentry.dn) == "@PROVISION" and\
535                     delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
536                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
537             delta.dn = refentry.dn
538             sam.add(delta)
539         else:
540             delta = sam.msg_diff(entry[0], refentry)
541             if str(refentry.dn) == "@ATTRIBUTES":
542                 deltaattr = sam.msg_diff(refentry, entry[0])
543             if str(refentry.dn) == "@PROVISION" and\
544                     delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
545                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
546             if len(delta.items()) > 1:
547                 delta.dn = refentry.dn
548                 sam.modify(delta)
549
550     return deltaattr
551
552
553 def construct_existor_expr(attrs):
554     """Construct a exists or LDAP search expression.
555
556     :param attrs: List of attribute on which we want to create the search
557         expression.
558     :return: A string representing the expression, if attrs is empty an
559         empty string is returned
560     """
561     expr = ""
562     if len(attrs) > 0:
563         expr = "(|"
564         for att in attrs:
565             expr = "%s(%s=*)" %(expr, att)
566         expr = "%s)" %expr
567     return expr
568
569
570 def update_machine_account_password(samdb, secrets_ldb, names):
571     """Update (change) the password of the current DC both in the SAM db and in
572        secret one
573
574     :param samdb: An LDB object related to the sam.ldb file of a given provision
575     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
576                         provision
577     :param names: List of key provision parameters"""
578
579     expression = "samAccountName=%s$" % names.netbiosname
580     secrets_msg = secrets_ldb.search(expression=expression,
581                                      attrs=["secureChannelType"])
582     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
583         res = samdb.search(expression=expression, attrs=[])
584         assert(len(res) == 1)
585
586         msg = ldb.Message(res[0].dn)
587         machinepass = samba.generate_random_machine_password(128, 255)
588         mputf16 = machinepass.encode('utf-16-le')
589         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
590                                                       ldb.FLAG_MOD_REPLACE,
591                                                       "clearTextPassword")
592         samdb.modify(msg)
593
594         res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
595                            attrs=["msDs-keyVersionNumber"])
596         assert(len(res) == 1)
597         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
598         secChanType = int(secrets_msg[0]["secureChannelType"][0])
599
600         secretsdb_self_join(secrets_ldb, domain=names.domain,
601                             realm=names.realm,
602                             domainsid=names.domainsid,
603                             dnsdomain=names.dnsdomain,
604                             netbiosname=names.netbiosname,
605                             machinepass=machinepass,
606                             key_version_number=kvno,
607                             secure_channel_type=secChanType)
608     else:
609         raise ProvisioningError("Unable to find a Secure Channel"
610                                 "of type SEC_CHAN_BDC")
611
612
613 def update_dns_account_password(samdb, secrets_ldb, names):
614     """Update (change) the password of the dns both in the SAM db and in
615        secret one
616
617     :param samdb: An LDB object related to the sam.ldb file of a given provision
618     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
619                         provision
620     :param names: List of key provision parameters"""
621
622     expression = "samAccountName=dns-%s" % names.netbiosname
623     secrets_msg = secrets_ldb.search(expression=expression)
624     if len(secrets_msg) == 1:
625         res = samdb.search(expression=expression, attrs=[])
626         assert(len(res) == 1)
627
628         msg = ldb.Message(res[0].dn)
629         machinepass = samba.generate_random_password(128, 255)
630         mputf16 = machinepass.encode('utf-16-le')
631         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
632                                                       ldb.FLAG_MOD_REPLACE,
633                                                       "clearTextPassword")
634
635         samdb.modify(msg)
636
637         res = samdb.search(expression=expression,
638                            attrs=["msDs-keyVersionNumber"])
639         assert(len(res) == 1)
640         kvno = str(res[0]["msDs-keyVersionNumber"])
641
642         msg = ldb.Message(secrets_msg[0].dn)
643         msg["secret"] = ldb.MessageElement(machinepass,
644                                            ldb.FLAG_MOD_REPLACE,
645                                            "secret")
646         msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
647                                                           ldb.FLAG_MOD_REPLACE,
648                                                           "msDS-KeyVersionNumber")
649
650         secrets_ldb.modify(msg)
651
652
653 def update_krbtgt_account_password(samdb):
654     """Update (change) the password of the krbtgt account
655
656     :param samdb: An LDB object related to the sam.ldb file of a given provision"""
657
658     expression = "samAccountName=krbtgt"
659     res = samdb.search(expression=expression, attrs=[])
660     assert(len(res) == 1)
661
662     msg = ldb.Message(res[0].dn)
663     machinepass = samba.generate_random_machine_password(128, 255)
664     mputf16 = machinepass.encode('utf-16-le')
665     msg["clearTextPassword"] = ldb.MessageElement(mputf16,
666                                                   ldb.FLAG_MOD_REPLACE,
667                                                   "clearTextPassword")
668
669     samdb.modify(msg)
670
671
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.
675
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."""
683
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
694
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])
703
704     return hashAtt
705
706
707 def findprovisionrange(samdb, basedn):
708     """ Find ranges of usn grouped by invocation id and then by timestamp
709         rouned at 1 minute
710
711         :param samdb: An LDB object pointing to the samdb
712         :param basedn: The DN of the forest
713
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.
719     """
720     nb_obj = 0
721     hash_id = {}
722
723     res = samdb.search(base=basedn, expression="objectClass=*",
724                        scope=ldb.SCOPE_SUBTREE,
725                        attrs=["replPropertyMetaData"],
726                        controls=["search_options:1:2"])
727
728     for e in res:
729         nb_obj = nb_obj + 1
730         obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
731                          str(e["replPropertyMetaData"])).ctr
732
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))
737
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
763
764     return (hash_id, nb_obj)
765
766
767 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
768     """ print the differents ranges passed as parameter
769
770         :param dic: A dictionnary 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
775     """
776     ldif = ""
777
778     for id in dic:
779         hash_ts = dic[id]
780         sorted_keys = []
781         sorted_keys.extend(hash_ts.keys())
782         sorted_keys.sort()
783
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)
794
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
807
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)
813
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)
824
825
826 def int64range2str(value):
827     """Display the int64 range stored in value as xxx-yyy
828
829     :param value: The int64 range
830     :return: A string of the representation of the range
831     """
832     lvalue = int(value)
833     str = "%d-%d" % (lvalue &0xFFFFFFFF, lvalue >>32)
834     return str