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