source: vendor/3.6.0/source4/scripting/bin/upgradeprovision

Last change on this file was 740, checked in by Silvan Scherrer, 13 years ago

Samba Server: update vendor to 3.6.0

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