s4 upgradeprovision: Emit message instead of crashing when not able to set acl
[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)" % \
214                             names.domain,base="CN=Primary Domains",
215                             scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
216     names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
217
218     names.smbconf = smbconf
219
220     # That's a bit simplistic but it's ok as long as we have only 3
221     # partitions
222     current = samdb.search(expression="(objectClass=*)", 
223         base="", scope=SCOPE_BASE,
224         attrs=["defaultNamingContext", "schemaNamingContext",
225                "configurationNamingContext","rootDomainNamingContext"])
226
227     names.configdn = current[0]["configurationNamingContext"]
228     configdn = str(names.configdn)
229     names.schemadn = current[0]["schemaNamingContext"]
230     if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
231                                        current[0]["defaultNamingContext"][0]))):
232         raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
233                                  "is not the same ..." % (paths.samdb,
234                                     str(current[0]["defaultNamingContext"][0]),
235                                     paths.smbconf, basedn)))
236
237     names.domaindn=current[0]["defaultNamingContext"]
238     names.rootdn=current[0]["rootDomainNamingContext"]
239     # default site name
240     res3 = samdb.search(expression="(objectClass=*)", 
241         base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
242     names.sitename = str(res3[0]["cn"])
243
244     # dns hostname and server dn
245     res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
246                             base="OU=Domain Controllers,%s" % basedn,
247                             scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
248     names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
249
250     server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
251                                 attrs=[], base=configdn)
252     names.serverdn = server_res[0].dn
253
254     # invocation id/objectguid
255     res5 = samdb.search(expression="(objectClass=*)",
256             base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
257             attrs=["invocationID", "objectGUID"])
258     names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
259     names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
260
261     # domain guid/sid
262     res6 = samdb.search(expression="(objectClass=*)", base=basedn,
263             scope=SCOPE_BASE, attrs=["objectGUID",
264                 "objectSid","msDS-Behavior-Version" ])
265     names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
266     names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
267     if res6[0].get("msDS-Behavior-Version") == None or \
268         int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
269         names.domainlevel = DS_DOMAIN_FUNCTION_2000
270     else:
271         names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
272
273     # policy guid
274     res7 = samdb.search(expression="(displayName=Default Domain Policy)",
275                         base="CN=Policies,CN=System," + basedn,
276                         scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
277     names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
278     # dc policy guid
279     res8 = samdb.search(expression="(displayName=Default Domain Controllers" \
280                                    " Policy)",
281                             base="CN=Policies,CN=System," + basedn,
282                             scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
283     if len(res8) == 1:
284         names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
285     else:
286         names.policyid_dc = None
287     res9 = idmapdb.search(expression="(cn=%s)" % \
288                             (security.SID_BUILTIN_ADMINISTRATORS),
289                             attrs=["xidNumber"])
290     if len(res9) == 1:
291         names.wheel_gid = res9[0]["xidNumber"]
292     else:
293         raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
294     return names
295
296
297 def newprovision(names, setup_dir, creds, session, smbconf, provdir, logger):
298     """Create a new provision.
299
300     This provision will be the reference for knowing what has changed in the
301     since the latest upgrade in the current provision
302
303     :param names: List of provision parameters
304     :param setup_dis: Directory where the setup files are stored
305     :param creds: Credentials for the authentification
306     :param session: Session object
307     :param smbconf: Path to the smb.conf file
308     :param provdir: Directory where the provision will be stored
309     :param logger: A `Logger`
310     """
311     if os.path.isdir(provdir):
312         shutil.rmtree(provdir)
313     os.chdir(os.path.join(setup_dir,".."))
314     os.mkdir(provdir)
315     logger.info("Provision stored in %s", provdir)
316     provision(setup_dir, logger, session, creds, smbconf=smbconf,
317             targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
318             domain=names.domain, domainguid=names.domainguid,
319             domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
320             policyguid=names.policyid, policyguid_dc=names.policyid_dc,
321             hostname=names.netbiosname, hostip=None, hostip6=None,
322             invocationid=names.invocation, adminpass=names.adminpass,
323             krbtgtpass=None, machinepass=None, dnspass=None, root=None,
324             nobody=None, wheel=None, users=None,
325             serverrole="domain controller", ldap_backend_extra_port=None,
326             backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
327             slapd_path=None, setup_ds_path=None, nosync=None,
328             dom_for_fun_level=names.domainlevel,
329             ldap_dryrun_mode=None, useeadb=True)
330
331
332 def dn_sort(x, y):
333     """Sorts two DNs in the lexicographical order it and put higher level DN
334     before.
335
336     So given the dns cn=bar,cn=foo and cn=foo the later will be return as
337     smaller
338
339     :param x: First object to compare
340     :param y: Second object to compare
341     """
342     p = re.compile(r'(?<!\\), ?')
343     tab1 = p.split(str(x))
344     tab2 = p.split(str(y))
345     minimum = min(len(tab1), len(tab2))
346     len1 = len(tab1)-1
347     len2 = len(tab2)-1
348     # Note: python range go up to upper limit but do not include it
349     for i in range(0, minimum):
350         ret = cmp(tab1[len1-i], tab2[len2-i])
351         if ret != 0:
352             return ret
353         else:
354             if i == minimum-1:
355                 assert len1!=len2,"PB PB PB" + " ".join(tab1)+" / " + " ".join(tab2)
356                 if len1 > len2:
357                     return 1
358                 else:
359                     return -1
360     return ret
361
362 def identic_rename(ldbobj, dn):
363     """Perform a back and forth rename to trigger renaming on attribute that
364        can't be directly modified.
365
366     :param lbdobj: An Ldb Object
367     :param dn: DN of the object to manipulate """
368     (before, sep, after)=str(dn).partition('=')
369     ldbobj.rename(dn, ldb.Dn(ldbobj, "%s=foo%s" % (before, after)))
370     ldbobj.rename(ldb.Dn(ldbobj, "%s=foo%s" % (before, after)), dn)
371
372 def chunck_acl(acl):
373     """Return separate ACE of an ACL
374
375     :param acl: A string representing the ACL
376     :return: A hash with different parts
377     """
378
379     p = re.compile(r'(\w+)?(\(.*?\))')
380     tab = p.findall(acl)
381
382     hash = {}
383     hash["aces"] = []
384     for e in tab:
385         if len(e[0]) > 0:
386             hash["flags"] = e[0]
387         hash["aces"].append(e[1])
388
389     return hash
390
391
392 def chunck_sddl(sddl):
393     """ Return separate parts of the SDDL (owner, group, ...)
394
395     :param sddl: An string containing the SDDL to chunk
396     :return: A hash with the different chunk
397     """
398
399     p = re.compile(r'([OGDS]:)(.*?)(?=(?:[GDS]:|$))')
400     tab = p.findall(sddl)
401
402     hash = {}
403     for e in tab:
404         if e[0] == "O:":
405             hash["owner"] = e[1]
406         if e[0] == "G:":
407             hash["group"] = e[1]
408         if e[0] == "D:":
409             hash["dacl"] = e[1]
410         if e[0] == "S:":
411             hash["sacl"] = e[1]
412
413     return hash
414
415 def get_diff_sddls(refsddl, cursddl):
416     """Get the difference between 2 sddl
417        This function split the textual representation of ACL into smaller
418        chunck in order to not to report a simple permutation as a difference
419
420        :param refsddl: First sddl to compare
421        :param cursddl: Second sddl to compare
422        :return: A string that explain difference between sddls"""
423
424     txt = ""
425     hash_new = chunck_sddl(cursddl)
426     hash_ref = chunck_sddl(refsddl)
427
428     if hash_new["owner"] != hash_ref["owner"]:
429         txt = "\tOwner mismatch: %s (in ref) %s" \
430               "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
431
432     if hash_new["group"] != hash_ref["group"]:
433         txt = "%s\tGroup mismatch: %s (in ref) %s" \
434               "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
435
436     for part in ["dacl", "sacl"]:
437         if hash_new.has_key(part) and hash_ref.has_key(part):
438
439             # both are present, check if they contain the same ACE
440             h_new = {}
441             h_ref = {}
442             c_new = chunck_acl(hash_new[part])
443             c_ref = chunck_acl(hash_ref[part])
444
445             for elem in c_new["aces"]:
446                 h_new[elem] = 1
447
448             for elem in c_ref["aces"]:
449                 h_ref[elem] = 1
450
451             for k in h_ref.keys():
452                 if h_new.has_key(k):
453                     h_new.pop(k)
454                     h_ref.pop(k)
455
456             if len(h_new.keys()) + len(h_ref.keys()) > 0:
457                 txt = "%s\tPart %s is different between reference" \
458                       " and current here is the detail:\n" % (txt, part)
459
460                 for item in h_new.keys():
461                     txt = "%s\t\t%s ACE is not present in the" \
462                           " reference\n" % (txt, item)
463
464                 for item in h_ref.keys():
465                     txt = "%s\t\t%s ACE is not present in the" \
466                           " current\n" % (txt, item)
467
468         elif hash_new.has_key(part) and not hash_ref.has_key(part):
469             txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
470         elif not hash_new.has_key(part) and hash_ref.has_key(part):
471             txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
472
473     return txt
474
475
476 def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
477     """Update secrets.ldb
478
479     :param newsecrets_ldb: An LDB object that is connected to the secrets.ldb
480                             of the reference provision
481     :param secrets_ldb: An LDB object that is connected to the secrets.ldb
482                             of the updated provision
483     """
484
485     messagefunc(SIMPLE, "update secrets.ldb")
486     reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
487                                         scope=SCOPE_SUBTREE)
488     current = secrets_ldb.search(expression="dn=@MODULES", base="",
489                                         scope=SCOPE_SUBTREE)
490     assert reference, "Reference modules list can not be empty"
491     if len(current) == 0:
492         # No modules present
493         delta = secrets_ldb.msg_diff(ldb.Message(), reference[0])
494         delta.dn = reference[0].dn
495         secrets_ldb.add(reference[0])
496     else:
497         delta = secrets_ldb.msg_diff(current[0], reference[0])
498         delta.dn = current[0].dn
499         secrets_ldb.modify(delta)
500
501     reference = newsecrets_ldb.search(expression="objectClass=top", base="",
502                                         scope=SCOPE_SUBTREE, attrs=["dn"])
503     current = secrets_ldb.search(expression="objectClass=top", base="",
504                                         scope=SCOPE_SUBTREE, attrs=["dn"])
505     hash_new = {}
506     hash = {}
507     listMissing = []
508     listPresent = []
509
510     empty = ldb.Message()
511     for i in range(0, len(reference)):
512         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
513
514     # Create a hash for speeding the search of existing object in the
515     # current provision
516     for i in range(0, len(current)):
517         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
518
519     for k in hash_new.keys():
520         if not hash.has_key(k):
521             listMissing.append(hash_new[k])
522         else:
523             listPresent.append(hash_new[k])
524
525     for entry in listMissing:
526         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
527                                             base="", scope=SCOPE_SUBTREE)
528         current = secrets_ldb.search(expression="dn=%s" % entry,
529                                             base="", scope=SCOPE_SUBTREE)
530         delta = secrets_ldb.msg_diff(empty, reference[0])
531         for att in hashAttrNotCopied.keys():
532             delta.remove(att)
533         messagefunc(CHANGE, "Entry %s is missing from secrets.ldb" % \
534                     reference[0].dn)
535         for att in delta:
536             messagefunc(CHANGE, " Adding attribute %s" % att)
537         delta.dn = reference[0].dn
538         secrets_ldb.add(delta)
539
540     for entry in listPresent:
541         reference = newsecrets_ldb.search(expression="dn=%s" % entry,
542                                             base="", scope=SCOPE_SUBTREE)
543         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
544                                             scope=SCOPE_SUBTREE)
545         delta = secrets_ldb.msg_diff(current[0], reference[0])
546         for att in hashAttrNotCopied.keys():
547             delta.remove(att)
548         for att in delta:
549             if att == "name":
550                 messagefunc(CHANGE, "Found attribute name on  %s," \
551                                     " must rename the DN" % (current[0].dn))
552                 identic_rename(secrets_ldb, reference[0].dn)
553             else:
554                 delta.remove(att)
555
556     for entry in listPresent:
557         reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
558                                             scope=SCOPE_SUBTREE)
559         current = secrets_ldb.search(expression="dn=%s" % entry, base="",
560                                             scope=SCOPE_SUBTREE)
561         delta = secrets_ldb.msg_diff(current[0], reference[0])
562         for att in hashAttrNotCopied.keys():
563             delta.remove(att)
564         for att in delta:
565             if att != "dn":
566                 messagefunc(CHANGE,
567                             "Adding/Changing attribute %s to %s" % \
568                             (att, current[0].dn))
569
570         delta.dn = current[0].dn
571         secrets_ldb.modify(delta)
572
573 def getOEMInfo(samdb, rootdn):
574     """Return OEM Information on the top level
575     Samba4 use to store version info in this field
576
577     :param samdb: An LDB object connect to sam.ldb
578     :param rootdn: Root DN of the domain
579     :return: The content of the field oEMInformation (if any)"""
580     res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
581                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
582     if len(res) > 0:
583         info = res[0]["oEMInformation"]
584         return info
585     else:
586         return ""
587
588 def updateOEMInfo(samdb, rootdn):
589     """Update the OEMinfo field to add information about upgrade
590        :param samdb: an LDB object connected to the sam DB
591        :param rootdn: The string representation of the root DN of
592                       the provision (ie. DC=...,DC=...)
593     """
594     res = samdb.search(expression="(objectClass=*)", base=rootdn,
595                             scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
596     if len(res) > 0:
597         info = res[0]["oEMInformation"]
598         info = "%s, upgrade to %s" % (info, version)
599         delta = ldb.Message()
600         delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
601         delta["oEMInformation"] = ldb.MessageElement(info, ldb.FLAG_MOD_REPLACE,
602                                                         "oEMInformation" )
603         samdb.modify(delta)
604
605 def update_gpo(paths, samdb, names, lp, message, force=0):
606     """Create missing GPO file object if needed
607
608     Set ACL correctly also.
609     Check ACLs for sysvol/netlogon dirs also
610     """
611     resetacls = 0
612     try:
613         ntacls.checkset_backend(lp, None, None)
614         eadbname = lp.get("posix:eadb")
615         if eadbname is not None and eadbname != "":
616             try:
617                 attribute = samba.xattr_tdb.wrap_getxattr(eadbname,
618                                 paths.sysvol, xattr.XATTR_NTACL_NAME)
619             except:
620                 attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
621                                 xattr.XATTR_NTACL_NAME)
622         else:
623             attribute = samba.xattr_native.wrap_getxattr(paths.sysvol,
624                                 xattr.XATTR_NTACL_NAME)
625     except:
626        resetacls = 1
627
628     if force:
629         resetacls = 1
630
631     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid)
632     if not os.path.isdir(dir):
633         create_gpo_struct(dir)
634
635     dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
636     if not os.path.isdir(dir):
637         create_gpo_struct(dir)
638     # We always reinforce acls on GPO folder because they have to be in sync
639     # with the one in DS
640     try:
641         set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
642             names.domaindn, samdb, lp)
643     except TypeError, e:
644         message(ERROR, "Unable to set ACLs on policies related objects, if not using posix:eadb, you must be root to do it")
645
646     if resetacls:
647        try:
648             setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
649                         names.domainsid, names.dnsdomain, names.domaindn, lp)
650        except TypeError, e:
651             message(ERROR, "Unable to set ACLs on sysvol share, if not using posix:eadb, you must be root to do it")
652
653 def delta_update_basesamdb(refsam, sam, creds, session, lp, message):
654     """Update the provision container db: sam.ldb
655     This function is aimed for alpha9 and newer;
656
657     :param refsam: Path to the samdb in the reference provision
658     :param sam: Path to the samdb in the upgraded provision
659     :param creds: Credential used for openning LDB files
660     :param session: Session to use for openning LDB files
661     :param lp: A loadparam object"""
662
663     message(SIMPLE,
664             "Update base samdb by searching difference with reference one")
665     refsam = Ldb(refsam, session_info=session, credentials=creds,
666                     lp=lp, options=["modules:"])
667     sam = Ldb(sam, session_info=session, credentials=creds, lp=lp,
668                 options=["modules:"])
669
670     empty = ldb.Message()
671
672     reference = refsam.search(expression="")
673
674     for refentry in reference:
675         entry = sam.search(expression="dn=%s" % refentry["dn"],
676                             scope=SCOPE_SUBTREE)
677         if not len(entry):
678             delta = sam.msg_diff(empty, refentry)
679             message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
680             if str(refentry.dn) == "@PROVISION" and\
681                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
682                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
683             delta.dn = refentry.dn
684             sam.add(delta)
685         else:
686             delta = sam.msg_diff(entry[0], refentry)
687             if str(refentry.dn) == "@PROVISION" and\
688                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
689                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
690             if len(delta.items()) > 1:
691                 delta.dn = refentry.dn
692                 sam.modify(delta)
693
694
695 def construct_existor_expr(attrs):
696     """Construct a exists or LDAP search expression.
697     ie (|(foo=*)(bar=*)
698
699     :param attrs: List of attribute on which we want to create the search
700                   expression.
701     :return: A string representing the expression, if attrs is empty an
702              empty string is returned"""
703     expr = ""
704     if len(attrs) > 0:
705         expr = "(|"
706         for att in attrs:
707             expr = "%s(%s=*)"%(expr,att)
708         expr = "%s)"%expr
709     return expr
710
711 def search_constructed_attrs_stored(samdb, rootdn, attrs):
712     """Search a given sam DB for calculated attributes that are
713     still stored in the db.
714
715     :param samdb: An LDB object pointing to the sam
716     :param rootdn: The base DN where the search should start
717     :param attrs: A list of attributes to be searched
718     :return: A hash with attributes as key and an array of
719              array. Each array contains the dn and the associated
720              values for this attribute as they are stored in the
721              sam."""
722
723     hashAtt = {}
724     expr = construct_existor_expr(attrs)
725     if expr == "":
726         return hashAtt
727     entry = samdb.search(expression=expr, base=ldb.Dn(samdb,str(rootdn)),
728                          scope=SCOPE_SUBTREE, attrs=attrs,
729                          controls=["search_options:1:2","bypassoperational:0"])
730     if len(entry) == 0:
731         # Nothing anymore
732         return hashAtt
733
734     for ent in entry:
735         for att in attrs:
736             if ent.get(att):
737                 if hashAtt.has_key(att):
738                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
739                 else:
740                     hashAtt[att] = {}
741                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
742
743     return hashAtt