Fix formatting.
[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 shutil
25 import optparse
26 import os
27 import sys
28 import re
29 import tempfile
30 # Allow to run from s4 source directory (without installing samba)
31 sys.path.insert(0, "bin/python")
32
33 import samba
34 import samba.getopt as options
35 from samba.credentials import DONT_USE_KERBEROS
36 from samba.auth import system_session, admin_session
37 from samba import Ldb
38 from ldb import SCOPE_SUBTREE, SCOPE_BASE, \
39                 FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE,\
40                 MessageElement, Message, Dn
41 from samba import param
42 from samba import glue
43 from samba.misc import messageEltFlagToString
44 from samba.provision import  find_setup_dir, get_domain_descriptor, get_config_descriptor, secretsdb_self_join
45 from samba.provisionexceptions import ProvisioningError
46 from samba.schema import get_linked_attributes, Schema, get_schema_descriptor
47 from samba.dcerpc import security
48 from samba.ndr import ndr_unpack
49 from samba.dcerpc.misc import SEC_CHAN_BDC
50 from samba.upgradehelpers import dn_sort, get_paths, newprovision, find_provision_key_parameters
51
52 never=0
53 replace=2^FLAG_MOD_REPLACE
54 add=2^FLAG_MOD_ADD
55 delete=2^FLAG_MOD_DELETE
56
57 #Errors are always logged
58 ERROR =     -1
59 SIMPLE =     0x00
60 CHANGE =     0x01
61 CHANGESD =     0x02
62 GUESS =     0x04
63 PROVISION =    0x08
64 CHANGEALL =    0xff
65
66 __docformat__ = "restructuredText"
67
68 # Attributes that are never copied from the reference provision (even if they
69 # do not exist in the destination object).
70 # This is most probably because they are populated automatcally when object is
71 # created
72 # This also apply to imported object from reference provision
73 hashAttrNotCopied = {     "dn": 1, "whenCreated": 1, "whenChanged": 1, "objectGUID": 1, "replPropertyMetaData": 1, "uSNChanged": 1,
74                         "uSNCreated": 1, "parentGUID": 1, "objectCategory": 1, "distinguishedName": 1,
75                         "showInAdvancedViewOnly": 1, "instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
76                         "nTMixedDomain": 1, "versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
77                         "dBCSPwd":1, "supplementalCredentials":1, "gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
78                         "maxPwdAge":1, "mail":1, "secret":1, "possibleInferiors":1, "sAMAccountType":1}
79
80 # Usually for an object that already exists we do not overwrite attributes as
81 # they might have been changed for good reasons. Anyway for a few of them it's
82 # mandatory to replace them otherwise the provision will be broken somehow.
83 hashOverwrittenAtt = {    "prefixMap": replace, "systemMayContain": replace, "systemOnly":replace, "searchFlags":replace,
84                         "mayContain":replace,  "systemFlags":replace, "description":replace,
85                         "oEMInformation":replace, "operatingSystemVersion":replace, "adminPropertyPages":replace,
86                         "defaultSecurityDescriptor": replace, "wellKnownObjects":replace, "privilege":delete, "groupType":replace,
87                         "rIDAvailablePool": never}
88
89
90 backlinked = []
91 dn_syntax_att = []
92 def define_what_to_log(opts):
93     what = 0
94     if opts.debugchange:
95         what = what | CHANGE
96     if opts.debugchangesd:
97         what = what | CHANGESD
98     if opts.debugguess:
99         what = what | GUESS
100     if opts.debugprovision:
101         what = what | PROVISION
102     if opts.debugall:
103         what = what | CHANGEALL
104     return what
105
106
107 parser = optparse.OptionParser("provision [options]")
108 sambaopts = options.SambaOptions(parser)
109 parser.add_option_group(sambaopts)
110 parser.add_option_group(options.VersionOptions(parser))
111 credopts = options.CredentialsOptions(parser)
112 parser.add_option_group(credopts)
113 parser.add_option("--setupdir", type="string", metavar="DIR",
114                     help="directory with setup files")
115 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
116 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
117 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
118 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
119 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
120 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
121
122 opts = parser.parse_args()[0]
123
124 whatToLog = define_what_to_log(opts)
125
126 def messageprovision(text):
127     """Print a message if quiet is not set
128
129     :param text: Message to print """
130     if opts.debugprovision or opts.debugall:
131         print text
132
133 def message(what,text):
134     """Print a message if this message type has been selected to be printed
135
136     :param what: Category of the message
137     :param text: Message to print """
138     if (whatToLog & what) or (what <= 0 ):
139         print text
140
141 if len(sys.argv) == 1:
142     opts.interactive = True
143 lp = sambaopts.get_loadparm()
144 smbconf = lp.configfile
145
146 creds = credopts.get_credentials(lp)
147 creds.set_kerberos_state(DONT_USE_KERBEROS)
148 setup_dir = opts.setupdir
149 if setup_dir is None:
150     setup_dir = find_setup_dir()
151
152 session = system_session()
153
154 def identic_rename(ldbobj,dn):
155     """Perform a back and forth rename to trigger renaming on attribute that can't be directly modified.
156
157     :param lbdobj: An Ldb Object
158     :param dn: DN of the object to manipulate """
159     (before,sep,after)=str(dn).partition('=')
160     ldbobj.rename(dn,Dn(ldbobj,"%s=foo%s"%(before,after)))
161     ldbobj.rename(Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
162
163 def populate_backlink(newpaths,creds,session,schemadn):
164     """Populate an array with all the back linked attributes
165
166     This attributes that are modified automaticaly when
167     front attibutes are changed
168
169     :param newpaths: a list of paths for different provision objects
170     :param creds: credential for the authentification
171     :param session: session for connexion
172     :param schemadn: DN of the schema for the partition"""
173     newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
174     linkedAttHash = get_linked_attributes(Dn(newsam_ldb,str(schemadn)),newsam_ldb)
175     backlinked.extend(linkedAttHash.values())
176
177 # Create an array of  attributes with a dn synthax (2.5.5.1)
178 def populate_dnsyntax(newpaths,creds,session,schemadn):
179     """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
180
181     :param newpaths: a list of paths for different provision objects
182     :param creds: credential for the authentification
183     :param session: session for connexion
184     :param schemadn: DN of the schema for the partition"""
185     newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
186     res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=Dn(newsam_ldb,str(schemadn)),
187                             scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"])
188     for elem in res:
189         dn_syntax_att.append(elem["lDAPDisplayName"])
190
191 def sanitychecks(credentials,session_info,names,paths):
192     """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
193
194     :param creds: credential for the authentification
195     :param session_info: session for connexion
196     :param names: list of key provision parameters
197     :param paths: list of path to provision object
198     :return: Status of check (1 for Ok, 0 for not Ok) """
199     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
200
201     sam_ldb.set_session_info(session)
202     res = sam_ldb.search(expression="objectClass=ntdsdsa",base=str(names.configdn),
203                          scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
204     if len(res) == 0:
205         print "No DC found, your provision is most probalby hardly broken !"
206         return 0
207     elif len(res) != 1:
208         print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
209 domain with more than one DC, please demote the other(s) DC(s) before upgrading"%len(res)
210         return 0
211     else:
212         return 1
213
214
215 def print_provision_key_parameters(names):
216     """Do a a pretty print of provision parameters
217
218     :param names: list of key provision parameters """
219     message(GUESS, "rootdn      :"+str(names.rootdn))
220     message(GUESS, "configdn    :"+str(names.configdn))
221     message(GUESS, "schemadn    :"+str(names.schemadn))
222     message(GUESS, "serverdn    :"+str(names.serverdn))
223     message(GUESS, "netbiosname :"+names.netbiosname)
224     message(GUESS, "defaultsite :"+names.sitename)
225     message(GUESS, "dnsdomain   :"+names.dnsdomain)
226     message(GUESS, "hostname    :"+names.hostname)
227     message(GUESS, "domain      :"+names.domain)
228     message(GUESS, "realm       :"+names.realm)
229     message(GUESS, "invocationid:"+names.invocation)
230     message(GUESS, "policyguid  :"+names.policyid)
231     message(GUESS, "policyguiddc:"+str(names.policyid_dc))
232     message(GUESS, "domainsid   :"+str(names.domainsid))
233     message(GUESS, "domainguid  :"+names.domainguid)
234     message(GUESS, "ntdsguid    :"+names.ntdsguid)
235     message(GUESS, "domainlevel :"+str(names.domainlevel))
236
237 def handle_security_desc(ischema, att, msgElt, hashallSD, old, new):
238     """Check if the security descriptor has been modified.
239
240     This function also populate a hash used for the upgrade process.
241     :param ischema: Boolean that indicate if it's the schema that is updated
242     :param att: Name of the attribute
243     :param msgElt: MessageElement object
244     :param hashallSD: Hash table with DN as key and the old SD as value
245     :param old: The updated LDAP object
246     :param new: The reference LDAP object
247     :return: 1 to indicate that the attribute should be kept, 0 for discarding it
248     """
249     if ischema == 1 and att == "defaultSecurityDescriptor"  and msgElt.flags() == FLAG_MOD_REPLACE:
250         hashSD = {}
251         hashSD["oldSD"] = old[0][att]
252         hashSD["newSD"] = new[0][att]
253         hashallSD[str(old[0].dn)] = hashSD
254         return 1
255     if att == "nTSecurityDescriptor"  and msgElt.flags() == FLAG_MOD_REPLACE:
256         if ischema == 0:
257             hashSD = {}
258             hashSD["oldSD"] =  ndr_unpack(security.descriptor, str(old[0][att]))
259             hashSD["newSD"] =  ndr_unpack(security.descriptor, str(new[0][att]))
260             hashallSD[str(old[0].dn)] = hashSD
261         return 0
262     return 0
263
264 def handle_special_case(att, delta, new, old, ischema):
265     """Define more complicate update rules for some attributes
266
267     :param att: The attribute to be updated
268     :param delta: A messageElement object that correspond to the difference between the updated object and the reference one
269     :param new: The reference object
270     :param old: The Updated object
271     :param ischema: A boolean that indicate that the attribute is part of a schema object
272     :return: 1 to indicate that the attribute should be kept, 0 for discarding it
273     """
274     flag = delta.get(att).flags()
275     if (att == "gPLink" or att == "gPCFileSysPath") and \
276         flag ==  FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
277         delta.remove(att)
278         return 1
279     if att == "forceLogoff":
280         ref=0x8000000000000000
281         oldval=int(old[0][att][0])
282         newval=int(new[0][att][0])
283         ref == old and ref == abs(new)
284         return 1
285     if (att == "adminDisplayName" or att == "adminDescription") and ischema:
286         return 1
287
288     if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn))\
289         and att == "defaultObjectCategory" and flag  == FLAG_MOD_REPLACE):
290         return 1
291
292     if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag  == FLAG_MOD_REPLACE):
293         return 1
294
295     if ( (att == "member" or att == "servicePrincipalName") and flag  == FLAG_MOD_REPLACE):
296         hash = {}
297         newval = []
298         changeDelta=0
299         for elem in old[0][att]:
300             hash[str(elem)]=1
301             newval.append(str(elem))
302
303         for elem in new[0][att]:
304             if not hash.has_key(str(elem)):
305                 changeDelta=1
306                 newval.append(str(elem))
307         if changeDelta == 1:
308             delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
309         else:
310             delta.remove(att)
311         return 1
312
313     if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag  == FLAG_MOD_REPLACE):
314         return 1
315     if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
316         return 1
317     return 0
318
319 def update_secrets(newpaths, paths, creds, session):
320     """Update secrets.ldb
321
322     :param newpaths: a list of paths for different provision objects from the reference provision
323     :param paths: a list of paths for different provision objects from the upgraded provision
324     :param creds: credential for the authentification
325     :param session: session for connexion"""
326
327     message(SIMPLE,"update secrets.ldb")
328     newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
329     secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
330     reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
331     current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
332     delta = secrets_ldb.msg_diff(current[0],reference[0])
333     delta.dn = current[0].dn
334     secrets_ldb.modify(delta)
335
336     newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
337     secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
338     reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
339     current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
340     hash_new = {}
341     hash = {}
342     listMissing = []
343     listPresent = []
344
345     empty = Message()
346     for i in range(0,len(reference)):
347         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
348
349     # Create a hash for speeding the search of existing object in the
350     # current provision
351     for i in range(0,len(current)):
352         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
353
354     for k in hash_new.keys():
355         if not hash.has_key(k):
356             listMissing.append(hash_new[k])
357         else:
358             listPresent.append(hash_new[k])
359
360     for entry in listMissing:
361         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
362         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
363         delta = secrets_ldb.msg_diff(empty,reference[0])
364         for att in hashAttrNotCopied.keys():
365             delta.remove(att)
366         message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
367         for att in delta:
368             message(CHANGE," Adding attribute %s"%att)
369         delta.dn = reference[0].dn
370         secrets_ldb.add(delta)
371
372     for entry in listPresent:
373         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
374         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
375         delta = secrets_ldb.msg_diff(current[0],reference[0])
376         i=0
377         for att in hashAttrNotCopied.keys():
378             delta.remove(att)
379         for att in delta:
380             i = i + 1
381
382             if att == "name":
383                 message(CHANGE,"Found attribute name on  %s, must rename the DN "%(current[0].dn))
384                 identic_rename(secrets_ldb,reference[0].dn)
385             else:
386                 delta.remove(att)
387
388     for entry in listPresent:
389         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
390         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
391         delta = secrets_ldb.msg_diff(current[0],reference[0])
392         i=0
393         for att in hashAttrNotCopied.keys():
394             delta.remove(att)
395         for att in delta:
396             i = i + 1
397             if att != "dn":
398                 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
399
400         delta.dn = current[0].dn
401         secrets_ldb.modify(delta)
402
403 def dump_denied_change(dn,att,flagtxt,current,reference):
404     """Print detailed information about why a changed is denied
405
406     :param dn: DN of the object which attribute is denied
407     :param att: Attribute that was supposed to be upgraded
408     :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
409     :param current: Value(s) of the current attribute
410     :param reference: Value(s) of the reference attribute"""
411
412     message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
413     if att != "objectSid" :
414         i = 0
415         for e in range(0,len(current)):
416             message(CHANGE,"old %d : %s"%(i,str(current[e])))
417             i=i+1
418         if reference != None:
419             i = 0
420             for e in range(0,len(reference)):
421                     message(CHANGE,"new %d : %s"%(i,str(reference[e])))
422                     i=i+1
423     else:
424         message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
425         message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
426
427 def handle_special_add(sam_ldb,dn,names):
428     """Handle special operation (like remove) on some object needed during upgrade
429
430     This is mostly due to wrong creation of the object in previous provision.
431     :param sam_ldb: An Ldb object representing the SAM database
432     :param dn: DN of the object to inspect
433     :param names: list of key provision parameters"""
434     dntoremove=None
435     if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
436         #This entry was misplaced lets remove it if it exists
437         dntoremove="CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
438
439     if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
440         #This entry was misplaced lets remove it if it exists
441         dntoremove="CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
442
443     if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
444         #This entry was misplaced lets remove it if it exists
445         dntoremove="CN=Event Log Readers,CN=Users,%s"%names.rootdn
446
447     if dntoremove != None:
448         res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
449         if len(res) > 0:
450             message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
451             sam_ldb.delete(res[0]["dn"])
452
453 #Check if the one of the dn in the listdn will be created after the current dn
454 #hash is indexed by dn to be created, with each key is associated the creation order
455 #First dn to be created has the creation order 0, second has 1, ...
456 #Index contain the current creation order
457 def check_dn_nottobecreated(hash,index,listdn):
458     """Check if one of the DN present in the list has a creation order greater than the current.
459
460     Hash is indexed by dn to be created, with each key is associated the creation order
461     First dn to be created has the creation order 0, second has 1, ...
462     Index contain the current creation order
463     :param hash: Hash holding the different DN of the object to be created as key
464     :param index: Current creation order
465     :param listdn: List of DNs on which the current DN depends on
466     :return: None if the current object do not depend on other object or if all object have been
467     created before."""
468     if listdn == None:
469         return None
470     for dn in listdn:
471         key = str(dn).lower()
472         if hash.has_key(key) and hash[key] > index:
473             return str(dn)
474     return None
475
476 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
477     """Add a new object if the dependencies are satisfied
478
479     The function add the object if the object on which it depends are already created
480     :param newsam_ldb: Ldb object representing the SAM db of the reference provision
481     :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
482     :param dn: DN of the object to be added
483     :param names: List of key provision parameters
484     :param basedn: DN of the partition to be updated
485     :param hash: Hash holding the different DN of the object to be created as key
486     :param index: Current creation order
487     :return: 1 if the object was created 0 otherwise"""
488     handle_special_add(sam_ldb,dn,names)
489     reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
490                     scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
491     empty = Message()
492     delta = sam_ldb.msg_diff(empty,reference[0])
493     for att in hashAttrNotCopied.keys():
494         delta.remove(att)
495     for att in backlinked:
496         delta.remove(att)
497     depend_on_yettobecreated = None
498     for att in dn_syntax_att:
499         depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
500         if depend_on_yet_tobecreated != None:
501             message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
502                             %(str(dn),depend_on_yet_tobecreated,str(att)))
503             return 0
504     delta.dn = dn
505     message(CHANGE,"Object %s will be added"%dn)
506     sam_ldb.add(delta,["relax:0"])
507     return 1
508
509 def gen_dn_index_hash(listMissing):
510     """Generate a hash associating the DN to its creation order
511
512     :param listMissing: List of DN
513     :return: Hash with DN as keys and creation order as values"""
514     hash = {}
515     for i in range(0,len(listMissing)):
516         hash[str(listMissing[i]).lower()] = i
517     return hash
518
519 def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list):
520     """Add the missing object whose DN is the list
521
522     The function add the object if the object on which it depends are already created
523     :param newsam_ldb: Ldb object representing the SAM db of the reference provision
524     :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
525     :param dn: DN of the object to be added
526     :param names: List of key provision parameters
527     :param basedn: DN of the partition to be updated
528     :param list: List of DN to be added in the upgraded provision"""
529     listMissing = []
530     listDefered = list
531
532     while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
533         index = 0
534         listMissing = listDefered
535         listDefered = []
536         hashMissing = gen_dn_index_hash(listMissing)
537         for dn in listMissing:
538             ret =  add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
539             index = index + 1
540             if ret == 0:
541                 #DN can't be created because it depends on some other DN in the list
542                 listDefered.append(dn)
543     if len(listDefered) != 0:
544         raise ProvisioningError("Unable to insert missing elements: circular references")
545
546
547
548
549 # Check difference between the current provision and the reference provision.
550 # It looks for all objects which base DN is name. If ischema is "false" then
551 # the scan is done in cross partition mode.
552 # If "ischema" is true, then special handling is done for dealing with schema
553 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
554     """Check differences between the reference provision and the upgraded one.
555
556     This function will also add the missing object and update existing object to add
557     or remove attributes that were missing.
558     :param newpaths: List of paths for different provision objects from the reference provision
559     :param paths: List of paths for different provision objects from the upgraded provision
560     :param creds: Credential for the authentification
561     :param session: Session for connexion
562     :param basedn: DN of the partition to update
563     :param names: List of key provision parameters
564     :param ischema: Boolean indicating if the update is about the schema only
565     :return: Hash of security descriptor to update"""
566
567     hash_new = {}
568     hash = {}
569     hashallSD = {}
570     listMissing = []
571     listPresent = []
572     reference = []
573     current = []
574     # Connect to the reference provision and get all the attribute in the
575     # partition referred by name
576     newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
577     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
578     sam_ldb.transaction_start()
579     if ischema:
580         reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
581         current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
582     else:
583         reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
584         current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
585
586     sam_ldb.transaction_commit()
587     # Create a hash for speeding the search of new object
588     for i in range(0,len(reference)):
589         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
590
591     # Create a hash for speeding the search of existing object in the
592     # current provision
593     for i in range(0,len(current)):
594         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
595
596     for k in hash_new.keys():
597         if not hash.has_key(k):
598             print hash_new[k]
599             listMissing.append(hash_new[k])
600         else:
601             listPresent.append(hash_new[k])
602
603     # Sort the missing object in order to have object of the lowest level
604     # first (which can be containers for higher level objects)
605     listMissing.sort(dn_sort)
606     listPresent.sort(dn_sort)
607
608     if ischema:
609         # The following lines (up to the for loop) is to load the up to
610         # date schema into our current LDB
611         # a complete schema is needed as the insertion of attributes
612         # and class is done against it
613         # and the schema is self validated
614         # The double ldb open and schema validation is taken from the
615         # initial provision script
616         # it's not certain that it is really needed ....
617         sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
618         schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
619         # Load the schema from the one we computed earlier
620         sam_ldb.set_schema_from_ldb(schema.ldb)
621         # And now we can connect to the DB - the schema won't be loaded
622         # from the DB
623         sam_ldb.connect(paths.samdb)
624     else:
625         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
626
627     sam_ldb.transaction_start()
628
629     message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
630     add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
631     changed = 0
632     for dn in listPresent:
633         reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
634         current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
635         if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
636             message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
637             identic_rename(sam_ldb,reference[0].dn)
638             current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
639
640         delta = sam_ldb.msg_diff(current[0],reference[0])
641         for att in hashAttrNotCopied.keys():
642             delta.remove(att)
643         for att in backlinked:
644             delta.remove(att)
645         delta.remove("parentGUID")
646         nb = 0
647         
648         for att in delta:
649             msgElt = delta.get(att)
650             if att == "dn":
651                 continue
652             if att == "name":
653                 delta.remove(att)
654                 continue
655             if handle_security_desc(ischema,att,msgElt,hashallSD,current,reference) == 0:
656                 delta.remove(att)
657                 continue
658             if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
659                 if  hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
660                     delta.remove(att)
661                     continue
662                 if  handle_special_case(att,delta,reference,current,ischema)==0 and msgElt.flags()!=FLAG_MOD_ADD:
663                     i = 0
664                     if opts.debugchange or opts.debugall:
665                         try:
666                             dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
667                         except:
668                             dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
669                     delta.remove(att)
670         delta.dn = dn
671         if len(delta.items()) >1:
672             attributes=",".join(delta.keys())
673             message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
674             changed = changed + 1
675             sam_ldb.modify(delta)
676
677     sam_ldb.transaction_commit()
678     message(SIMPLE,"There are %d changed objects"%(changed))
679     return hashallSD
680
681 def check_updated_sd(newpaths, paths, creds, session, names):
682     """Check if the security descriptor in the upgraded provision are the same as the reference
683
684     :param newpaths: List of paths for different provision objects from the reference provision
685     :param paths: List of paths for different provision objects from the upgraded provision
686     :param creds: Credential for the authentification
687     :param session: Session for connexion
688     :param basedn: DN of the partition to update
689     :param names: List of key provision parameters"""
690     newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
691     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
692     reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
693     current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
694     hash_new = {}
695     for i in range(0,len(reference)):
696         hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
697
698     for i in range(0,len(current)):
699         key = str(current[i]["dn"]).lower()
700         if hash_new.has_key(key):
701             sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
702             if sddl != hash_new[key]:
703                 print "%s new sddl/sddl in ref"%key
704                 print "%s\n%s"%(sddl,hash_new[key])
705
706 def update_sd(paths, creds, session, names):
707     """Update security descriptor of the current provision
708
709     During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
710     This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
711     and so SD can be safely recalculated from scratch to get them right.
712
713     :param paths: List of paths for different provision objects from the upgraded provision
714     :param creds: Credential for the authentification
715     :param session: Session for connexion
716     :param names: List of key provision parameters"""
717
718     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
719     sam_ldb.transaction_start()
720     # First update the SD for the rootdn
721     sam_ldb.set_session_info(session)
722     res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
723                          attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
724     delta = Message()
725     delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
726     descr = get_domain_descriptor(names.domainsid)
727     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor")
728     sam_ldb.modify(delta,["recalculate_sd:0"])
729     # Then the config dn
730     res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
731     delta = Message()
732     delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
733     descr = get_config_descriptor(names.domainsid)
734     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
735     sam_ldb.modify(delta,["recalculate_sd:0"])
736     # Then the schema dn
737     res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
738     delta = Message()
739     delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
740     descr = get_schema_descriptor(names.domainsid)
741     delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
742     sam_ldb.modify(delta,["recalculate_sd:0"])
743
744     # Then the rest
745     hash = {}
746     res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
747     for obj in res:
748         if not (str(obj["dn"]) == str(names.rootdn) or
749             str(obj["dn"]) == str(names.configdn) or \
750             str(obj["dn"]) == str(names.schemadn)):
751             hash[str(obj["dn"])] = obj["whenCreated"]
752
753     listkeys = hash.keys()
754     listkeys.sort(dn_sort)
755
756     for key in listkeys:
757         try:
758             delta = Message()
759             delta.dn = Dn(sam_ldb,key)
760             delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
761             sam_ldb.modify(delta,["recalculate_sd:0"])
762         except:
763             sam_ldb.transaction_cancel()
764             res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
765                                  attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
766             print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
767             return
768     sam_ldb.transaction_commit()
769
770
771 def update_basesamdb(newpaths, paths, names):
772     """Update the provision container db: sam.ldb
773
774     :param newpaths: List of paths for different provision objects from the reference provision
775     :param paths: List of paths for different provision objects from the upgraded provision
776     :param names: List of key provision parameters"""
777
778     message(SIMPLE,"Copy samdb")
779     shutil.copy(newpaths.samdb,paths.samdb)
780
781     message(SIMPLE,"Update partitions filename if needed")
782     schemaldb=os.path.join(paths.private_dir,"schema.ldb")
783     configldb=os.path.join(paths.private_dir,"configuration.ldb")
784     usersldb=os.path.join(paths.private_dir,"users.ldb")
785     samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
786
787     if not os.path.isdir(samldbdir):
788         os.mkdir(samldbdir)
789         os.chmod(samldbdir,0700)
790     if os.path.isfile(schemaldb):
791         shutil.copy(schemaldb,os.path.join(samldbdir,"%s.ldb"%str(names.schemadn).upper()))
792         os.remove(schemaldb)
793     if os.path.isfile(usersldb):
794         shutil.copy(usersldb,os.path.join(samldbdir,"%s.ldb"%str(names.rootdn).upper()))
795         os.remove(usersldb)
796     if os.path.isfile(configldb):
797         shutil.copy(configldb,os.path.join(samldbdir,"%s.ldb"%str(names.configdn).upper()))
798         os.remove(configldb)
799
800 def update_privilege(newpaths, paths):
801     """Update the privilege database
802
803     :param newpaths: List of paths for different provision objects from the reference provision
804     :param paths: List of paths for different provision objects from the upgraded provision"""
805     message(SIMPLE,"Copy privilege")
806     shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb"))
807
808 # For each partition check the differences
809 def update_samdb(newpaths, paths, creds, session, names):
810     """Upgrade the SAM DB contents for all the provision
811
812     :param newpaths: List of paths for different provision objects from the reference provision
813     :param paths: List of paths for different provision objects from the upgraded provision
814     :param creds: Credential for the authentification
815     :param session: Session for connexion
816     :param names: List of key provision parameters"""
817
818     message(SIMPLE, "Doing schema update")
819     hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
820     message(SIMPLE,"Done with schema update")
821     message(SIMPLE,"Scanning whole provision for updates and additions")
822     hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
823     message(SIMPLE,"Done with scanning")
824
825 def update_machine_account_password(paths, creds, session, names):
826     """Update (change) the password of the current DC both in the SAM db and in secret one
827
828     :param paths: List of paths for different provision objects from the upgraded provision
829     :param creds: Credential for the authentification
830     :param session: Session for connexion
831     :param names: List of key provision parameters"""
832
833     secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
834     secrets_ldb.transaction_start()
835     secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
836     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
837     sam_ldb.transaction_start()
838     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
839         res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
840         assert(len(res) == 1)
841
842         msg = Message(res[0].dn)
843         machinepass = glue.generate_random_password(128, 255)
844         msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
845         sam_ldb.modify(msg)
846
847         res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
848                      attrs=["msDs-keyVersionNumber"])
849         assert(len(res) == 1)
850         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
851
852         secretsdb_self_join(secrets_ldb, domain=names.domain,
853                     realm=names.realm,
854                     domainsid=names.domainsid,
855                     dnsdomain=names.dnsdomain,
856                     netbiosname=names.netbiosname,
857                     machinepass=machinepass,
858                     key_version_number=kvno,
859                     secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
860         sam_ldb.transaction_prepare_commit()
861         secrets_ldb.transaction_prepare_commit()
862         sam_ldb.transaction_commit()
863         secrets_ldb.transaction_commit()
864     else:
865         secrets_ldb.transaction_cancel()
866
867 def setup_path(file):
868     return os.path.join(setup_dir, file)
869
870 cmd = os.environ["_"]
871 m = re.match('(^|.*/)pydoc$',cmd)
872 if not m:
873     # From here start the big steps of the program
874     # First get files paths
875     paths=get_paths(param,smbconf=smbconf)
876     paths.setup = setup_dir
877     # Guess all the needed names (variables in fact) from the current
878     # provision.
879
880     names = find_provision_key_parameters(param, creds, session, paths, smbconf)
881     if not sanitychecks(creds,session,names,paths):
882         message(SIMPLE,"Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
883         sys.exit(1)
884     # Let's see them
885     print_provision_key_parameters(names)
886     # With all this information let's create a fresh new provision used as reference
887     message(SIMPLE,"Creating a reference provision")
888     provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
889     newprovision(names, setup_dir, creds, session, smbconf, provisiondir, messageprovision)
890     # Get file paths of this new provision
891     newpaths = get_paths(param, targetdir=provisiondir)
892     populate_backlink(newpaths, creds, session,names.schemadn)
893     populate_dnsyntax(newpaths, creds, session,names.schemadn)
894     # Check the difference
895     update_basesamdb(newpaths, paths,names)
896
897     if opts.full:
898         update_samdb(newpaths, paths, creds, session, names)
899     update_secrets(newpaths, paths, creds, session)
900     update_privilege(newpaths, paths)
901     update_machine_account_password(paths, creds, session, names)
902     # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
903     # to recreate them with the good form but with system account and then give the ownership to admin ...
904     admin_session_info = admin_session(lp, str(names.domainsid))
905     message(SIMPLE,"Updating SD")
906     update_sd(paths, creds, session,names)
907     update_sd(paths, creds, admin_session_info, names)
908     check_updated_sd(newpaths, paths, creds, session, names)
909     message(SIMPLE,"Upgrade finished !")
910     # remove reference provision now that everything is done !
911     shutil.rmtree(provisiondir)