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