s4 upgradeprovision: Fixes for increment_keyversion
[mat/samba.git] / source4 / scripting / bin / upgradeprovision
1 #!/usr/bin/env python
2 # vim: expandtab
3 #
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 logging
26 import optparse
27 import os
28 import shutil
29 import sys
30 import tempfile
31 import re
32 import traceback
33 # Allow to run from s4 source directory (without installing samba)
34 sys.path.insert(0, "bin/python")
35
36 import ldb
37 import samba
38 import samba.getopt as options
39 from samba.credentials import DONT_USE_KERBEROS
40 from samba.auth import system_session, admin_session
41 from ldb import (SCOPE_SUBTREE, SCOPE_BASE,
42                 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,
43                 MessageElement, Message, Dn)
44 from samba import param
45 from samba.provision import (find_setup_dir, get_domain_descriptor,
46                             get_config_descriptor,
47                             ProvisioningError, get_last_provision_usn,
48                             get_max_usn, update_provision_usn)
49 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
50 from samba.dcerpc import security, drsblobs, xattr
51 from samba.ndr import ndr_unpack
52 from samba.upgradehelpers import (dn_sort, get_paths, newprovision,
53                                  find_provision_key_parameters, get_ldbs,
54                                  usn_in_range, identic_rename, get_diff_sddls,
55                                  update_secrets, CHANGE, ERROR, SIMPLE,
56                                  CHANGEALL, GUESS, CHANGESD, PROVISION,
57                                  updateOEMInfo, getOEMInfo, update_gpo,
58                                  delta_update_basesamdb, update_policyids,
59                                  update_machine_account_password,
60                                  search_constructed_attrs_stored,
61                                  increment_calculated_keyversion_number)
62
63 replace=2**FLAG_MOD_REPLACE
64 add=2**FLAG_MOD_ADD
65 delete=2**FLAG_MOD_DELETE
66 never=0
67
68
69 # Will be modified during provision to tell if default sd has been modified
70 # somehow ...
71
72 #Errors are always logged
73
74 __docformat__ = "restructuredText"
75
76 # Attributes that are never copied from the reference provision (even if they
77 # do not exist in the destination object).
78 # This is most probably because they are populated automatcally when object is
79 # created
80 # This also apply to imported object from reference provision
81 hashAttrNotCopied = {   "dn": 1, "whenCreated": 1, "whenChanged": 1,
82                         "objectGUID": 1, "uSNCreated": 1,
83                         "replPropertyMetaData": 1, "uSNChanged": 1,
84                         "parentGUID": 1, "objectCategory": 1,
85                         "distinguishedName": 1, "nTMixedDomain": 1,
86                         "showInAdvancedViewOnly": 1, "instanceType": 1,
87                         "msDS-Behavior-Version":1, "nextRid":1, "cn": 1,
88                         "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1,
89                         "ntPwdHistory":1, "unicodePwd":1,"dBCSPwd":1,
90                         "supplementalCredentials":1, "gPCUserExtensionNames":1,
91                         "gPCMachineExtensionNames":1,"maxPwdAge":1, "secret":1,
92                         "possibleInferiors":1, "privilege":1,
93                         "sAMAccountType":1 }
94
95 # Usually for an object that already exists we do not overwrite attributes as
96 # they might have been changed for good reasons. Anyway for a few of them it's
97 # mandatory to replace them otherwise the provision will be broken somehow.
98 # But for attribute that are just missing we do not have to specify them as the default
99 # behavior is to add missing attribute
100 hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,
101                         "systemOnly":replace, "searchFlags":replace,
102                         "mayContain":replace, "systemFlags":replace+add,
103                         "description":replace, "operatingSystemVersion":replace,
104                         "adminPropertyPages":replace, "groupType":replace,
105                         "wellKnownObjects":replace, "privilege":never,
106                         "defaultSecurityDescriptor": replace,
107                         "rIDAvailablePool": never,
108                         "defaultSecurityDescriptor": replace + add,
109                         "isMemberOfPartialAttributeSet": delete,
110                         "attributeDisplayNames": replace + add}
111
112
113 backlinked = []
114 forwardlinked = set()
115 dn_syntax_att = []
116 def define_what_to_log(opts):
117     what = 0
118     if opts.debugchange:
119         what = what | CHANGE
120     if opts.debugchangesd:
121         what = what | CHANGESD
122     if opts.debugguess:
123         what = what | GUESS
124     if opts.debugprovision:
125         what = what | PROVISION
126     if opts.debugall:
127         what = what | CHANGEALL
128     return what
129
130
131 parser = optparse.OptionParser("provision [options]")
132 sambaopts = options.SambaOptions(parser)
133 parser.add_option_group(sambaopts)
134 parser.add_option_group(options.VersionOptions(parser))
135 credopts = options.CredentialsOptions(parser)
136 parser.add_option_group(credopts)
137 parser.add_option("--setupdir", type="string", metavar="DIR",
138                   help="directory with setup files")
139 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
140 parser.add_option("--debugguess", action="store_true",
141                   help="Print information on what is different but won't be changed")
142 parser.add_option("--debugchange", action="store_true",
143                   help="Print information on what is different but won't be changed")
144 parser.add_option("--debugchangesd", action="store_true",
145                   help="Print information security descriptors differences")
146 parser.add_option("--debugall", action="store_true",
147                   help="Print all available information (very verbose)")
148 parser.add_option("--resetfileacl", action="store_true",
149                   help="Force a reset on filesystem acls in sysvol / netlogon share")
150 parser.add_option("--full", action="store_true",
151                   help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
152
153 opts = parser.parse_args()[0]
154
155 handler = logging.StreamHandler(sys.stdout)
156 upgrade_logger = logging.getLogger("upgradeprovision")
157 upgrade_logger.setLevel(logging.INFO)
158
159 upgrade_logger.addHandler(handler)
160
161 provision_logger = logging.getLogger("provision")
162 provision_logger.addHandler(handler)
163
164 whatToLog = define_what_to_log(opts)
165
166 def message(what, text):
167     """Print a message if this message type has been selected to be printed
168
169     :param what: Category of the message
170     :param text: Message to print """
171     if (whatToLog & what) or what <= 0:
172         upgrade_logger.info("%s", text)
173
174 if len(sys.argv) == 1:
175     opts.interactive = True
176 lp = sambaopts.get_loadparm()
177 smbconf = lp.configfile
178
179 creds = credopts.get_credentials(lp)
180 creds.set_kerberos_state(DONT_USE_KERBEROS)
181 setup_dir = opts.setupdir
182 if setup_dir is None:
183     setup_dir = find_setup_dir()
184
185
186
187 def check_for_DNS(refprivate, private):
188     """Check if the provision has already the requirement for dynamic dns
189
190     :param refprivate: The path to the private directory of the reference
191                        provision
192     :param private: The path to the private directory of the upgraded
193                     provision"""
194
195     spnfile = "%s/spn_update_list" % private
196     namedfile = lp.get("dnsupdate:path")
197
198     if not namedfile:
199        namedfile = "%s/named.conf.update" % private
200
201     if not os.path.exists(spnfile):
202         shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
203
204     destdir = "%s/new_dns" % private
205     dnsdir = "%s/dns" % private
206
207     if not os.path.exists(namedfile):
208         if not os.path.exists(destdir):
209             os.mkdir(destdir)
210         if not os.path.exists(dnsdir):
211             os.mkdir(dnsdir)
212         shutil.copy("%s/named.conf" % refprivate, "%s/named.conf" % destdir)
213         shutil.copy("%s/named.txt" % refprivate, "%s/named.txt" % destdir)
214         message(SIMPLE, "It seems that you provision didn't integrate new rules "
215                 "for dynamic dns update of domain related entries")
216         message(SIMPLE, "A copy of the new bind configuration files and "
217                 "template as been put in %s, you should read them and configure dynamic "
218                 " dns update" % destdir)
219
220
221 def populate_links(samdb, schemadn):
222     """Populate an array with all the back linked attributes
223
224     This attributes that are modified automaticaly when
225     front attibutes are changed
226
227     :param samdb: A LDB object for sam.ldb file
228     :param schemadn: DN of the schema for the partition"""
229     linkedAttHash = get_linked_attributes(Dn(samdb, str(schemadn)), samdb)
230     backlinked.extend(linkedAttHash.values())
231     for t in linkedAttHash.keys():
232         forwardlinked.add(t)
233
234
235 def populate_dnsyntax(samdb, schemadn):
236     """Populate an array with all the attributes that have DN synthax
237        (oid 2.5.5.1)
238
239     :param samdb: A LDB object for sam.ldb file
240     :param schemadn: DN of the schema for the partition"""
241     res = samdb.search(expression="(attributeSyntax=2.5.5.1)", base=Dn(samdb,
242                         str(schemadn)), scope=SCOPE_SUBTREE,
243                         attrs=["lDAPDisplayName"])
244     for elem in res:
245         dn_syntax_att.append(elem["lDAPDisplayName"])
246
247
248 def sanitychecks(samdb, names):
249     """Make some checks before trying to update
250
251     :param samdb: An LDB object opened on sam.ldb
252     :param names: list of key provision parameters
253     :return: Status of check (1 for Ok, 0 for not Ok) """
254     res = samdb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
255                          scope=SCOPE_SUBTREE, attrs=["dn"],
256                          controls=["search_options:1:2"])
257     if len(res) == 0:
258         print "No DC found, your provision is most probably hardly broken !"
259         return False
260     elif len(res) != 1:
261         print "Found %d domain controllers, for the moment upgradeprovision" \
262               "is not able to handle upgrade on domain with more than one DC, please demote" \
263               " the other(s) DC(s) before upgrading" % len(res)
264         return False
265     else:
266         return True
267
268
269 def print_provision_key_parameters(names):
270     """Do a a pretty print of provision parameters
271
272     :param names: list of key provision parameters """
273     message(GUESS, "rootdn      :" + str(names.rootdn))
274     message(GUESS, "configdn    :" + str(names.configdn))
275     message(GUESS, "schemadn    :" + str(names.schemadn))
276     message(GUESS, "serverdn    :" + str(names.serverdn))
277     message(GUESS, "netbiosname :" + names.netbiosname)
278     message(GUESS, "defaultsite :" + names.sitename)
279     message(GUESS, "dnsdomain   :" + names.dnsdomain)
280     message(GUESS, "hostname    :" + names.hostname)
281     message(GUESS, "domain      :" + names.domain)
282     message(GUESS, "realm       :" + names.realm)
283     message(GUESS, "invocationid:" + names.invocation)
284     message(GUESS, "policyguid  :" + names.policyid)
285     message(GUESS, "policyguiddc:" + str(names.policyid_dc))
286     message(GUESS, "domainsid   :" + str(names.domainsid))
287     message(GUESS, "domainguid  :" + names.domainguid)
288     message(GUESS, "ntdsguid    :" + names.ntdsguid)
289     message(GUESS, "domainlevel :" + str(names.domainlevel))
290
291
292 def handle_special_case(att, delta, new, old, usn, basedn, aldb):
293     """Define more complicate update rules for some attributes
294
295     :param att: The attribute to be updated
296     :param delta: A messageElement object that correspond to the difference
297                   between the updated object and the reference one
298     :param new: The reference object
299     :param old: The Updated object
300     :param usn: The highest usn modified by a previous (upgrade)provision
301     :param basedn: The base DN of the provision
302     :param aldb: An ldb object used to build DN
303     :return: True to indicate that the attribute should be kept, False for
304              discarding it"""
305
306     flag = delta.get(att).flags()
307     # We do most of the special case handle if we do not have the
308     # highest usn as otherwise the replPropertyMetaData will guide us more
309     # correctly
310     if usn is None:
311         if (att == "sPNMappings" and flag == FLAG_MOD_REPLACE and
312             ldb.Dn(aldb, "CN=Directory Service,CN=Windows NT,"
313                         "CN=Services,CN=Configuration,%s" % basedn)
314                         == old[0].dn):
315             return True
316         if (att == "userAccountControl" and flag == FLAG_MOD_REPLACE and
317             ldb.Dn(aldb, "CN=Administrator,CN=Users,%s" % basedn)
318                         == old[0].dn):
319             message(SIMPLE, "We suggest that you change the userAccountControl"
320                             " for user Administrator from value %d to %d" %
321                             (int(str(old[0][att])), int(str(new[0][att]))))
322             return False
323         if (att == "minPwdAge" and flag == FLAG_MOD_REPLACE):
324             if (long(str(old[0][att])) == 0):
325                 delta[att] = MessageElement(new[0][att], FLAG_MOD_REPLACE, att)
326             return True
327
328         if (att == "member" and flag == FLAG_MOD_REPLACE):
329             hash = {}
330             newval = []
331             changeDelta=0
332             for elem in old[0][att]:
333                 hash[str(elem).lower()]=1
334                 newval.append(str(elem))
335
336             for elem in new[0][att]:
337                 if not hash.has_key(str(elem).lower()):
338                     changeDelta=1
339                     newval.append(str(elem))
340             if changeDelta == 1:
341                 delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
342             else:
343                 delta.remove(att)
344             return True
345
346         if (att in ("gPLink", "gPCFileSysPath") and
347             flag == FLAG_MOD_REPLACE and
348             str(new[0].dn).lower() == str(old[0].dn).lower()):
349             delta.remove(att)
350             return True
351
352         if att == "forceLogoff":
353             ref=0x8000000000000000
354             oldval=int(old[0][att][0])
355             newval=int(new[0][att][0])
356             ref == old and ref == abs(new)
357             return True
358
359         if att in ("adminDisplayName", "adminDescription"):
360             return True
361
362         if (str(old[0].dn) == "CN=Samba4-Local-Domain, %s" % (names.schemadn)
363             and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
364             return True
365
366         if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
367                 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
368             return True
369
370         if (str(old[0].dn) == "%s" % (str(names.rootdn))
371                 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
372             return True
373
374         if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
375             return True
376
377     # This is a bit of special animal as we might have added
378     # already SPN entries to the list that has to be modified
379     # So we go in detail to try to find out what has to be added ...
380     if (att == "servicePrincipalName" and flag == FLAG_MOD_REPLACE):
381         hash = {}
382         newval = []
383         changeDelta=0
384         for elem in old[0][att]:
385             hash[str(elem)]=1
386             newval.append(str(elem))
387
388         for elem in new[0][att]:
389             if not hash.has_key(str(elem)):
390                 changeDelta=1
391                 newval.append(str(elem))
392         if changeDelta == 1:
393             delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
394         else:
395             delta.remove(att)
396         return True
397
398     return False
399
400 def dump_denied_change(dn, att, flagtxt, current, reference):
401     """Print detailed information about why a changed is denied
402
403     :param dn: DN of the object which attribute is denied
404     :param att: Attribute that was supposed to be upgraded
405     :param flagtxt: Type of the update that should be performed
406                     (add, change, remove, ...)
407     :param current: Value(s) of the current attribute
408     :param reference: Value(s) of the reference attribute"""
409
410     message(CHANGE, "dn= " + str(dn)+" " + att+" with flag " + flagtxt
411                 +" is not allowed to be changed/removed, I discard this change")
412     if att != "objectSid" :
413         i = 0
414         for e in range(0, len(current)):
415             message(CHANGE, "old %d : %s" % (i, str(current[e])))
416             i+=1
417         if reference is not None:
418             i = 0
419             for e in range(0, len(reference)):
420                 message(CHANGE, "new %d : %s" % (i, str(reference[e])))
421                 i+=1
422     else:
423         message(CHANGE, "old : %s" % ndr_unpack(security.dom_sid, current[0]))
424         message(CHANGE, "new : %s" % ndr_unpack(security.dom_sid, reference[0]))
425
426
427 def handle_special_add(samdb, dn, names):
428     """Handle special operation (like remove) on some object needed during
429     upgrade
430
431     This is mostly due to wrong creation of the object in previous provision.
432     :param samdb: An Ldb object representing the SAM database
433     :param dn: DN of the object to inspect
434     :param names: list of key provision parameters
435     """
436
437     dntoremove = None
438     objDn = Dn(samdb, "CN=IIS_IUSRS, CN=Builtin, %s" % names.rootdn)
439     if dn == objDn :
440         #This entry was misplaced lets remove it if it exists
441         dntoremove = "CN=IIS_IUSRS, CN=Users, %s" % names.rootdn
442
443     objDn = Dn(samdb,
444                 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names.rootdn)
445     if dn == objDn:
446         #This entry was misplaced lets remove it if it exists
447         dntoremove = "CN=Certificate Service DCOM Access,"\
448                      "CN=Users, %s" % names.rootdn
449         print dntoremove
450
451     objDn = Dn(samdb, "CN=Cryptographic Operators, CN=Builtin, %s" % names.rootdn)
452     if dn == objDn:
453         #This entry was misplaced lets remove it if it exists
454         dntoremove = "CN=Cryptographic Operators, CN=Users, %s" % names.rootdn
455
456     objDn = Dn(samdb, "CN=Event Log Readers, CN=Builtin, %s" % names.rootdn)
457     if dn == objDn:
458         #This entry was misplaced lets remove it if it exists
459         dntoremove = "CN=Event Log Readers, CN=Users, %s" % names.rootdn
460
461     objDn = Dn(samdb,"CN=System,CN=WellKnown Security Principals,"
462                      "CN=Configuration,%s" % names.rootdn)
463     if dn == objDn:
464         oldDn = Dn(samdb,"CN=Well-Known-Security-Id-System,"
465                          "CN=WellKnown Security Principals,"
466                          "CN=Configuration,%s" % names.rootdn)
467
468         res = samdb.search(expression="(dn=%s)" % oldDn,
469                             base=str(names.rootdn),
470                             scope=SCOPE_SUBTREE, attrs=["dn"],
471                             controls=["search_options:1:2"])
472         if len(res) > 0:
473             message(CHANGE, "Existing object %s must be replaced by %s,"
474                             "Renaming old object" % (str(oldDn), str(dn)))
475             samdb.rename(oldDn, objDn)
476
477         return 1
478
479     if dntoremove is not None:
480         res = samdb.search(expression="(dn=%s)" % dntoremove,
481                             base=str(names.rootdn),
482                             scope=SCOPE_SUBTREE, attrs=["dn"],
483                             controls=["search_options:1:2"])
484         if len(res) > 0:
485             message(CHANGE, "Existing object %s must be replaced by %s,"
486                             "removing old object" % (dntoremove, str(dn)))
487             samdb.delete(res[0]["dn"])
488     return 0
489
490
491 def check_dn_nottobecreated(hash, index, listdn):
492     """Check if one of the DN present in the list has a creation order
493        greater than the current.
494
495     Hash is indexed by dn to be created, with each key
496     is associated the creation order.
497
498     First dn to be created has the creation order 0, second has 1, ...
499     Index contain the current creation order
500
501     :param hash: Hash holding the different DN of the object to be
502                   created as key
503     :param index: Current creation order
504     :param listdn: List of DNs on which the current DN depends on
505     :return: None if the current object do not depend on other
506               object or if all object have been created before."""
507     if listdn is None:
508         return None
509     for dn in listdn:
510         key = str(dn).lower()
511         if hash.has_key(key) and hash[key] > index:
512             return str(dn)
513     return None
514
515
516
517 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
518     """Add a new object if the dependencies are satisfied
519
520     The function add the object if the object on which it depends are already
521     created
522
523     :param ref_samdb: Ldb object representing the SAM db of the reference
524                        provision
525     :param samdb: Ldb object representing the SAM db of the upgraded
526                    provision
527     :param dn: DN of the object to be added
528     :param names: List of key provision parameters
529     :param basedn: DN of the partition to be updated
530     :param hash: Hash holding the different DN of the object to be
531                   created as key
532     :param index: Current creation order
533     :return: True if the object was created False otherwise"""
534
535     if handle_special_add(samdb, dn, names):
536         return
537     reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
538                     scope=SCOPE_SUBTREE, controls=["search_options:1:2"])
539     empty = Message()
540     delta = samdb.msg_diff(empty, reference[0])
541     delta.dn
542     skip = False
543     try:
544         if str(reference[0].get("cn"))  == "RID Set":
545             for klass in reference[0].get("objectClass"):
546                 if str(klass).lower() == "ridset":
547                     skip = True
548     finally:
549         if delta.get("objectSid"):
550             sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
551             m = re.match(r".*-(\d+)$", sid)
552             if m and int(m.group(1))>999:
553                 delta.remove("objectSid")
554         for att in hashAttrNotCopied.keys():
555             delta.remove(att)
556         for att in backlinked:
557             delta.remove(att)
558         depend_on_yettobecreated = None
559         for att in dn_syntax_att:
560             depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
561                                                                 delta.get(str(att)))
562             if depend_on_yet_tobecreated is not None:
563                 message(CHANGE, "Object %s depends on %s in attribute %s,"
564                                 "delaying the creation" % (dn,
565                                           depend_on_yet_tobecreated, att))
566                 return False
567
568         delta.dn = dn
569         if not skip:
570             message(CHANGE,"Object %s will be added" % dn)
571             samdb.add(delta, ["relax:0"])
572         else:
573             message(CHANGE,"Object %s was skipped" % dn)
574
575         return True
576
577 def gen_dn_index_hash(listMissing):
578     """Generate a hash associating the DN to its creation order
579
580     :param listMissing: List of DN
581     :return: Hash with DN as keys and creation order as values"""
582     hash = {}
583     for i in range(0, len(listMissing)):
584         hash[str(listMissing[i]).lower()] = i
585     return hash
586
587 def add_deletedobj_containers(ref_samdb, samdb, names):
588     """Add the object containter: CN=Deleted Objects
589
590     This function create the container for each partition that need one and
591     then reference the object into the root of the partition
592
593     :param ref_samdb: Ldb object representing the SAM db of the reference
594                        provision
595     :param samdb: Ldb object representing the SAM db of the upgraded provision
596     :param names: List of key provision parameters"""
597
598
599     wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
600     partitions = [str(names.rootdn), str(names.configdn)]
601     for part in partitions:
602         ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
603                                             base=part, scope=SCOPE_SUBTREE,
604                                             attrs=["dn"],
605                                             controls=["show_deleted:0"])
606         delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
607                                     base=part, scope=SCOPE_SUBTREE,
608                                     attrs=["dn"],
609                                     controls=["show_deleted:0"])
610         if len(ref_delObjCnt) > len(delObjCnt):
611             reference = ref_samdb.search(expression="cn=Deleted Objects",
612                                             base=part, scope=SCOPE_SUBTREE,
613                                             controls=["show_deleted:0"])
614             empty = Message()
615             delta = samdb.msg_diff(empty, reference[0])
616
617             delta.dn = Dn(samdb, str(reference[0]["dn"]))
618             for att in hashAttrNotCopied.keys():
619                 delta.remove(att)
620             samdb.add(delta)
621
622             listwko = []
623             res = samdb.search(expression="(objectClass=*)", base=part,
624                                scope=SCOPE_BASE,
625                                attrs=["dn", "wellKnownObjects"])
626
627             targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
628             found = False
629
630             if len(res[0]) > 0:
631                 wko = res[0]["wellKnownObjects"]
632
633                 # The wellKnownObject that we want to add.
634                 for o in wko:
635                     if str(o) == targetWKO:
636                         found = True
637                     listwko.append(str(o))
638
639             if not found:
640                 listwko.append(targetWKO)
641
642                 delta = Message()
643                 delta.dn = Dn(samdb, str(res[0]["dn"]))
644                 delta["wellKnownObjects"] = MessageElement(listwko,
645                                                 FLAG_MOD_REPLACE,
646                                                 "wellKnownObjects" )
647                 samdb.modify(delta)
648
649 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
650     """Add the missing object whose DN is the list
651
652     The function add the object if the objects on which it depends are
653     already created.
654
655     :param ref_samdb: Ldb object representing the SAM db of the reference
656                       provision
657     :param samdb: Ldb object representing the SAM db of the upgraded
658                   provision
659     :param dn: DN of the object to be added
660     :param names: List of key provision parameters
661     :param basedn: DN of the partition to be updated
662     :param list: List of DN to be added in the upgraded provision"""
663
664     listMissing = []
665     listDefered = list
666
667     while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
668         index = 0
669         listMissing = listDefered
670         listDefered = []
671         hashMissing = gen_dn_index_hash(listMissing)
672         for dn in listMissing:
673             ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
674                                         hashMissing, index)
675             index = index + 1
676             if ret == 0:
677                 # DN can't be created because it depends on some
678                 # other DN in the list
679                 listDefered.append(dn)
680     if len(listDefered) != 0:
681         raise ProvisioningError("Unable to insert missing elements:" \
682                                 "circular references")
683
684 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
685     """This function handle updates on links
686
687     :param samdb: An LDB object pointing to the updated provision
688     :param att: Attribute to update
689     :param basedn: The root DN of the provision
690     :param dn: The DN of the inspected object
691     :param value: The value of the attribute
692     :param ref_value: The value of this attribute in the reference provision
693     :param delta: The MessageElement object that will be applied for
694                    transforming the current provision"""
695
696     res = samdb.search(expression="dn=%s" % dn, base=basedn,
697                         controls=["search_options:1:2", "reveal:1"],
698                         attrs=[att])
699
700     blacklist = {}
701     hash = {}
702     newlinklist = []
703     changed = False
704
705     newlinklist.extend(value)
706
707     for e in value:
708         hash[e] = 1
709     # for w2k domain level the reveal won't reveal anything ...
710     # it means that we can readd links that were removed on purpose ...
711     # Also this function in fact just accept add not removal
712
713     for e in res[0][att]:
714         if not hash.has_key(e):
715             # We put in the blacklist all the element that are in the "revealed"
716             # result and not in the "standard" result
717             # This element are links that were removed before and so that
718             # we don't wan't to readd
719             blacklist[e] = 1
720
721     for e in ref_value:
722         if not blacklist.has_key(e) and not hash.has_key(e):
723             newlinklist.append(str(e))
724             changed = True
725     if changed:
726         delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
727     else:
728         delta.remove(att)
729
730
731 msg_elt_flag_strs = {
732     ldb.FLAG_MOD_ADD: "MOD_ADD",
733     ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
734     ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
735
736
737 def update_present(ref_samdb, samdb, basedn, listPresent, usns, invocationid):
738     """ This function updates the object that are already present in the
739         provision
740
741     :param ref_samdb: An LDB object pointing to the reference provision
742     :param samdb: An LDB object pointing to the updated provision
743     :param basedn: A string with the value of the base DN for the provision
744                    (ie. DC=foo, DC=bar)
745     :param listPresent: A list of object that is present in the provision
746     :param usns: A list of USN range modified by previous provision and
747                  upgradeprovision
748     :param invocationid: The value of the invocationid for the current DC"""
749
750     global defSDmodified
751     # This hash is meant to speedup lookup of attribute name from an oid,
752     # it's for the replPropertyMetaData handling
753     hash_oid_name = {}
754     res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
755                         controls=["search_options:1:2"], attrs=["attributeID",
756                         "lDAPDisplayName"])
757     if len(res) > 0:
758         for e in res:
759             strDisplay = str(e.get("lDAPDisplayName"))
760             hash_oid_name[str(e.get("attributeID"))] = strDisplay
761     else:
762         msg = "Unable to insert missing elements: circular references"
763         raise ProvisioningError(msg)
764
765     changed = 0
766     controls = ["search_options:1:2", "sd_flags:1:2"]
767     for dn in listPresent:
768         reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
769                                         scope=SCOPE_SUBTREE,
770                                         controls=controls)
771         current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
772                                 scope=SCOPE_SUBTREE, controls=controls)
773
774         if (
775              (str(current[0].dn) != str(reference[0].dn)) and
776              (str(current[0].dn).upper() == str(reference[0].dn).upper())
777            ):
778             message(CHANGE, "Name are the same but case change,"\
779                             "let's rename %s to %s" % (str(current[0].dn),
780                                                        str(reference[0].dn)))
781             identic_rename(samdb, reference[0].dn)
782             current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
783                                     scope=SCOPE_SUBTREE,
784                                     controls=["search_options:1:2"])
785
786         delta = samdb.msg_diff(current[0], reference[0])
787
788         for att in hashAttrNotCopied.keys():
789             delta.remove(att)
790
791         for att in backlinked:
792             delta.remove(att)
793
794         delta.remove("name")
795
796         if len(delta.items()) > 1 and usns is not None:
797             # Fetch the replPropertyMetaData
798             res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
799                                 scope=SCOPE_SUBTREE, controls=controls,
800                                 attrs=["replPropertyMetaData"])
801             ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
802                                 str(res[0]["replPropertyMetaData"])).ctr
803
804             hash_attr_usn = {}
805             for o in ctr.array:
806                 # We put in this hash only modification
807                 # made on the current host
808                 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
809                 if str(o.originating_invocation_id) == str(invocationid):
810                 # Note we could just use 1 here
811                     hash_attr_usn[att] = o.originating_usn
812                 else:
813                     hash_attr_usn[att] = -1
814
815         isFirst = 0
816         txt = ""
817
818         for att in delta:
819             if usns is not None:
820                 # We have updated by provision usn information so let's exploit
821                 # replMetadataProperties
822                 if att in forwardlinked:
823                     handle_links(samdb, att, basedn, current[0]["dn"],
824                                     current[0][att], reference[0][att], delta)
825
826                 if isFirst == 0 and len(delta.items())>1:
827                     isFirst = 1
828                     txt = "%s\n" % (str(dn))
829                 if att == "dn":
830                     # There is always a dn attribute after a msg_diff
831                     continue
832                 if att == "rIDAvailablePool":
833                     delta.remove(att)
834                     continue
835                 if att == "objectSid":
836                     delta.remove(att)
837                     continue
838                 if att == "creationTime":
839                     delta.remove(att)
840                     continue
841                 if att == "oEMInformation":
842                     delta.remove(att)
843                     continue
844                 if att == "msDs-KeyVersionNumber":
845                 # This is the kvno of the computer/user it's a very bad
846                 # idea to change it
847                     delta.remove(att)
848                     continue
849                 if handle_special_case(att, delta, reference, current, usns, basedn, samdb):
850                     # This attribute is "complicated" to handle and handling
851                     # was done in handle_special_case
852                     continue
853                 attrUSN = hash_attr_usn.get(att)
854                 if att == "forceLogoff" and attrUSN is None:
855                     continue
856                 if  attrUSN is None:
857                     delta.remove(att)
858                     continue
859
860                 if attrUSN == -1:
861                     # This attribute was last modified by another DC forget
862                     # about it
863                     message(CHANGE, "%sAttribute: %s has been"
864                             "created/modified/deleted  by another DC,"
865                             " do nothing" % (txt, att ))
866                     txt = ""
867                     delta.remove(att)
868                     continue
869                 elif not usn_in_range(int(attrUSN), usns):
870                     message(CHANGE, "%sAttribute: %s has been"
871                                     "created/modified/deleted not during a"
872                                     " provision or upgradeprovision: current"
873                                     " usn %d , do nothing" % (txt, att, attrUSN))
874                     txt = ""
875                     delta.remove(att)
876                     continue
877                 else:
878                     if att == "defaultSecurityDescriptor":
879                         defSDmodified = True
880                     if attrUSN:
881                         message(CHANGE, "%sAttribute: %s will be modified"
882                                         "/deleted it was last modified"
883                                         "during a provision, current usn:"
884                                         "%d" % (txt, att,  attrUSN))
885                         txt = ""
886                     else:
887                         message(CHANGE, "%sAttribute: %s will be added because"
888                                         " it hasn't existed before " % (txt, att))
889                         txt = ""
890                     continue
891
892             else:
893             # Old school way of handling things for pre alpha12 upgrade
894                 defSDmodified = True
895                 msgElt = delta.get(att)
896
897                 if att == "nTSecurityDescriptor":
898                     delta.remove(att)
899                     continue
900
901                 if att == "dn":
902                     continue
903
904                 if not hashOverwrittenAtt.has_key(att):
905                     if msgElt.flags() != FLAG_MOD_ADD:
906                         if not handle_special_case(att, delta, reference, current,
907                                                     usns, basedn, samdb):
908                             if opts.debugchange or opts.debugall:
909                                 try:
910                                     dump_denied_change(dn, att,
911                                         msg_elt_flag_strs[msgElt.flags()],
912                                         current[0][att], reference[0][att])
913                                 except KeyError:
914                                     dump_denied_change(dn, att,
915                                         msg_elt_flag_strs[msgElt.flags()],
916                                         current[0][att], None)
917                             delta.remove(att)
918                         continue
919                 else:
920                     if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
921                         continue
922                     elif  hashOverwrittenAtt.get(att)==never:
923                         delta.remove(att)
924                         continue
925
926         delta.dn = dn
927         if len(delta.items()) >1:
928             attributes=", ".join(delta.keys())
929             message(CHANGE, "%s is different from the reference one, changed"
930                             " attributes: %s\n" % (dn, attributes))
931             changed += 1
932             samdb.modify(delta)
933     return changed
934
935
936 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs):
937     """Check differences between the reference provision and the upgraded one.
938
939     It looks for all objects which base DN is name.
940
941     This function will also add the missing object and update existing object
942     to add or remove attributes that were missing.
943
944     :param ref_sambdb: An LDB object conntected to the sam.ldb of the
945                        reference provision
946     :param samdb: An LDB object connected to the sam.ldb of the update
947                   provision
948     :param basedn: String value of the DN of the partition
949     :param names: List of key provision parameters
950     :param schema: A Schema object
951     :param provisionUSNs:  The USNs modified by provision/upgradeprovision
952                            last time"""
953
954     hash_new = {}
955     hash = {}
956     listMissing = []
957     listPresent = []
958     reference = []
959     current = []
960
961     # Connect to the reference provision and get all the attribute in the
962     # partition referred by name
963     reference = ref_samdb.search(expression="objectClass=*", base=basedn,
964                                     scope=SCOPE_SUBTREE, attrs=["dn"],
965                                     controls=["search_options:1:2"])
966
967     current = samdb.search(expression="objectClass=*", base=basedn,
968                                 scope=SCOPE_SUBTREE, attrs=["dn"],
969                                 controls=["search_options:1:2"])
970     # Create a hash for speeding the search of new object
971     for i in range(0, len(reference)):
972         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
973
974     # Create a hash for speeding the search of existing object in the
975     # current provision
976     for i in range(0, len(current)):
977         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
978
979
980     for k in hash_new.keys():
981         if not hash.has_key(k):
982             if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
983                 listMissing.append(hash_new[k])
984         else:
985             listPresent.append(hash_new[k])
986
987     # Sort the missing object in order to have object of the lowest level
988     # first (which can be containers for higher level objects)
989     listMissing.sort(dn_sort)
990     listPresent.sort(dn_sort)
991
992     # The following lines is to load the up to
993     # date schema into our current LDB
994     # a complete schema is needed as the insertion of attributes
995     # and class is done against it
996     # and the schema is self validated
997     samdb.set_schema(schema)
998     try:
999         message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
1000         add_deletedobj_containers(ref_samdb, samdb, names)
1001
1002         add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
1003         changed = update_present(ref_samdb, samdb, basedn, listPresent,
1004                                     provisionUSNs, names.invocation)
1005         message(SIMPLE, "There are %d changed objects" % (changed))
1006         return 1
1007
1008     except StandardError, err:
1009         message(ERROR, "Exception during upgrade of samdb:")
1010         (typ, val, tb) = sys.exc_info()
1011         traceback.print_exception(typ, val, tb)
1012         return 0
1013
1014
1015 def check_updated_sd(ref_sam, cur_sam, names):
1016     """Check if the security descriptor in the upgraded provision are the same
1017        as the reference
1018
1019     :param ref_sam: A LDB object connected to the sam.ldb file used as
1020                     the reference provision
1021     :param cur_sam: A LDB object connected to the sam.ldb file used as
1022                     upgraded provision
1023     :param names: List of key provision parameters"""
1024     reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1025                                 scope=SCOPE_SUBTREE,
1026                                 attrs=["dn", "nTSecurityDescriptor"],
1027                                 controls=["search_options:1:2"])
1028     current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1029                                 scope=SCOPE_SUBTREE,
1030                                 attrs=["dn", "nTSecurityDescriptor"],
1031                                 controls=["search_options:1:2"])
1032     hash = {}
1033     for i in range(0, len(reference)):
1034         refsd = ndr_unpack(security.descriptor,
1035                     str(reference[i]["nTSecurityDescriptor"]))
1036         hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1037
1038
1039     for i in range(0, len(current)):
1040         key = str(current[i]["dn"]).lower()
1041         if hash.has_key(key):
1042             cursd = ndr_unpack(security.descriptor,
1043                         str(current[i]["nTSecurityDescriptor"]))
1044             sddl = cursd.as_sddl(names.domainsid)
1045             if sddl != hash[key]:
1046                 txt = get_diff_sddls(hash[key], sddl)
1047                 if txt != "":
1048                     message(CHANGESD, "On object %s ACL is different"
1049                                       " \n%s" % (current[i]["dn"], txt))
1050
1051
1052
1053 def fix_partition_sd(samdb, names):
1054     """This function fix the SD for partition containers (basedn, configdn, ...)
1055     This is needed because some provision use to have broken SD on containers
1056
1057     :param samdb: An LDB object pointing to the sam of the current provision
1058     :param names: A list of key provision parameters
1059     """
1060     # First update the SD for the rootdn
1061     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1062                          scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1063                          controls=["search_options:1:2"])
1064     delta = Message()
1065     delta.dn = Dn(samdb, str(res[0]["dn"]))
1066     descr = get_domain_descriptor(names.domainsid)
1067     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1068                                                     "nTSecurityDescriptor")
1069     samdb.modify(delta, ["recalculate_sd:0"])
1070     # Then the config dn
1071     res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1072                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1073                         controls=["search_options:1:2"])
1074     delta = Message()
1075     delta.dn = Dn(samdb, str(res[0]["dn"]))
1076     descr = get_config_descriptor(names.domainsid)
1077     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1078                                                     "nTSecurityDescriptor" )
1079     samdb.modify(delta, ["recalculate_sd:0"])
1080     # Then the schema dn
1081     res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1082                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1083                         controls=["search_options:1:2"])
1084
1085     delta = Message()
1086     delta.dn = Dn(samdb, str(res[0]["dn"]))
1087     descr = get_schema_descriptor(names.domainsid)
1088     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1089                                                     "nTSecurityDescriptor" )
1090     samdb.modify(delta, ["recalculate_sd:0"])
1091
1092 def rebuild_sd(samdb, names):
1093     """Rebuild security descriptor of the current provision from scratch
1094
1095     During the different pre release of samba4 security descriptors (SD)
1096     were notarly broken (up to alpha11 included)
1097     This function allow to get them back in order, this function make the
1098     assumption that nobody has modified manualy an SD
1099     and so SD can be safely recalculated from scratch to get them right.
1100
1101     :param names: List of key provision parameters"""
1102
1103
1104     hash = {}
1105     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1106                         scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1107                         controls=["search_options:1:2"])
1108     for obj in res:
1109         if not (str(obj["dn"]) == str(names.rootdn) or
1110             str(obj["dn"]) == str(names.configdn) or
1111             str(obj["dn"]) == str(names.schemadn)):
1112             hash[str(obj["dn"])] = obj["whenCreated"]
1113
1114     listkeys = hash.keys()
1115     listkeys.sort(dn_sort)
1116
1117     for key in listkeys:
1118         try:
1119             delta = Message()
1120             delta.dn = Dn(samdb, key)
1121             delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1122                                                     "whenCreated" )
1123             samdb.modify(delta, ["recalculate_sd:0"])
1124         except:
1125             # XXX: We should always catch an explicit exception.
1126             # What could go wrong here?
1127             samdb.transaction_cancel()
1128             res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1129                                 scope=SCOPE_SUBTREE,
1130                                 attrs=["dn", "nTSecurityDescriptor"],
1131                                 controls=["search_options:1:2"])
1132             badsd = ndr_unpack(security.descriptor,
1133                         str(res[0]["nTSecurityDescriptor"]))
1134             print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1135             return
1136
1137 def removeProvisionUSN(samdb):
1138         attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1139         entry = samdb.search(expression="dn=@PROVISION", base = "",
1140                                 scope=SCOPE_SUBTREE,
1141                                 controls=["search_options:1:2"],
1142                                 attrs=attrs)
1143         empty = Message()
1144         empty.dn = entry[0].dn
1145         delta = samdb.msg_diff(entry[0], empty)
1146         delta.remove("dn")
1147         delta.dn = entry[0].dn
1148         samdb.modify(delta)
1149
1150 def remove_stored_generated_attrs(paths, creds, session, lp):
1151     """Remove previously stored constructed attributes
1152
1153     :param paths: List of paths for different provision objects
1154                         from the upgraded provision
1155     :param creds: A credential object
1156     :param session: A session object
1157     :param lp: A line parser object
1158     :return: An associative array whose key are the different constructed
1159              attributes and the value the dn where this attributes were found.
1160      """
1161
1162
1163 def simple_update_basesamdb(newpaths, paths, names):
1164     """Update the provision container db: sam.ldb
1165     This function is aimed at very old provision (before alpha9)
1166
1167     :param newpaths: List of paths for different provision objects
1168                         from the reference provision
1169     :param paths: List of paths for different provision objects
1170                         from the upgraded provision
1171     :param names: List of key provision parameters"""
1172
1173     message(SIMPLE, "Copy samdb")
1174     shutil.copy(newpaths.samdb, paths.samdb)
1175
1176     message(SIMPLE, "Update partitions filename if needed")
1177     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1178     configldb = os.path.join(paths.private_dir, "configuration.ldb")
1179     usersldb = os.path.join(paths.private_dir, "users.ldb")
1180     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1181
1182     if not os.path.isdir(samldbdir):
1183         os.mkdir(samldbdir)
1184         os.chmod(samldbdir, 0700)
1185     if os.path.isfile(schemaldb):
1186         shutil.copy(schemaldb, os.path.join(samldbdir,
1187                                             "%s.ldb"%str(names.schemadn).upper()))
1188         os.remove(schemaldb)
1189     if os.path.isfile(usersldb):
1190         shutil.copy(usersldb, os.path.join(samldbdir,
1191                                             "%s.ldb"%str(names.rootdn).upper()))
1192         os.remove(usersldb)
1193     if os.path.isfile(configldb):
1194         shutil.copy(configldb, os.path.join(samldbdir,
1195                                             "%s.ldb"%str(names.configdn).upper()))
1196         os.remove(configldb)
1197
1198
1199 def update_privilege(ref_private_path, cur_private_path):
1200     """Update the privilege database
1201
1202     :param ref_private_path: Path to the private directory of the reference
1203                              provision.
1204     :param cur_private_path: Path to the private directory of the current
1205                              (and to be updated) provision."""
1206     message(SIMPLE, "Copy privilege")
1207     shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1208                 os.path.join(cur_private_path, "privilege.ldb"))
1209
1210
1211 def update_samdb(ref_samdb, samdb, names, highestUSN, schema):
1212     """Upgrade the SAM DB contents for all the provision partitions
1213
1214     :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1215                        provision
1216     :param samdb: An LDB object connected to the sam.ldb of the update
1217                   provision
1218     :param names: List of key provision parameters
1219     :param highestUSN:  The highest USN modified by provision/upgradeprovision
1220                         last time
1221     :param schema: A Schema object that represent the schema of the provision"""
1222
1223     message(SIMPLE, "Starting update of samdb")
1224     ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1225                             schema, highestUSN)
1226     if ret:
1227         message(SIMPLE, "Update of samdb finished")
1228         return 1
1229     else:
1230         message(SIMPLE, "Update failed")
1231         return 0
1232
1233
1234 def copyxattrs(dir, refdir):
1235     """ Copy owner, groups, extended ACL and NT acls from
1236     a reference dir to a destination dir
1237
1238     Both dir are supposed to hold the same files
1239     :param dir: Destination dir
1240     :param refdir: Reference directory"""
1241
1242     noxattr = 0
1243     for root, dirs, files in os.walk(dir, topdown=True):
1244         for name in files:
1245             subdir=root[len(dir):]
1246             ref = os.path.join("%s%s" % (refdir, subdir), name)
1247             statsinfo = os.stat(ref)
1248             tgt = os.path.join(root, name)
1249             try:
1250
1251                 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1252                 # Get the xattr attributes if any
1253                 try:
1254                     attribute = samba.xattr_native.wrap_getxattr(ref,
1255                                                  xattr.XATTR_NTACL_NAME)
1256                     samba.xattr_native.wrap_setxattr(tgt,
1257                                                  xattr.XATTR_NTACL_NAME,
1258                                                  attribute)
1259                 except:
1260                     noxattr = 1
1261                 attribute = samba.xattr_native.wrap_getxattr(ref,
1262                                                  "system.posix_acl_access")
1263                 samba.xattr_native.wrap_setxattr(tgt,
1264                                                  "system.posix_acl_access",
1265                                                   attribute)
1266             except:
1267                 continue
1268         for name in dirs:
1269             subdir=root[len(dir):]
1270             ref = os.path.join("%s%s" % (refdir, subdir), name)
1271             statsinfo = os.stat(ref)
1272             tgt = os.path.join(root, name)
1273             try:
1274                 os.chown(os.path.join(root, name), statsinfo.st_uid,
1275                           statsinfo.st_gid)
1276                 try:
1277                     attribute = samba.xattr_native.wrap_getxattr(ref,
1278                                                  xattr.XATTR_NTACL_NAME)
1279                     samba.xattr_native.wrap_setxattr(tgt,
1280                                                  xattr.XATTR_NTACL_NAME,
1281                                                  attribute)
1282                 except:
1283                     noxattr = 1
1284                 attribute = samba.xattr_native.wrap_getxattr(ref,
1285                                                  "system.posix_acl_access")
1286                 samba.xattr_native.wrap_setxattr(tgt,
1287                                                  "system.posix_acl_access",
1288                                                   attribute)
1289
1290             except:
1291                 continue
1292
1293
1294 def backup_provision(paths, dir):
1295     """This function backup the provision files so that a rollback
1296     is possible
1297
1298     :param paths: Paths to different objects
1299     :param dir: Directory where to store the backup
1300     """
1301
1302     shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1303     copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1304     shutil.copy2(paths.samdb, dir)
1305     shutil.copy2(paths.secrets, dir)
1306     shutil.copy2(paths.idmapdb, dir)
1307     shutil.copy2(paths.privilege, dir)
1308     if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1309         shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1310     shutil.copy2(paths.smbconf, dir)
1311     shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1312
1313     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1314     if not os.path.isdir(samldbdir):
1315         samldbdir = paths.private_dir
1316         schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1317         configldb = os.path.join(paths.private_dir, "configuration.ldb")
1318         usersldb = os.path.join(paths.private_dir, "users.ldb")
1319         shutil.copy2(schemaldb, dir)
1320         shutil.copy2(usersldb, dir)
1321         shutil.copy2(configldb, dir)
1322     else:
1323         shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1324
1325
1326
1327
1328 def sync_calculated_attributes(samdb, names):
1329    """Synchronize attributes used for constructed ones, with the
1330       old constructed that were stored in the database.
1331
1332       This apply for instance to msds-keyversionnumber that was
1333       stored and that is now constructed from replpropertymetadata.
1334
1335       :param samdb: An LDB object attached to the currently upgraded samdb
1336       :param names: Various key parameter about current provision.
1337    """
1338    listAttrs = ["msDs-KeyVersionNumber"]
1339    hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1340    if hash.has_key("msDs-KeyVersionNumber"):
1341        increment_calculated_keyversion_number(samdb, names.rootdn,
1342                                             hash["msDs-KeyVersionNumber"])
1343
1344 def setup_path(file):
1345     return os.path.join(setup_dir, file)
1346
1347 # Synopsis for updateprovision
1348 # 1) get path related to provision to be update (called current)
1349 # 2) open current provision ldbs
1350 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1351 #    of the DC ....)
1352 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1353 #    by either upgradeprovision or provision
1354 # 5) creation of a new provision the latest version of provision script
1355 #    (called reference)
1356 # 6) get reference provision paths
1357 # 7) open reference provision ldbs
1358 # 8) setup helpers data that will help the update process
1359 # 9) update the privilege ldb by copying the one of referecence provision to
1360 #    the current provision
1361 # 10)get the oemInfo field, this field contains information about the different
1362 #    provision that have been done
1363 # 11)Depending  on whether oemInfo has the string "alpha9" or alphaxx (x as an
1364 #    integer) or none of this the following things are done
1365 #    A) When alpha9 or alphaxx is present
1366 #       The base sam.ldb file is updated by looking at the difference between
1367 #       referrence one and the current one. Everything is copied with the
1368 #       exception of lastProvisionUSN attributes.
1369 #    B) Other case (it reflect that that provision was done before alpha9)
1370 #       The base sam.ldb of the reference provision is copied over
1371 #       the current one, if necessary ldb related to partitions are moved
1372 #       and renamed
1373 # The highest used USN is fetched so that changed by upgradeprovision
1374 # usn can be tracked
1375 # 12)A Schema object is created, it will be used to provide a complete
1376 #    schema to current provision during update (as the schema of the
1377 #    current provision might not be complete and so won't allow some
1378 #    object to be created)
1379 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1380 # 14)The secrets db is updated by pull all the difference from the reference
1381 #    provision into the current provision
1382 # 15)As the previous step has most probably modified the password stored in
1383 #    in secret for the current DC, a new password is generated,
1384 #    the kvno is bumped and the entry in samdb is also updated
1385 # 16)For current provision older than alpha9, we must fix the SD a little bit
1386 #    administrator to update them because SD used to be generated with the
1387 #    system account before alpha9.
1388 # 17)The highest usn modified so far is searched in the database it will be
1389 #    the upper limit for usn modified during provision.
1390 #    This is done before potential SD recalculation because we do not want
1391 #    SD modified during recalculation to be marked as modified during provision
1392 #    (and so possibly remplaced at next upgradeprovision)
1393 # 18)Rebuilt SD if the flag indicate to do so
1394 # 19)Check difference between SD of reference provision and those of the
1395 #    current provision. The check is done by getting the sddl representation
1396 #    of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1397 #    Each part is verified separetly, for dacl and sacl ACL is splited into
1398 #    ACEs and each ACE is verified separately (so that a permutation in ACE
1399 #    didn't raise as an error).
1400 # 20)The oemInfo field is updated to add information about the fact that the
1401 #    provision has been updated by the upgradeprovision version xxx
1402 #    (the version is the one obtained when starting samba with the --version
1403 #    parameter)
1404 # 21)Check if the current provision has all the settings needed for dynamic
1405 #    DNS update to work (that is to say the provision is newer than
1406 #    january 2010). If not dns configuration file from reference provision
1407 #    are copied in a sub folder and the administrator is invited to
1408 #    do what is needed.
1409 # 22)If the lastProvisionUSN attribute was present it is updated to add
1410 #    the range of usns modified by the current upgradeprovision
1411
1412
1413 # About updating the sam DB
1414 # The update takes place in update_partition function
1415 # This function read both current and reference provision and list all
1416 # the available DN of objects
1417 # If the string representation of a DN in reference provision is
1418 # equal to the string representation of a DN in current provision
1419 # (without taking care of case) then the object is flaged as being
1420 # present. If the object is not present in current provision the object
1421 # is being flaged as missing in current provision. Object present in current
1422 # provision but not in reference provision are ignored.
1423 # Once the list of objects present and missing is done, the deleted object
1424 # containers are created in the differents partitions (if missing)
1425 #
1426 # Then the function add_missing_entries is called
1427 # This function will go through the list of missing entries by calling
1428 # add_missing_object for the given object. If this function returns 0
1429 # it means that the object needs some other object in order to be created
1430 # The object is reappended at the end of the list to be created later
1431 # (and preferably after all the needed object have been created)
1432 # The function keeps on looping on the list of object to be created until
1433 # it's empty or that the number of defered creation is equal to the number
1434 # of object that still needs to be created.
1435
1436 # The function add_missing_object will first check if the object can be created.
1437 # That is to say that it didn't depends other not yet created objects
1438 # If requisit can't be fullfilled it exists with 0
1439 # Then it will try to create the missing entry by creating doing
1440 # an ldb_message_diff between the object in the reference provision and
1441 # an empty object.
1442 # This resulting object is filtered to remove all the back link attribute
1443 # (ie. memberOf) as they will be created by the other linked object (ie.
1444 # the one with the member attribute)
1445 # All attributes specified in the hashAttrNotCopied associative array are
1446 # also removed it's most of the time generated attributes
1447
1448 # After missing entries have been added the update_partition function will
1449 # take care of object that exist but that need some update.
1450 # In order to do so the function update_present is called with the list
1451 # of object that are present in both provision and that might need an update.
1452
1453 # This function handle first case mismatch so that the DN in the current
1454 # provision have the same case as in reference provision
1455
1456 # It will then construct an associative array consiting of attributes as
1457 # key and invocationid as value( if the originating invocation id is
1458 # different from the invocation id of the current DC the value is -1 instead).
1459
1460 # If the range of provision modified attributes is present, the function will
1461 # use the replMetadataProperty update method which is the following:
1462 #  Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1463 #   creationTime, msDs-KeyVersionNumber, oEMInformation
1464 #  Check for each attribute if its usn is within one of the modified by
1465 #   provision range and if its originating id is the invocation id of the
1466 #   current DC, then validate the update from reference to current.
1467 #   If not or if there is no replMetatdataProperty for this attribute then we
1468 #   do not update it.
1469 # Otherwise (case the range of provision modified attribute is not present) it
1470 # use the following process:
1471 #  All attributes that need to be added are accepted at the exeption of those
1472 #   listed in hashOverwrittenAtt, in this case the attribute needs to have the
1473 #   correct flags specified.
1474 #  For attributes that need to be modified or removed, a check is performed
1475 #  in OverwrittenAtt, if the attribute is present and the modification flag
1476 #  (remove, delete) is one of those listed for this attribute then modification
1477 #  is accepted. For complicated handling of attribute update, the control is passed
1478 #  to handle_special_case
1479
1480
1481
1482 if __name__ == '__main__':
1483     global defSDmodified
1484     defSDmodified = False
1485     # From here start the big steps of the program
1486     # 1) First get files paths
1487     paths = get_paths(param, smbconf=smbconf)
1488     paths.setup = setup_dir
1489     # Get ldbs with the system session, it is needed for searching
1490     # provision parameters
1491     session = system_session()
1492
1493     # This variable will hold the last provision USN once if it exists.
1494     minUSN = 0
1495     # 2)
1496     ldbs = get_ldbs(paths, creds, session, lp)
1497     backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1498                                     prefix="backupprovision")
1499     backup_provision(paths, backupdir)
1500     try:
1501         ldbs.startTransactions()
1502
1503         # 3) Guess all the needed names (variables in fact) from the current
1504         # provision.
1505         names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1506                                                 paths, smbconf, lp)
1507         # 4)
1508         lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1509         if lastProvisionUSNs is not None:
1510             message(CHANGE,
1511                 "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1512
1513         # Objects will be created with the admin session
1514         # (not anymore system session)
1515         adm_session = admin_session(lp, str(names.domainsid))
1516         # So we reget handle on objects
1517         # ldbs = get_ldbs(paths, creds, adm_session, lp)
1518
1519         if not sanitychecks(ldbs.sam, names):
1520             message(SIMPLE, "Sanity checks for the upgrade fails, checks messages"
1521                             " and correct them before rerunning upgradeprovision")
1522             sys.exit(1)
1523
1524         # Let's see provision parameters
1525         print_provision_key_parameters(names)
1526
1527         # 5) With all this information let's create a fresh new provision used as
1528         # reference
1529         message(SIMPLE, "Creating a reference provision")
1530         provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1531                                         prefix="referenceprovision")
1532         newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1533                         provision_logger)
1534
1535         # TODO
1536         # 6) and 7)
1537         # We need to get a list of object which SD is directly computed from
1538         # defaultSecurityDescriptor.
1539         # This will allow us to know which object we can rebuild the SD in case
1540         # of change of the parent's SD or of the defaultSD.
1541         # Get file paths of this new provision
1542         newpaths = get_paths(param, targetdir=provisiondir)
1543         new_ldbs = get_ldbs(newpaths, creds, session, lp)
1544         new_ldbs.startTransactions()
1545
1546         # 8) Populate some associative array to ease the update process
1547         # List of attribute which are link and backlink
1548         populate_links(new_ldbs.sam, names.schemadn)
1549         # List of attribute with ASN DN synthax)
1550         populate_dnsyntax(new_ldbs.sam, names.schemadn)
1551         # 9)
1552         update_privilege(newpaths.private_dir, paths.private_dir)
1553         # 10)
1554         oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1555         # Do some modification on sam.ldb
1556         ldbs.groupedCommit()
1557         new_ldbs.groupedCommit()
1558
1559         # 11)
1560         if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1561             # 11) A
1562             # Starting from alpha9 we can consider that the structure is quite ok
1563             # and that we should do only dela
1564             delta_update_basesamdb(newpaths.samdb, paths.samdb, creds, session, lp, message)
1565         else:
1566             # 11) B
1567             simple_update_basesamdb(newpaths, paths, names)
1568             ldbs = get_ldbs(paths, creds, session, lp)
1569             removeProvisionUSN(ldbs.sam)
1570
1571         ldbs.startTransactions()
1572         minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1573         new_ldbs.startTransactions()
1574
1575         # 12)
1576         schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
1577                          serverdn=str(names.serverdn))
1578
1579         # 13)
1580         if opts.full:
1581             if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1582                                 schema):
1583                 message(SIMPLE, "Rollbacking every changes. Check the reason"
1584                                 " of the problem")
1585                 message(SIMPLE, "In any case your system as it was before"
1586                                 " the upgrade")
1587                 ldbs.groupedRollback()
1588                 new_ldbs.groupedRollback()
1589                 shutil.rmtree(provisiondir)
1590                 sys.exit(1)
1591             else:
1592                 sync_calculated_attributes(ldbs.sam, names)
1593         # 14)
1594         update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1595         # 15)
1596         message(SIMPLE, "Update machine account")
1597         update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1598
1599         # 16) SD should be created with admin but as some previous acl were so wrong
1600         # that admin can't modify them we have first to recreate them with the good
1601         # form but with system account and then give the ownership to admin ...
1602         if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1603             message(SIMPLE, "Fixing old povision SD")
1604             fix_partition_sd(ldbs.sam, names)
1605             rebuild_sd(ldbs.sam, names)
1606
1607         # We calculate the max USN before recalculating the SD because we might
1608         # touch object that have been modified after a provision and we do not
1609         # want that the next upgradeprovision thinks that it has a green light
1610         # to modify them
1611
1612         # 17)
1613         maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1614
1615         # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1616         # But in fact we should do it also if one object has its SD modified as
1617         # child might need rebuild
1618         if defSDmodified:
1619             message(SIMPLE, "Updating SD")
1620             ldbs.sam.set_session_info(adm_session)
1621             # Alpha10 was a bit broken still
1622             if re.match(r'.*alpha(\d|10)', str(oem)):
1623                 fix_partition_sd(ldbs.sam, names)
1624             rebuild_sd(ldbs.sam, names)
1625
1626         # 19)
1627         # Now we are quite confident in the recalculate process of the SD, we make
1628         # it optional.
1629         # Also the check must be done in a clever way as for the moment we just
1630         # compare SDDL
1631         if opts.debugchangesd:
1632             check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1633
1634         # 20)
1635         updateOEMInfo(ldbs.sam, str(names.rootdn))
1636         # 21)
1637         check_for_DNS(newpaths.private_dir, paths.private_dir)
1638         # 22)
1639         if lastProvisionUSNs is not None:
1640             update_provision_usn(ldbs.sam, minUSN, maxUSN)
1641         if opts.full and (names.policyid is None or names.policyid_dc is None):
1642             update_policyids(names, ldbs.sam)
1643         if opts.full or opts.resetfileacl:
1644             try:
1645                 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1646             except ProvisioningError, e:
1647                 message(ERROR, "The policy for domain controller is missing,"
1648                                " you should restart upgradeprovision with --full")
1649         else:
1650             try:
1651                 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1652             except ProvisioningError, e:
1653                 message(ERROR, "The policy for domain controller is missing,"
1654                                " you should restart upgradeprovision with --full")
1655         ldbs.groupedCommit()
1656         new_ldbs.groupedCommit()
1657         message(SIMPLE, "Upgrade finished !")
1658         # remove reference provision now that everything is done !
1659         shutil.rmtree(provisiondir)
1660     except StandardError, err:
1661         message(ERROR,"A problem has occured when trying to upgrade your provision,"
1662                       " a full backup is located at %s" % backupdir)
1663         if opts.debugall or opts.debugchange:
1664             (typ, val, tb) = sys.exc_info()
1665             traceback.print_exception(typ, val, tb)