s4 provision: Add some documentation to GPO related functions
[nivanova/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_gpos_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_gpos_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,"
708                        " if not using posix:eadb, you must be root to do it")
709
710     if resetacls:
711        try:
712             setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
713                         names.domainsid, names.dnsdomain, names.domaindn, lp)
714        except TypeError, e:
715             message(ERROR, "Unable to set ACLs on sysvol share, if not using"
716                            "posix:eadb, you must be root to do it")
717
718 def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
719     """For a given hash associating dn and a number, this function will
720     update the replPropertyMetaData of each dn in the hash, so that the
721     calculated value of the msDs-KeyVersionNumber is equal or superior to the
722     one associated to the given dn.
723
724     :param samdb: An SamDB object pointing to the sam
725     :param rootdn: The base DN where we want to start
726     :param hashDns: A hash with dn as key and number representing the
727                  minimum value of msDs-KeyVersionNumber that we want to
728                  have
729     """
730     entry = samdb.search(expression='(objectClass=user)',
731                          base=ldb.Dn(samdb,str(rootdn)),
732                          scope=SCOPE_SUBTREE, attrs=["msDs-KeyVersionNumber"],
733                          controls=["search_options:1:2"])
734     done = 0
735     hashDone = {}
736     if len(entry) == 0:
737         raise ProvisioningError("Unable to find msDs-KeyVersionNumber")
738     else:
739         for e in entry:
740             if hashDns.has_key(str(e.dn).lower()):
741                 val = e.get("msDs-KeyVersionNumber")
742                 if not val:
743                     val = "0"
744                 version = int(str(hashDns[str(e.dn).lower()]))
745                 if int(str(val)) < version:
746                     done = done + 1
747                     samdb.set_attribute_replmetadata_version(str(e.dn),
748                                                               "unicodePwd",
749                                                               version, True)
750 def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
751     """Update the provision container db: sam.ldb
752     This function is aimed for alpha9 and newer;
753
754     :param refsampath: Path to the samdb in the reference provision
755     :param sampath: Path to the samdb in the upgraded provision
756     :param creds: Credential used for openning LDB files
757     :param session: Session to use for openning LDB files
758     :param lp: A loadparam object
759     :return: A msg_diff object with the difference between the @ATTRIBUTES
760              of the current provision and the reference provision
761     """
762
763     message(SIMPLE,
764             "Update base samdb by searching difference with reference one")
765     refsam = Ldb(refsampath, session_info=session, credentials=creds,
766                     lp=lp, options=["modules:"])
767     sam = Ldb(sampath, session_info=session, credentials=creds, lp=lp,
768                 options=["modules:"])
769
770     empty = ldb.Message()
771     deltaattr = None
772     reference = refsam.search(expression="")
773
774     for refentry in reference:
775         entry = sam.search(expression="dn=%s" % refentry["dn"],
776                             scope=SCOPE_SUBTREE)
777         if not len(entry):
778             delta = sam.msg_diff(empty, refentry)
779             message(CHANGE, "Adding %s to sam db" % str(refentry.dn))
780             if str(refentry.dn) == "@PROVISION" and\
781                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
782                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
783             delta.dn = refentry.dn
784             sam.add(delta)
785         else:
786             delta = sam.msg_diff(entry[0], refentry)
787             if str(refentry.dn) == "@ATTRIBUTES":
788                 deltaattr = sam.msg_diff(refentry, entry[0])
789             if str(refentry.dn) == "@PROVISION" and\
790                 delta.get(samba.provision.LAST_PROVISION_USN_ATTRIBUTE):
791                 delta.remove(samba.provision.LAST_PROVISION_USN_ATTRIBUTE)
792             if len(delta.items()) > 1:
793                 delta.dn = refentry.dn
794                 sam.modify(delta)
795
796     return deltaattr
797
798 def construct_existor_expr(attrs):
799     """Construct a exists or LDAP search expression.
800     ie (|(foo=*)(bar=*)
801
802     :param attrs: List of attribute on which we want to create the search
803                   expression.
804     :return: A string representing the expression, if attrs is empty an
805              empty string is returned"""
806     expr = ""
807     if len(attrs) > 0:
808         expr = "(|"
809         for att in attrs:
810             expr = "%s(%s=*)"%(expr,att)
811         expr = "%s)"%expr
812     return expr
813
814 def update_machine_account_password(samdb, secrets_ldb, names):
815     """Update (change) the password of the current DC both in the SAM db and in
816        secret one
817
818     :param samdb: An LDB object related to the sam.ldb file of a given provision
819     :param secrets_ldb: An LDB object related to the secrets.ldb file of a given
820                         provision
821     :param names: List of key provision parameters"""
822
823     expression = "samAccountName=%s$" % names.netbiosname
824     secrets_msg = secrets_ldb.search(expression=expression,
825                                         attrs=["secureChannelType"])
826     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
827         res = samdb.search(expression=expression, attrs=[])
828         assert(len(res) == 1)
829
830         msg = ldb.Message(res[0].dn)
831         machinepass = samba.generate_random_password(128, 255)
832         msg["userPassword"] = ldb.MessageElement(machinepass,
833                                                 ldb.FLAG_MOD_REPLACE,
834                                                 "userPassword")
835         samdb.modify(msg)
836
837         res = samdb.search(expression=("samAccountName=%s$" % names.netbiosname),
838                      attrs=["msDs-keyVersionNumber"])
839         assert(len(res) == 1)
840         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
841         secChanType = int(secrets_msg[0]["secureChannelType"][0])
842
843         secretsdb_self_join(secrets_ldb, domain=names.domain,
844                     realm=names.realm,
845                     domainsid=names.domainsid,
846                     dnsdomain=names.dnsdomain,
847                     netbiosname=names.netbiosname,
848                     machinepass=machinepass,
849                     key_version_number=kvno,
850                     secure_channel_type=secChanType)
851     else:
852         raise ProvisioningError("Unable to find a Secure Channel"
853                                 "of type SEC_CHAN_BDC")
854
855
856 def search_constructed_attrs_stored(samdb, rootdn, attrs):
857     """Search a given sam DB for calculated attributes that are
858     still stored in the db.
859
860     :param samdb: An LDB object pointing to the sam
861     :param rootdn: The base DN where the search should start
862     :param attrs: A list of attributes to be searched
863     :return: A hash with attributes as key and an array of
864              array. Each array contains the dn and the associated
865              values for this attribute as they are stored in the
866              sam."""
867
868     hashAtt = {}
869     expr = construct_existor_expr(attrs)
870     if expr == "":
871         return hashAtt
872     entry = samdb.search(expression=expr, base=ldb.Dn(samdb, str(rootdn)),
873                          scope=SCOPE_SUBTREE, attrs=attrs,
874                          controls=["search_options:1:2","bypassoperational:0"])
875     if len(entry) == 0:
876         # Nothing anymore
877         return hashAtt
878
879     for ent in entry:
880         for att in attrs:
881             if ent.get(att):
882                 if hashAtt.has_key(att):
883                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
884                 else:
885                     hashAtt[att] = {}
886                     hashAtt[att][str(ent.dn).lower()] = str(ent[att])
887
888     return hashAtt
889
890 def int64range2str(value):
891     """Display the int64 range stored in value as xxx-yyy
892
893     :param value: The int64 range
894     :return: A string of the representation of the range
895     """
896
897     lvalue = long(value)
898     str = "%d-%d" % (lvalue&0xFFFFFFFF, lvalue>>32)
899     return str