s4 provision: DNS backend should be set by caller
[obnox/samba/samba-obnox.git] / source4 / scripting / python / samba / upgradehelpers.py
1 #!/usr/bin/env python
2 #
3 # Helpers for provision stuff
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
5 #
6 # Based on provision a Samba4 server by
7 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
8 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
9 #
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23
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 import Ldb, version, ntacls
32 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
33 import ldb
34 from samba.provision import (provision_paths_from_lp,
35                             getpolicypath, set_gpos_acl, create_gpo_struct,
36                             FILL_FULL, provision, ProvisioningError,
37                             setsysvolacl, secretsdb_self_join)
38 from samba.dcerpc import xattr
39 from samba.dcerpc.misc import SEC_CHAN_BDC
40 from samba.samdb import SamDB
41
42 # All the ldb related to registry are commented because the path for them is
43 # relative in the provisionPath object
44 # And so opening them create a file in the current directory which is not what
45 # we want
46 # I still keep them commented because I plan soon to make more cleaner
47 ERROR =     -1
48 SIMPLE =     0x00
49 CHANGE =     0x01
50 CHANGESD =     0x02
51 GUESS =     0x04
52 PROVISION =    0x08
53 CHANGEALL =    0xff
54
55 hashAttrNotCopied = set(["dn", "whenCreated", "whenChanged", "objectGUID",
56     "uSNCreated", "replPropertyMetaData", "uSNChanged", "parentGUID",
57     "objectCategory", "distinguishedName", "nTMixedDomain",
58     "showInAdvancedViewOnly", "instanceType", "msDS-Behavior-Version",
59     "nextRid", "cn", "versionNumber", "lmPwdHistory", "pwdLastSet",
60     "ntPwdHistory", "unicodePwd","dBCSPwd", "supplementalCredentials",
61     "gPCUserExtensionNames", "gPCMachineExtensionNames","maxPwdAge", "secret",
62     "possibleInferiors", "privilege", "sAMAccountType"])
63
64
65 class ProvisionLDB(object):
66
67     def __init__(self):
68         self.sam = None
69         self.secrets = None
70         self.idmap = None
71         self.privilege = None
72         self.hkcr = None
73         self.hkcu = None
74         self.hku = None
75         self.hklm = None
76
77     def startTransactions(self):
78         self.sam.transaction_start()
79         self.secrets.transaction_start()
80         self.idmap.transaction_start()
81         self.privilege.transaction_start()
82 # TO BE DONE
83 #        self.hkcr.transaction_start()
84 #        self.hkcu.transaction_start()
85 #        self.hku.transaction_start()
86 #        self.hklm.transaction_start()
87
88     def groupedRollback(self):
89         ok = True
90         try:
91             self.sam.transaction_cancel()
92         except Exception:
93             ok = False
94
95         try:
96             self.secrets.transaction_cancel()
97         except Exception:
98             ok = False
99
100         try:
101             self.idmap.transaction_cancel()
102         except Exception:
103             ok = False
104
105         try:
106             self.privilege.transaction_cancel()
107         except Exception:
108             ok = False
109
110         return ok
111 # TO BE DONE
112 #        self.hkcr.transaction_cancel()
113 #        self.hkcu.transaction_cancel()
114 #        self.hku.transaction_cancel()
115 #        self.hklm.transaction_cancel()
116
117     def groupedCommit(self):
118         try:
119             self.sam.transaction_prepare_commit()
120             self.secrets.transaction_prepare_commit()
121             self.idmap.transaction_prepare_commit()
122             self.privilege.transaction_prepare_commit()
123         except Exception:
124             return self.groupedRollback()
125 # TO BE DONE
126 #        self.hkcr.transaction_prepare_commit()
127 #        self.hkcu.transaction_prepare_commit()
128 #        self.hku.transaction_prepare_commit()
129 #        self.hklm.transaction_prepare_commit()
130         try:
131             self.sam.transaction_commit()
132             self.secrets.transaction_commit()
133             self.idmap.transaction_commit()
134             self.privilege.transaction_commit()
135         except Exception:
136             return self.groupedRollback()
137
138 # TO BE DONE
139 #        self.hkcr.transaction_commit()
140 #        self.hkcu.transaction_commit()
141 #        self.hku.transaction_commit()
142 #        self.hklm.transaction_commit()
143         return True
144
145 def get_ldbs(paths, creds, session, lp):
146     """Return LDB object mapped on most important databases
147
148     :param paths: An object holding the different importants paths for provision object
149     :param creds: Credential used for openning LDB files
150     :param session: Session to use for openning LDB files
151     :param lp: A loadparam object
152     :return: A ProvisionLDB object that contains LDB object for the different LDB files of the provision"""
153
154     ldbs = ProvisionLDB()
155
156     ldbs.sam = SamDB(paths.samdb, session_info=session, credentials=creds, lp=lp, options=["modules:samba_dsdb"])
157     ldbs.secrets = Ldb(paths.secrets, session_info=session, credentials=creds, lp=lp)
158     ldbs.idmap = Ldb(paths.idmapdb, session_info=session, credentials=creds, lp=lp)
159     ldbs.privilege = Ldb(paths.privilege, session_info=session, credentials=creds, lp=lp)
160 #    ldbs.hkcr = Ldb(paths.hkcr, session_info=session, credentials=creds, lp=lp)
161 #    ldbs.hkcu = Ldb(paths.hkcu, session_info=session, credentials=creds, lp=lp)
162 #    ldbs.hku = Ldb(paths.hku, session_info=session, credentials=creds, lp=lp)
163 #    ldbs.hklm = Ldb(paths.hklm, session_info=session, credentials=creds, lp=lp)
164
165     return ldbs
166
167
168 def usn_in_range(usn, range):
169     """Check if the usn is in one of the range provided.
170     To do so, the value is checked to be between the lower bound and
171     higher bound of a range
172
173     :param usn: A integer value corresponding to the usn that we want to update
174     :param range: A list of integer representing ranges, lower bounds are in
175                   the even indices, higher in odd indices
176     :return: True if the usn is in one of the range, False otherwise
177     """
178
179     idx = 0
180     cont = True
181     ok = False
182     while cont:
183         if idx ==  len(range):
184             cont = False
185             continue
186         if usn < int(range[idx]):
187             if idx %2 == 1:
188                 ok = True
189             cont = False
190         if usn == int(range[idx]):
191             cont = False
192             ok = True
193         idx = idx + 1
194     return ok
195
196
197 def get_paths(param, targetdir=None, smbconf=None):
198     """Get paths to important provision objects (smb.conf, ldb files, ...)
199
200     :param param: Param object
201     :param targetdir: Directory where the provision is (or will be) stored
202     :param smbconf: Path to the smb.conf file
203     :return: A list with the path of important provision objects"""
204     if targetdir is not None:
205         etcdir = os.path.join(targetdir, "etc")
206         if not os.path.exists(etcdir):
207             os.makedirs(etcdir)
208         smbconf = os.path.join(etcdir, "smb.conf")
209     if smbconf is None:
210         smbconf = param.default_path()
211
212     if not os.path.exists(smbconf):
213         raise ProvisioningError("Unable to find smb.conf")
214
215     lp = param.LoadParm()
216     lp.load(smbconf)
217     paths = provision_paths_from_lp(lp, lp.get("realm"))
218     return paths
219
220 def update_policyids(names, samdb):
221     """Update policy ids that could have changed after sam update
222
223     :param names: List of key provision parameters
224     :param samdb: An Ldb object conntected with the sam DB
225     """
226     # policy guid
227     res = samdb.search(expression="(displayName=Default Domain Policy)",
228                         base="CN=Policies,CN=System," + str(names.rootdn),
229                         scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
230     names.policyid = str(res[0]["cn"]).replace("{","").replace("}","")
231     # dc policy guid
232     res2 = samdb.search(expression="(displayName=Default Domain Controllers"
233                                    " Policy)",
234                             base="CN=Policies,CN=System," + str(names.rootdn),
235                             scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
236     if len(res2) == 1:
237         names.policyid_dc = str(res2[0]["cn"]).replace("{","").replace("}","")
238     else:
239         names.policyid_dc = None
240
241
242 def newprovision(names, creds, session, smbconf, provdir, logger):
243     """Create a new provision.
244
245     This provision will be the reference for knowing what has changed in the
246     since the latest upgrade in the current provision
247
248     :param names: List of provision parameters
249     :param creds: Credentials for the authentification
250     :param session: Session object
251     :param smbconf: Path to the smb.conf file
252     :param provdir: Directory where the provision will be stored
253     :param logger: A Logger
254     """
255     if os.path.isdir(provdir):
256         shutil.rmtree(provdir)
257     os.mkdir(provdir)
258     logger.info("Provision stored in %s", provdir)
259     dns_backend="BIND9_FLATFILE"
260     provision(logger, session, creds, smbconf=smbconf,
261             targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
262             domain=names.domain, domainguid=names.domainguid,
263             domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
264             policyguid=names.policyid, policyguid_dc=names.policyid_dc,
265             hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
266             invocationid=names.invocation, adminpass=names.adminpass,
267             krbtgtpass=None, machinepass=None, dnspass=None, root=None,
268             nobody=None, wheel=None, users=None,
269             serverrole="domain controller", ldap_backend_extra_port=None,
270             backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
271             slapd_path=None, setup_ds_path=None, nosync=None,
272             dom_for_fun_level=names.domainlevel, dns_backend=dns_backend,
273             ldap_dryrun_mode=None, useeadb=True)
274
275
276 def dn_sort(x, y):
277     """Sorts two DNs in the lexicographical order it and put higher level DN
278     before.
279
280     So given the dns cn=bar,cn=foo and cn=foo the later will be return as
281     smaller
282
283     :param x: First object to compare
284     :param y: Second object to compare
285     """
286     p = re.compile(r'(?<!\\), ?')
287     tab1 = p.split(str(x))
288     tab2 = p.split(str(y))
289     minimum = min(len(tab1), len(tab2))
290     len1 = len(tab1)-1
291     len2 = len(tab2)-1
292     # Note: python range go up to upper limit but do not include it
293     for i in range(0, minimum):
294         ret = cmp(tab1[len1-i], tab2[len2-i])
295         if ret != 0:
296             return ret
297         else:
298             if i == minimum-1:
299                 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
300                 if len1 > len2:
301                     return 1
302                 else:
303                     return -1
304     return ret
305
306
307 def identic_rename(ldbobj, dn):
308     """Perform a back and forth rename to trigger renaming on attribute that
309     can't be directly modified.
310
311     :param lbdobj: An Ldb Object
312     :param dn: DN of the object to manipulate
313     """
314     (before, after) = str(dn).split('=', 1)
315     # we need to use relax to avoid the subtree_rename constraints
316     ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), ["relax:0"])
317     ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn, ["relax:0"])
318
319
320 def chunck_acl(acl):
321     """Return separate ACE of an ACL
322
323     :param acl: A string representing the ACL
324     :return: A hash with different parts
325     """
326
327     p = re.compile(r'(\w+)?(\(.*?\))')
328     tab = p.findall(acl)
329
330     hash = {}
331     hash["aces"] = []
332     for e in tab:
333         if len(e[0]) > 0:
334             hash["flags"] = e[0]
335         hash["aces"].append(e[1])
336
337     return hash
338
339
340 def chunck_sddl(sddl):
341     """ Return separate parts of the SDDL (owner, group, ...)
342
343     :param sddl: An string containing the SDDL to chunk
344     :return: A hash with the different chunk
345     """
346
347     p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
348     tab = p.findall(sddl)
349
350     hash = {}
351     for e in tab:
352         if e[0] == "O:":
353             hash["owner"] = e[1]
354         if e[0] == "G:":
355             hash["group"] = e[1]
356         if e[0] == "D:":
357             hash["dacl"] = e[1]
358         if e[0] == "S:":
359             hash["sacl"] = e[1]
360
361     return hash
362
363
364 def get_diff_sddls(refsddl, cursddl, checkSacl = True):
365     """Get the difference between 2 sddl
366
367     This function split the textual representation of ACL into smaller
368     chunck in order to not to report a simple permutation as a difference
369
370     :param refsddl: First sddl to compare
371     :param cursddl: Second sddl to compare
372     :param checkSacl: If false we skip the sacl checks
373     :return: A string that explain difference between sddls
374     """
375
376     txt = ""
377     hash_cur = chunck_sddl(cursddl)
378     hash_ref = chunck_sddl(refsddl)
379
380     if not hash_cur.has_key("owner"):
381         txt = "\tNo owner in current SD"
382     elif hash_cur["owner"] != hash_ref["owner"]:
383         txt = "\tOwner mismatch: %s (in ref) %s" \
384               "(in current)\n" % (hash_ref["owner"], hash_cur["owner"])
385
386     if not hash_cur.has_key("group"):
387         txt = "%s\tNo group in current SD" % txt
388     elif hash_cur["group"] != hash_ref["group"]:
389         txt = "%s\tGroup mismatch: %s (in ref) %s" \
390               "(in current)\n" % (txt, hash_ref["group"], hash_cur["group"])
391
392     parts = [ "dacl" ]
393     if checkSacl:
394         parts.append("sacl")
395     for part in parts:
396         if hash_cur.has_key(part) and hash_ref.has_key(part):
397
398             # both are present, check if they contain the same ACE
399             h_cur = set()
400             h_ref = set()
401             c_cur = chunck_acl(hash_cur[part])
402             c_ref = chunck_acl(hash_ref[part])
403
404             for elem in c_cur["aces"]:
405                 h_cur.add(elem)
406
407             for elem in c_ref["aces"]:
408                 h_ref.add(elem)
409
410             for k in set(h_ref):
411                 if k in h_cur:
412                     h_cur.remove(k)
413                     h_ref.remove(k)
414
415             if len(h_cur) + len(h_ref) > 0:
416                 txt = "%s\tPart %s is different between reference" \
417                       " and current here is the detail:\n" % (txt, part)
418
419                 for item in h_cur:
420                     txt = "%s\t\t%s ACE is not present in the" \
421                           " reference\n" % (txt, item)
422
423                 for item in h_ref:
424                     txt = "%s\t\t%s ACE is not present in the" \
425                           " current\n" % (txt, item)
426
427         elif hash_cur.has_key(part) and not hash_ref.has_key(part):
428             txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
429         elif not hash_cur.has_key(part) and hash_ref.has_key(part):
430             txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
431
432     return txt
433
434
435 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
436     """Update secrets.ldb
437
438     :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
439         of the reference provision
440     :param secrets_ldb: An LDB object that is connected to the secrets.ldb
441         of the updated provision
442     """
443
444     messagefunc(SIMPLE, "Update of secrets.ldb")
445     reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
446     current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
447     assert reference, "Reference modules list can not be empty"
448     if len(current) == 0:
449         # No modules present
450         delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
451         delta.dn = reference[0].dn
452         secrets_ldb.add(reference[0])
453     else:
454         delta = secrets_ldb.msg_diff(current[0], reference[0])
455         delta.dn = current[0].dn
456         secrets_ldb.modify(delta)
457
458     reference = newsecrets_ldb.search(expression="objectClass=top", base="",
459                                         scope=SCOPE_SUBTREE, attrs=["dn"])
460     current = secrets_ldb.search(expression="objectClass=top", base="",
461                                         scope=SCOPE_SUBTREE, attrs=["dn"])
462     hash_new = {}
463     hash = {}
464     listMissing = []
465     listPresent = []
466
467     empty = ldb.Message()
468     for i in range(0, len(reference)):
469         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
470
471     # Create a hash for speeding the search of existing object in the
472     # current provision
473     for i in range(0, len(current)):
474         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
475
476     for k in hash_new.keys():
477         if not hash.has_key(k):
478             listMissing.append(hash_new[k])
479         else:
480             listPresent.append(hash_new[k])
481
482     for entry in listMissing:
483         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
484                                             base="", scope=SCOPE_SUBTREE)
485         current = secrets_ldb.search(expression="dn=%s" % entry,
486                                             base="", scope=SCOPE_SUBTREE)
487         delta = secrets_ldb.msg_diff(empty, reference[0])
488         for att in hashAttrNotCopied:
489             delta.remove(att)
490         messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" %
491                     reference[0].dn)
492         for att in delta:
493             messagefunc(CHANGE, " Adding attribute %s" % att)
494         delta.dn = reference[0].dn
495         secrets_ldb.add(delta)
496
497     for entry in listPresent:
498         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
499                                             base="", scope=SCOPE_SUBTREE)
500         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
501                                             scope=SCOPE_SUBTREE)
502         delta = secrets_ldb.msg_diff(current[0], reference[0])
503         for att in hashAttrNotCopied:
504             delta.remove(att)
505         for att in delta:
506             if att == "name":
507                 messagefunc(CHANGE, "Found attribute name on  %s,"
508                                     " must rename the DN" % (current[0].dn))
509                 identic_rename(secrets_ldb, reference[0].dn)
510             else:
511                 delta.remove(att)
512
513     for entry in listPresent:
514         reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
515                                             scope=SCOPE_SUBTREE)
516         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
517                                             scope=SCOPE_SUBTREE)
518         delta = secrets_ldb.msg_diff(current[0], reference[0])
519         for att in hashAttrNotCopied:
520             delta.remove(att)
521         for att in delta:
522             if att == "msDS-KeyVersionNumber":
523                 delta.remove(att)
524             if att != "dn":
525                 messagefunc(CHANGE,
526                             "Adding/Changing attribute %s to %s" %
527                             (att, current[0].dn))
528
529         delta.dn = current[0].dn
530         secrets_ldb.modify(delta)
531
532     res2 = secrets_ldb.search(expression="(samaccountname=dns)",
533                                 scope=SCOPE_SUBTREE, attrs=["dn"])
534
535     if (len(res2) == 1):
536             messagefunc(SIMPLE, "Remove old dns account")
537             secrets_ldb.delete(res2[0]["dn"])
538
539
540 def getOEMInfo(samdb, rootdn):
541     """Return OEM Information on the top level Samba4 use to store version
542     info in this field
543
544     :param samdb: An LDB object connect to sam.ldb
545     :param rootdn: Root DN of the domain
546     :return: The content of the field oEMInformation (if any)
547     """
548     res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
549                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
550     if len(res) > 0 and res[0].get("oEMInformation"):
551         info = res[0]["oEMInformation"]
552         return info
553     else:
554         return ""
555
556
557 def updateOEMInfo(samdb, rootdn):
558     """Update the OEMinfo field to add information about upgrade
559
560     :param samdb: an LDB object connected to the sam DB
561     :param rootdn: The string representation of the root DN of
562         the provision (ie. DC=...,DC=...)
563     """
564     res = samdb.search(expression="(objectClass=*)", base=rootdn,
565                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
566     if len(res) > 0:
567         if res[0].get("oEMInformation"):
568             info = str(res[0]["oEMInformation"])
569         else:
570             info = ""
571         info = "%s, upgrade to %s" % (info, version)
572         delta = ldb.Message()
573         delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
574         delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
575                                                         "oEMInformation" )
576         samdb.modify(delta)
577
578 def update_gpo(paths, samdb, names, lp, message, force=0):
579     """Create missing GPO file object if needed
580
581     Set ACL correctly also.
582     Check ACLs for sysvol/netlogon dirs also
583     """
584     resetacls = False
585     try:
586         ntacls.checkset_backend(lp, None, None)
587         eadbname = lp.get("posix:eadb")
588         if eadbname is not None and eadbname != "":
589             try:
590                 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
591                                 paths.sysvol, xattr.XATTR_NTACL_NAME)
592             except Exception:
593                 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
594                                 xattr.XATTR_NTACL_NAME)
595         else:
596             attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
597                                 xattr.XATTR_NTACL_NAME)
598     except Exception:
599        resetacls = True
600
601     if force:
602         resetacls = True
603
604     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
605     if not os.path.isdir(dir):
606         create_gpo_struct(dir)
607
608     if names.policyid_dc is None:
609         raise ProvisioningError("Policy ID for Domain controller is missing")
610     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
611     if not os.path.isdir(dir):
612         create_gpo_struct(dir)
613
614     def acl_error(e):
615         if os.geteuid() == 0:
616             message(ERROR, "Unable to set ACLs on policies related objects: %s" % e)
617         else:
618             message(ERROR, "Unable to set ACLs on policies related objects. "
619                     "ACLs must be set as root if file system ACLs "
620                     "(rather than posix:eadb) are used.")
621
622     # We always reinforce acls on GPO folder because they have to be in sync
623     # with the one in DS
624     try:
625         set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
626             names.domaindn, samdb, lp)
627     except TypeError, e:
628         acl_error(e)
629
630     if resetacls:
631        try:
632             setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
633                         names.domainsid, names.dnsdomain, names.domaindn, lp)
634        except TypeError, e:
635            acl_error(e)
636
637
638 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
639     """For a given hash associating dn and a number, this function will
640     update the replPropertyMetaData of each dn in the hash, so that the
641     calculated value of the msDs-KeyVersionNumber is equal or superior to the
642     one associated to the given dn.
643
644     :param samdb: An SamDB object pointing to the sam
645     :param rootdn: The base DN where we want to start
646     :param hashDns: A hash with dn as key and number representing the
647                  minimum value of msDs-KeyVersionNumber that we want to
648                  have
649     """
650     entry = samdb.search(expression='(objectClass=user)',
651                          base=ldb.Dn(samdb,str(rootdn)),
652                          scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
653                          controls=["search_options:1:2"])
654     done = 0
655     hashDone = {}
656     if len(entry) == 0:
657         raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
658     else:
659         for e in entry:
660             if hashDns.has_key(str(e.dn).lower()):
661                 val = e.get("msDs-KeyVersionNumber")
662                 if not val:
663                     val = "0"
664                 version = int(str(hashDns[str(e.dn).lower()]))
665                 if int(str(val)) < version:
666                     done = done + 1
667                     samdb.set_attribute_replmetadata_version(str(e.dn),
668                                                               "unicodePwd",
669                                                               version, True)
670 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
671     """Update the provision container db: sam.ldb
672     This function is aimed for alpha9 and newer;
673
674     :param refsampath: Path to the samdb in the reference provision
675     :param sampath: Path to the samdb in the upgraded provision
676     :param creds: Credential used for openning LDB files
677     :param session: Session to use for openning LDB files
678     :param lp: A loadparam object
679     :return: A msg_diff object with the difference between the @ATTRIBUTES
680              of the current provision and the reference provision
681     """
682
683     message(SIMPLE,
684             "Update base samdb by searching difference with reference one")
685     refsam = Ldb(refsampath, session_info=session, credentials=creds,
686                     lp=lp, options=["modules:"])
687     sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
688                 options=["modules:"])
689
690     empty = ldb.Message()
691     deltaattr = None
692     reference = refsam.search(expression="")
693
694     for refentry in reference:
695         entry = sam.search(expression="dn=%s" % refentry["dn"],
696                             scope=SCOPE_SUBTREE)
697         if not len(entry):
698             delta = sam.msg_diff(empty, refentry)
699             message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
700             if str(refentry.dn) == "@PROVISION" and\
701                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
702                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
703             delta.dn = refentry.dn
704             sam.add(delta)
705         else:
706             delta = sam.msg_diff(entry[0], refentry)
707             if str(refentry.dn) == "@ATTRIBUTES":
708                 deltaattr = sam.msg_diff(refentry, entry[0])
709             if str(refentry.dn) == "@PROVISION" and\
710                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
711                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
712             if len(delta.items()) > 1:
713                 delta.dn = refentry.dn
714                 sam.modify(delta)
715
716     return deltaattr
717
718
719 def construct_existor_expr(attrs):
720     """Construct a exists or LDAP search expression.
721
722     :param attrs: List of attribute on which we want to create the search
723         expression.
724     :return: A string representing the expression, if attrs is empty an
725         empty string is returned
726     """
727     expr = ""
728     if len(attrs) > 0:
729         expr = "(|"
730         for att in attrs:
731             expr = "%s(%s=*)"%(expr,att)
732         expr = "%s)"%expr
733     return expr
734
735 def update_machine_account_password(samdb, secrets_ldb, names):
736     """Update (change) the password of the current DC both in the SAM db and in
737        secret one
738
739     :param samdb: An LDB object related to the sam.ldb file of a given provision
740     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
741                         provision
742     :param names: List of key provision parameters"""
743
744     expression = "samAccountName=%s$" % names.netbiosname
745     secrets_msg = secrets_ldb.search(expression=expression,
746                                         attrs=["secureChannelType"])
747     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
748         res = samdb.search(expression=expression, attrs=[])
749         assert(len(res) == 1)
750
751         msg = ldb.Message(res[0].dn)
752         machinepass = samba.generate_random_password(128, 255)
753         mputf16 = machinepass.encode('utf-16-le')
754         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
755                                                 ldb.FLAG_MOD_REPLACE,
756                                                 "clearTextPassword")
757         samdb.modify(msg)
758
759         res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
760                      attrs=["msDs-keyVersionNumber"])
761         assert(len(res) == 1)
762         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
763         secChanType = int(secrets_msg[0]["secureChannelType"][0])
764
765         secretsdb_self_join(secrets_ldb, domain=names.domain,
766                     realm=names.realm,
767                     domainsid=names.domainsid,
768                     dnsdomain=names.dnsdomain,
769                     netbiosname=names.netbiosname,
770                     machinepass=machinepass,
771                     key_version_number=kvno,
772                     secure_channel_type=secChanType)
773     else:
774         raise ProvisioningError("Unable to find a Secure Channel"
775                                 "of type SEC_CHAN_BDC")
776
777 def update_dns_account_password(samdb, secrets_ldb, names):
778     """Update (change) the password of the dns both in the SAM db and in
779        secret one
780
781     :param samdb: An LDB object related to the sam.ldb file of a given provision
782     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
783                         provision
784     :param names: List of key provision parameters"""
785
786     expression = "samAccountName=dns-%s" % names.netbiosname
787     secrets_msg = secrets_ldb.search(expression=expression)
788     if len(secrets_msg) == 1:
789         res = samdb.search(expression=expression, attrs=[])
790         assert(len(res) == 1)
791
792         msg = ldb.Message(res[0].dn)
793         machinepass = samba.generate_random_password(128, 255)
794         mputf16 = machinepass.encode('utf-16-le')
795         msg["clearTextPassword"] = ldb.MessageElement(mputf16,
796                                                 ldb.FLAG_MOD_REPLACE,
797                                                 "clearTextPassword")
798
799         samdb.modify(msg)
800
801         res = samdb.search(expression=expression,
802                      attrs=["msDs-keyVersionNumber"])
803         assert(len(res) == 1)
804         kvno = str(res[0]["msDs-keyVersionNumber"])
805
806         msg = ldb.Message(secrets_msg[0].dn)
807         msg["secret"] = ldb.MessageElement(machinepass,
808                                                 ldb.FLAG_MOD_REPLACE,
809                                                 "secret")
810         msg["msDS-KeyVersionNumber"] = ldb.MessageElement(kvno,
811                                                 ldb.FLAG_MOD_REPLACE,
812                                                 "msDS-KeyVersionNumber")
813
814         secrets_ldb.modify(msg)
815     else:
816         raise ProvisioningError("Unable to find an object"
817                                 " with %s" % expression )
818
819 def search_constructed_attrs_stored(samdb, rootdn, attrs):
820     """Search a given sam DB for calculated attributes that are
821     still stored in the db.
822
823     :param samdb: An LDB object pointing to the sam
824     :param rootdn: The base DN where the search should start
825     :param attrs: A list of attributes to be searched
826     :return: A hash with attributes as key and an array of
827              array. Each array contains the dn and the associated
828              values for this attribute as they are stored in the
829              sam."""
830
831     hashAtt = {}
832     expr = construct_existor_expr(attrs)
833     if expr == "":
834         return hashAtt
835     entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
836                          scope=SCOPE_SUBTREE, attrs=attrs,
837                          controls=["search_options:1:2","bypassoperational:0"])
838     if len(entry) == 0:
839         # Nothing anymore
840         return hashAtt
841
842     for ent in entry:
843         for att in attrs:
844             if ent.get(att):
845                 if hashAtt.has_key(att):
846                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
847                 else:
848                     hashAtt[att] = {}
849                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
850
851     return hashAtt
852
853 def int64range2str(value):
854     """Display the int64 range stored in value as xxx-yyy
855
856     :param value: The int64 range
857     :return: A string of the representation of the range
858     """
859
860     lvalue = long(value)
861     str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
862     return str