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