WHATSNEW: Update changes.
[Samba.git] / source4 / scripting / bin / upgradeprovision
blobdeb50e36fbb26d5ca4f336f0c5583542bd22f652
1 #!/usr/bin/env python
2 # vim: expandtab
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009 - 2010
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
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.
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.
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/>.
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")
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)
63 replace=2**FLAG_MOD_REPLACE
64 add=2**FLAG_MOD_ADD
65 delete=2**FLAG_MOD_DELETE
66 never=0
69 # Will be modified during provision to tell if default sd has been modified
70 # somehow ...
72 #Errors are always logged
74 __docformat__ = "restructuredText"
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 }
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}
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
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, ...")
153 opts = parser.parse_args()[0]
155 handler = logging.StreamHandler(sys.stdout)
156 upgrade_logger = logging.getLogger("upgradeprovision")
157 upgrade_logger.setLevel(logging.INFO)
159 upgrade_logger.addHandler(handler)
161 provision_logger = logging.getLogger("provision")
162 provision_logger.addHandler(handler)
164 whatToLog = define_what_to_log(opts)
166 def message(what, text):
167 """Print a message if this message type has been selected to be printed
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)
174 if len(sys.argv) == 1:
175 opts.interactive = True
176 lp = sambaopts.get_loadparm()
177 smbconf = lp.configfile
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()
187 def check_for_DNS(refprivate, private):
188 """Check if the provision has already the requirement for dynamic dns
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"""
195 spnfile = "%s/spn_update_list" % private
196 namedfile = lp.get("dnsupdate:path")
198 if not namedfile:
199 namedfile = "%s/named.conf.update" % private
201 if not os.path.exists(spnfile):
202 shutil.copy("%s/spn_update_list" % refprivate, "%s" % spnfile)
204 destdir = "%s/new_dns" % private
205 dnsdir = "%s/dns" % private
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)
221 def populate_links(samdb, schemadn):
222 """Populate an array with all the back linked attributes
224 This attributes that are modified automaticaly when
225 front attibutes are changed
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)
235 def populate_dnsyntax(samdb, schemadn):
236 """Populate an array with all the attributes that have DN synthax
237 (oid 2.5.5.1)
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"])
248 def sanitychecks(samdb, names):
249 """Make some checks before trying to update
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
269 def print_provision_key_parameters(names):
270 """Do a a pretty print of provision parameters
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))
292 def handle_special_case(att, delta, new, old, usn, basedn, aldb):
293 """Define more complicate update rules for some attributes
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"""
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
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))
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
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
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
359 if att in ("adminDisplayName", "adminDescription"):
360 return True
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
366 if (str(old[0].dn) == "CN=Title, %s" % (str(names.schemadn)) and
367 att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
368 return True
370 if (str(old[0].dn) == "%s" % (str(names.rootdn))
371 and att == "subRefs" and flag == FLAG_MOD_REPLACE):
372 return True
374 if str(delta.dn).endswith("CN=DisplaySpecifiers, %s" % names.configdn):
375 return True
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))
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
398 return False
400 def dump_denied_change(dn, att, flagtxt, current, reference):
401 """Print detailed information about why a changed is denied
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"""
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]))
427 def handle_special_add(samdb, dn, names):
428 """Handle special operation (like remove) on some object needed during
429 upgrade
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
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
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
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
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
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)
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)
477 return 1
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
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.
495 Hash is indexed by dn to be created, with each key
496 is associated the creation order.
498 First dn to be created has the creation order 0, second has 1, ...
499 Index contain the current creation order
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
517 def add_missing_object(ref_samdb, samdb, dn, names, basedn, hash, index):
518 """Add a new object if the dependencies are satisfied
520 The function add the object if the object on which it depends are already
521 created
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"""
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
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)
575 return True
577 def gen_dn_index_hash(listMissing):
578 """Generate a hash associating the DN to its creation order
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
587 def add_deletedobj_containers(ref_samdb, samdb, names):
588 """Add the object containter: CN=Deleted Objects
590 This function create the container for each partition that need one and
591 then reference the object into the root of the partition
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"""
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])
617 delta.dn = Dn(samdb, str(reference[0]["dn"]))
618 for att in hashAttrNotCopied.keys():
619 delta.remove(att)
620 samdb.add(delta)
622 listwko = []
623 res = samdb.search(expression="(objectClass=*)", base=part,
624 scope=SCOPE_BASE,
625 attrs=["dn", "wellKnownObjects"])
627 targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
628 found = False
630 if len(res[0]) > 0:
631 wko = res[0]["wellKnownObjects"]
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))
639 if not found:
640 listwko.append(targetWKO)
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)
649 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
650 """Add the missing object whose DN is the list
652 The function add the object if the objects on which it depends are
653 already created.
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"""
664 listMissing = []
665 listDefered = list
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")
684 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
685 """This function handle updates on links
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"""
696 res = samdb.search(expression="dn=%s" % dn, base=basedn,
697 controls=["search_options:1:2", "reveal:1"],
698 attrs=[att])
700 blacklist = {}
701 hash = {}
702 newlinklist = []
703 changed = False
705 newlinklist.extend(value)
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
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
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)
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" }
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
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"""
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)
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)
774 if (
775 (str(current[0].dn) != str(reference[0].dn)) and
776 (str(current[0].dn).upper() == str(reference[0].dn).upper())
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"])
786 delta = samdb.msg_diff(current[0], reference[0])
788 for att in hashAttrNotCopied.keys():
789 delta.remove(att)
791 for att in backlinked:
792 delta.remove(att)
794 delta.remove("name")
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
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
815 isFirst = 0
816 txt = ""
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)
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
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
892 else:
893 # Old school way of handling things for pre alpha12 upgrade
894 defSDmodified = True
895 msgElt = delta.get(att)
897 if att == "nTSecurityDescriptor":
898 delta.remove(att)
899 continue
901 if att == "dn":
902 continue
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
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
936 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs):
937 """Check differences between the reference provision and the upgraded one.
939 It looks for all objects which base DN is name.
941 This function will also add the missing object and update existing object
942 to add or remove attributes that were missing.
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"""
954 hash_new = {}
955 hash = {}
956 listMissing = []
957 listPresent = []
958 reference = []
959 current = []
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"])
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"]
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"]
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])
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)
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)
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
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
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
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)
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))
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
1057 :param samdb: An LDB object pointing to the sam of the current provision
1058 :param names: A list of key provision parameters
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"])
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"])
1092 def rebuild_sd(samdb, names):
1093 """Rebuild security descriptor of the current provision from scratch
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.
1101 :param names: List of key provision parameters"""
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"]
1114 listkeys = hash.keys()
1115 listkeys.sort(dn_sort)
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
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)
1150 def remove_stored_generated_attrs(paths, creds, session, lp):
1151 """Remove previously stored constructed attributes
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.
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)
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"""
1173 message(SIMPLE, "Copy samdb")
1174 shutil.copy(newpaths.samdb, paths.samdb)
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")
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)
1199 def update_privilege(ref_private_path, cur_private_path):
1200 """Update the privilege database
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"))
1211 def update_samdb(ref_samdb, samdb, names, highestUSN, schema):
1212 """Upgrade the SAM DB contents for all the provision partitions
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"""
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
1234 def copyxattrs(dir, refdir):
1235 """ Copy owner, groups, extended ACL and NT acls from
1236 a reference dir to a destination dir
1238 Both dir are supposed to hold the same files
1239 :param dir: Destination dir
1240 :param refdir: Reference directory"""
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:
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)
1290 except:
1291 continue
1294 def backup_provision(paths, dir):
1295 """This function backup the provision files so that a rollback
1296 is possible
1298 :param paths: Paths to different objects
1299 :param dir: Directory where to store the backup
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)
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"))
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.
1332 This apply for instance to msds-keyversionnumber that was
1333 stored and that is now constructed from replpropertymetadata.
1335 :param samdb: An LDB object attached to the currently upgraded samdb
1336 :param names: Various key parameter about current provision.
1338 listAttrs = ["msDs-KeyVersionAttribute"]
1339 hash = search_constructed_attrs_stored(samdb, names.rootdn, listAttrs)
1340 increment_calculated_keyversion_number(samdb, names.rootdn, hash)
1342 def setup_path(file):
1343 return os.path.join(setup_dir, file)
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
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)
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.
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
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.
1451 # This function handle first case mismatch so that the DN in the current
1452 # provision have the same case as in reference provision
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).
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
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()
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()
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))
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)
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)
1522 # Let's see provision parameters
1523 print_provision_key_parameters(names)
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)
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()
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()
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)
1569 ldbs.startTransactions()
1570 minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1571 new_ldbs.startTransactions()
1573 # 12)
1574 schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
1575 serverdn=str(names.serverdn))
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)
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)
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
1610 # 17)
1611 maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
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)
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)
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)