888f3a82565f266d13f200946b1514e39fc248b6
[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     skip = False
520     try:
521         if str(reference[0].get("cn"))  == "RID Set":
522             for klass in reference[0].get("objectClass"):
523                 if str(klass).lower == "ridset":
524                     skip = True
525     finally:
526         if delta.get("objectSid"):
527             sid = str(ndr_unpack(security.dom_sid, str(reference[0]["objectSid"])))
528             m = re.match(r".*-(\d+)$", sid)
529             if m and int(m.group(1))>999:
530                 delta.remove("objectSid")
531         for att in hashAttrNotCopied.keys():
532             delta.remove(att)
533         for att in backlinked:
534             delta.remove(att)
535         depend_on_yettobecreated = None
536         for att in dn_syntax_att:
537             depend_on_yet_tobecreated = check_dn_nottobecreated(hash, index,
538                                                                 delta.get(str(att)))
539             if depend_on_yet_tobecreated is not None:
540                 message(CHANGE, "Object %s depends on %s in attribute %s,"
541                                 "delaying the creation" % (dn,
542                                           depend_on_yet_tobecreated, att))
543                 return False
544
545         delta.dn = dn
546         if not skip:
547             message(CHANGE,"Object %s will be added" % dn)
548             samdb.add(delta, ["relax:0"])
549         else:
550             message(CHANGE,"Object %s was skipped" % dn)
551
552         return True
553
554 def gen_dn_index_hash(listMissing):
555     """Generate a hash associating the DN to its creation order
556
557     :param listMissing: List of DN
558     :return: Hash with DN as keys and creation order as values"""
559     hash = {}
560     for i in range(0, len(listMissing)):
561         hash[str(listMissing[i]).lower()] = i
562     return hash
563
564 def add_deletedobj_containers(ref_samdb, samdb, names):
565     """Add the object containter: CN=Deleted Objects
566
567     This function create the container for each partition that need one and
568     then reference the object into the root of the partition
569
570     :param ref_samdb: Ldb object representing the SAM db of the reference
571                        provision
572     :param samdb: Ldb object representing the SAM db of the upgraded provision
573     :param names: List of key provision parameters"""
574
575
576     wkoPrefix = "B:32:18E2EA80684F11D2B9AA00C04F79F805"
577     partitions = [str(names.rootdn), str(names.configdn)]
578     for part in partitions:
579         ref_delObjCnt = ref_samdb.search(expression="(cn=Deleted Objects)",
580                                             base=part, scope=SCOPE_SUBTREE,
581                                             attrs=["dn"],
582                                             controls=["show_deleted:0"])
583         delObjCnt = samdb.search(expression="(cn=Deleted Objects)",
584                                     base=part, scope=SCOPE_SUBTREE,
585                                     attrs=["dn"],
586                                     controls=["show_deleted:0"])
587         if len(ref_delObjCnt) > len(delObjCnt):
588             reference = ref_samdb.search(expression="cn=Deleted Objects",
589                                             base=part, scope=SCOPE_SUBTREE,
590                                             controls=["show_deleted:0"])
591             empty = Message()
592             delta = samdb.msg_diff(empty, reference[0])
593
594             delta.dn = Dn(samdb, str(reference[0]["dn"]))
595             for att in hashAttrNotCopied.keys():
596                 delta.remove(att)
597             samdb.add(delta)
598
599             listwko = []
600             res = samdb.search(expression="(objectClass=*)", base=part,
601                                scope=SCOPE_BASE,
602                                attrs=["dn", "wellKnownObjects"])
603
604             targetWKO = "%s:%s" % (wkoPrefix, str(reference[0]["dn"]))
605             found = False
606
607             if len(res[0]) > 0:
608                 wko = res[0]["wellKnownObjects"]
609
610                 # The wellKnownObject that we want to add.
611                 for o in wko:
612                     if str(o) == targetWKO:
613                         found = True
614                     listwko.append(str(o))
615
616             if not found:
617                 listwko.append(targetWKO)
618
619                 delta = Message()
620                 delta.dn = Dn(samdb, str(res[0]["dn"]))
621                 delta["wellKnownObjects"] = MessageElement(listwko,
622                                                 FLAG_MOD_REPLACE,
623                                                 "wellKnownObjects" )
624                 samdb.modify(delta)
625
626 def add_missing_entries(ref_samdb, samdb, names, basedn, list):
627     """Add the missing object whose DN is the list
628
629     The function add the object if the objects on which it depends are
630     already created.
631
632     :param ref_samdb: Ldb object representing the SAM db of the reference
633                       provision
634     :param samdb: Ldb object representing the SAM db of the upgraded
635                   provision
636     :param dn: DN of the object to be added
637     :param names: List of key provision parameters
638     :param basedn: DN of the partition to be updated
639     :param list: List of DN to be added in the upgraded provision"""
640
641     listMissing = []
642     listDefered = list
643
644     while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
645         index = 0
646         listMissing = listDefered
647         listDefered = []
648         hashMissing = gen_dn_index_hash(listMissing)
649         for dn in listMissing:
650             ret = add_missing_object(ref_samdb, samdb, dn, names, basedn,
651                                         hashMissing, index)
652             index = index + 1
653             if ret == 0:
654                 # DN can't be created because it depends on some
655                 # other DN in the list
656                 listDefered.append(dn)
657     if len(listDefered) != 0:
658         raise ProvisioningError("Unable to insert missing elements:" \
659                                 "circular references")
660
661 def handle_links(samdb, att, basedn, dn, value, ref_value, delta):
662     """This function handle updates on links
663
664     :param samdb: An LDB object pointing to the updated provision
665     :param att: Attribute to update
666     :param basedn: The root DN of the provision
667     :param dn: The DN of the inspected object
668     :param value: The value of the attribute
669     :param ref_value: The value of this attribute in the reference provision
670     :param delta: The MessageElement object that will be applied for
671                    transforming the current provision"""
672
673     res = samdb.search(expression="dn=%s" % dn, base=basedn,
674                         controls=["search_options:1:2", "reveal:1"],
675                         attrs=[att])
676
677     blacklist = {}
678     hash = {}
679     newlinklist = []
680     changed = False
681
682     newlinklist.extend(value)
683
684     for e in value:
685         hash[e] = 1
686     # for w2k domain level the reveal won't reveal anything ...
687     # it means that we can readd links that were removed on purpose ...
688     # Also this function in fact just accept add not removal
689
690     for e in res[0][att]:
691         if not hash.has_key(e):
692             # We put in the blacklist all the element that are in the "revealed"
693             # result and not in the "standard" result
694             # This element are links that were removed before and so that
695             # we don't wan't to readd
696             blacklist[e] = 1
697
698     for e in ref_value:
699         if not blacklist.has_key(e) and not hash.has_key(e):
700             newlinklist.append(str(e))
701             changed = True
702     if changed:
703         delta[att] = MessageElement(newlinklist, FLAG_MOD_REPLACE, att)
704     else:
705         delta.remove(att)
706
707
708 msg_elt_flag_strs = {
709     ldb.FLAG_MOD_ADD: "MOD_ADD",
710     ldb.FLAG_MOD_REPLACE: "MOD_REPLACE",
711     ldb.FLAG_MOD_DELETE: "MOD_DELETE" }
712
713
714 def update_present(ref_samdb, samdb, basedn, listPresent, usns, invocationid):
715     """ This function updates the object that are already present in the
716         provision
717
718     :param ref_samdb: An LDB object pointing to the reference provision
719     :param samdb: An LDB object pointing to the updated provision
720     :param basedn: A string with the value of the base DN for the provision
721                    (ie. DC=foo, DC=bar)
722     :param listPresent: A list of object that is present in the provision
723     :param usns: A list of USN range modified by previous provision and
724                  upgradeprovision
725     :param invocationid: The value of the invocationid for the current DC"""
726
727     global defSDmodified
728     # This hash is meant to speedup lookup of attribute name from an oid,
729     # it's for the replPropertyMetaData handling
730     hash_oid_name = {}
731     res = samdb.search(expression="objectClass=attributeSchema", base=basedn,
732                         controls=["search_options:1:2"], attrs=["attributeID",
733                         "lDAPDisplayName"])
734     if len(res) > 0:
735         for e in res:
736             strDisplay = str(e.get("lDAPDisplayName"))
737             hash_oid_name[str(e.get("attributeID"))] = strDisplay
738     else:
739         msg = "Unable to insert missing elements: circular references"
740         raise ProvisioningError(msg)
741
742     changed = 0
743     controls = ["search_options:1:2", "sd_flags:1:2"]
744     for dn in listPresent:
745         reference = ref_samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
746                                         scope=SCOPE_SUBTREE,
747                                         controls=controls)
748         current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
749                                 scope=SCOPE_SUBTREE, controls=controls)
750
751         if (
752              (str(current[0].dn) != str(reference[0].dn)) and
753              (str(current[0].dn).upper() == str(reference[0].dn).upper())
754            ):
755             message(CHANGE, "Name are the same but case change,"\
756                             "let's rename %s to %s" % (str(current[0].dn),
757                                                        str(reference[0].dn)))
758             identic_rename(samdb, reference[0].dn)
759             current = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
760                                     scope=SCOPE_SUBTREE,
761                                     controls=["search_options:1:2"])
762
763         delta = samdb.msg_diff(current[0], reference[0])
764
765         for att in hashAttrNotCopied.keys():
766             delta.remove(att)
767
768         for att in backlinked:
769             delta.remove(att)
770
771         delta.remove("name")
772
773         if len(delta.items()) > 1 and usns is not None:
774             # Fetch the replPropertyMetaData
775             res = samdb.search(expression="dn=%s" % (str(dn)), base=basedn,
776                                 scope=SCOPE_SUBTREE, controls=controls,
777                                 attrs=["replPropertyMetaData"])
778             ctr = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
779                                 str(res[0]["replPropertyMetaData"])).ctr
780
781             hash_attr_usn = {}
782             for o in ctr.array:
783                 # We put in this hash only modification
784                 # made on the current host
785                 att = hash_oid_name[samdb.get_oid_from_attid(o.attid)]
786                 if str(o.originating_invocation_id) == str(invocationid):
787                 # Note we could just use 1 here
788                     hash_attr_usn[att] = o.originating_usn
789                 else:
790                     hash_attr_usn[att] = -1
791
792         isFirst = 0
793         txt = ""
794
795         for att in delta:
796             if usns is not None:
797                 # We have updated by provision usn information so let's exploit
798                 # replMetadataProperties
799                 if att in forwardlinked:
800                     handle_links(samdb, att, basedn, current[0]["dn"],
801                                     current[0][att], reference[0][att], delta)
802
803                 if isFirst == 0 and len(delta.items())>1:
804                     isFirst = 1
805                     txt = "%s\n" % (str(dn))
806                 if att == "dn":
807                     # There is always a dn attribute after a msg_diff
808                     continue
809                 if att == "rIDAvailablePool":
810                     delta.remove(att)
811                     continue
812                 if att == "objectSid":
813                     delta.remove(att)
814                     continue
815                 if att == "creationTime":
816                     delta.remove(att)
817                     continue
818                 if att == "oEMInformation":
819                     delta.remove(att)
820                     continue
821                 if att == "msDs-KeyVersionNumber":
822                 # This is the kvno of the computer/user it's a very bad
823                 # idea to change it
824                     delta.remove(att)
825                     continue
826                 if handle_special_case(att, delta, reference, current, usns):
827                     # This attribute is "complicated" to handle and handling
828                     # was done in handle_special_case
829                     continue
830                 attrUSN = hash_attr_usn.get(att)
831                 if att == "forceLogoff" and attrUSN is None:
832                     continue
833                 if  attrUSN is None:
834                     delta.remove(att)
835                     continue
836
837                 if attrUSN == -1:
838                     # This attribute was last modified by another DC forget
839                     # about it
840                     message(CHANGE, "%sAttribute: %s has been"
841                             "created/modified/deleted  by another DC,"
842                             " do nothing" % (txt, att ))
843                     txt = ""
844                     delta.remove(att)
845                     continue
846                 elif not usn_in_range(int(attrUSN), usns):
847                     message(CHANGE, "%sAttribute: %s has been"
848                                     "created/modified/deleted not during a"
849                                     " provision or upgradeprovision: current"
850                                     " usn %d , do nothing" % (txt, att, attrUSN))
851                     txt = ""
852                     delta.remove(att)
853                     continue
854                 else:
855                     if att == "defaultSecurityDescriptor":
856                         defSDmodified = True
857                     if attrUSN:
858                         message(CHANGE, "%sAttribute: %s will be modified"
859                                         "/deleted it was last modified"
860                                         "during a provision, current usn:"
861                                         "%d" % (txt, att,  attrUSN))
862                         txt = ""
863                     else:
864                         message(CHANGE, "%sAttribute: %s will be added because"
865                                         " it hasn't existed before " % (txt, att))
866                         txt = ""
867                     continue
868
869             else:
870             # Old school way of handling things for pre alpha12 upgrade
871                 defSDmodified = True
872                 msgElt = delta.get(att)
873
874                 if att == "nTSecurityDescriptor":
875                     delta.remove(att)
876                     continue
877
878                 if att == "dn":
879                     continue
880
881                 if not hashOverwrittenAtt.has_key(att):
882                     if msgElt.flags() != FLAG_MOD_ADD:
883                         if not handle_special_case(att, delta, reference, current,
884                                                     usns):
885                             if opts.debugchange or opts.debugall:
886                                 try:
887                                     dump_denied_change(dn, att,
888                                         msg_elt_flag_strs[msgElt.flags()],
889                                         current[0][att], reference[0][att])
890                                 except KeyError:
891                                     dump_denied_change(dn, att,
892                                         msg_elt_flag_strs[msgElt.flags()],
893                                         current[0][att], None)
894                             delta.remove(att)
895                         continue
896                 else:
897                     if hashOverwrittenAtt.get(att)&2**msgElt.flags() :
898                         continue
899                     elif  hashOverwrittenAtt.get(att)==never:
900                         delta.remove(att)
901                         continue
902
903         delta.dn = dn
904         if len(delta.items()) >1:
905             attributes=", ".join(delta.keys())
906             message(CHANGE, "%s is different from the reference one, changed"
907                             " attributes: %s\n" % (dn, attributes))
908             changed += 1
909             samdb.modify(delta)
910     return changed
911
912
913 def update_partition(ref_samdb, samdb, basedn, names, schema, provisionUSNs):
914     """Check differences between the reference provision and the upgraded one.
915
916     It looks for all objects which base DN is name.
917
918     This function will also add the missing object and update existing object
919     to add or remove attributes that were missing.
920
921     :param ref_sambdb: An LDB object conntected to the sam.ldb of the
922                        reference provision
923     :param samdb: An LDB object connected to the sam.ldb of the update
924                   provision
925     :param basedn: String value of the DN of the partition
926     :param names: List of key provision parameters
927     :param schema: A Schema object
928     :param provisionUSNs:  The USNs modified by provision/upgradeprovision
929                            last time"""
930
931     hash_new = {}
932     hash = {}
933     listMissing = []
934     listPresent = []
935     reference = []
936     current = []
937
938     # Connect to the reference provision and get all the attribute in the
939     # partition referred by name
940     reference = ref_samdb.search(expression="objectClass=*", base=basedn,
941                                     scope=SCOPE_SUBTREE, attrs=["dn"],
942                                     controls=["search_options:1:2"])
943
944     current = samdb.search(expression="objectClass=*", base=basedn,
945                                 scope=SCOPE_SUBTREE, attrs=["dn"],
946                                 controls=["search_options:1:2"])
947     # Create a hash for speeding the search of new object
948     for i in range(0, len(reference)):
949         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
950
951     # Create a hash for speeding the search of existing object in the
952     # current provision
953     for i in range(0, len(current)):
954         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
955
956
957     for k in hash_new.keys():
958         if not hash.has_key(k):
959             if not str(hash_new[k]) == "CN=Deleted Objects, %s" % names.rootdn:
960                 listMissing.append(hash_new[k])
961         else:
962             listPresent.append(hash_new[k])
963
964     # Sort the missing object in order to have object of the lowest level
965     # first (which can be containers for higher level objects)
966     listMissing.sort(dn_sort)
967     listPresent.sort(dn_sort)
968
969     # The following lines is to load the up to
970     # date schema into our current LDB
971     # a complete schema is needed as the insertion of attributes
972     # and class is done against it
973     # and the schema is self validated
974     samdb.set_schema(schema)
975     try:
976         message(SIMPLE, "There are %d missing objects" % (len(listMissing)))
977         add_deletedobj_containers(ref_samdb, samdb, names)
978
979         add_missing_entries(ref_samdb, samdb, names, basedn, listMissing)
980         changed = update_present(ref_samdb, samdb, basedn, listPresent,
981                                     provisionUSNs, names.invocation)
982         message(SIMPLE, "There are %d changed objects" % (changed))
983         return 1
984
985     except StandardError, err:
986         message(ERROR, "Exception during upgrade of samdb:")
987         (typ, val, tb) = sys.exc_info()
988         traceback.print_exception(typ, val, tb)
989         return 0
990
991
992 def check_updated_sd(ref_sam, cur_sam, names):
993     """Check if the security descriptor in the upgraded provision are the same
994        as the reference
995
996     :param ref_sam: A LDB object connected to the sam.ldb file used as
997                     the reference provision
998     :param cur_sam: A LDB object connected to the sam.ldb file used as
999                     upgraded provision
1000     :param names: List of key provision parameters"""
1001     reference = ref_sam.search(expression="objectClass=*", base=str(names.rootdn),
1002                                 scope=SCOPE_SUBTREE,
1003                                 attrs=["dn", "nTSecurityDescriptor"],
1004                                 controls=["search_options:1:2"])
1005     current = cur_sam.search(expression="objectClass=*", base=str(names.rootdn),
1006                                 scope=SCOPE_SUBTREE,
1007                                 attrs=["dn", "nTSecurityDescriptor"],
1008                                 controls=["search_options:1:2"])
1009     hash = {}
1010     for i in range(0, len(reference)):
1011         refsd = ndr_unpack(security.descriptor,
1012                     str(reference[i]["nTSecurityDescriptor"]))
1013         hash[str(reference[i]["dn"]).lower()] = refsd.as_sddl(names.domainsid)
1014
1015
1016     for i in range(0, len(current)):
1017         key = str(current[i]["dn"]).lower()
1018         if hash.has_key(key):
1019             cursd = ndr_unpack(security.descriptor,
1020                         str(current[i]["nTSecurityDescriptor"]))
1021             sddl = cursd.as_sddl(names.domainsid)
1022             if sddl != hash[key]:
1023                 txt = get_diff_sddls(hash[key], sddl)
1024                 if txt != "":
1025                     message(CHANGESD, "On object %s ACL is different"
1026                                       " \n%s" % (current[i]["dn"], txt))
1027
1028
1029
1030 def fix_partition_sd(samdb, names):
1031     """This function fix the SD for partition containers (basedn, configdn, ...)
1032     This is needed because some provision use to have broken SD on containers
1033
1034     :param samdb: An LDB object pointing to the sam of the current provision
1035     :param names: A list of key provision parameters
1036     """
1037     # First update the SD for the rootdn
1038     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
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_domain_descriptor(names.domainsid)
1044     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1045                                                     "nTSecurityDescriptor")
1046     samdb.modify(delta, ["recalculate_sd:0"])
1047     # Then the config dn
1048     res = samdb.search(expression="objectClass=*", base=str(names.configdn),
1049                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1050                         controls=["search_options:1:2"])
1051     delta = Message()
1052     delta.dn = Dn(samdb, str(res[0]["dn"]))
1053     descr = get_config_descriptor(names.domainsid)
1054     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1055                                                     "nTSecurityDescriptor" )
1056     samdb.modify(delta, ["recalculate_sd:0"])
1057     # Then the schema dn
1058     res = samdb.search(expression="objectClass=*", base=str(names.schemadn),
1059                         scope=SCOPE_BASE, attrs=["dn", "whenCreated"],
1060                         controls=["search_options:1:2"])
1061
1062     delta = Message()
1063     delta.dn = Dn(samdb, str(res[0]["dn"]))
1064     descr = get_schema_descriptor(names.domainsid)
1065     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE,
1066                                                     "nTSecurityDescriptor" )
1067     samdb.modify(delta, ["recalculate_sd:0"])
1068
1069 def rebuild_sd(samdb, names):
1070     """Rebuild security descriptor of the current provision from scratch
1071
1072     During the different pre release of samba4 security descriptors (SD)
1073     were notarly broken (up to alpha11 included)
1074     This function allow to get them back in order, this function make the
1075     assumption that nobody has modified manualy an SD
1076     and so SD can be safely recalculated from scratch to get them right.
1077
1078     :param names: List of key provision parameters"""
1079
1080
1081     hash = {}
1082     res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1083                         scope=SCOPE_SUBTREE, attrs=["dn", "whenCreated"],
1084                         controls=["search_options:1:2"])
1085     for obj in res:
1086         if not (str(obj["dn"]) == str(names.rootdn) or
1087             str(obj["dn"]) == str(names.configdn) or
1088             str(obj["dn"]) == str(names.schemadn)):
1089             hash[str(obj["dn"])] = obj["whenCreated"]
1090
1091     listkeys = hash.keys()
1092     listkeys.sort(dn_sort)
1093
1094     for key in listkeys:
1095         try:
1096             delta = Message()
1097             delta.dn = Dn(samdb, key)
1098             delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE,
1099                                                     "whenCreated" )
1100             samdb.modify(delta, ["recalculate_sd:0"])
1101         except:
1102             # XXX: We should always catch an explicit exception.
1103             # What could go wrong here?
1104             samdb.transaction_cancel()
1105             res = samdb.search(expression="objectClass=*", base=str(names.rootdn),
1106                                 scope=SCOPE_SUBTREE,
1107                                 attrs=["dn", "nTSecurityDescriptor"],
1108                                 controls=["search_options:1:2"])
1109             badsd = ndr_unpack(security.descriptor,
1110                         str(res[0]["nTSecurityDescriptor"]))
1111             print "bad stuff %s" % badsd.as_sddl(names.domainsid)
1112             return
1113
1114 def removeProvisionUSN(samdb):
1115         attrs = [samba.provision.LAST_PROVISION_USN_ATTRIBUTE, "dn"]
1116         entry = samdb.search(expression="dn=@PROVISION", base = "",
1117                                 scope=SCOPE_SUBTREE,
1118                                 controls=["search_options:1:2"],
1119                                 attrs=attrs)
1120         empty = Message()
1121         empty.dn = entry[0].dn
1122         delta = samdb.msg_diff(entry[0], empty)
1123         delta.remove("dn")
1124         delta.dn = entry[0].dn
1125         samdb.modify(delta)
1126
1127 def remove_stored_generated_attrs(paths, creds, session, lp):
1128     """Remove previously stored constructed attributes
1129
1130     :param paths: List of paths for different provision objects
1131                         from the upgraded provision
1132     :param creds: A credential object
1133     :param session: A session object
1134     :param lp: A line parser object
1135     :return: An associative array whose key are the different constructed
1136              attributes and the value the dn where this attributes were found.
1137      """
1138
1139
1140 def simple_update_basesamdb(newpaths, paths, names):
1141     """Update the provision container db: sam.ldb
1142     This function is aimed at very old provision (before alpha9)
1143
1144     :param newpaths: List of paths for different provision objects
1145                         from the reference provision
1146     :param paths: List of paths for different provision objects
1147                         from the upgraded provision
1148     :param names: List of key provision parameters"""
1149
1150     message(SIMPLE, "Copy samdb")
1151     shutil.copy(newpaths.samdb, paths.samdb)
1152
1153     message(SIMPLE, "Update partitions filename if needed")
1154     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1155     configldb = os.path.join(paths.private_dir, "configuration.ldb")
1156     usersldb = os.path.join(paths.private_dir, "users.ldb")
1157     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1158
1159     if not os.path.isdir(samldbdir):
1160         os.mkdir(samldbdir)
1161         os.chmod(samldbdir, 0700)
1162     if os.path.isfile(schemaldb):
1163         shutil.copy(schemaldb, os.path.join(samldbdir,
1164                                             "%s.ldb"%str(names.schemadn).upper()))
1165         os.remove(schemaldb)
1166     if os.path.isfile(usersldb):
1167         shutil.copy(usersldb, os.path.join(samldbdir,
1168                                             "%s.ldb"%str(names.rootdn).upper()))
1169         os.remove(usersldb)
1170     if os.path.isfile(configldb):
1171         shutil.copy(configldb, os.path.join(samldbdir,
1172                                             "%s.ldb"%str(names.configdn).upper()))
1173         os.remove(configldb)
1174
1175
1176 def update_privilege(ref_private_path, cur_private_path):
1177     """Update the privilege database
1178
1179     :param ref_private_path: Path to the private directory of the reference
1180                              provision.
1181     :param cur_private_path: Path to the private directory of the current
1182                              (and to be updated) provision."""
1183     message(SIMPLE, "Copy privilege")
1184     shutil.copy(os.path.join(ref_private_path, "privilege.ldb"),
1185                 os.path.join(cur_private_path, "privilege.ldb"))
1186
1187
1188 def update_samdb(ref_samdb, samdb, names, highestUSN, schema):
1189     """Upgrade the SAM DB contents for all the provision partitions
1190
1191     :param ref_sambdb: An LDB object conntected to the sam.ldb of the reference
1192                        provision
1193     :param samdb: An LDB object connected to the sam.ldb of the update
1194                   provision
1195     :param names: List of key provision parameters
1196     :param highestUSN:  The highest USN modified by provision/upgradeprovision
1197                         last time
1198     :param schema: A Schema object that represent the schema of the provision"""
1199
1200     message(SIMPLE, "Starting update of samdb")
1201     ret = update_partition(ref_samdb, samdb, str(names.rootdn), names,
1202                             schema, highestUSN)
1203     if ret:
1204         message(SIMPLE, "Update of samdb finished")
1205         return 1
1206     else:
1207         message(SIMPLE, "Update failed")
1208         return 0
1209
1210
1211 def copyxattrs(dir, refdir):
1212     """ Copy owner, groups, extended ACL and NT acls from
1213     a reference dir to a destination dir
1214
1215     Both dir are supposed to hold the same files
1216     :param dir: Destination dir
1217     :param refdir: Reference directory"""
1218
1219     noxattr = 0
1220     for root, dirs, files in os.walk(dir, topdown=True):
1221         for name in files:
1222             subdir=root[len(dir):]
1223             ref = os.path.join("%s%s" % (refdir, subdir), name)
1224             statsinfo = os.stat(ref)
1225             tgt = os.path.join(root, name)
1226             try:
1227
1228                 os.chown(tgt, statsinfo.st_uid, statsinfo.st_gid)
1229                 # Get the xattr attributes if any
1230                 try:
1231                     attribute = samba.xattr_native.wrap_getxattr(ref,
1232                                                  xattr.XATTR_NTACL_NAME)
1233                     samba.xattr_native.wrap_setxattr(tgt,
1234                                                  xattr.XATTR_NTACL_NAME,
1235                                                  attribute)
1236                 except:
1237                     noxattr = 1
1238                 attribute = samba.xattr_native.wrap_getxattr(ref,
1239                                                  "system.posix_acl_access")
1240                 samba.xattr_native.wrap_setxattr(tgt,
1241                                                  "system.posix_acl_access",
1242                                                   attribute)
1243             except:
1244                 continue
1245         for name in dirs:
1246             subdir=root[len(dir):]
1247             ref = os.path.join("%s%s" % (refdir, subdir), name)
1248             statsinfo = os.stat(ref)
1249             tgt = os.path.join(root, name)
1250             try:
1251                 os.chown(os.path.join(root, name), statsinfo.st_uid,
1252                           statsinfo.st_gid)
1253                 try:
1254                     attribute = samba.xattr_native.wrap_getxattr(ref,
1255                                                  xattr.XATTR_NTACL_NAME)
1256                     samba.xattr_native.wrap_setxattr(tgt,
1257                                                  xattr.XATTR_NTACL_NAME,
1258                                                  attribute)
1259                 except:
1260                     noxattr = 1
1261                 attribute = samba.xattr_native.wrap_getxattr(ref,
1262                                                  "system.posix_acl_access")
1263                 samba.xattr_native.wrap_setxattr(tgt,
1264                                                  "system.posix_acl_access",
1265                                                   attribute)
1266
1267             except:
1268                 continue
1269
1270
1271 def backup_provision(paths, dir):
1272     """This function backup the provision files so that a rollback
1273     is possible
1274
1275     :param paths: Paths to different objects
1276     :param dir: Directory where to store the backup
1277     """
1278
1279     shutil.copytree(paths.sysvol, os.path.join(dir, "sysvol"))
1280     copyxattrs(os.path.join(dir, "sysvol"), paths.sysvol)
1281     shutil.copy2(paths.samdb, dir)
1282     shutil.copy2(paths.secrets, dir)
1283     shutil.copy2(paths.idmapdb, dir)
1284     shutil.copy2(paths.privilege, dir)
1285     if os.path.isfile(os.path.join(paths.private_dir,"eadb.tdb")):
1286         shutil.copy2(os.path.join(paths.private_dir,"eadb.tdb"), dir)
1287     shutil.copy2(paths.smbconf, dir)
1288     shutil.copy2(os.path.join(paths.private_dir,"secrets.keytab"), dir)
1289
1290     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
1291     if not os.path.isdir(samldbdir):
1292         samldbdir = paths.private_dir
1293         schemaldb = os.path.join(paths.private_dir, "schema.ldb")
1294         configldb = os.path.join(paths.private_dir, "configuration.ldb")
1295         usersldb = os.path.join(paths.private_dir, "users.ldb")
1296         shutil.copy2(schemaldb, dir)
1297         shutil.copy2(usersldb, dir)
1298         shutil.copy2(configldb, dir)
1299     else:
1300         shutil.copytree(samldbdir, os.path.join(dir, "sam.ldb.d"))
1301
1302 def setup_path(file):
1303     return os.path.join(setup_dir, file)
1304
1305 # Synopsis for updateprovision
1306 # 1) get path related to provision to be update (called current)
1307 # 2) open current provision ldbs
1308 # 3) fetch the key provision parameter (domain sid, domain guid, invocationid
1309 #    of the DC ....)
1310 # 4) research of lastProvisionUSN in order to get ranges of USN modified
1311 #    by either upgradeprovision or provision
1312 # 5) creation of a new provision the latest version of provision script
1313 #    (called reference)
1314 # 6) get reference provision paths
1315 # 7) open reference provision ldbs
1316 # 8) setup helpers data that will help the update process
1317 # 9) update the privilege ldb by copying the one of referecence provision to
1318 #    the current provision
1319 # 10)get the oemInfo field, this field contains information about the different
1320 #    provision that have been done
1321 # 11)Depending  on whether oemInfo has the string "alpha9" or alphaxx (x as an
1322 #    integer) or none of this the following things are done
1323 #    A) When alpha9 or alphaxx is present
1324 #       The base sam.ldb file is updated by looking at the difference between
1325 #       referrence one and the current one. Everything is copied with the
1326 #       exception of lastProvisionUSN attributes.
1327 #    B) Other case (it reflect that that provision was done before alpha9)
1328 #       The base sam.ldb of the reference provision is copied over
1329 #       the current one, if necessary ldb related to partitions are moved
1330 #       and renamed
1331 # The highest used USN is fetched so that changed by upgradeprovision
1332 # usn can be tracked
1333 # 12)A Schema object is created, it will be used to provide a complete
1334 #    schema to current provision during update (as the schema of the
1335 #    current provision might not be complete and so won't allow some
1336 #    object to be created)
1337 # 13)Proceed to full update of sam DB (see the separate paragraph about i)
1338 # 14)The secrets db is updated by pull all the difference from the reference
1339 #    provision into the current provision
1340 # 15)As the previous step has most probably modified the password stored in
1341 #    in secret for the current DC, a new password is generated,
1342 #    the kvno is bumped and the entry in samdb is also updated
1343 # 16)For current provision older than alpha9, we must fix the SD a little bit
1344 #    administrator to update them because SD used to be generated with the
1345 #    system account before alpha9.
1346 # 17)The highest usn modified so far is searched in the database it will be
1347 #    the upper limit for usn modified during provision.
1348 #    This is done before potential SD recalculation because we do not want
1349 #    SD modified during recalculation to be marked as modified during provision
1350 #    (and so possibly remplaced at next upgradeprovision)
1351 # 18)Rebuilt SD if the flag indicate to do so
1352 # 19)Check difference between SD of reference provision and those of the
1353 #    current provision. The check is done by getting the sddl representation
1354 #    of the SD. Each sddl in chuncked into parts (user,group,dacl,sacl)
1355 #    Each part is verified separetly, for dacl and sacl ACL is splited into
1356 #    ACEs and each ACE is verified separately (so that a permutation in ACE
1357 #    didn't raise as an error).
1358 # 20)The oemInfo field is updated to add information about the fact that the
1359 #    provision has been updated by the upgradeprovision version xxx
1360 #    (the version is the one obtained when starting samba with the --version
1361 #    parameter)
1362 # 21)Check if the current provision has all the settings needed for dynamic
1363 #    DNS update to work (that is to say the provision is newer than
1364 #    january 2010). If not dns configuration file from reference provision
1365 #    are copied in a sub folder and the administrator is invited to
1366 #    do what is needed.
1367 # 22)If the lastProvisionUSN attribute was present it is updated to add
1368 #    the range of usns modified by the current upgradeprovision
1369
1370
1371 # About updating the sam DB
1372 # The update takes place in update_partition function
1373 # This function read both current and reference provision and list all
1374 # the available DN of objects
1375 # If the string representation of a DN in reference provision is
1376 # equal to the string representation of a DN in current provision
1377 # (without taking care of case) then the object is flaged as being
1378 # present. If the object is not present in current provision the object
1379 # is being flaged as missing in current provision. Object present in current
1380 # provision but not in reference provision are ignored.
1381 # Once the list of objects present and missing is done, the deleted object
1382 # containers are created in the differents partitions (if missing)
1383 #
1384 # Then the function add_missing_entries is called
1385 # This function will go through the list of missing entries by calling
1386 # add_missing_object for the given object. If this function returns 0
1387 # it means that the object needs some other object in order to be created
1388 # The object is reappended at the end of the list to be created later
1389 # (and preferably after all the needed object have been created)
1390 # The function keeps on looping on the list of object to be created until
1391 # it's empty or that the number of defered creation is equal to the number
1392 # of object that still needs to be created.
1393
1394 # The function add_missing_object will first check if the object can be created.
1395 # That is to say that it didn't depends other not yet created objects
1396 # If requisit can't be fullfilled it exists with 0
1397 # Then it will try to create the missing entry by creating doing
1398 # an ldb_message_diff between the object in the reference provision and
1399 # an empty object.
1400 # This resulting object is filtered to remove all the back link attribute
1401 # (ie. memberOf) as they will be created by the other linked object (ie.
1402 # the one with the member attribute)
1403 # All attributes specified in the hashAttrNotCopied associative array are
1404 # also removed it's most of the time generated attributes
1405
1406 # After missing entries have been added the update_partition function will
1407 # take care of object that exist but that need some update.
1408 # In order to do so the function update_present is called with the list
1409 # of object that are present in both provision and that might need an update.
1410
1411 # This function handle first case mismatch so that the DN in the current
1412 # provision have the same case as in reference provision
1413
1414 # It will then construct an associative array consiting of attributes as
1415 # key and invocationid as value( if the originating invocation id is
1416 # different from the invocation id of the current DC the value is -1 instead).
1417
1418 # If the range of provision modified attributes is present, the function will
1419 # use the replMetadataProperty update method which is the following:
1420 #  Removing attributes that should not be updated: rIDAvailablePool, objectSid,
1421 #   creationTime, msDs-KeyVersionNumber, oEMInformation
1422 #  Check for each attribute if its usn is within one of the modified by
1423 #   provision range and if its originating id is the invocation id of the
1424 #   current DC, then validate the update from reference to current.
1425 #   If not or if there is no replMetatdataProperty for this attribute then we
1426 #   do not update it.
1427 # Otherwise (case the range of provision modified attribute is not present) it
1428 # use the following process:
1429 #  All attributes that need to be added are accepted at the exeption of those
1430 #   listed in hashOverwrittenAtt, in this case the attribute needs to have the
1431 #   correct flags specified.
1432 #  For attributes that need to be modified or removed, a check is performed
1433 #  in OverwrittenAtt, if the attribute is present and the modification flag
1434 #  (remove, delete) is one of those listed for this attribute then modification
1435 #  is accepted. For complicated handling of attribute update, the control is passed
1436 #  to handle_special_case
1437
1438
1439
1440 if __name__ == '__main__':
1441     global defSDmodified
1442     defSDmodified = False
1443     # From here start the big steps of the program
1444     # 1) First get files paths
1445     paths = get_paths(param, smbconf=smbconf)
1446     paths.setup = setup_dir
1447     # Get ldbs with the system session, it is needed for searching
1448     # provision parameters
1449     session = system_session()
1450
1451     # This variable will hold the last provision USN once if it exists.
1452     minUSN = 0
1453     # 2)
1454     ldbs = get_ldbs(paths, creds, session, lp)
1455     backupdir = tempfile.mkdtemp(dir=paths.private_dir,
1456                                     prefix="backupprovision")
1457     backup_provision(paths, backupdir)
1458     try:
1459         ldbs.startTransactions()
1460
1461         # 3) Guess all the needed names (variables in fact) from the current
1462         # provision.
1463         names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
1464                                                 paths, smbconf, lp)
1465         # 4)
1466         lastProvisionUSNs = get_last_provision_usn(ldbs.sam)
1467         if lastProvisionUSNs is not None:
1468             message(CHANGE,
1469                 "Find a last provision USN, %d range(s)" % len(lastProvisionUSNs))
1470
1471         # Objects will be created with the admin session
1472         # (not anymore system session)
1473         adm_session = admin_session(lp, str(names.domainsid))
1474         # So we reget handle on objects
1475         # ldbs = get_ldbs(paths, creds, adm_session, lp)
1476
1477         if not sanitychecks(ldbs.sam, names):
1478             message(SIMPLE, "Sanity checks for the upgrade fails, checks messages"
1479                             " and correct them before rerunning upgradeprovision")
1480             sys.exit(1)
1481
1482         # Let's see provision parameters
1483         print_provision_key_parameters(names)
1484
1485         # 5) With all this information let's create a fresh new provision used as
1486         # reference
1487         message(SIMPLE, "Creating a reference provision")
1488         provisiondir = tempfile.mkdtemp(dir=paths.private_dir,
1489                                         prefix="referenceprovision")
1490         newprovision(names, setup_dir, creds, session, smbconf, provisiondir,
1491                         provision_logger)
1492
1493         # TODO
1494         # 6) and 7)
1495         # We need to get a list of object which SD is directly computed from
1496         # defaultSecurityDescriptor.
1497         # This will allow us to know which object we can rebuild the SD in case
1498         # of change of the parent's SD or of the defaultSD.
1499         # Get file paths of this new provision
1500         newpaths = get_paths(param, targetdir=provisiondir)
1501         new_ldbs = get_ldbs(newpaths, creds, session, lp)
1502         new_ldbs.startTransactions()
1503
1504         # 8) Populate some associative array to ease the update process
1505         # List of attribute which are link and backlink
1506         populate_links(new_ldbs.sam, names.schemadn)
1507         # List of attribute with ASN DN synthax)
1508         populate_dnsyntax(new_ldbs.sam, names.schemadn)
1509         # 9)
1510         update_privilege(newpaths.private_dir, paths.private_dir)
1511         # 10)
1512         oem = getOEMInfo(ldbs.sam, str(names.rootdn))
1513         # Do some modification on sam.ldb
1514         ldbs.groupedCommit()
1515         new_ldbs.groupedCommit()
1516
1517         # 11)
1518         if re.match(".*alpha((9)|(\d\d+)).*", str(oem)):
1519             # 11) A
1520             # Starting from alpha9 we can consider that the structure is quite ok
1521             # and that we should do only dela
1522             delta_update_basesamdb(newpaths.samdb, paths.samdb, creds, session, lp, message)
1523         else:
1524             # 11) B
1525             simple_update_basesamdb(newpaths, paths, names)
1526             ldbs = get_ldbs(paths, creds, session, lp)
1527             removeProvisionUSN(ldbs.sam)
1528
1529         ldbs.startTransactions()
1530         minUSN = int(str(get_max_usn(ldbs.sam, str(names.rootdn)))) + 1
1531         new_ldbs.startTransactions()
1532
1533         # 12)
1534         schema = Schema(setup_path, names.domainsid, schemadn=str(names.schemadn),
1535                          serverdn=str(names.serverdn))
1536
1537         # 13)
1538         if opts.full:
1539             if not update_samdb(new_ldbs.sam, ldbs.sam, names, lastProvisionUSNs,
1540                                 schema):
1541                 message(SIMPLE, "Rollbacking every changes. Check the reason"
1542                                 " of the problem")
1543                 message(SIMPLE, "In any case your system as it was before"
1544                                 " the upgrade")
1545                 ldbs.groupedRollback()
1546                 new_ldbs.groupedRollback()
1547                 shutil.rmtree(provisiondir)
1548                 sys.exit(1)
1549         # 14)
1550         update_secrets(new_ldbs.secrets, ldbs.secrets, message)
1551         # 15)
1552         message(SIMPLE, "Update machine account")
1553         update_machine_account_password(ldbs.sam, ldbs.secrets, names)
1554
1555         # 16) SD should be created with admin but as some previous acl were so wrong
1556         # that admin can't modify them we have first to recreate them with the good
1557         # form but with system account and then give the ownership to admin ...
1558         if not re.match(r'.*alpha(9|\d\d+)', str(oem)):
1559             message(SIMPLE, "Fixing old povision SD")
1560             fix_partition_sd(ldbs.sam, names)
1561             rebuild_sd(ldbs.sam, names)
1562
1563         # We calculate the max USN before recalculating the SD because we might
1564         # touch object that have been modified after a provision and we do not
1565         # want that the next upgradeprovision thinks that it has a green light
1566         # to modify them
1567
1568         # 17)
1569         maxUSN = get_max_usn(ldbs.sam, str(names.rootdn))
1570
1571         # 18) We rebuild SD only if defaultSecurityDescriptor is modified
1572         # But in fact we should do it also if one object has its SD modified as
1573         # child might need rebuild
1574         if defSDmodified:
1575             message(SIMPLE, "Updating SD")
1576             ldbs.sam.set_session_info(adm_session)
1577             # Alpha10 was a bit broken still
1578             if re.match(r'.*alpha(\d|10)', str(oem)):
1579                 fix_partition_sd(ldbs.sam, names)
1580             rebuild_sd(ldbs.sam, names)
1581
1582         # 19)
1583         # Now we are quite confident in the recalculate process of the SD, we make
1584         # it optional.
1585         # Also the check must be done in a clever way as for the moment we just
1586         # compare SDDL
1587         if opts.debugchangesd:
1588             check_updated_sd(new_ldbs.sam, ldbs.sam, names)
1589
1590         # 20)
1591         updateOEMInfo(ldbs.sam, str(names.rootdn))
1592         # 21)
1593         check_for_DNS(newpaths.private_dir, paths.private_dir)
1594         # 22)
1595         if lastProvisionUSNs is not None:
1596             update_provision_usn(ldbs.sam, minUSN, maxUSN)
1597         if opts.full and (names.policyid is None or names.policyid_dc is None):
1598             update_policyids(names, ldbs.sam)
1599         if opts.full or opts.resetfileacl:
1600             try:
1601                 update_gpo(paths, ldbs.sam, names, lp, message, 1)
1602             except ProvisioningError, e:
1603                 message(ERROR, "The policy for domain controller is missing,"
1604                                " you should restart upgradeprovision with --full")
1605         else:
1606             try:
1607                 update_gpo(paths, ldbs.sam, names, lp, message, 0)
1608             except ProvisioningError, e:
1609                 message(ERROR, "The policy for domain controller is missing,"
1610                                " you should restart upgradeprovision with --full")
1611         ldbs.groupedCommit()
1612         new_ldbs.groupedCommit()
1613         message(SIMPLE, "Upgrade finished !")
1614         # remove reference provision now that everything is done !
1615         shutil.rmtree(provisiondir)
1616     except StandardError, err:
1617         message(ERROR,"A problem has occured when trying to upgrade your provision,"
1618                       " a full backup is located at %s" % backupdir)
1619         if opts.changeall:
1620             (typ, val, tb) = sys.exc_info()
1621             traceback.print_exception(typ, val, tb)