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/>.
33 # Allow to run from s4 source directory (without installing samba)
34 sys
.path
.insert(0, "bin/python")
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 samba
import tdb_util
44 from ldb
import (SCOPE_SUBTREE
, SCOPE_BASE
,
45 FLAG_MOD_REPLACE
, FLAG_MOD_ADD
, FLAG_MOD_DELETE
,
46 MessageElement
, Message
, Dn
, LdbError
)
47 from samba
import param
, dsdb
, Ldb
48 from samba
.common
import confirm
49 from samba
.provision
.descriptor
import get_wellknown_sds
, get_empty_descriptor
50 from samba
.provision
import (find_provision_key_parameters
,
51 ProvisioningError
, get_last_provision_usn
,
52 get_max_usn
, update_provision_usn
, setup_path
)
53 from samba
.schema
import get_linked_attributes
, Schema
, get_schema_descriptor
54 from samba
.dcerpc
import security
, drsblobs
55 from samba
.dcerpc
.security
import (
56 SECINFO_OWNER
, SECINFO_GROUP
, SECINFO_DACL
, SECINFO_SACL
)
57 from samba
.ndr
import ndr_unpack
58 from samba
.upgradehelpers
import (dn_sort
, get_paths
, newprovision
,
59 get_ldbs
, findprovisionrange
,
60 usn_in_range
, identic_rename
, get_diff_sds
,
61 update_secrets
, CHANGE
, ERROR
, SIMPLE
,
62 CHANGEALL
, GUESS
, CHANGESD
, PROVISION
,
63 updateOEMInfo
, getOEMInfo
, update_gpo
,
64 delta_update_basesamdb
, update_policyids
,
65 update_machine_account_password
,
66 search_constructed_attrs_stored
,
67 int64range2str
, update_dns_account_password
,
68 increment_calculated_keyversion_number
,
69 print_provision_ranges
)
70 from samba
.xattr
import copytree_with_xattrs
72 # make sure the script dies immediately when hitting control-C,
73 # rather than raising KeyboardInterrupt. As we do all database
74 # operations using transactions, this is safe.
76 signal
.signal(signal
.SIGINT
, signal
.SIG_DFL
)
78 replace
=2**FLAG_MOD_REPLACE
80 delete
=2**FLAG_MOD_DELETE
84 # Will be modified during provision to tell if default sd has been modified
87 #Errors are always logged
89 __docformat__
= "restructuredText"
91 # Attributes that are never copied from the reference provision (even if they
92 # do not exist in the destination object).
93 # This is most probably because they are populated automatcally when object is
95 # This also apply to imported object from reference provision
96 replAttrNotCopied
= [ "dn", "whenCreated", "whenChanged", "objectGUID",
97 "parentGUID", "objectCategory", "distinguishedName",
99 "lmPwdHistory", "pwdLastSet", "ntPwdHistory",
100 "unicodePwd", "dBCSPwd", "supplementalCredentials",
101 "gPCUserExtensionNames", "gPCMachineExtensionNames",
102 "maxPwdAge", "secret", "possibleInferiors", "privilege",
103 "sAMAccountType", "oEMInformation", "creationTime" ]
105 nonreplAttrNotCopied
= ["uSNCreated", "replPropertyMetaData", "uSNChanged",
106 "nextRid" ,"rIDNextRID", "rIDPreviousAllocationPool"]
108 nonDSDBAttrNotCopied
= ["msDS-KeyVersionNumber", "priorSecret", "priorWhenChanged"]
111 attrNotCopied
= replAttrNotCopied
112 attrNotCopied
.extend(nonreplAttrNotCopied
)
113 attrNotCopied
.extend(nonDSDBAttrNotCopied
)
114 # Usually for an object that already exists we do not overwrite attributes as
115 # they might have been changed for good reasons. Anyway for a few of them it's
116 # mandatory to replace them otherwise the provision will be broken somehow.
117 # But for attribute that are just missing we do not have to specify them as the default
118 # behavior is to add missing attribute
119 hashOverwrittenAtt
= { "prefixMap": replace
, "systemMayContain": replace
,
120 "systemOnly":replace
, "searchFlags":replace
,
121 "mayContain":replace
, "systemFlags":replace
+add
,
122 "description":replace
, "operatingSystemVersion":replace
,
123 "adminPropertyPages":replace
, "groupType":replace
,
124 "wellKnownObjects":replace
, "privilege":never
,
125 "defaultSecurityDescriptor": replace
,
126 "rIDAvailablePool": never
,
127 "versionNumber" : add
,
128 "rIDNextRID": add
, "rIDUsedPool": never
,
129 "defaultSecurityDescriptor": replace
+ add
,
130 "isMemberOfPartialAttributeSet": delete
,
131 "attributeDisplayNames": replace
+ add
,
132 "versionNumber": add
}
134 dnNotToRecalculateFound
= False
137 forwardlinked
= set()
140 def define_what_to_log(opts
):
144 if opts
.debugchangesd
:
145 what
= what | CHANGESD
148 if opts
.debugprovision
:
149 what
= what | PROVISION
151 what
= what | CHANGEALL
155 parser
= optparse
.OptionParser("provision [options]")
156 sambaopts
= options
.SambaOptions(parser
)
157 parser
.add_option_group(sambaopts
)
158 parser
.add_option_group(options
.VersionOptions(parser
))
159 credopts
= options
.CredentialsOptions(parser
)
160 parser
.add_option_group(credopts
)
161 parser
.add_option("--setupdir", type="string", metavar
="DIR",
162 help="directory with setup files")
163 parser
.add_option("--debugprovision", help="Debug provision", action
="store_true")
164 parser
.add_option("--debugguess", action
="store_true",
165 help="Print information on which values are guessed")
166 parser
.add_option("--debugchange", action
="store_true",
167 help="Print information on what is different but won't be changed")
168 parser
.add_option("--debugchangesd", action
="store_true",
169 help="Print security descriptor differences")
170 parser
.add_option("--debugall", action
="store_true",
171 help="Print all available information (very verbose)")
172 parser
.add_option("--db_backup_only", action
="store_true",
173 help="Do the backup of the database in the provision, skip the sysvol / netlogon shares")
174 parser
.add_option("--full", action
="store_true",
175 help="Perform full upgrade of the samdb (schema, configuration, new objects, ...")
176 parser
.add_option("--very-old-pre-alpha9", action
="store_true",
177 help="Perform additional forced SD resets required for a database from before Samba 4.0.0alpha9.")
179 opts
= parser
.parse_args()[0]
181 handler
= logging
.StreamHandler(sys
.stdout
)
182 upgrade_logger
= logging
.getLogger("upgradeprovision")
183 upgrade_logger
.setLevel(logging
.INFO
)
185 upgrade_logger
.addHandler(handler
)
187 provision_logger
= logging
.getLogger("provision")
188 provision_logger
.addHandler(handler
)
190 whatToLog
= define_what_to_log(opts
)
192 def message(what
, text
):
193 """Print a message if this message type has been selected to be printed
195 :param what: Category of the message
196 :param text: Message to print """
197 if (whatToLog
& what
) or what
<= 0:
198 upgrade_logger
.info("%s", text
)
200 if len(sys
.argv
) == 1:
201 opts
.interactive
= True
202 lp
= sambaopts
.get_loadparm()
203 smbconf
= lp
.configfile
205 creds
= credopts
.get_credentials(lp
)
206 creds
.set_kerberos_state(DONT_USE_KERBEROS
)
210 def check_for_DNS(refprivate
, private
, dns_backend
):
211 """Check if the provision has already the requirement for dynamic dns
213 :param refprivate: The path to the private directory of the reference
215 :param private: The path to the private directory of the upgraded
218 spnfile
= "%s/spn_update_list" % private
219 dnsfile
= "%s/dns_update_list" % private
221 if not os
.path
.exists(spnfile
):
222 shutil
.copy("%s/spn_update_list" % refprivate
, "%s" % spnfile
)
224 if not os
.path
.exists(dnsfile
):
225 shutil
.copy("%s/dns_update_list" % refprivate
, "%s" % dnsfile
)
227 if dns_backend
not in ['BIND9_DLZ', 'BIND9_FLATFILE']:
230 namedfile
= lp
.get("dnsupdate:path")
232 namedfile
= "%s/named.conf.update" % private
233 if not os
.path
.exists(namedfile
):
234 destdir
= "%s/new_dns" % private
235 dnsdir
= "%s/dns" % private
237 if not os
.path
.exists(destdir
):
239 if not os
.path
.exists(dnsdir
):
241 shutil
.copy("%s/named.conf" % refprivate
, "%s/named.conf" % destdir
)
242 shutil
.copy("%s/named.txt" % refprivate
, "%s/named.txt" % destdir
)
243 message(SIMPLE
, "It seems that your provision did not integrate "
244 "new rules for dynamic dns update of domain related entries")
245 message(SIMPLE
, "A copy of the new bind configuration files and "
246 "template has been put in %s, you should read them and "
247 "configure dynamic dns updates" % destdir
)
250 def populate_links(samdb
, schemadn
):
251 """Populate an array with all the back linked attributes
253 This attributes that are modified automaticaly when
254 front attibutes are changed
256 :param samdb: A LDB object for sam.ldb file
257 :param schemadn: DN of the schema for the partition"""
258 linkedAttHash
= get_linked_attributes(Dn(samdb
, str(schemadn
)), samdb
)
259 backlinked
.extend(linkedAttHash
.values())
260 for t
in linkedAttHash
.keys():
263 def isReplicated(att
):
264 """ Indicate if the attribute is replicated or not
266 :param att: Name of the attribute to be tested
267 :return: True is the attribute is replicated, False otherwise
270 return (att
not in not_replicated
)
272 def populateNotReplicated(samdb
, schemadn
):
273 """Populate an array with all the attributes that are not replicated
275 :param samdb: A LDB object for sam.ldb file
276 :param schemadn: DN of the schema for the partition"""
277 res
= samdb
.search(expression
="(&(objectclass=attributeSchema)(systemflags:1.2.840.113556.1.4.803:=1))", base
=Dn(samdb
,
278 str(schemadn
)), scope
=SCOPE_SUBTREE
,
279 attrs
=["lDAPDisplayName"])
281 not_replicated
.append(str(elem
["lDAPDisplayName"]))
284 def populate_dnsyntax(samdb
, schemadn
):
285 """Populate an array with all the attributes that have DN synthax
288 :param samdb: A LDB object for sam.ldb file
289 :param schemadn: DN of the schema for the partition"""
290 res
= samdb
.search(expression
="(attributeSyntax=2.5.5.1)", base
=Dn(samdb
,
291 str(schemadn
)), scope
=SCOPE_SUBTREE
,
292 attrs
=["lDAPDisplayName"])
294 dn_syntax_att
.append(elem
["lDAPDisplayName"])
297 def sanitychecks(samdb
, names
):
298 """Make some checks before trying to update
300 :param samdb: An LDB object opened on sam.ldb
301 :param names: list of key provision parameters
302 :return: Status of check (1 for Ok, 0 for not Ok) """
303 res
= samdb
.search(expression
="objectClass=ntdsdsa", base
=str(names
.configdn
),
304 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
305 controls
=["search_options:1:2"])
307 print "No DC found. Your provision is most probably broken!"
310 print "Found %d domain controllers. For the moment " \
311 "upgradeprovision is not able to handle an upgrade on a " \
312 "domain with more than one DC. Please demote the other " \
313 "DC(s) before upgrading" % len(res
)
319 def print_provision_key_parameters(names
):
320 """Do a a pretty print of provision parameters
322 :param names: list of key provision parameters """
323 message(GUESS
, "rootdn :" + str(names
.rootdn
))
324 message(GUESS
, "configdn :" + str(names
.configdn
))
325 message(GUESS
, "schemadn :" + str(names
.schemadn
))
326 message(GUESS
, "serverdn :" + str(names
.serverdn
))
327 message(GUESS
, "netbiosname :" + names
.netbiosname
)
328 message(GUESS
, "defaultsite :" + names
.sitename
)
329 message(GUESS
, "dnsdomain :" + names
.dnsdomain
)
330 message(GUESS
, "hostname :" + names
.hostname
)
331 message(GUESS
, "domain :" + names
.domain
)
332 message(GUESS
, "realm :" + names
.realm
)
333 message(GUESS
, "invocationid:" + names
.invocation
)
334 message(GUESS
, "policyguid :" + names
.policyid
)
335 message(GUESS
, "policyguiddc:" + str(names
.policyid_dc
))
336 message(GUESS
, "domainsid :" + str(names
.domainsid
))
337 message(GUESS
, "domainguid :" + names
.domainguid
)
338 message(GUESS
, "ntdsguid :" + names
.ntdsguid
)
339 message(GUESS
, "domainlevel :" + str(names
.domainlevel
))
342 def handle_special_case(att
, delta
, new
, old
, useReplMetadata
, basedn
, aldb
):
343 """Define more complicate update rules for some attributes
345 :param att: The attribute to be updated
346 :param delta: A messageElement object that correspond to the difference
347 between the updated object and the reference one
348 :param new: The reference object
349 :param old: The Updated object
350 :param useReplMetadata: A boolean that indicate if the update process
351 use replPropertyMetaData to decide what has to be updated.
352 :param basedn: The base DN of the provision
353 :param aldb: An ldb object used to build DN
354 :return: True to indicate that the attribute should be kept, False for
357 # We do most of the special case handle if we do not have the
358 # highest usn as otherwise the replPropertyMetaData will guide us more
360 if not useReplMetadata
:
361 flag
= delta
.get(att
).flags()
362 if (att
== "sPNMappings" and flag
== FLAG_MOD_REPLACE
and
363 ldb
.Dn(aldb
, "CN=Directory Service,CN=Windows NT,"
364 "CN=Services,CN=Configuration,%s" % basedn
)
367 if (att
== "userAccountControl" and flag
== FLAG_MOD_REPLACE
and
368 ldb
.Dn(aldb
, "CN=Administrator,CN=Users,%s" % basedn
)
370 message(SIMPLE
, "We suggest that you change the userAccountControl"
371 " for user Administrator from value %d to %d" %
372 (int(str(old
[0][att
])), int(str(new
[0][att
]))))
374 if (att
== "minPwdAge" and flag
== FLAG_MOD_REPLACE
):
375 if (long(str(old
[0][att
])) == 0):
376 delta
[att
] = MessageElement(new
[0][att
], FLAG_MOD_REPLACE
, att
)
379 if (att
== "member" and flag
== FLAG_MOD_REPLACE
):
383 for elem
in old
[0][att
]:
384 hash[str(elem
).lower()]=1
385 newval
.append(str(elem
))
387 for elem
in new
[0][att
]:
388 if not hash.has_key(str(elem
).lower()):
390 newval
.append(str(elem
))
392 delta
[att
] = MessageElement(newval
, FLAG_MOD_REPLACE
, att
)
397 if (att
in ("gPLink", "gPCFileSysPath") and
398 flag
== FLAG_MOD_REPLACE
and
399 str(new
[0].dn
).lower() == str(old
[0].dn
).lower()):
403 if att
== "forceLogoff":
404 ref
=0x8000000000000000
405 oldval
=int(old
[0][att
][0])
406 newval
=int(new
[0][att
][0])
407 ref
== old
and ref
== abs(new
)
410 if att
in ("adminDisplayName", "adminDescription"):
413 if (str(old
[0].dn
) == "CN=Samba4-Local-Domain, %s" % (names
.schemadn
)
414 and att
== "defaultObjectCategory" and flag
== FLAG_MOD_REPLACE
):
417 if (str(old
[0].dn
) == "CN=Title, %s" % (str(names
.schemadn
)) and
418 att
== "rangeUpper" and flag
== FLAG_MOD_REPLACE
):
421 if (str(old
[0].dn
) == "%s" % (str(names
.rootdn
))
422 and att
== "subRefs" and flag
== FLAG_MOD_REPLACE
):
424 #Allow to change revision of ForestUpdates objects
425 if (att
== "revision" or att
== "objectVersion"):
426 if str(delta
.dn
).lower().find("domainupdates") and str(delta
.dn
).lower().find("forestupdates") > 0:
428 if str(delta
.dn
).endswith("CN=DisplaySpecifiers, %s" % names
.configdn
):
431 # This is a bit of special animal as we might have added
432 # already SPN entries to the list that has to be modified
433 # So we go in detail to try to find out what has to be added ...
434 if (att
== "servicePrincipalName" and delta
.get(att
).flags() == FLAG_MOD_REPLACE
):
438 for elem
in old
[0][att
]:
440 newval
.append(str(elem
))
442 for elem
in new
[0][att
]:
443 if not hash.has_key(str(elem
)):
445 newval
.append(str(elem
))
447 delta
[att
] = MessageElement(newval
, FLAG_MOD_REPLACE
, att
)
454 def dump_denied_change(dn
, att
, flagtxt
, current
, reference
):
455 """Print detailed information about why a change is denied
457 :param dn: DN of the object which attribute is denied
458 :param att: Attribute that was supposed to be upgraded
459 :param flagtxt: Type of the update that should be performed
460 (add, change, remove, ...)
461 :param current: Value(s) of the current attribute
462 :param reference: Value(s) of the reference attribute"""
464 message(CHANGE
, "dn= " + str(dn
)+" " + att
+" with flag " + flagtxt
465 + " must not be changed/removed. Discarding the change")
466 if att
== "objectSid" :
467 message(CHANGE
, "old : %s" % ndr_unpack(security
.dom_sid
, current
[0]))
468 message(CHANGE
, "new : %s" % ndr_unpack(security
.dom_sid
, reference
[0]))
469 elif att
== "rIDPreviousAllocationPool" or att
== "rIDAllocationPool":
470 message(CHANGE
, "old : %s" % int64range2str(current
[0]))
471 message(CHANGE
, "new : %s" % int64range2str(reference
[0]))
474 for e
in range(0, len(current
)):
475 message(CHANGE
, "old %d : %s" % (i
, str(current
[e
])))
477 if reference
is not None:
479 for e
in range(0, len(reference
)):
480 message(CHANGE
, "new %d : %s" % (i
, str(reference
[e
])))
483 def handle_special_add(samdb
, dn
, names
):
484 """Handle special operation (like remove) on some object needed during
487 This is mostly due to wrong creation of the object in previous provision.
488 :param samdb: An Ldb object representing the SAM database
489 :param dn: DN of the object to inspect
490 :param names: list of key provision parameters
494 objDn
= Dn(samdb
, "CN=IIS_IUSRS, CN=Builtin, %s" % names
.rootdn
)
496 #This entry was misplaced lets remove it if it exists
497 dntoremove
= "CN=IIS_IUSRS, CN=Users, %s" % names
.rootdn
500 "CN=Certificate Service DCOM Access, CN=Builtin, %s" % names
.rootdn
)
502 #This entry was misplaced lets remove it if it exists
503 dntoremove
= "CN=Certificate Service DCOM Access,"\
504 "CN=Users, %s" % names
.rootdn
506 objDn
= Dn(samdb
, "CN=Cryptographic Operators, CN=Builtin, %s" % names
.rootdn
)
508 #This entry was misplaced lets remove it if it exists
509 dntoremove
= "CN=Cryptographic Operators, CN=Users, %s" % names
.rootdn
511 objDn
= Dn(samdb
, "CN=Event Log Readers, CN=Builtin, %s" % names
.rootdn
)
513 #This entry was misplaced lets remove it if it exists
514 dntoremove
= "CN=Event Log Readers, CN=Users, %s" % names
.rootdn
516 objDn
= Dn(samdb
,"CN=System,CN=WellKnown Security Principals,"
517 "CN=Configuration,%s" % names
.rootdn
)
519 oldDn
= Dn(samdb
,"CN=Well-Known-Security-Id-System,"
520 "CN=WellKnown Security Principals,"
521 "CN=Configuration,%s" % names
.rootdn
)
523 res
= samdb
.search(expression
="(distinguishedName=%s)" % oldDn
,
524 base
=str(names
.rootdn
),
525 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
526 controls
=["search_options:1:2"])
528 res2
= samdb
.search(expression
="(distinguishedName=%s)" % dn
,
529 base
=str(names
.rootdn
),
530 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
531 controls
=["search_options:1:2"])
533 if len(res
) > 0 and len(res2
) == 0:
534 message(CHANGE
, "Existing object %s must be replaced by %s. "
535 "Renaming old object" % (str(oldDn
), str(dn
)))
536 samdb
.rename(oldDn
, objDn
, ["relax:0", "provision:0"])
540 if dntoremove
is not None:
541 res
= samdb
.search(expression
="(cn=RID Set)",
542 base
=str(names
.rootdn
),
543 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
544 controls
=["search_options:1:2"])
548 res
= samdb
.search(expression
="(distinguishedName=%s)" % dntoremove
,
549 base
=str(names
.rootdn
),
550 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
551 controls
=["search_options:1:2"])
553 message(CHANGE
, "Existing object %s must be replaced by %s. "
554 "Removing old object" % (dntoremove
, str(dn
)))
555 samdb
.delete(res
[0]["dn"])
561 def check_dn_nottobecreated(hash, index
, listdn
):
562 """Check if one of the DN present in the list has a creation order
563 greater than the current.
565 Hash is indexed by dn to be created, with each key
566 is associated the creation order.
568 First dn to be created has the creation order 0, second has 1, ...
569 Index contain the current creation order
571 :param hash: Hash holding the different DN of the object to be
573 :param index: Current creation order
574 :param listdn: List of DNs on which the current DN depends on
575 :return: None if the current object do not depend on other
576 object or if all object have been created before."""
580 key
= str(dn
).lower()
581 if hash.has_key(key
) and hash[key
] > index
:
587 def add_missing_object(ref_samdb
, samdb
, dn
, names
, basedn
, hash, index
):
588 """Add a new object if the dependencies are satisfied
590 The function add the object if the object on which it depends are already
593 :param ref_samdb: Ldb object representing the SAM db of the reference
595 :param samdb: Ldb object representing the SAM db of the upgraded
597 :param dn: DN of the object to be added
598 :param names: List of key provision parameters
599 :param basedn: DN of the partition to be updated
600 :param hash: Hash holding the different DN of the object to be
602 :param index: Current creation order
603 :return: True if the object was created False otherwise"""
605 ret
= handle_special_add(samdb
, dn
, names
)
614 reference
= ref_samdb
.search(expression
="(distinguishedName=%s)" % (str(dn
)),
615 base
=basedn
, scope
=SCOPE_SUBTREE
,
616 controls
=["search_options:1:2"])
618 delta
= samdb
.msg_diff(empty
, reference
[0])
622 if str(reference
[0].get("cn")) == "RID Set":
623 for klass
in reference
[0].get("objectClass"):
624 if str(klass
).lower() == "ridset":
627 if delta
.get("objectSid"):
628 sid
= str(ndr_unpack(security
.dom_sid
, str(reference
[0]["objectSid"])))
629 m
= re
.match(r
".*-(\d+)$", sid
)
630 if m
and int(m
.group(1))>999:
631 delta
.remove("objectSid")
632 for att
in attrNotCopied
:
634 for att
in backlinked
:
636 depend_on_yettobecreated
= None
637 for att
in dn_syntax_att
:
638 depend_on_yet_tobecreated
= check_dn_nottobecreated(hash, index
,
640 if depend_on_yet_tobecreated
is not None:
641 message(CHANGE
, "Object %s depends on %s in attribute %s. "
642 "Delaying the creation" % (dn
,
643 depend_on_yet_tobecreated
, att
))
648 message(CHANGE
,"Object %s will be added" % dn
)
649 samdb
.add(delta
, ["relax:0", "provision:0"])
651 message(CHANGE
,"Object %s was skipped" % dn
)
655 def gen_dn_index_hash(listMissing
):
656 """Generate a hash associating the DN to its creation order
658 :param listMissing: List of DN
659 :return: Hash with DN as keys and creation order as values"""
661 for i
in range(0, len(listMissing
)):
662 hash[str(listMissing
[i
]).lower()] = i
665 def add_deletedobj_containers(ref_samdb
, samdb
, names
):
666 """Add the object containter: CN=Deleted Objects
668 This function create the container for each partition that need one and
669 then reference the object into the root of the partition
671 :param ref_samdb: Ldb object representing the SAM db of the reference
673 :param samdb: Ldb object representing the SAM db of the upgraded provision
674 :param names: List of key provision parameters"""
677 wkoPrefix
= "B:32:18E2EA80684F11D2B9AA00C04F79F805"
678 partitions
= [str(names
.rootdn
), str(names
.configdn
)]
679 for part
in partitions
:
680 ref_delObjCnt
= ref_samdb
.search(expression
="(cn=Deleted Objects)",
681 base
=part
, scope
=SCOPE_SUBTREE
,
683 controls
=["show_deleted:0",
685 delObjCnt
= samdb
.search(expression
="(cn=Deleted Objects)",
686 base
=part
, scope
=SCOPE_SUBTREE
,
688 controls
=["show_deleted:0",
690 if len(ref_delObjCnt
) > len(delObjCnt
):
691 reference
= ref_samdb
.search(expression
="cn=Deleted Objects",
692 base
=part
, scope
=SCOPE_SUBTREE
,
693 controls
=["show_deleted:0",
696 delta
= samdb
.msg_diff(empty
, reference
[0])
698 delta
.dn
= Dn(samdb
, str(reference
[0]["dn"]))
699 for att
in attrNotCopied
:
702 modcontrols
= ["relax:0", "provision:0"]
703 samdb
.add(delta
, modcontrols
)
706 res
= samdb
.search(expression
="(objectClass=*)", base
=part
,
708 attrs
=["dn", "wellKnownObjects"])
710 targetWKO
= "%s:%s" % (wkoPrefix
, str(reference
[0]["dn"]))
714 wko
= res
[0]["wellKnownObjects"]
716 # The wellKnownObject that we want to add.
718 if str(o
) == targetWKO
:
720 listwko
.append(str(o
))
723 listwko
.append(targetWKO
)
726 delta
.dn
= Dn(samdb
, str(res
[0]["dn"]))
727 delta
["wellKnownObjects"] = MessageElement(listwko
,
732 def add_missing_entries(ref_samdb
, samdb
, names
, basedn
, list):
733 """Add the missing object whose DN is the list
735 The function add the object if the objects on which it depends are
738 :param ref_samdb: Ldb object representing the SAM db of the reference
740 :param samdb: Ldb object representing the SAM db of the upgraded
742 :param dn: DN of the object to be added
743 :param names: List of key provision parameters
744 :param basedn: DN of the partition to be updated
745 :param list: List of DN to be added in the upgraded provision"""
750 while(len(listDefered
) != len(listMissing
) and len(listDefered
) > 0):
752 listMissing
= listDefered
754 hashMissing
= gen_dn_index_hash(listMissing
)
755 for dn
in listMissing
:
756 ret
= add_missing_object(ref_samdb
, samdb
, dn
, names
, basedn
,
760 # DN can't be created because it depends on some
761 # other DN in the list
762 listDefered
.append(dn
)
764 if len(listDefered
) != 0:
765 raise ProvisioningError("Unable to insert missing elements: "
766 "circular references")
768 def handle_links(samdb
, att
, basedn
, dn
, value
, ref_value
, delta
):
769 """This function handle updates on links
771 :param samdb: An LDB object pointing to the updated provision
772 :param att: Attribute to update
773 :param basedn: The root DN of the provision
774 :param dn: The DN of the inspected object
775 :param value: The value of the attribute
776 :param ref_value: The value of this attribute in the reference provision
777 :param delta: The MessageElement object that will be applied for
778 transforming the current provision"""
780 res
= samdb
.search(base
=dn
, controls
=["search_options:1:2", "reveal:1"],
789 newlinklist
.append(str(v
))
793 # for w2k domain level the reveal won't reveal anything ...
794 # it means that we can readd links that were removed on purpose ...
795 # Also this function in fact just accept add not removal
797 for e
in res
[0][att
]:
798 if not hash.has_key(e
):
799 # We put in the blacklist all the element that are in the "revealed"
800 # result and not in the "standard" result
801 # This element are links that were removed before and so that
802 # we don't wan't to readd
806 if not blacklist
.has_key(e
) and not hash.has_key(e
):
807 newlinklist
.append(str(e
))
810 delta
[att
] = MessageElement(newlinklist
, FLAG_MOD_REPLACE
, att
)
817 def checkKeepAttributeWithMetadata(delta
, att
, message
, reference
, current
,
818 hash_attr_usn
, basedn
, usns
, samdb
):
819 """ Check if we should keep the attribute modification or not
821 :param delta: A message diff object
822 :param att: An attribute
823 :param message: A function to print messages
824 :param reference: A message object for the current entry comming from
825 the reference provision.
826 :param current: A message object for the current entry commin from
827 the current provision.
828 :param hash_attr_usn: A dictionnary with attribute name as keys,
829 USN and invocation id as values.
830 :param basedn: The DN of the partition
831 :param usns: A dictionnary with invocation ID as keys and USN ranges
833 :param samdb: A ldb object pointing to the sam DB
835 :return: The modified message diff.
842 for att
in list(delta
):
843 if att
in ["dn", "objectSid"]:
847 # We have updated by provision usn information so let's exploit
848 # replMetadataProperties
849 if att
in forwardlinked
:
850 curval
= current
[0].get(att
, ())
851 refval
= reference
[0].get(att
, ())
852 delta
= handle_links(samdb
, att
, basedn
, current
[0]["dn"],
853 curval
, refval
, delta
)
857 if isFirst
and len(list(delta
)) > 1:
859 txt
= "%s\n" % (str(dn
))
861 if handle_special_case(att
, delta
, reference
, current
, True, None, None):
862 # This attribute is "complicated" to handle and handling
863 # was done in handle_special_case
867 if hash_attr_usn
.get(att
):
868 [attrUSN
, attInvId
] = hash_attr_usn
.get(att
)
871 # If it's a replicated attribute and we don't have any USN
872 # information about it. It means that we never saw it before
874 # If it is a replicated attribute but we are not master on it
875 # (ie. not initially added in the provision we masterize).
877 if isReplicated(att
):
880 message(CHANGE
, "Non replicated attribute %s changed" % att
)
883 if att
== "nTSecurityDescriptor":
884 cursd
= ndr_unpack(security
.descriptor
,
885 str(current
[0]["nTSecurityDescriptor"]))
886 refsd
= ndr_unpack(security
.descriptor
,
887 str(reference
[0]["nTSecurityDescriptor"]))
889 diff
= get_diff_sds(refsd
, cursd
, names
.domainsid
)
891 # FIXME find a way to have it only with huge huge verbose mode
892 # message(CHANGE, "%ssd are identical" % txt)
898 message(CHANGESD
, "%ssd are not identical:\n%s" % (txt
, diff
))
901 message(CHANGESD
, "But the SD has been changed by someonelse "
902 "so it's impossible to know if the difference"
903 " cames from the modification or from a previous bug")
904 dnNotToRecalculateFound
= True
906 dnToRecalculate
.append(dn
)
910 # This attribute was last modified by another DC forget
912 message(CHANGE
, "%sAttribute: %s has been "
913 "created/modified/deleted by another DC. "
914 "Doing nothing" % (txt
, att
))
918 elif not usn_in_range(int(attrUSN
), usns
.get(attInvId
)):
919 message(CHANGE
, "%sAttribute: %s was not "
920 "created/modified/deleted during a "
921 "provision or upgradeprovision. Current "
922 "usn: %d. Doing nothing" % (txt
, att
,
928 if att
== "defaultSecurityDescriptor":
931 message(CHANGE
, "%sAttribute: %s will be modified"
932 "/deleted it was last modified "
933 "during a provision. Current usn: "
934 "%d" % (txt
, att
, attrUSN
))
937 message(CHANGE
, "%sAttribute: %s will be added because "
938 "it did not exist before" % (txt
, att
))
944 def update_present(ref_samdb
, samdb
, basedn
, listPresent
, usns
):
945 """ This function updates the object that are already present in the
948 :param ref_samdb: An LDB object pointing to the reference provision
949 :param samdb: An LDB object pointing to the updated provision
950 :param basedn: A string with the value of the base DN for the provision
952 :param listPresent: A list of object that is present in the provision
953 :param usns: A list of USN range modified by previous provision and
954 upgradeprovision grouped by invocation ID
957 # This hash is meant to speedup lookup of attribute name from an oid,
958 # it's for the replPropertyMetaData handling
960 res
= samdb
.search(expression
="objectClass=attributeSchema", base
=basedn
,
961 controls
=["search_options:1:2"], attrs
=["attributeID",
965 strDisplay
= str(e
.get("lDAPDisplayName"))
966 hash_oid_name
[str(e
.get("attributeID"))] = strDisplay
968 msg
= "Unable to insert missing elements: circular references"
969 raise ProvisioningError(msg
)
972 sd_flags
= SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
973 controls
= ["search_options:1:2", "sd_flags:1:%d" % sd_flags
]
974 message(CHANGE
, "Using replPropertyMetadata for change selection")
975 for dn
in listPresent
:
976 reference
= ref_samdb
.search(expression
="(distinguishedName=%s)" % (str(dn
)), base
=basedn
,
979 current
= samdb
.search(expression
="(distinguishedName=%s)" % (str(dn
)), base
=basedn
,
980 scope
=SCOPE_SUBTREE
, controls
=controls
)
983 (str(current
[0].dn
) != str(reference
[0].dn
)) and
984 (str(current
[0].dn
).upper() == str(reference
[0].dn
).upper())
986 message(CHANGE
, "Names are the same except for the case. "
987 "Renaming %s to %s" % (str(current
[0].dn
),
988 str(reference
[0].dn
)))
989 identic_rename(samdb
, reference
[0].dn
)
990 current
= samdb
.search(expression
="(distinguishedName=%s)" % (str(dn
)), base
=basedn
,
994 delta
= samdb
.msg_diff(current
[0], reference
[0])
996 for att
in backlinked
:
999 for att
in attrNotCopied
:
1002 delta
.remove("name")
1004 nb_items
= len(list(delta
))
1010 # Fetch the replPropertyMetaData
1011 res
= samdb
.search(expression
="(distinguishedName=%s)" % (str(dn
)), base
=basedn
,
1012 scope
=SCOPE_SUBTREE
, controls
=controls
,
1013 attrs
=["replPropertyMetaData"])
1014 ctr
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1015 str(res
[0]["replPropertyMetaData"])).ctr
1019 # We put in this hash only modification
1020 # made on the current host
1021 att
= hash_oid_name
[samdb
.get_oid_from_attid(o
.attid
)]
1022 if str(o
.originating_invocation_id
) in usns
.keys():
1023 hash_attr_usn
[att
] = [o
.originating_usn
, str(o
.originating_invocation_id
)]
1025 hash_attr_usn
[att
] = [-1, None]
1027 delta
= checkKeepAttributeWithMetadata(delta
, att
, message
, reference
,
1028 current
, hash_attr_usn
,
1029 basedn
, usns
, samdb
)
1035 # Skip dn as the value is not really changed ...
1036 attributes
=", ".join(delta
.keys()[1:])
1038 relaxedatt
= ['iscriticalsystemobject', 'grouptype']
1039 # Let's try to reduce as much as possible the use of relax control
1040 for attr
in delta
.keys():
1041 if attr
.lower() in relaxedatt
:
1042 modcontrols
= ["relax:0", "provision:0"]
1043 message(CHANGE
, "%s is different from the reference one, changed"
1044 " attributes: %s\n" % (dn
, attributes
))
1046 samdb
.modify(delta
, modcontrols
)
1049 def reload_full_schema(samdb
, names
):
1050 """Load the updated schema with all the new and existing classes
1053 :param samdb: An LDB object connected to the sam.ldb of the update
1055 :param names: List of key provision parameters
1058 schemadn
= str(names
.schemadn
)
1059 current
= samdb
.search(expression
="objectClass=*", base
=schemadn
,
1060 scope
=SCOPE_SUBTREE
)
1065 schema_ldif
+= samdb
.write_ldif(ent
, ldb
.CHANGETYPE_NONE
)
1067 prefixmap_data
= open(setup_path("prefixMap.txt"), 'r').read()
1068 prefixmap_data
= b64encode(prefixmap_data
)
1070 # We don't actually add this ldif, just parse it
1071 prefixmap_ldif
= "dn: %s\nprefixMap:: %s\n\n" % (schemadn
, prefixmap_data
)
1073 dsdb
._dsdb
_set
_schema
_from
_ldif
(samdb
, prefixmap_ldif
, schema_ldif
, schemadn
)
1076 def update_partition(ref_samdb
, samdb
, basedn
, names
, schema
, provisionUSNs
, prereloadfunc
):
1077 """Check differences between the reference provision and the upgraded one.
1079 It looks for all objects which base DN is name.
1081 This function will also add the missing object and update existing object
1082 to add or remove attributes that were missing.
1084 :param ref_sambdb: An LDB object conntected to the sam.ldb of the
1086 :param samdb: An LDB object connected to the sam.ldb of the update
1088 :param basedn: String value of the DN of the partition
1089 :param names: List of key provision parameters
1090 :param schema: A Schema object
1091 :param provisionUSNs: A dictionnary with range of USN modified during provision
1092 or upgradeprovision. Ranges are grouped by invocationID.
1093 :param prereloadfunc: A function that must be executed just before the reload
1104 # Connect to the reference provision and get all the attribute in the
1105 # partition referred by name
1106 reference
= ref_samdb
.search(expression
="objectClass=*", base
=basedn
,
1107 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
1108 controls
=["search_options:1:2"])
1110 current
= samdb
.search(expression
="objectClass=*", base
=basedn
,
1111 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
1112 controls
=["search_options:1:2"])
1113 # Create a hash for speeding the search of new object
1114 for i
in range(0, len(reference
)):
1115 hash_new
[str(reference
[i
]["dn"]).lower()] = reference
[i
]["dn"]
1117 # Create a hash for speeding the search of existing object in the
1119 for i
in range(0, len(current
)):
1120 hash[str(current
[i
]["dn"]).lower()] = current
[i
]["dn"]
1123 for k
in hash_new
.keys():
1124 if not hash.has_key(k
):
1125 if not str(hash_new
[k
]) == "CN=Deleted Objects, %s" % names
.rootdn
:
1126 listMissing
.append(hash_new
[k
])
1128 listPresent
.append(hash_new
[k
])
1130 # Sort the missing object in order to have object of the lowest level
1131 # first (which can be containers for higher level objects)
1132 listMissing
.sort(dn_sort
)
1133 listPresent
.sort(dn_sort
)
1135 # The following lines is to load the up to
1136 # date schema into our current LDB
1137 # a complete schema is needed as the insertion of attributes
1138 # and class is done against it
1139 # and the schema is self validated
1140 samdb
.set_schema(schema
)
1142 message(SIMPLE
, "There are %d missing objects" % (len(listMissing
)))
1143 add_deletedobj_containers(ref_samdb
, samdb
, names
)
1145 add_missing_entries(ref_samdb
, samdb
, names
, basedn
, listMissing
)
1148 message(SIMPLE
, "Reloading a merged schema, which might trigger "
1149 "reindexing so please be patient")
1150 reload_full_schema(samdb
, names
)
1151 message(SIMPLE
, "Schema reloaded!")
1153 changed
= update_present(ref_samdb
, samdb
, basedn
, listPresent
,
1155 message(SIMPLE
, "There are %d changed objects" % (changed
))
1158 except StandardError, err
:
1159 message(ERROR
, "Exception during upgrade of samdb:")
1160 (typ
, val
, tb
) = sys
.exc_info()
1161 traceback
.print_exception(typ
, val
, tb
)
1165 def check_updated_sd(ref_sam
, cur_sam
, names
):
1166 """Check if the security descriptor in the upgraded provision are the same
1169 :param ref_sam: A LDB object connected to the sam.ldb file used as
1170 the reference provision
1171 :param cur_sam: A LDB object connected to the sam.ldb file used as
1173 :param names: List of key provision parameters"""
1174 reference
= ref_sam
.search(expression
="objectClass=*", base
=str(names
.rootdn
),
1175 scope
=SCOPE_SUBTREE
,
1176 attrs
=["dn", "nTSecurityDescriptor"],
1177 controls
=["search_options:1:2"])
1178 current
= cur_sam
.search(expression
="objectClass=*", base
=str(names
.rootdn
),
1179 scope
=SCOPE_SUBTREE
,
1180 attrs
=["dn", "nTSecurityDescriptor"],
1181 controls
=["search_options:1:2"])
1183 for i
in range(0, len(reference
)):
1184 refsd_blob
= str(reference
[i
]["nTSecurityDescriptor"])
1185 hash[str(reference
[i
]["dn"]).lower()] = refsd_blob
1188 for i
in range(0, len(current
)):
1189 key
= str(current
[i
]["dn"]).lower()
1190 if hash.has_key(key
):
1191 cursd_blob
= str(current
[i
]["nTSecurityDescriptor"])
1192 cursd
= ndr_unpack(security
.descriptor
,
1194 if cursd_blob
!= hash[key
]:
1195 refsd
= ndr_unpack(security
.descriptor
,
1197 txt
= get_diff_sds(refsd
, cursd
, names
.domainsid
, False)
1199 message(CHANGESD
, "On object %s ACL is different"
1200 " \n%s" % (current
[i
]["dn"], txt
))
1204 def fix_wellknown_sd(samdb
, names
):
1205 """This function fix the SD for partition/wellknown containers (basedn, configdn, ...)
1206 This is needed because some provision use to have broken SD on containers
1208 :param samdb: An LDB object pointing to the sam of the current provision
1209 :param names: A list of key provision parameters
1212 list_wellknown_dns
= []
1214 subcontainers
= get_wellknown_sds(names
, samdb
)
1216 for [dn
, descriptor_fn
] in subcontainers
:
1217 list_wellknown_dns
.append(dn
)
1218 if dn
in dnToRecalculate
:
1221 descr
= descriptor_fn(names
.domainsid
, name_map
=names
.name_map
)
1222 delta
["nTSecurityDescriptor"] = MessageElement(descr
, FLAG_MOD_REPLACE
,
1223 "nTSecurityDescriptor" )
1225 message(CHANGESD
, "nTSecurityDescriptor updated on wellknown DN: %s" % delta
.dn
)
1227 return list_wellknown_dns
1229 def rebuild_sd(samdb
, names
):
1230 """Rebuild security descriptor of the current provision from scratch
1232 During the different pre release of samba4 security descriptors
1233 (SD) were notarly broken (up to alpha11 included)
1235 This function allows to get them back in order, this function works
1236 only after the database comparison that --full mode uses and which
1237 populates the dnToRecalculate and dnNotToRecalculate lists.
1239 The idea is that the SD can be safely recalculated from scratch to get it right.
1241 :param names: List of key provision parameters"""
1243 listWellknown
= fix_wellknown_sd(samdb
, names
)
1245 if len(dnToRecalculate
) != 0:
1246 message(CHANGESD
, "%d DNs have been marked as needed to be recalculated"
1247 % (len(dnToRecalculate
)))
1249 for dn
in dnToRecalculate
:
1250 # well known SDs have already been reset
1251 if dn
in listWellknown
:
1255 sd_flags
= SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL
1257 descr
= get_empty_descriptor(names
.domainsid
)
1258 delta
["nTSecurityDescriptor"] = MessageElement(descr
, FLAG_MOD_REPLACE
,
1259 "nTSecurityDescriptor")
1260 samdb
.modify(delta
, ["sd_flags:1:%d" % sd_flags
,"relax:0","local_oid:%s:0" % dsdb
.DSDB_CONTROL_DBCHECK
])
1262 samdb
.transaction_cancel()
1263 res
= samdb
.search(expression
="objectClass=*", base
=str(delta
.dn
),
1265 attrs
=["nTSecurityDescriptor"],
1266 controls
=["sd_flags:1:%d" % sd_flags
])
1267 badsd
= ndr_unpack(security
.descriptor
,
1268 str(res
[0]["nTSecurityDescriptor"]))
1269 message(ERROR
, "On %s bad stuff %s" % (str(delta
.dn
),badsd
.as_sddl(names
.domainsid
)))
1272 def hasATProvision(samdb
):
1273 entry
= samdb
.search(expression
="(distinguishedName=@PROVISION)", base
= "",
1277 if entry
is not None and len(entry
) == 1:
1282 def removeProvisionUSN(samdb
):
1283 attrs
= [samba
.provision
.LAST_PROVISION_USN_ATTRIBUTE
, "dn"]
1284 entry
= samdb
.search(expression
="(distinguishedName=@PROVISION)", base
= "",
1288 empty
.dn
= entry
[0].dn
1289 delta
= samdb
.msg_diff(entry
[0], empty
)
1291 delta
.dn
= entry
[0].dn
1294 def remove_stored_generated_attrs(paths
, creds
, session
, lp
):
1295 """Remove previously stored constructed attributes
1297 :param paths: List of paths for different provision objects
1298 from the upgraded provision
1299 :param creds: A credential object
1300 :param session: A session object
1301 :param lp: A line parser object
1302 :return: An associative array whose key are the different constructed
1303 attributes and the value the dn where this attributes were found.
1307 def simple_update_basesamdb(newpaths
, paths
, names
):
1308 """Update the provision container db: sam.ldb
1309 This function is aimed at very old provision (before alpha9)
1311 :param newpaths: List of paths for different provision objects
1312 from the reference provision
1313 :param paths: List of paths for different provision objects
1314 from the upgraded provision
1315 :param names: List of key provision parameters"""
1317 message(SIMPLE
, "Copy samdb")
1318 tdb_util
.tdb_copy(newpaths
.samdb
, paths
.samdb
)
1320 message(SIMPLE
, "Update partitions filename if needed")
1321 schemaldb
= os
.path
.join(paths
.private_dir
, "schema.ldb")
1322 configldb
= os
.path
.join(paths
.private_dir
, "configuration.ldb")
1323 usersldb
= os
.path
.join(paths
.private_dir
, "users.ldb")
1324 samldbdir
= os
.path
.join(paths
.private_dir
, "sam.ldb.d")
1326 if not os
.path
.isdir(samldbdir
):
1328 os
.chmod(samldbdir
, 0700)
1329 if os
.path
.isfile(schemaldb
):
1330 tdb_util
.tdb_copy(schemaldb
, os
.path
.join(samldbdir
,
1331 "%s.ldb"%str
(names
.schemadn
).upper()))
1332 os
.remove(schemaldb
)
1333 if os
.path
.isfile(usersldb
):
1334 tdb_util
.tdb_copy(usersldb
, os
.path
.join(samldbdir
,
1335 "%s.ldb"%str
(names
.rootdn
).upper()))
1337 if os
.path
.isfile(configldb
):
1338 tdb_util
.tdb_copy(configldb
, os
.path
.join(samldbdir
,
1339 "%s.ldb"%str
(names
.configdn
).upper()))
1340 os
.remove(configldb
)
1343 def update_samdb(ref_samdb
, samdb
, names
, provisionUSNs
, schema
, prereloadfunc
):
1344 """Upgrade the SAM DB contents for all the provision partitions
1346 :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1348 :param samdb: An LDB object connected to the sam.ldb of the update
1350 :param names: List of key provision parameters
1351 :param provisionUSNs: A dictionnary with range of USN modified during provision
1352 or upgradeprovision. Ranges are grouped by invocationID.
1353 :param schema: A Schema object that represent the schema of the provision
1354 :param prereloadfunc: A function that must be executed just before the reload
1358 message(SIMPLE
, "Starting update of samdb")
1359 ret
= update_partition(ref_samdb
, samdb
, str(names
.rootdn
), names
,
1360 schema
, provisionUSNs
, prereloadfunc
)
1362 message(SIMPLE
, "Update of samdb finished")
1365 message(SIMPLE
, "Update failed")
1369 def backup_provision(paths
, dir, only_db
):
1370 """This function backup the provision files so that a rollback
1373 :param paths: Paths to different objects
1374 :param dir: Directory where to store the backup
1375 :param only_db: Skip sysvol for users with big sysvol
1377 if paths
.sysvol
and not only_db
:
1378 copytree_with_xattrs(paths
.sysvol
, os
.path
.join(dir, "sysvol"))
1379 tdb_util
.tdb_copy(paths
.samdb
, os
.path
.join(dir, os
.path
.basename(paths
.samdb
)))
1380 tdb_util
.tdb_copy(paths
.secrets
, os
.path
.join(dir, os
.path
.basename(paths
.secrets
)))
1381 tdb_util
.tdb_copy(paths
.idmapdb
, os
.path
.join(dir, os
.path
.basename(paths
.idmapdb
)))
1382 tdb_util
.tdb_copy(paths
.privilege
, os
.path
.join(dir, os
.path
.basename(paths
.privilege
)))
1383 if os
.path
.isfile(os
.path
.join(paths
.private_dir
,"eadb.tdb")):
1384 tdb_util
.tdb_copy(os
.path
.join(paths
.private_dir
,"eadb.tdb"), os
.path
.join(dir, "eadb.tdb"))
1385 shutil
.copy2(paths
.smbconf
, dir)
1386 shutil
.copy2(os
.path
.join(paths
.private_dir
,"secrets.keytab"), dir)
1388 samldbdir
= os
.path
.join(paths
.private_dir
, "sam.ldb.d")
1389 if not os
.path
.isdir(samldbdir
):
1390 samldbdir
= paths
.private_dir
1391 schemaldb
= os
.path
.join(paths
.private_dir
, "schema.ldb")
1392 configldb
= os
.path
.join(paths
.private_dir
, "configuration.ldb")
1393 usersldb
= os
.path
.join(paths
.private_dir
, "users.ldb")
1394 tdb_util
.tdb_copy(schemaldb
, os
.path
.join(dir, "schema.ldb"))
1395 tdb_util
.tdb_copy(usersldb
, os
.path
.join(dir, "configuration.ldb"))
1396 tdb_util
.tdb_copy(configldb
, os
.path
.join(dir, "users.ldb"))
1398 os
.mkdir(os
.path
.join(dir, "sam.ldb.d"), 0700)
1400 for ldb
in os
.listdir(samldbdir
):
1401 tdb_util
.tdb_copy(os
.path
.join(samldbdir
, ldb
),
1402 os
.path
.join(dir, "sam.ldb.d", ldb
))
1405 def sync_calculated_attributes(samdb
, names
):
1406 """Synchronize attributes used for constructed ones, with the
1407 old constructed that were stored in the database.
1409 This apply for instance to msds-keyversionnumber that was
1410 stored and that is now constructed from replpropertymetadata.
1412 :param samdb: An LDB object attached to the currently upgraded samdb
1413 :param names: Various key parameter about current provision.
1415 listAttrs
= ["msDs-KeyVersionNumber"]
1416 hash = search_constructed_attrs_stored(samdb
, names
.rootdn
, listAttrs
)
1417 if hash.has_key("msDs-KeyVersionNumber"):
1418 increment_calculated_keyversion_number(samdb
, names
.rootdn
,
1419 hash["msDs-KeyVersionNumber"])
1421 # Synopsis for updateprovision
1422 # 1) get path related to provision to be update (called current)
1423 # 2) open current provision ldbs
1424 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1426 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1427 # by either upgradeprovision or provision
1428 # 5) creation of a new provision the latest version of provision script
1429 # (called reference)
1430 # 6) get reference provision paths
1431 # 7) open reference provision ldbs
1432 # 8) setup helpers data that will help the update process
1433 # 9) (SKIPPED) we no longer update the privilege ldb by copying the one of referecence provision to
1434 # the current provision, because a shutil.copy would break the transaction locks both databases are under
1435 # and this database has not changed between 2009 and Samba 4.0.3 in Feb 2013 (at least)
1436 # 10)get the oemInfo field, this field contains information about the different
1437 # provision that have been done
1438 # 11)Depending on if the --very-old-pre-alpha9 flag is set the following things are done
1439 # A) When alpha9 or alphaxx not specified (default)
1440 # The base sam.ldb file is updated by looking at the difference between
1441 # referrence one and the current one. Everything is copied with the
1442 # exception of lastProvisionUSN attributes.
1443 # B) Other case (it reflect that that provision was done before alpha9)
1444 # The base sam.ldb of the reference provision is copied over
1445 # the current one, if necessary ldb related to partitions are moved
1447 # The highest used USN is fetched so that changed by upgradeprovision
1448 # usn can be tracked
1449 # 12)A Schema object is created, it will be used to provide a complete
1450 # schema to current provision during update (as the schema of the
1451 # current provision might not be complete and so won't allow some
1452 # object to be created)
1453 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1454 # 14)The secrets db is updated by pull all the difference from the reference
1455 # provision into the current provision
1456 # 15)As the previous step has most probably modified the password stored in
1457 # in secret for the current DC, a new password is generated,
1458 # the kvno is bumped and the entry in samdb is also updated
1459 # 16)For current provision older than alpha9, we must fix the SD a little bit
1460 # administrator to update them because SD used to be generated with the
1461 # system account before alpha9.
1462 # 17)The highest usn modified so far is searched in the database it will be
1463 # the upper limit for usn modified during provision.
1464 # This is done before potential SD recalculation because we do not want
1465 # SD modified during recalculation to be marked as modified during provision
1466 # (and so possibly remplaced at next upgradeprovision)
1467 # 18)Rebuilt SD if the flag indicate to do so
1468 # 19)Check difference between SD of reference provision and those of the
1469 # current provision. The check is done by getting the sddl representation
1470 # of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1471 # Each part is verified separetly, for dacl and sacl ACL is splited into
1472 # ACEs and each ACE is verified separately (so that a permutation in ACE
1473 # didn't raise as an error).
1474 # 20)The oemInfo field is updated to add information about the fact that the
1475 # provision has been updated by the upgradeprovision version xxx
1476 # (the version is the one obtained when starting samba with the --version
1478 # 21)Check if the current provision has all the settings needed for dynamic
1479 # DNS update to work (that is to say the provision is newer than
1480 # january 2010). If not dns configuration file from reference provision
1481 # are copied in a sub folder and the administrator is invited to
1482 # do what is needed.
1483 # 22)If the lastProvisionUSN attribute was present it is updated to add
1484 # the range of usns modified by the current upgradeprovision
1487 # About updating the sam DB
1488 # The update takes place in update_partition function
1489 # This function read both current and reference provision and list all
1490 # the available DN of objects
1491 # If the string representation of a DN in reference provision is
1492 # equal to the string representation of a DN in current provision
1493 # (without taking care of case) then the object is flaged as being
1494 # present. If the object is not present in current provision the object
1495 # is being flaged as missing in current provision. Object present in current
1496 # provision but not in reference provision are ignored.
1497 # Once the list of objects present and missing is done, the deleted object
1498 # containers are created in the differents partitions (if missing)
1500 # Then the function add_missing_entries is called
1501 # This function will go through the list of missing entries by calling
1502 # add_missing_object for the given object. If this function returns 0
1503 # it means that the object needs some other object in order to be created
1504 # The object is reappended at the end of the list to be created later
1505 # (and preferably after all the needed object have been created)
1506 # The function keeps on looping on the list of object to be created until
1507 # it's empty or that the number of defered creation is equal to the number
1508 # of object that still needs to be created.
1510 # The function add_missing_object will first check if the object can be created.
1511 # That is to say that it didn't depends other not yet created objects
1512 # If requisit can't be fullfilled it exists with 0
1513 # Then it will try to create the missing entry by creating doing
1514 # an ldb_message_diff between the object in the reference provision and
1516 # This resulting object is filtered to remove all the back link attribute
1517 # (ie. memberOf) as they will be created by the other linked object (ie.
1518 # the one with the member attribute)
1519 # All attributes specified in the attrNotCopied array are
1520 # also removed it's most of the time generated attributes
1522 # After missing entries have been added the update_partition function will
1523 # take care of object that exist but that need some update.
1524 # In order to do so the function update_present is called with the list
1525 # of object that are present in both provision and that might need an update.
1527 # This function handle first case mismatch so that the DN in the current
1528 # provision have the same case as in reference provision
1530 # It will then construct an associative array consiting of attributes as
1531 # key and invocationid as value( if the originating invocation id is
1532 # different from the invocation id of the current DC the value is -1 instead).
1534 # If the range of provision modified attributes is present, the function will
1535 # use the replMetadataProperty update method which is the following:
1536 # Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1537 # creationTime, msDs-KeyVersionNumber, oEMInformation
1538 # Check for each attribute if its usn is within one of the modified by
1539 # provision range and if its originating id is the invocation id of the
1540 # current DC, then validate the update from reference to current.
1541 # If not or if there is no replMetatdataProperty for this attribute then we
1543 # Otherwise (case the range of provision modified attribute is not present) it
1544 # use the following process:
1545 # All attributes that need to be added are accepted at the exeption of those
1546 # listed in hashOverwrittenAtt, in this case the attribute needs to have the
1547 # correct flags specified.
1548 # For attributes that need to be modified or removed, a check is performed
1549 # in OverwrittenAtt, if the attribute is present and the modification flag
1550 # (remove, delete) is one of those listed for this attribute then modification
1551 # is accepted. For complicated handling of attribute update, the control is passed
1552 # to handle_special_case
1556 if __name__
== '__main__':
1557 global defSDmodified
1558 defSDmodified
= False
1560 # From here start the big steps of the program
1561 # 1) First get files paths
1562 paths
= get_paths(param
, smbconf
=smbconf
)
1563 # Get ldbs with the system session, it is needed for searching
1564 # provision parameters
1565 session
= system_session()
1567 # This variable will hold the last provision USN once if it exists.
1570 ldbs
= get_ldbs(paths
, creds
, session
, lp
)
1571 backupdir
= tempfile
.mkdtemp(dir=paths
.private_dir
,
1572 prefix
="backupprovision")
1573 backup_provision(paths
, backupdir
, opts
.db_backup_only
)
1575 ldbs
.startTransactions()
1577 # 3) Guess all the needed names (variables in fact) from the current
1579 names
= find_provision_key_parameters(ldbs
.sam
, ldbs
.secrets
, ldbs
.idmap
,
1582 lastProvisionUSNs
= get_last_provision_usn(ldbs
.sam
)
1583 if lastProvisionUSNs
is not None:
1585 for k
in lastProvisionUSNs
.keys():
1586 for r
in lastProvisionUSNs
[k
]:
1590 "Find last provision USN, %d invocation(s) for a total of %d ranges" %
1591 (len(lastProvisionUSNs
.keys()), v
/2 ))
1593 if lastProvisionUSNs
.get("default") is not None:
1594 message(CHANGE
, "Old style for usn ranges used")
1595 lastProvisionUSNs
[str(names
.invocation
)] = lastProvisionUSNs
["default"]
1596 del lastProvisionUSNs
["default"]
1598 message(SIMPLE
, "Your provision lacks provision range information")
1599 if confirm("Do you want to run findprovisionusnranges to try to find them ?", False):
1600 ldbs
.groupedRollback()
1602 (hash_id
, nb_obj
) = findprovisionrange(ldbs
.sam
, ldb
.Dn(ldbs
.sam
, str(names
.rootdn
)))
1603 message(SIMPLE
, "Here is a list of changes that modified more than %d objects in 1 minute." % minobj
)
1604 message(SIMPLE
, "Usually changes made by provision and upgradeprovision are those who affect a couple"
1605 " of hundred of objects or more")
1606 message(SIMPLE
, "Total number of objects: %d" % nb_obj
)
1609 print_provision_ranges(hash_id
, minobj
, None, str(paths
.samdb
), str(names
.invocation
))
1611 message(SIMPLE
, "Once you applied/adapted the change(s) please restart the upgradeprovision script")
1614 # Objects will be created with the admin session
1615 # (not anymore system session)
1616 adm_session
= admin_session(lp
, str(names
.domainsid
))
1617 # So we reget handle on objects
1618 # ldbs = get_ldbs(paths, creds, adm_session, lp)
1620 if not sanitychecks(ldbs
.sam
, names
):
1621 message(SIMPLE
, "Sanity checks for the upgrade have failed. "
1622 "Check the messages and correct the errors "
1623 "before rerunning upgradeprovision")
1624 ldbs
.groupedRollback()
1627 # Let's see provision parameters
1628 print_provision_key_parameters(names
)
1630 # 5) With all this information let's create a fresh new provision used as
1632 message(SIMPLE
, "Creating a reference provision")
1633 provisiondir
= tempfile
.mkdtemp(dir=paths
.private_dir
,
1634 prefix
="referenceprovision")
1635 result
= newprovision(names
, creds
, session
, smbconf
, provisiondir
,
1637 result
.report_logger(provision_logger
)
1641 # We need to get a list of object which SD is directly computed from
1642 # defaultSecurityDescriptor.
1643 # This will allow us to know which object we can rebuild the SD in case
1644 # of change of the parent's SD or of the defaultSD.
1645 # Get file paths of this new provision
1646 newpaths
= get_paths(param
, targetdir
=provisiondir
)
1647 new_ldbs
= get_ldbs(newpaths
, creds
, session
, lp
)
1648 new_ldbs
.startTransactions()
1650 populateNotReplicated(new_ldbs
.sam
, names
.schemadn
)
1651 # 8) Populate some associative array to ease the update process
1652 # List of attribute which are link and backlink
1653 populate_links(new_ldbs
.sam
, names
.schemadn
)
1654 # List of attribute with ASN DN synthax)
1655 populate_dnsyntax(new_ldbs
.sam
, names
.schemadn
)
1656 # 9) (now skipped, was copy of privileges.ldb)
1658 oem
= getOEMInfo(ldbs
.sam
, str(names
.rootdn
))
1659 # Do some modification on sam.ldb
1660 ldbs
.groupedCommit()
1661 new_ldbs
.groupedCommit()
1665 if oem
is None or hasATProvision(ldbs
.sam
) or not opts
.very_old_pre_alpha9
:
1667 # Starting from alpha9 we can consider that the structure is quite ok
1668 # and that we should do only dela
1669 deltaattr
= delta_update_basesamdb(newpaths
.samdb
,
1677 simple_update_basesamdb(newpaths
, paths
, names
)
1678 ldbs
= get_ldbs(paths
, creds
, session
, lp
)
1679 removeProvisionUSN(ldbs
.sam
)
1681 ldbs
.startTransactions()
1682 minUSN
= int(str(get_max_usn(ldbs
.sam
, str(names
.rootdn
)))) + 1
1683 new_ldbs
.startTransactions()
1686 schema
= Schema(names
.domainsid
, schemadn
=str(names
.schemadn
))
1687 # We create a closure that will be invoked just before schema reload
1688 def schemareloadclosure():
1689 basesam
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
, lp
=lp
,
1690 options
=["modules:"])
1692 if deltaattr
is not None and len(deltaattr
) > 1:
1695 deltaattr
.remove("dn")
1696 for att
in deltaattr
:
1697 if att
.lower() == "dn":
1699 if (deltaattr
.get(att
) is not None
1700 and deltaattr
.get(att
).flags() != FLAG_MOD_ADD
):
1702 elif deltaattr
.get(att
) is None:
1705 message(CHANGE
, "Applying delta to @ATTRIBUTES")
1706 deltaattr
.dn
= ldb
.Dn(basesam
, "@ATTRIBUTES")
1707 basesam
.modify(deltaattr
)
1709 message(CHANGE
, "Not applying delta to @ATTRIBUTES because "
1710 "there is not only add")
1713 if not update_samdb(new_ldbs
.sam
, ldbs
.sam
, names
, lastProvisionUSNs
,
1714 schema
, schemareloadclosure
):
1715 message(SIMPLE
, "Rolling back all changes. Check the cause"
1717 message(SIMPLE
, "Your system is as it was before the upgrade")
1718 ldbs
.groupedRollback()
1719 new_ldbs
.groupedRollback()
1720 shutil
.rmtree(provisiondir
)
1723 # Try to reapply the change also when we do not change the sam
1724 # as the delta_upgrade
1725 schemareloadclosure()
1726 sync_calculated_attributes(ldbs
.sam
, names
)
1727 res
= ldbs
.sam
.search(expression
="(samaccountname=dns)",
1728 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
1729 controls
=["search_options:1:2"])
1731 message(SIMPLE
, "You still have the old DNS object for managing "
1732 "dynamic DNS, but you didn't supply --full so "
1733 "a correct update can't be done")
1734 ldbs
.groupedRollback()
1735 new_ldbs
.groupedRollback()
1736 shutil
.rmtree(provisiondir
)
1739 update_secrets(new_ldbs
.secrets
, ldbs
.secrets
, message
)
1741 res
= ldbs
.sam
.search(expression
="(samaccountname=dns)",
1742 scope
=SCOPE_SUBTREE
, attrs
=["dn"],
1743 controls
=["search_options:1:2"])
1746 ldbs
.sam
.delete(res
[0]["dn"])
1747 res2
= ldbs
.secrets
.search(expression
="(samaccountname=dns)",
1748 scope
=SCOPE_SUBTREE
, attrs
=["dn"])
1749 update_dns_account_password(ldbs
.sam
, ldbs
.secrets
, names
)
1750 message(SIMPLE
, "IMPORTANT!!! "
1751 "If you were using Dynamic DNS before you need "
1752 "to update your configuration, so that the "
1753 "tkey-gssapi-credential has the following value: "
1754 "DNS/%s.%s" % (names
.netbiosname
.lower(),
1755 names
.realm
.lower()))
1757 message(SIMPLE
, "Update machine account")
1758 update_machine_account_password(ldbs
.sam
, ldbs
.secrets
, names
)
1760 # 16) SD should be created with admin but as some previous acl were so wrong
1761 # that admin can't modify them we have first to recreate them with the good
1762 # form but with system account and then give the ownership to admin ...
1763 if opts
.very_old_pre_alpha9
:
1764 message(SIMPLE
, "Fixing very old provision SD")
1765 rebuild_sd(ldbs
.sam
, names
)
1767 # We calculate the max USN before recalculating the SD because we might
1768 # touch object that have been modified after a provision and we do not
1769 # want that the next upgradeprovision thinks that it has a green light
1773 maxUSN
= get_max_usn(ldbs
.sam
, str(names
.rootdn
))
1775 # 18) We rebuild SD if a we have a list of DN to recalculate or if the
1776 # defSDmodified is set.
1777 if opts
.full
and (defSDmodified
or len(dnToRecalculate
) >0):
1778 message(SIMPLE
, "Some (default) security descriptors (SDs) have "
1779 "changed, recalculating them")
1780 ldbs
.sam
.set_session_info(adm_session
)
1781 rebuild_sd(ldbs
.sam
, names
)
1784 # Now we are quite confident in the recalculate process of the SD, we make
1785 # it optional. And we don't do it if there is DN that we must touch
1786 # as we are assured that on this DNs we will have differences !
1787 # Also the check must be done in a clever way as for the moment we just
1789 if dnNotToRecalculateFound
== False and (opts
.debugchangesd
or opts
.debugall
):
1790 message(CHANGESD
, "Checking recalculated SDs")
1791 check_updated_sd(new_ldbs
.sam
, ldbs
.sam
, names
)
1794 updateOEMInfo(ldbs
.sam
, str(names
.rootdn
))
1796 check_for_DNS(newpaths
.private_dir
, paths
.private_dir
, names
.dns_backend
)
1798 update_provision_usn(ldbs
.sam
, minUSN
, maxUSN
, names
.invocation
)
1799 if opts
.full
and (names
.policyid
is None or names
.policyid_dc
is None):
1800 update_policyids(names
, ldbs
.sam
)
1804 update_gpo(paths
, ldbs
.sam
, names
, lp
, message
)
1805 except ProvisioningError
, e
:
1806 message(ERROR
, "The policy for domain controller is missing. "
1807 "You should restart upgradeprovision with --full")
1809 ldbs
.groupedCommit()
1810 new_ldbs
.groupedCommit()
1811 message(SIMPLE
, "Upgrade finished!")
1812 # remove reference provision now that everything is done !
1813 # So we have reindexed first if need when the merged schema was reloaded
1814 # (as new attributes could have quick in)
1815 # But the second part of the update (when we update existing objects
1816 # can also have an influence on indexing as some attribute might have their
1817 # searchflag modificated
1818 message(SIMPLE
, "Reopening samdb to trigger reindexing if needed "
1819 "after modification")
1820 samdb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
, lp
=lp
)
1821 message(SIMPLE
, "Reindexing finished")
1823 shutil
.rmtree(provisiondir
)
1824 except StandardError, err
:
1825 message(ERROR
, "A problem occurred while trying to upgrade your "
1826 "provision. A full backup is located at %s" % backupdir
)
1827 if opts
.debugall
or opts
.debugchange
:
1828 (typ
, val
, tb
) = sys
.exc_info()
1829 traceback
.print_exception(typ
, val
, tb
)