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