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