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