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