upgradeprovision: split the big script to put reusable functions appart
[mat/samba.git] / source4 / scripting / bin / upgradeprovision
1 #!/usr/bin/python
2 #
3 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
4 #
5 # Based on provision a Samba4 server by
6 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
7 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
8 #
9 #
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
23
24 import getopt
25 import shutil
26 import optparse
27 import os
28 import sys
29 import random
30 import string
31 import re
32 import tempfile
33 # Allow to run from s4 source directory (without installing samba)
34 sys.path.insert(0, "bin/python")
35
36
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 samba import Ldb
42 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE, MessageElement, Message, Dn
43 from samba.samdb import SamDB
44 from samba import param
45 from samba import glue
46 from samba.misc import messageEltFlagToString
47 from samba.provision import  find_setup_dir, get_domain_descriptor, get_config_descriptor, secretsdb_self_join
48 from samba.provisionexceptions import ProvisioningError
49 from samba.schema import get_dnsyntax_attributes, get_linked_attributes, Schema, get_schema_descriptor
50 from samba.dcerpc import misc, security
51 from samba.ndr import ndr_pack, ndr_unpack
52 from samba.dcerpc.misc import SEC_CHAN_BDC
53 from samba.upgradehelpers import dn_sort, get_paths, newprovision, find_provision_key_parameters, rmall
54
55 never=0
56 replace=2^FLAG_MOD_REPLACE
57 add=2^FLAG_MOD_ADD
58 delete=2^FLAG_MOD_DELETE
59
60 #Errors are always logged
61 ERROR =         -1
62 SIMPLE =        0x00
63 CHANGE =        0x01
64 CHANGESD =      0x02
65 GUESS =         0x04
66 PROVISION =     0x08
67 CHANGEALL =     0xff
68
69 # Attributes that are never copied from the reference provision (even if they
70 # do not exist in the destination object).
71 # This is most probably because they are populated automatcally when object is
72 # created
73 # This also apply to imported object from reference provision
74 hashAttrNotCopied = {   "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,
75                                                 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,
76                                                 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
77                                                 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
78                                                 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
79                                                 "maxPwdAge":1, "mail":1, "secret":1,"possibleInferiors":1, "sAMAccountType":1}
80
81 # Usually for an object that already exists we do not overwrite attributes as
82 # they might have been changed for good reasons. Anyway for a few of them it's
83 # mandatory to replace them otherwise the provision will be broken somehow.
84 hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,"systemOnly":replace, "searchFlags":replace,
85                                                 "mayContain":replace,  "systemFlags":replace,"description":replace,
86                                                 "oEMInformation":replace, "operatingSystemVersion":replace, "adminPropertyPages":replace,
87                                                 "defaultSecurityDescriptor": replace,"wellKnownObjects":replace,"privilege":delete,"groupType":replace,
88                                                 "rIDAvailablePool": never}
89
90
91 backlinked = []
92 dn_syntax_att = []
93 def define_what_to_log(opts):
94         what = 0
95         if opts.debugchange:
96                 what = what | CHANGE
97         if opts.debugchangesd:
98                 what = what | CHANGESD
99         if opts.debugguess:
100                 what = what | GUESS
101         if opts.debugprovision:
102                 what = what | PROVISION
103         if opts.debugall:
104                 what = what | CHANGEALL
105         return what
106
107
108 parser = optparse.OptionParser("provision [options]")
109 sambaopts = options.SambaOptions(parser)
110 parser.add_option_group(sambaopts)
111 parser.add_option_group(options.VersionOptions(parser))
112 credopts = options.CredentialsOptions(parser)
113 parser.add_option_group(credopts)
114 parser.add_option("--setupdir", type="string", metavar="DIR",
115                                         help="directory with setup files")
116 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
117 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
118 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
119 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
120 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
121 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
122
123 opts = parser.parse_args()[0]
124
125 whatToLog = define_what_to_log(opts)
126
127 def messageprovision(text):
128         """print a message if quiet is not set."""
129         if opts.debugprovision or opts.debugall:
130                 print text
131
132 def message(what,text):
133         """print a message if quiet is not set."""
134         if (whatToLog & what) or (what <= 0 ):
135                 print text
136
137 if len(sys.argv) == 1:
138         opts.interactive = True
139 lp = sambaopts.get_loadparm()
140 smbconf = lp.configfile
141
142 creds = credopts.get_credentials(lp)
143 creds.set_kerberos_state(DONT_USE_KERBEROS)
144 setup_dir = opts.setupdir
145 if setup_dir is None:
146     setup_dir = find_setup_dir()
147
148 session = system_session()
149
150 # simple helper to allow back and forth rename
151 def identic_rename(ldbobj,dn):
152         (before,sep,after)=str(dn).partition('=')
153         ldbobj.rename(dn,Dn(ldbobj,"%s=foo%s"%(before,after)))
154         ldbobj.rename(Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
155
156 # Create an array of backlinked attributes
157 def populate_backlink(newpaths,creds,session,schemadn):
158         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
159         linkedAttHash = get_linked_attributes(Dn(newsam_ldb,str(schemadn)),newsam_ldb)
160         backlinked.extend(linkedAttHash.values())
161
162 # Create an array of  attributes with a dn synthax (2.5.5.1)
163 def populate_dnsyntax(newpaths,creds,session,schemadn):
164         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
165         res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=Dn(newsam_ldb,str(schemadn)),
166                                                         scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"])
167         for elem in res:
168                 dn_syntax_att.append(elem["lDAPDisplayName"])
169
170
171
172 def sanitychecks(credentials,session_info,names,paths):
173         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
174         # First update the SD for the rootdn
175         sam_ldb.set_session_info(session)
176         res = sam_ldb.search(expression="objectClass=ntdsdsa",base=str(names.configdn), scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
177         if len(res) == 0:
178                 print "No DC found, your provision is most probalby hardly broken !"
179                 return 0
180         elif len(res) != 1:
181                 print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
182 domain with more than one DC, please demote the other DC before upgrading"%len(res)
183                 return 0
184         else:
185                 return 1
186
187
188 # Debug a little bit
189 def print_provision_key_parameters(names):
190         message(GUESS, "rootdn      :"+str(names.rootdn))
191         message(GUESS, "configdn    :"+str(names.configdn))
192         message(GUESS, "schemadn    :"+str(names.schemadn))
193         message(GUESS, "serverdn    :"+str(names.serverdn))
194         message(GUESS, "netbiosname :"+names.netbiosname)
195         message(GUESS, "defaultsite :"+names.sitename)
196         message(GUESS, "dnsdomain   :"+names.dnsdomain)
197         message(GUESS, "hostname    :"+names.hostname)
198         message(GUESS, "domain      :"+names.domain)
199         message(GUESS, "realm       :"+names.realm)
200         message(GUESS, "invocationid:"+names.invocation)
201         message(GUESS, "policyguid  :"+names.policyid)
202         message(GUESS, "policyguiddc:"+str(names.policyid_dc))
203         message(GUESS, "domainsid   :"+str(names.domainsid))
204         message(GUESS, "domainguid  :"+names.domainguid)
205         message(GUESS, "ntdsguid    :"+names.ntdsguid)
206         message(GUESS, "domainlevel :"+str(names.domainlevel))
207
208 # Check for security descriptors modifications return 1 if it is and 0 otherwise
209 # it also populate hash structure for later use in the upgrade process
210 def handle_security_desc(ischema,att,msgElt,hashallSD,old,new):
211         if ischema == 1 and att == "defaultSecurityDescriptor"  and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
212                 hashSD = {}
213                 hashSD["oldSD"] = old[0][att]
214                 hashSD["newSD"] = new[0][att]
215                 hashallSD[str(old[0].dn)] = hashSD
216                 return 0
217         if att == "nTSecurityDescriptor"  and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
218                 if ischema == 0:
219                         hashSD = {}
220                         hashSD["oldSD"] =  ndr_unpack(security.descriptor,str(old[0][att]))
221                         hashSD["newSD"] =  ndr_unpack(security.descriptor,str(new[0][att]))
222                         hashallSD[str(old[0].dn)] = hashSD
223                 return 1
224         return 0
225
226 # Handle special cases ... That's when we want to update a particular attribute
227 # only, e.g. if it has a certain value or if it's for a certain object or
228 # a class of object.
229 # It can be also if we want to do a merge of value instead of a simple replace
230 def handle_special_case(att,delta,new,old,ischema):
231         flag = delta.get(att).flags()
232         if (att == "gPLink" or att == "gPCFileSysPath") and flag ==  FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
233                 delta.remove(att)
234                 return 1
235         if att == "forceLogoff":
236                 ref=0x8000000000000000
237                 oldval=int(old[0][att][0])
238                 newval=int(new[0][att][0])
239                 ref == old and ref == abs(new)
240                 return 1
241         if (att == "adminDisplayName" or att == "adminDescription") and ischema:
242                 return 1
243         if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn)) and att == "defaultObjectCategory" and flag  == FLAG_MOD_REPLACE):
244                 return 1
245
246         if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag  == FLAG_MOD_REPLACE):
247                 return 1
248         if ( (att == "member" or att == "servicePrincipalName") and flag  == FLAG_MOD_REPLACE):
249
250                 hash = {}
251                 newval = []
252                 changeDelta=0
253                 for elem in old[0][att]:
254                         hash[str(elem)]=1
255                         newval.append(str(elem))
256
257                 for elem in new[0][att]:
258                         if not hash.has_key(str(elem)):
259                                 changeDelta=1
260                                 newval.append(str(elem))
261                 if changeDelta == 1:
262                         delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
263                 else:
264                         delta.remove(att)
265                 return 1
266         if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag  == FLAG_MOD_REPLACE):
267                 return 1
268         if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
269                 return 1
270         return 0
271
272 def update_secrets(newpaths,paths,creds,session):
273         message(SIMPLE,"update secrets.ldb")
274         newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
275         secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
276         reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
277         current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
278         delta = secrets_ldb.msg_diff(current[0],reference[0])
279         delta.dn = current[0].dn
280         secrets_ldb.modify(delta)
281
282         newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
283         secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
284         reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
285         current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
286         hash_new = {}
287         hash = {}
288         listMissing = []
289         listPresent = []
290
291         empty = Message()
292         for i in range(0,len(reference)):
293                 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
294
295         # Create a hash for speeding the search of existing object in the
296         # current provision
297         for i in range(0,len(current)):
298                 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
299
300         for k in hash_new.keys():
301                 if not hash.has_key(k):
302                         listMissing.append(hash_new[k])
303                 else:
304                         listPresent.append(hash_new[k])
305         for entry in listMissing:
306                 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
307                 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
308                 delta = secrets_ldb.msg_diff(empty,reference[0])
309                 for att in hashAttrNotCopied.keys():
310                         delta.remove(att)
311                 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
312                 for att in delta:
313                         message(CHANGE," Adding attribute %s"%att)
314                 delta.dn = reference[0].dn
315                 secrets_ldb.add(delta)
316
317         for entry in listPresent:
318                 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
319                 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
320                 delta = secrets_ldb.msg_diff(current[0],reference[0])
321                 i=0
322                 for att in hashAttrNotCopied.keys():
323                         delta.remove(att)
324                 for att in delta:
325                         i = i + 1
326
327                         if att == "name":
328                                 message(CHANGE,"Found attribute name on  %s, must rename the DN "%(current[0].dn))
329                                 identic_rename(secrets_ldb,reference[0].dn)
330                         else:
331                                 delta.remove(att)
332
333
334         for entry in listPresent:
335                 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
336                 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
337                 delta = secrets_ldb.msg_diff(current[0],reference[0])
338                 i=0
339                 for att in hashAttrNotCopied.keys():
340                         delta.remove(att)
341                 for att in delta:
342                         i = i + 1
343                         if att != "dn":
344                                 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
345
346                 delta.dn = current[0].dn
347                 secrets_ldb.modify(delta)
348
349 def dump_denied_change(dn,att,flagtxt,current,reference):
350         message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
351         if att != "objectSid" :
352                 i = 0
353                 for e in range(0,len(current)):
354                         message(CHANGE,"old %d : %s"%(i,str(current[e])))
355                         i=i+1
356                 if reference != None:
357                         i = 0
358                         for e in range(0,len(reference)):
359                                         message(CHANGE,"new %d : %s"%(i,str(reference[e])))
360                                         i=i+1
361         else:
362                 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
363                 message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
364
365 #This function is for doing case by case treatment on special object
366
367 def handle_special_add(sam_ldb,dn,names):
368         dntoremove=None
369         if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
370                 #This entry was misplaced lets remove it if it exists
371                 dntoremove="CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
372
373         if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
374                 #This entry was misplaced lets remove it if it exists
375                 dntoremove="CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
376
377         if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
378                 #This entry was misplaced lets remove it if it exists
379                 dntoremove="CN=Event Log Readers,CN=Users,%s"%names.rootdn
380
381         if dntoremove != None:
382                 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
383                 if len(res) > 0:
384                         message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
385                         sam_ldb.delete(res[0]["dn"])
386
387 #Check if the one of the dn in the listdn will be created after the current dn
388 #hash is indexed by dn to be created, with each key is associated the creation order
389 #First dn to be created has the creation order 0, second has 1, ...
390 #Index contain the current creation order
391 def check_dn_nottobecreated(hash,index,listdn):
392         if listdn == None:
393                 return None
394         for dn in listdn:
395                 key = str(dn).lower()
396                 if hash.has_key(key) and hash[key] > index:
397                         return str(dn)
398         return None
399
400 #This function tries to add the missing object "dn" if this object depends on some others
401 # the function returns 0, if the object was created 1 is returned
402 def add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hash,index):
403         handle_special_add(sam_ldb,dn,names)
404         reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
405                                         scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
406         empty = Message()
407         delta = sam_ldb.msg_diff(empty,reference[0])
408         for att in hashAttrNotCopied.keys():
409                 delta.remove(att)
410         for att in backlinked:
411                 delta.remove(att)
412         depend_on_yettobecreated = None
413         for att in dn_syntax_att:
414                 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
415                 if depend_on_yet_tobecreated != None:
416                         message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
417                                                         %(str(dn),depend_on_yet_tobecreated,str(att)))
418                         return 0
419         delta.dn = dn
420         message(CHANGE,"Object %s will be added"%dn)
421         sam_ldb.add(delta,["relax:0"])
422         return 1
423
424 def gen_dn_index_hash(listMissing):
425         hash = {}
426         for i in range(0,len(listMissing)):
427                 hash[str(listMissing[i]).lower()] = i
428         return hash
429
430 def add_missing_entries(newsam_ldb,sam_ldb,names,basedn,list):
431         listMissing = []
432         listDefered = list
433
434         while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
435                 index = 0
436                 listMissing = listDefered
437                 listDefered = []
438                 hashMissing = gen_dn_index_hash(listMissing)
439                 for dn in listMissing:
440                         ret =  add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
441                         index = index + 1
442                         if ret == 0:
443                                 #DN can't be created because it depends on some other DN in the list
444                                 listDefered.append(dn)
445         if len(listDefered) != 0:
446                 raise ProvisioningError("Unable to insert missing elements: circular references")
447
448
449
450
451 # Check difference between the current provision and the reference provision.
452 # It looks for all objects which base DN is name. If ischema is "false" then
453 # the scan is done in cross partition mode.
454 # If "ischema" is true, then special handling is done for dealing with schema
455 def check_diff_name(newpaths,paths,creds,session,basedn,names,ischema):
456         hash_new = {}
457         hash = {}
458         hashallSD = {}
459         listMissing = []
460         listPresent = []
461         reference = []
462         current = []
463         # Connect to the reference provision and get all the attribute in the
464         # partition referred by name
465         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
466         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
467         sam_ldb.transaction_start()
468         if ischema:
469                 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
470                 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
471         else:
472                 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
473                 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
474
475         sam_ldb.transaction_commit()
476         # Create a hash for speeding the search of new object
477         for i in range(0,len(reference)):
478                 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
479
480         # Create a hash for speeding the search of existing object in the
481         # current provision
482         for i in range(0,len(current)):
483                 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
484
485         for k in hash_new.keys():
486                 if not hash.has_key(k):
487                         print hash_new[k]
488                         listMissing.append(hash_new[k])
489                 else:
490                         listPresent.append(hash_new[k])
491
492         # Sort the missing object in order to have object of the lowest level
493         # first (which can be containers for higher level objects)
494         listMissing.sort(dn_sort)
495         listPresent.sort(dn_sort)
496
497         if ischema:
498                 # The following lines (up to the for loop) is to load the up to
499                 # date schema into our current LDB
500                 # a complete schema is needed as the insertion of attributes
501                 # and class is done against it
502                 # and the schema is self validated
503                 # The double ldb open and schema validation is taken from the
504                 # initial provision script
505                 # it's not certain that it is really needed ....
506                 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
507                 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
508                 # Load the schema from the one we computed earlier
509                 sam_ldb.set_schema_from_ldb(schema.ldb)
510                 # And now we can connect to the DB - the schema won't be loaded
511                 # from the DB
512                 sam_ldb.connect(paths.samdb)
513         else:
514                 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
515
516         sam_ldb.transaction_start()
517
518         message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
519         add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
520         changed = 0
521         for dn in listPresent:
522                 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
523                 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
524                 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
525                         message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
526                         identic_rename(sam_ldb,reference[0].dn)
527                         current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
528
529                 delta = sam_ldb.msg_diff(current[0],reference[0])
530                 for att in hashAttrNotCopied.keys():
531                         delta.remove(att)
532                 for att in backlinked:
533                         delta.remove(att)
534                 delta.remove("parentGUID")
535                 nb = 0
536                 
537                 for att in delta:
538                         msgElt = delta.get(att)
539                         if att == "dn":
540                                 continue
541                         if att == "name":
542                                 delta.remove(att)
543                                 continue
544                         if handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
545                                 delta.remove(att)
546                                 continue
547                         if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
548                                 if  hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
549                                         delta.remove(att)
550                                         continue
551                                 if  handle_special_case(att,delta,reference,current,ischema)==0 and msgElt.flags()!=FLAG_MOD_ADD:
552                                         i = 0
553                                         if opts.debugchange or opts.debugall:
554                                                 try:
555                                                         dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
556                                                 except:
557                                                         dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
558                                         delta.remove(att)
559                 delta.dn = dn
560                 if len(delta.items()) >1:
561                         attributes=",".join(delta.keys())
562                         message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
563                         changed = changed + 1
564                         sam_ldb.modify(delta)
565
566         sam_ldb.transaction_commit()
567         message(SIMPLE,"There are %d changed objects"%(changed))
568         return hashallSD
569
570 # Check that SD are correct
571 def check_updated_sd(newpaths,paths,creds,session,names):
572         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
573         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
574         reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
575         current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
576         hash_new = {}
577         for i in range(0,len(reference)):
578                 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
579
580         for i in range(0,len(current)):
581                 key = str(current[i]["dn"]).lower()
582                 if hash_new.has_key(key):
583                         sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
584                         if sddl != hash_new[key]:
585                                 print "%s new sddl/sddl in ref"%key
586                                 print "%s\n%s"%(sddl,hash_new[key])
587
588 # Simple update method for updating the SD that rely on the fact that nobody
589 # should have modified the SD
590 # This assumption is safe right now (alpha9) but should be removed asap
591 def update_sd(paths,creds,session,names):
592         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
593         sam_ldb.transaction_start()
594         # First update the SD for the rootdn
595         sam_ldb.set_session_info(session)
596         res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
597         delta = Message()
598         delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
599         descr = get_domain_descriptor(names.domainsid)
600         delta["nTSecurityDescriptor"] = MessageElement( descr,FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
601         sam_ldb.modify(delta,["recalculate_sd:0"])
602         # Then the config dn
603         res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
604         delta = Message()
605         delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
606         descr = get_config_descriptor(names.domainsid)
607         delta["nTSecurityDescriptor"] = MessageElement( descr,FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
608         sam_ldb.modify(delta,["recalculate_sd:0"])
609         # Then the schema dn
610         res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
611         delta = Message()
612         delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
613         descr = get_schema_descriptor(names.domainsid)
614         delta["nTSecurityDescriptor"] = MessageElement( descr,FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
615         sam_ldb.modify(delta,["recalculate_sd:0"])
616
617         # Then the rest
618         hash = {}
619         res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
620         for obj in res:
621                 if not (str(obj["dn"]) == str(names.rootdn) or
622                         str(obj["dn"]) == str(names.configdn) or \
623                         str(obj["dn"]) == str(names.schemadn)):
624                         hash[str(obj["dn"])] = obj["whenCreated"]
625
626         listkeys = hash.keys()
627         listkeys.sort(dn_sort)
628
629         for key in listkeys:
630                 try:
631                         delta = Message()
632                         delta.dn = Dn(sam_ldb,key)
633                         delta["whenCreated"] = MessageElement( hash[key],FLAG_MOD_REPLACE,"whenCreated" )
634                         sam_ldb.modify(delta,["recalculate_sd:0"])
635                 except:
636                         sam_ldb.transaction_cancel()
637                         res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
638                         print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
639                         return
640         sam_ldb.transaction_commit()
641
642
643 def update_basesamdb(newpaths,paths,names):
644         message(SIMPLE,"Copy samdb")
645         shutil.copy(newpaths.samdb,paths.samdb)
646
647         message(SIMPLE,"Update partitions filename if needed")
648         schemaldb=os.path.join(paths.private_dir,"schema.ldb")
649         configldb=os.path.join(paths.private_dir,"configuration.ldb")
650         usersldb=os.path.join(paths.private_dir,"users.ldb")
651         samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
652
653         if not os.path.isdir(samldbdir):
654                 os.mkdir(samldbdir)
655                 os.chmod(samldbdir,0700)
656         if os.path.isfile(schemaldb):
657                 shutil.copy(schemaldb,os.path.join(samldbdir,"%s.ldb"%str(names.schemadn).upper()))
658                 os.remove(schemaldb)
659         if os.path.isfile(usersldb):
660                 shutil.copy(usersldb,os.path.join(samldbdir,"%s.ldb"%str(names.rootdn).upper()))
661                 os.remove(usersldb)
662         if os.path.isfile(configldb):
663                 shutil.copy(configldb,os.path.join(samldbdir,"%s.ldb"%str(names.configdn).upper()))
664                 os.remove(configldb)
665
666 def update_privilege(newpaths,paths):
667         message(SIMPLE,"Copy privilege")
668         shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb"))
669
670 # For each partition check the differences
671 def update_samdb(newpaths,paths,creds,session,names):
672
673         message(SIMPLE, "Doing schema update")
674         hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
675         message(SIMPLE,"Done with schema update")
676         message(SIMPLE,"Scanning whole provision for updates and additions")
677         hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
678         message(SIMPLE,"Done with scanning")
679
680 def update_machine_account_password(paths,creds,session,names):
681
682         secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
683         secrets_ldb.transaction_start()
684         secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
685         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
686         sam_ldb.transaction_start()
687         if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
688                 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
689                 assert(len(res) == 1)
690
691                 msg = ldb.Message(res[0].dn)
692                 machinepass = glue.generate_random_password(128, 255)
693                 msg["userPassword"] = ldb.MessageElement(machinepass, ldb.FLAG_MOD_REPLACE, "userPassword")
694                 sam_ldb.modify(msg)
695
696                 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
697                                      attrs=["msDs-keyVersionNumber"])
698                 assert(len(res) == 1)
699                 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
700
701                 secretsdb_self_join(secrets_ldb, domain=names.domain,
702                                     realm=names.realm,
703                                     domainsid=names.domainsid,
704                                     dnsdomain=names.dnsdomain,
705                                     netbiosname=names.netbiosname,
706                                     machinepass=machinepass,
707                                     key_version_number=kvno,
708                                     secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
709                 sam_ldb.transaction_prepare_commit()
710                 secrets_ldb.transaction_prepare_commit()
711                 sam_ldb.transaction_commit()
712                 secrets_ldb.transaction_commit()
713         else:
714                 secrets_ldb.transaction_cancel()
715
716 def setup_path(file):
717         return os.path.join(setup_dir, file)
718 # From here start the big steps of the program
719 # First get files paths
720 paths=get_paths(param,smbconf=smbconf)
721 paths.setup = setup_dir
722 # Guess all the needed names (variables in fact) from the current
723 # provision.
724
725 names = find_provision_key_parameters(param,creds,session,paths,smbconf)
726 if not sanitychecks(creds,session,names,paths):
727         message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
728         sys.exit(1)
729 # Let's see them
730 print_provision_key_parameters(names)
731 # With all this information let's create a fresh new provision used as reference
732 message(SIMPLE,"Creating a reference provision")
733 provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
734 newprovision(names,setup_dir,creds,session,smbconf,provisiondir,messageprovision)
735 # Get file paths of this new provision
736 newpaths = get_paths(param,targetdir=provisiondir)
737 populate_backlink(newpaths,creds,session,names.schemadn)
738 populate_dnsyntax(newpaths,creds,session,names.schemadn)
739 # Check the difference
740 update_basesamdb(newpaths,paths,names)
741
742 if opts.full:
743         update_samdb(newpaths,paths,creds,session,names)
744 update_secrets(newpaths,paths,creds,session)
745 update_privilege(newpaths,paths)
746 update_machine_account_password(paths,creds,session,names)
747 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
748 # to recreate them with the good form but with system account and then give the ownership to admin ...
749 admin_session_info = admin_session(lp, str(names.domainsid))
750 message(SIMPLE,"Updating SD")
751 update_sd(paths,creds,session,names)
752 update_sd(paths,creds,admin_session_info,names)
753 check_updated_sd(newpaths,paths,creds,session,names)
754 message(SIMPLE,"Upgrade finished !")
755 # remove reference provision now that everything is done !
756 rmall(provisiondir)