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