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