2c4e986d01a40847d100cb566ffa2712c7d253cd
[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, set_gpos_acl, create_gpo_struct,
37                              provision, ProvisioningError,
38                              setsysvolacl, 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 def update_policyids(names, samdb):
216     """Update policy ids that could have changed after sam update
217
218     :param names: List of key provision parameters
219     :param samdb: An Ldb object conntected with the sam DB
220     """
221     # policy guid
222     res = samdb.search(expression="(displayName=Default Domain Policy)",
223                        base="CN=Policies,CN=System," + str(names.rootdn),
224                        scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
225     names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
226     # dc policy guid
227     res2 = samdb.search(expression="(displayName=Default Domain Controllers"
228                                    " Policy)",
229                         base="CN=Policies,CN=System," + str(names.rootdn),
230                         scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
231     if len(res2) == 1:
232         names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
233     else:
234         names.policyid_dc = None
235
236
237 def newprovision(names, session, smbconf, provdir, logger, base_schema=None):
238     """Create a new provision.
239
240     This provision will be the reference for knowing what has changed in the
241     since the latest upgrade in the current provision
242
243     :param names: List of provision parameters
244     :param creds: Credentials for the authentification
245     :param session: Session object
246     :param smbconf: Path to the smb.conf file
247     :param provdir: Directory where the provision will be stored
248     :param logger: A Logger
249     """
250     if os.path.isdir(provdir):
251         shutil.rmtree(provdir)
252     os.mkdir(provdir)
253     logger.info("Provision stored in %s", provdir)
254     return provision(logger, session, smbconf=smbconf,
255                      targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
256                      domain=names.domain, domainguid=names.domainguid,
257                      domainsid=names.domainsid, ntdsguid=names.ntdsguid,
258                      policyguid=names.policyid, policyguid_dc=names.policyid_dc,
259                      hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
260                      invocationid=names.invocation, adminpass=names.adminpass,
261                      krbtgtpass=None, machinepass=None, dnspass=None, root=None,
262                      nobody=None, users=None,
263                      serverrole="domain controller",
264                      backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
265                      slapd_path=None,
266                      dom_for_fun_level=names.domainlevel, dns_backend=names.dns_backend,
267                      useeadb=True, use_ntvfs=True, base_schema=base_schema)
268
269
270 def dn_sort(x, y):
271     """Sorts two DNs in the lexicographical order it and put higher level DN
272     before.
273
274     So given the dns cn=bar,cn=foo and cn=foo the later will be return as
275     smaller
276
277     :param x: First object to compare
278     :param y: Second object to compare
279     """
280     p = re.compile(r'(?<!\\), ?')
281     tab1 = p.split(str(x))
282     tab2 = p.split(str(y))
283     minimum = min(len(tab1), len(tab2))
284     len1 = len(tab1)-1
285     len2 = len(tab2)-1
286     # Note: python range go up to upper limit but do not include it
287     for i in range(0, minimum):
288         ret = cmp_fn(tab1[len1-i], tab2[len2-i])
289         if ret != 0:
290             return ret
291         else:
292             if i == minimum-1:
293                 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
294                 if len1 > len2:
295                     return 1
296                 else:
297                     return -1
298     return ret
299
300
301 def identic_rename(ldbobj, dn):
302     """Perform a back and forth rename to trigger renaming on attribute that
303     can't be directly modified.
304
305     :param lbdobj: An Ldb Object
306     :param dn: DN of the object to manipulate
307     """
308     (before, after) = str(dn).split('=', 1)
309     # we need to use relax to avoid the subtree_rename constraints
310     ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
311     ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
312
313
314 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
315     """Update secrets.ldb
316
317     :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
318         of the reference provision
319     :param secrets_ldb: An LDB object that is connected to the secrets.ldb
320         of the updated provision
321     """
322
323     messagefunc(SIMPLE, "Update of secrets.ldb")
324     reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
325     current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
326     assert reference, "Reference modules list can not be empty"
327     if len(current) == 0:
328         # No modules present
329         delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
330         delta.dn = reference[0].dn
331         secrets_ldb.add(reference[0])
332     else:
333         delta = secrets_ldb.msg_diff(current[0], reference[0])
334         delta.dn = current[0].dn
335         secrets_ldb.modify(delta)
336
337     reference = newsecrets_ldb.search(expression="objectClass=top", base="",
338                                       scope=SCOPE_SUBTREE, attrs=["dn"])
339     current = secrets_ldb.search(expression="objectClass=top", base="",
340                                  scope=SCOPE_SUBTREE, attrs=["dn"])
341     hash_new = {}
342     hash = {}
343     listMissing = []
344     listPresent = []
345
346     empty = ldb.Message()
347     for i in range(0, len(reference)):
348         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
349
350     # Create a hash for speeding the search of existing object in the
351     # current provision
352     for i in range(0, len(current)):
353         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
354
355     for k in hash_new.keys():
356         if k not in hash:
357             listMissing.append(hash_new[k])
358         else:
359             listPresent.append(hash_new[k])
360
361     for entry in listMissing:
362         reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
363                                           base="", scope=SCOPE_SUBTREE)
364         current = secrets_ldb.search(expression="distinguishedName=%s" % entry,
365                                      base="", scope=SCOPE_SUBTREE)
366         delta = secrets_ldb.msg_diff(empty, reference[0])
367         for att in hashAttrNotCopied:
368             delta.remove(att)
369         messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
370                     reference[0].dn)
371         for att in delta:
372             messagefunc(CHANGE, " Adding attribute %s" % att)
373         delta.dn = reference[0].dn
374         secrets_ldb.add(delta)
375
376     for entry in listPresent:
377         reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
378                                           base="", scope=SCOPE_SUBTREE)
379         current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
380                                      scope=SCOPE_SUBTREE)
381         delta = secrets_ldb.msg_diff(current[0], reference[0])
382         for att in hashAttrNotCopied:
383             delta.remove(att)
384         for att in delta:
385             if att == "name":
386                 messagefunc(CHANGE, "Found attribute name on  %s,"
387                                     " must rename the DN" % (current[0].dn))
388                 identic_rename(secrets_ldb, reference[0].dn)
389             else:
390                 delta.remove(att)
391
392     for entry in listPresent:
393         reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
394                                           scope=SCOPE_SUBTREE)
395         current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
396                                      scope=SCOPE_SUBTREE)
397         delta = secrets_ldb.msg_diff(current[0], reference[0])
398         for att in hashAttrNotCopied:
399             delta.remove(att)
400         for att in delta:
401             if att == "msDS-KeyVersionNumber":
402                 delta.remove(att)
403             if att != "dn":
404                 messagefunc(CHANGE,
405                             "Adding/Changing attribute %s to %s" %
406                             (att, current[0].dn))
407
408         delta.dn = current[0].dn
409         secrets_ldb.modify(delta)
410
411     res2 = secrets_ldb.search(expression="(samaccountname=dns)",
412                               scope=SCOPE_SUBTREE, attrs=["dn"])
413
414     if len(res2) == 1:
415             messagefunc(SIMPLE, "Remove old dns account")
416             secrets_ldb.delete(res2[0]["dn"])
417
418
419 def getOEMInfo(samdb, rootdn):
420     """Return OEM Information on the top level Samba4 use to store version
421     info in this field
422
423     :param samdb: An LDB object connect to sam.ldb
424     :param rootdn: Root DN of the domain
425     :return: The content of the field oEMInformation (if any)
426     """
427     res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
428                        scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
429     if len(res) > 0 and res[0].get("oEMInformation"):
430         info = res[0]["oEMInformation"]
431         return info
432     else:
433         return ""
434
435
436 def updateOEMInfo(samdb, rootdn):
437     """Update the OEMinfo field to add information about upgrade
438
439     :param samdb: an LDB object connected to the sam DB
440     :param rootdn: The string representation of the root DN of
441         the provision (ie. DC=...,DC=...)
442     """
443     res = samdb.search(expression="(objectClass=*)", base=rootdn,
444                        scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
445     if len(res) > 0:
446         if res[0].get("oEMInformation"):
447             info = str(res[0]["oEMInformation"])
448         else:
449             info = ""
450         info = "%s, upgrade to %s" % (info, version)
451         delta = ldb.Message()
452         delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
453         delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
454                                                      "oEMInformation" )
455         samdb.modify(delta)
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 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
471     """For a given hash associating dn and a number, this function will
472     update the replPropertyMetaData of each dn in the hash, so that the
473     calculated value of the msDs-KeyVersionNumber is equal or superior to the
474     one associated to the given dn.
475
476     :param samdb: An SamDB object pointing to the sam
477     :param rootdn: The base DN where we want to start
478     :param hashDns: A hash with dn as key and number representing the
479                  minimum value of msDs-KeyVersionNumber that we want to
480                  have
481     """
482     entry = samdb.search(expression='(objectClass=user)',
483                          base=ldb.Dn(samdb,str(rootdn)),
484                          scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
485                          controls=["search_options:1:2"])
486     done = 0
487     hashDone = {}
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 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
503     """Update the provision container db: sam.ldb
504     This function is aimed for alpha9 and newer;
505
506     :param refsampath: Path to the samdb in the reference provision
507     :param sampath: Path to the samdb in the upgraded provision
508     :param creds: Credential used for openning LDB files
509     :param session: Session to use for openning LDB files
510     :param lp: A loadparam object
511     :return: A msg_diff object with the difference between the @ATTRIBUTES
512              of the current provision and the reference provision
513     """
514
515     message(SIMPLE,
516             "Update base samdb by searching difference with reference one")
517     refsam = Ldb(refsampath, session_info=session, credentials=creds,
518                  lp=lp, options=["modules:"])
519     sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
520               options=["modules:"])
521
522     empty = ldb.Message()
523     deltaattr = None
524     reference = refsam.search(expression="")
525
526     for refentry in reference:
527         entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
528                            scope=SCOPE_SUBTREE)
529         if not len(entry):
530             delta = sam.msg_diff(empty, refentry)
531             message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
532             if str(refentry.dn) == "@PROVISION" and\
533                     delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
534                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
535             delta.dn = refentry.dn
536             sam.add(delta)
537         else:
538             delta = sam.msg_diff(entry[0], refentry)
539             if str(refentry.dn) == "@ATTRIBUTES":
540                 deltaattr = sam.msg_diff(refentry, entry[0])
541             if str(refentry.dn) == "@PROVISION" and\
542                     delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
543                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
544             if len(delta.items()) > 1:
545                 delta.dn = refentry.dn
546                 sam.modify(delta)
547
548     return deltaattr
549
550
551 def construct_existor_expr(attrs):
552     """Construct a exists or LDAP search expression.
553
554     :param attrs: List of attribute on which we want to create the search
555         expression.
556     :return: A string representing the expression, if attrs is empty an
557         empty string is returned
558     """
559     expr = ""
560     if len(attrs) > 0:
561         expr = "(|"
562         for att in attrs:
563             expr = "%s(%s=*)"%(expr,att)
564         expr = "%s)"%expr
565     return expr
566
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
570
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"""
575
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)
582
583         msg = ldb.Message(res[0].dn)
584         machinepass = samba.generate_random_machine_password(128, 255)
585         mputf16 = machinepass.encode('utf-16-le')
586         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
587                                                       ldb.FLAG_MOD_REPLACE,
588                                                       "clearTextPassword")
589         samdb.modify(msg)
590
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])
596
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")
608
609 def update_dns_account_password(samdb, secrets_ldb, names):
610     """Update (change) the password of the dns both in the SAM db and in
611        secret one
612
613     :param samdb: An LDB object related to the sam.ldb file of a given provision
614     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
615                         provision
616     :param names: List of key provision parameters"""
617
618     expression = "samAccountName=dns-%s" % names.netbiosname
619     secrets_msg = secrets_ldb.search(expression=expression)
620     if len(secrets_msg) == 1:
621         res = samdb.search(expression=expression, attrs=[])
622         assert(len(res) == 1)
623
624         msg = ldb.Message(res[0].dn)
625         machinepass = samba.generate_random_password(128, 255)
626         mputf16 = machinepass.encode('utf-16-le')
627         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
628                                                       ldb.FLAG_MOD_REPLACE,
629                                                       "clearTextPassword")
630
631         samdb.modify(msg)
632
633         res = samdb.search(expression=expression,
634                            attrs=["msDs-keyVersionNumber"])
635         assert(len(res) == 1)
636         kvno = str(res[0]["msDs-keyVersionNumber"])
637
638         msg = ldb.Message(secrets_msg[0].dn)
639         msg["secret"] = ldb.MessageElement(machinepass,
640                                            ldb.FLAG_MOD_REPLACE,
641                                            "secret")
642         msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
643                                                           ldb.FLAG_MOD_REPLACE,
644                                                           "msDS-KeyVersionNumber")
645
646         secrets_ldb.modify(msg)
647
648 def update_krbtgt_account_password(samdb):
649     """Update (change) the password of the krbtgt account
650
651     :param samdb: An LDB object related to the sam.ldb file of a given provision"""
652
653     expression = "samAccountName=krbtgt"
654     res = samdb.search(expression=expression, attrs=[])
655     assert(len(res) == 1)
656
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")
663
664     samdb.modify(msg)
665
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.
669
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."""
677
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
688
689     for ent in entry:
690         for att in attrs:
691             if ent.get(att):
692                 if att in hashAtt:
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])
697
698     return hashAtt
699
700 def findprovisionrange(samdb, basedn):
701     """ Find ranges of usn grouped by invocation id and then by timestamp
702         rouned at 1 minute
703
704         :param samdb: An LDB object pointing to the samdb
705         :param basedn: The DN of the forest
706
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.
712     """
713     nb_obj = 0
714     hash_id = {}
715
716     res = samdb.search(base=basedn, expression="objectClass=*",
717                        scope=ldb.SCOPE_SUBTREE,
718                        attrs=["replPropertyMetaData"],
719                        controls=["search_options:1:2"])
720
721     for e in res:
722         nb_obj = nb_obj + 1
723         obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
724                          str(e["replPropertyMetaData"])).ctr
725
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))
730
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
756
757     return (hash_id, nb_obj)
758
759 def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
760     """ print the differents ranges passed as parameter
761
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
767     """
768     ldif = ""
769
770     for id in dic:
771         hash_ts = dic[id]
772         sorted_keys = []
773         sorted_keys.extend(hash_ts.keys())
774         sorted_keys.sort()
775
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)
786
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
799
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)
805
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)
815
816 def int64range2str(value):
817     """Display the int64 range stored in value as xxx-yyy
818
819     :param value: The int64 range
820     :return: A string of the representation of the range
821     """
822
823     lvalue = long(value)
824     str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
825     return str