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