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