upgradeprovision: Use logging infrastructure.
[samba.git] / source4 / scripting / bin / upgradeprovision
1 #!/usr/bin/env python
2 # vim: expandtab
3 #
4 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
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 # Allow to run from s4 source directory (without installing samba)
32 sys.path.insert(0, "bin/python")
33
34 import samba
35 import samba.getopt as options
36 from samba.credentials import DONT_USE_KERBEROS
37 from samba.auth import system_session, admin_session
38 from samba import Ldb, version
39 from ldb import (SCOPE_SUBTREE, SCOPE_BASE, FLAG_MOD_REPLACE,
40     FLAG_MOD_ADD, FLAG_MOD_DELETE, MessageElement, Message, Dn)
41 from samba import param
42 from samba.misc import messageEltFlagToString
43 from samba.provision import (find_setup_dir, get_domain_descriptor,
44     get_config_descriptor, secretsdb_self_join, set_gpo_acl, 
45     getpolicypath, create_gpo_struct, 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":never, "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 handler = logging.StreamHandler(sys.stdout)
125 upgrade_logger = logging.getLogger("upgradeprovision")
126 upgrade_logger.addHandler(handler)
127
128 provision_logger = logging.getLogger("provision")
129 provision_logger.addHandler(handler)
130
131 whatToLog = define_what_to_log(opts)
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         upgrade_logger.info("%s", 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
164 def populate_backlink(newpaths,creds,session,schemadn):
165     """Populate an array with all the back linked attributes
166
167     This attributes that are modified automaticaly when
168     front attibutes are changed
169
170     :param newpaths: a list of paths for different provision objects
171     :param creds: credential for the authentification
172     :param session: session for connexion
173     :param schemadn: DN of the schema for the partition"""
174     newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
175     linkedAttHash = get_linked_attributes(Dn(newsam_ldb,str(schemadn)),newsam_ldb)
176     backlinked.extend(linkedAttHash.values())
177
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
192 def sanitychecks(credentials,session_info,names,paths):
193     """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1)
194
195     :param creds: credential for the authentification
196     :param session_info: session for connexion
197     :param names: list of key provision parameters
198     :param paths: list of path to provision object
199     :return: Status of check (1 for Ok, 0 for not Ok) """
200     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
201
202     sam_ldb.set_session_info(session)
203     res = sam_ldb.search(expression="objectClass=ntdsdsa", base=str(names.configdn),
204                          scope=SCOPE_SUBTREE, attrs=["dn"], controls=["search_options:1:2"])
205     if len(res) == 0:
206         print "No DC found, your provision is most probably hardly broken !"
207         return False
208     elif len(res) != 1:
209         print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
210 domain with more than one DC, please demote the other(s) DC(s) before upgrading"%len(res)
211         return False
212     else:
213         return True
214
215
216 def print_provision_key_parameters(names):
217     """Do a a pretty print of provision parameters
218
219     :param names: list of key provision parameters """
220     message(GUESS, "rootdn      :"+str(names.rootdn))
221     message(GUESS, "configdn    :"+str(names.configdn))
222     message(GUESS, "schemadn    :"+str(names.schemadn))
223     message(GUESS, "serverdn    :"+str(names.serverdn))
224     message(GUESS, "netbiosname :"+names.netbiosname)
225     message(GUESS, "defaultsite :"+names.sitename)
226     message(GUESS, "dnsdomain   :"+names.dnsdomain)
227     message(GUESS, "hostname    :"+names.hostname)
228     message(GUESS, "domain      :"+names.domain)
229     message(GUESS, "realm       :"+names.realm)
230     message(GUESS, "invocationid:"+names.invocation)
231     message(GUESS, "policyguid  :"+names.policyid)
232     message(GUESS, "policyguiddc:"+str(names.policyid_dc))
233     message(GUESS, "domainsid   :"+str(names.domainsid))
234     message(GUESS, "domainguid  :"+names.domainguid)
235     message(GUESS, "ntdsguid    :"+names.ntdsguid)
236     message(GUESS, "domainlevel :"+str(names.domainlevel))
237
238
239 def handle_security_desc(ischema, att, msgElt, hashallSD, old, new):
240     """Check if the security descriptor has been modified.
241
242     This function also populate a hash used for the upgrade process.
243     :param ischema: Boolean that indicate if it's the schema that is updated
244     :param att: Name of the attribute
245     :param msgElt: MessageElement object
246     :param hashallSD: Hash table with DN as key and the old SD as value
247     :param old: The updated LDAP object
248     :param new: The reference LDAP object
249     :return: 1 to indicate that the attribute should be kept, 0 for discarding it
250     """
251     if ischema == 1 and att == "defaultSecurityDescriptor"  and msgElt.flags() == FLAG_MOD_REPLACE:
252         hashSD = {}
253         hashSD["oldSD"] = old[0][att]
254         hashSD["newSD"] = new[0][att]
255         hashallSD[str(old[0].dn)] = hashSD
256         return True
257     if att == "nTSecurityDescriptor"  and msgElt.flags() == FLAG_MOD_REPLACE:
258         if ischema == 0:
259             hashSD = {}
260             hashSD["oldSD"] = ndr_unpack(security.descriptor, str(old[0][att]))
261             hashSD["newSD"] = ndr_unpack(security.descriptor, str(new[0][att]))
262             hashallSD[str(old[0].dn)] = hashSD
263         return False
264     return False
265
266
267 def handle_special_case(att, delta, new, old, ischema):
268     """Define more complicate update rules for some attributes
269
270     :param att: The attribute to be updated
271     :param delta: A messageElement object that correspond to the difference between the updated object and the reference one
272     :param new: The reference object
273     :param old: The Updated object
274     :param ischema: A boolean that indicate that the attribute is part of a schema object
275     :return: Tru to indicate that the attribute should be kept, False for discarding it
276     """
277     flag = delta.get(att).flags()
278     if (att == "gPLink" or att == "gPCFileSysPath") and \
279         flag == FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
280         delta.remove(att)
281         return True
282     if att == "forceLogoff":
283         ref=0x8000000000000000
284         oldval=int(old[0][att][0])
285         newval=int(new[0][att][0])
286         ref == old and ref == abs(new)
287         return True
288     if (att == "adminDisplayName" or att == "adminDescription") and ischema:
289         return True
290
291     if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s" % (str(names.schemadn))\
292         and att == "defaultObjectCategory" and flag == FLAG_MOD_REPLACE):
293         return True
294
295     if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == FLAG_MOD_REPLACE):
296         return True
297
298     if ((att == "member" or att == "servicePrincipalName") and flag == FLAG_MOD_REPLACE):
299         hash = {}
300         newval = []
301         changeDelta=0
302         for elem in old[0][att]:
303             hash[str(elem)]=1
304             newval.append(str(elem))
305
306         for elem in new[0][att]:
307             if not hash.has_key(str(elem)):
308                 changeDelta=1
309                 newval.append(str(elem))
310         if changeDelta == 1:
311             delta[att] = MessageElement(newval, FLAG_MOD_REPLACE, att)
312         else:
313             delta.remove(att)
314         return True
315
316     if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == FLAG_MOD_REPLACE):
317         return True
318     if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
319         return True
320     return False
321
322 def update_secrets(newpaths, paths, creds, session):
323     """Update secrets.ldb
324
325     :param newpaths: a list of paths for different provision objects from the
326         reference provision
327     :param paths: a list of paths for different provision objects from the
328         upgraded provision
329     :param creds: credential for the authentification
330     :param session: session for connexion"""
331
332     message(SIMPLE, "update secrets.ldb")
333     newsecrets_ldb = Ldb(newpaths.secrets, session_info=session,
334         credentials=creds,lp=lp)
335     secrets_ldb = Ldb(paths.secrets, session_info=session,
336         credentials=creds,lp=lp, options=["modules:samba_secrets"])
337     reference = newsecrets_ldb.search(expression="dn=@MODULES",base="",
338         scope=SCOPE_SUBTREE)
339     current = secrets_ldb.search(expression="dn=@MODULES",base="",
340         scope=SCOPE_SUBTREE)
341     delta = secrets_ldb.msg_diff(current[0],reference[0])
342     delta.dn = current[0].dn
343     secrets_ldb.modify(delta)
344
345     newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
346     secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
347     reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
348     current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
349     hash_new = {}
350     hash = {}
351     listMissing = []
352     listPresent = []
353
354     empty = Message()
355     for i in range(0,len(reference)):
356         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
357
358     # Create a hash for speeding the search of existing object in the
359     # current provision
360     for i in range(0,len(current)):
361         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
362
363     for k in hash_new.keys():
364         if not hash.has_key(k):
365             listMissing.append(hash_new[k])
366         else:
367             listPresent.append(hash_new[k])
368
369     for entry in listMissing:
370         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
371         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
372         delta = secrets_ldb.msg_diff(empty,reference[0])
373         for att in hashAttrNotCopied.keys():
374             delta.remove(att)
375         message(CHANGE, "Entry %s is missing from secrets.ldb"%reference[0].dn)
376         for att in delta:
377             message(CHANGE, " Adding attribute %s"%att)
378         delta.dn = reference[0].dn
379         secrets_ldb.add(delta)
380
381     for entry in listPresent:
382         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
383         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
384         delta = secrets_ldb.msg_diff(current[0],reference[0])
385         for att in hashAttrNotCopied.keys():
386             delta.remove(att)
387         for att in delta:
388             if att == "name":
389                 message(CHANGE, "Found attribute name on  %s, must rename the DN "%(current[0].dn))
390                 identic_rename(secrets_ldb,reference[0].dn)
391             else:
392                 delta.remove(att)
393
394     for entry in listPresent:
395         reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
396         current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
397         delta = secrets_ldb.msg_diff(current[0],reference[0])
398         for att in hashAttrNotCopied.keys():
399             delta.remove(att)
400         for att in delta:
401             if att != "dn":
402                 message(CHANGE, " Adding/Changing attribute %s to %s"%(att,current[0].dn))
403
404         delta.dn = current[0].dn
405         secrets_ldb.modify(delta)
406
407
408 def dump_denied_change(dn,att,flagtxt,current,reference):
409     """Print detailed information about why a changed is denied
410
411     :param dn: DN of the object which attribute is denied
412     :param att: Attribute that was supposed to be upgraded
413     :param flagtxt: Type of the update that should be performed (add, change, remove, ...)
414     :param current: Value(s) of the current attribute
415     :param reference: Value(s) of the reference attribute"""
416
417     message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
418     if att != "objectSid" :
419         i = 0
420         for e in range(0,len(current)):
421             message(CHANGE, "old %d : %s"%(i,str(current[e])))
422             i+=1
423         if reference != None:
424             i = 0
425             for e in range(0,len(reference)):
426                 message(CHANGE, "new %d : %s"%(i,str(reference[e])))
427                 i+=1
428     else:
429         message(CHANGE, "old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
430         message(CHANGE, "new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
431
432
433 def handle_special_add(sam_ldb,dn,names):
434     """Handle special operation (like remove) on some object needed during upgrade
435
436     This is mostly due to wrong creation of the object in previous provision.
437     :param sam_ldb: An Ldb object representing the SAM database
438     :param dn: DN of the object to inspect
439     :param names: list of key provision parameters"""
440     dntoremove = None
441     if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
442         #This entry was misplaced lets remove it if it exists
443         dntoremove = "CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
444
445     if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
446         #This entry was misplaced lets remove it if it exists
447         dntoremove = "CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
448
449     if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
450         #This entry was misplaced lets remove it if it exists
451         dntoremove = "CN=Event Log Readers,CN=Users,%s"%names.rootdn
452
453     if dntoremove != None:
454         res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
455         if len(res) > 0:
456             message(CHANGE, "Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
457             sam_ldb.delete(res[0]["dn"])
458
459
460 def check_dn_nottobecreated(hash, index, listdn):
461     """Check if one of the DN present in the list has a creation order greater than the current.
462
463     Hash is indexed by dn to be created, with each key is associated the creation order
464     First dn to be created has the creation order 0, second has 1, ...
465     Index contain the current creation order
466
467     :param hash: Hash holding the different DN of the object to be created as key
468     :param index: Current creation order
469     :param listdn: List of DNs on which the current DN depends on
470     :return: None if the current object do not depend on other object or if all object have been
471     created before."""
472     if listdn == None:
473         return None
474     for dn in listdn:
475         key = str(dn).lower()
476         if hash.has_key(key) and hash[key] > index:
477             return str(dn)
478     return None
479
480
481 def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index):
482     """Add a new object if the dependencies are satisfied
483
484     The function add the object if the object on which it depends are already created
485     :param newsam_ldb: Ldb object representing the SAM db of the reference provision
486     :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
487     :param dn: DN of the object to be added
488     :param names: List of key provision parameters
489     :param basedn: DN of the partition to be updated
490     :param hash: Hash holding the different DN of the object to be created as key
491     :param index: Current creation order
492     :return: True if the object was created False otherwise"""
493     handle_special_add(sam_ldb,dn,names)
494     reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
495                     scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
496     empty = Message()
497     delta = sam_ldb.msg_diff(empty,reference[0])
498     for att in hashAttrNotCopied.keys():
499         delta.remove(att)
500     for att in backlinked:
501         delta.remove(att)
502     depend_on_yettobecreated = None
503     for att in dn_syntax_att:
504         depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
505         if depend_on_yet_tobecreated != None:
506             message(CHANGE, "Object %s depends on %s in attribute %s, delaying the creation"
507                             %(str(dn),depend_on_yet_tobecreated,str(att)))
508             return False
509     delta.dn = dn
510     message(CHANGE, "Object %s will be added"%dn)
511     sam_ldb.add(delta,["relax:0"])
512     return True
513
514
515 def gen_dn_index_hash(listMissing):
516     """Generate a hash associating the DN to its creation order
517
518     :param listMissing: List of DN
519     :return: Hash with DN as keys and creation order as values"""
520     hash = {}
521     for i in range(0,len(listMissing)):
522         hash[str(listMissing[i]).lower()] = i
523     return hash
524
525
526 def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list):
527     """Add the missing object whose DN is the list
528
529     The function add the object if the object on which it depends are already created
530     :param newsam_ldb: Ldb object representing the SAM db of the reference provision
531     :param sam_ldb: Ldb object representing the SAM db of the upgraded provision
532     :param dn: DN of the object to be added
533     :param names: List of key provision parameters
534     :param basedn: DN of the partition to be updated
535     :param list: List of DN to be added in the upgraded provision"""
536     listMissing = []
537     listDefered = list
538
539     while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
540         index = 0
541         listMissing = listDefered
542         listDefered = []
543         hashMissing = gen_dn_index_hash(listMissing)
544         for dn in listMissing:
545             ret = add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
546             index = index + 1
547             if ret == 0:
548                 #DN can't be created because it depends on some other DN in the list
549                 listDefered.append(dn)
550     if len(listDefered) != 0:
551         raise ProvisioningError("Unable to insert missing elements: circular references")
552
553
554 def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema):
555     """Check differences between the reference provision and the upgraded one.
556
557     It looks for all objects which base DN is name. If ischema is "false" then
558     the scan is done in cross partition mode.
559     If "ischema" is true, then special handling is done for dealing with schema
560
561     This function will also add the missing object and update existing object to add
562     or remove attributes that were missing.
563     :param newpaths: List of paths for different provision objects from the reference provision
564     :param paths: List of paths for different provision objects from the upgraded provision
565     :param creds: Credential for the authentification
566     :param session: Session for connexion
567     :param basedn: DN of the partition to update
568     :param names: List of key provision parameters
569     :param ischema: Boolean indicating if the update is about the schema only
570     :return: Hash of security descriptor to update"""
571
572     hash_new = {}
573     hash = {}
574     hashallSD = {}
575     listMissing = []
576     listPresent = []
577     reference = []
578     current = []
579     # Connect to the reference provision and get all the attribute in the
580     # partition referred by name
581     newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
582     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
583     sam_ldb.transaction_start()
584     try:
585         if ischema:
586             reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
587             current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
588         else:
589             reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
590             current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
591     except:
592         sam_ldb.transaction_cancel()
593         raise
594     else:
595         sam_ldb.transaction_commit()
596     # Create a hash for speeding the search of new object
597     for i in range(0,len(reference)):
598         hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
599
600     # Create a hash for speeding the search of existing object in the
601     # current provision
602     for i in range(0,len(current)):
603         hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
604
605     for k in hash_new.keys():
606         if not hash.has_key(k):
607             print hash_new[k]
608             listMissing.append(hash_new[k])
609         else:
610             listPresent.append(hash_new[k])
611
612     # Sort the missing object in order to have object of the lowest level
613     # first (which can be containers for higher level objects)
614     listMissing.sort(dn_sort)
615     listPresent.sort(dn_sort)
616
617     if ischema:
618         # The following lines (up to the for loop) is to load the up to
619         # date schema into our current LDB
620         # a complete schema is needed as the insertion of attributes
621         # and class is done against it
622         # and the schema is self validated
623         # The double ldb open and schema validation is taken from the
624         # initial provision script
625         # it's not certain that it is really needed ....
626         sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
627         schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
628         # Load the schema from the one we computed earlier
629         sam_ldb.set_schema_from_ldb(schema.ldb)
630         # And now we can connect to the DB - the schema won't be loaded
631         # from the DB
632         sam_ldb.connect(paths.samdb)
633     else:
634         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
635
636     sam_ldb.transaction_start()
637     try:
638         # XXX: This needs to be wrapped in try/except so we
639         # abort on exceptions.
640         message(SIMPLE, "There are %d missing objects"%(len(listMissing)))
641         add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
642         changed = 0
643         for dn in listPresent:
644             reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
645             current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
646             if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
647                 message(CHANGE, "Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
648                 identic_rename(sam_ldb,reference[0].dn)
649                 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
650
651             delta = sam_ldb.msg_diff(current[0],reference[0])
652             for att in hashAttrNotCopied.keys():
653                 delta.remove(att)
654             for att in backlinked:
655                 delta.remove(att)
656             delta.remove("parentGUID")
657             nb = 0
658             
659             for att in delta:
660                 msgElt = delta.get(att)
661                 if att == "dn":
662                     continue
663                 if att == "name":
664                     delta.remove(att)
665                     continue
666                 if not handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
667                     delta.remove(att)
668                     continue
669                 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
670                     if  hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
671                         delta.remove(att)
672                         continue
673                     if not handle_special_case(att,delta,reference,current,ischema) and msgElt.flags()!=FLAG_MOD_ADD:
674                         if opts.debugchange or opts.debugall:
675                             try:
676                                 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
677                             except:
678                                 # FIXME: Should catch an explicit exception here
679                                 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
680                         delta.remove(att)
681             delta.dn = dn
682             if len(delta.items()) >1:
683                 attributes=",".join(delta.keys())
684                 message(CHANGE, "%s is different from the reference one, changed attributes: %s"%(dn,attributes))
685                 changed = changed + 1
686                 sam_ldb.modify(delta)
687     except:
688         sam_ldb.transaction_cancel()
689         raise
690     else:
691         sam_ldb.transaction_commit()
692     message(SIMPLE, "There are %d changed objects"%(changed))
693     return hashallSD
694
695
696 def check_updated_sd(newpaths, paths, creds, session, names):
697     """Check if the security descriptor in the upgraded provision are the same as the reference
698
699     :param newpaths: List of paths for different provision objects from the reference provision
700     :param paths: List of paths for different provision objects from the upgraded provision
701     :param creds: Credential for the authentification
702     :param session: Session for connexion
703     :param basedn: DN of the partition to update
704     :param names: List of key provision parameters"""
705     newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
706     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
707     reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
708     current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
709     hash_new = {}
710     for i in range(0,len(reference)):
711         hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
712
713     for i in range(0,len(current)):
714         key = str(current[i]["dn"]).lower()
715         if hash_new.has_key(key):
716             sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
717             if sddl != hash_new[key]:
718                 print "%s new sddl/sddl in ref"%key
719                 print "%s\n%s"%(sddl,hash_new[key])
720
721
722 def update_sd(paths, creds, session, names):
723     """Update security descriptor of the current provision
724
725     During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included)
726     This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD
727     and so SD can be safely recalculated from scratch to get them right.
728
729     :param paths: List of paths for different provision objects from the upgraded provision
730     :param creds: Credential for the authentification
731     :param session: Session for connexion
732     :param names: List of key provision parameters"""
733
734     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
735     sam_ldb.transaction_start()
736     try:
737         # First update the SD for the rootdn
738         sam_ldb.set_session_info(session)
739         res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_BASE,\
740                              attrs=["dn", "whenCreated"], controls=["search_options:1:2"])
741         delta = Message()
742         delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
743         descr = get_domain_descriptor(names.domainsid)
744         delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor")
745         sam_ldb.modify(delta,["recalculate_sd:0"])
746         # Then the config dn
747         res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
748         delta = Message()
749         delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
750         descr = get_config_descriptor(names.domainsid)
751         delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
752         sam_ldb.modify(delta,["recalculate_sd:0"])
753         # Then the schema dn
754         res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
755         delta = Message()
756         delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
757         descr = get_schema_descriptor(names.domainsid)
758         delta["nTSecurityDescriptor"] = MessageElement(descr, FLAG_MOD_REPLACE, "nTSecurityDescriptor" )
759         sam_ldb.modify(delta,["recalculate_sd:0"])
760
761         # Then the rest
762         hash = {}
763         res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
764         for obj in res:
765             if not (str(obj["dn"]) == str(names.rootdn) or
766                 str(obj["dn"]) == str(names.configdn) or \
767                 str(obj["dn"]) == str(names.schemadn)):
768                 hash[str(obj["dn"])] = obj["whenCreated"]
769
770         listkeys = hash.keys()
771         listkeys.sort(dn_sort)
772
773         for key in listkeys:
774             try:
775                 delta = Message()
776                 delta.dn = Dn(sam_ldb,key)
777                 delta["whenCreated"] = MessageElement(hash[key], FLAG_MOD_REPLACE, "whenCreated" )
778                 sam_ldb.modify(delta,["recalculate_sd:0"])
779             except:
780                 # XXX: We should always catch an explicit exception.
781                 # What could go wrong here?
782                 sam_ldb.transaction_cancel()
783                 res = sam_ldb.search(expression="objectClass=*", base=str(names.rootdn), scope=SCOPE_SUBTREE,\
784                                      attrs=["dn","nTSecurityDescriptor"], controls=["search_options:1:2"])
785                 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
786                 return
787     except:
788         sam_ldb.transaction_cancel()
789         raise
790     else:
791         sam_ldb.transaction_commit()
792
793
794 def update_basesamdb(newpaths, paths, names):
795     """Update the provision container db: sam.ldb
796
797     :param newpaths: List of paths for different provision objects from the reference provision
798     :param paths: List of paths for different provision objects from the upgraded provision
799     :param names: List of key provision parameters"""
800
801     message(SIMPLE, "Copy samdb")
802     shutil.copy(newpaths.samdb,paths.samdb)
803
804     message(SIMPLE, "Update partitions filename if needed")
805     schemaldb = os.path.join(paths.private_dir, "schema.ldb")
806     configldb = os.path.join(paths.private_dir, "configuration.ldb")
807     usersldb = os.path.join(paths.private_dir, "users.ldb")
808     samldbdir = os.path.join(paths.private_dir, "sam.ldb.d")
809
810     if not os.path.isdir(samldbdir):
811         os.mkdir(samldbdir)
812         os.chmod(samldbdir,0700)
813     if os.path.isfile(schemaldb):
814         shutil.copy(schemaldb, os.path.join(samldbdir, "%s.ldb" % str(names.schemadn).upper()))
815         os.remove(schemaldb)
816     if os.path.isfile(usersldb):
817         shutil.copy(usersldb, os.path.join(samldbdir, "%s.ldb" % str(names.rootdn).upper()))
818         os.remove(usersldb)
819     if os.path.isfile(configldb):
820         shutil.copy(configldb, os.path.join(samldbdir, "%s.ldb" % str(names.configdn).upper()))
821         os.remove(configldb)
822
823
824 def update_privilege(newpaths, paths):
825     """Update the privilege database
826
827     :param newpaths: List of paths for different provision objects from the reference provision
828     :param paths: List of paths for different provision objects from the upgraded provision"""
829     message(SIMPLE, "Copy privilege")
830     shutil.copy(os.path.join(newpaths.private_dir, "privilege.ldb"), 
831                 os.path.join(paths.private_dir, "privilege.ldb"))
832
833
834 def update_samdb(newpaths, paths, creds, session, names):
835     """Upgrade the SAM DB contents for all the provision
836
837     :param newpaths: List of paths for different provision objects from the reference provision
838     :param paths: List of paths for different provision objects from the upgraded provision
839     :param creds: Credential for the authentification
840     :param session: Session for connexion
841     :param names: List of key provision parameters"""
842
843     message(SIMPLE, "Doing schema update")
844     hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
845     message(SIMPLE, "Done with schema update")
846     message(SIMPLE, "Scanning whole provision for updates and additions")
847     hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
848     message(SIMPLE, "Done with scanning")
849
850
851 def update_machine_account_password(paths, creds, session, names):
852     """Update (change) the password of the current DC both in the SAM db and in secret one
853
854     :param paths: List of paths for different provision objects from the upgraded provision
855     :param creds: Credential for the authentification
856     :param session: Session for connexion
857     :param names: List of key provision parameters"""
858
859     secrets_ldb = Ldb(paths.secrets, session_info=session,
860         credentials=creds,lp=lp)
861     secrets_ldb.transaction_start()
862     secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
863     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
864     sam_ldb.transaction_start()
865     if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
866         res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
867         assert(len(res) == 1)
868
869         msg = Message(res[0].dn)
870         machinepass = samba.generate_random_password(128, 255)
871         msg["userPassword"] = MessageElement(machinepass, FLAG_MOD_REPLACE, "userPassword")
872         sam_ldb.modify(msg)
873
874         res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
875                      attrs=["msDs-keyVersionNumber"])
876         assert(len(res) == 1)
877         kvno = int(str(res[0]["msDs-keyVersionNumber"]))
878
879         secretsdb_self_join(secrets_ldb, domain=names.domain,
880                     realm=names.realm or sambaopts._lp.get('realm'),
881                     domainsid=names.domainsid,
882                     dnsdomain=names.dnsdomain,
883                     netbiosname=names.netbiosname,
884                     machinepass=machinepass,
885                     key_version_number=kvno,
886                     secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
887         sam_ldb.transaction_prepare_commit()
888         secrets_ldb.transaction_prepare_commit()
889         sam_ldb.transaction_commit()
890         secrets_ldb.transaction_commit()
891     else:
892         secrets_ldb.transaction_cancel()
893
894
895 def update_gpo(paths,creds,session,names):
896     """Create missing GPO file object if needed
897
898     Set ACL correctly also.
899     """
900     dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid)
901     if not os.path.isdir(dir):
902         create_gpo_struct(dir)
903
904     dir = getpolicypath(paths.sysvol,names.dnsdomain,names.policyid_dc)
905     if not os.path.isdir(dir):
906         create_gpo_struct(dir)
907     samdb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
908     set_gpo_acl(paths.sysvol, names.dnsdomain, names.domainsid,
909         names.domaindn, samdb, lp)
910
911
912 def updateOEMInfo(paths, creds, session,names):
913     sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds, lp=lp,
914         options=["modules:samba_dsdb"])
915     res = sam_ldb.search(expression="(objectClass=*)",base=str(names.rootdn),
916                             scope=SCOPE_BASE, attrs=["dn","oEMInformation"])
917     if len(res) > 0:
918         info = res[0]["oEMInformation"]
919         info = "%s, upgrade to %s"%(info,version)
920         delta = Message()
921         delta.dn = Dn(sam_ldb,str(res[0]["dn"]))
922         descr = get_schema_descriptor(names.domainsid)
923         delta["oEMInformation"] = MessageElement(info, FLAG_MOD_REPLACE,
924             "oEMInformation" )
925         sam_ldb.modify(delta)
926
927
928 def setup_path(file):
929     return os.path.join(setup_dir, file)
930
931
932 if __name__ == '__main__':
933     # From here start the big steps of the program
934     # First get files paths
935     paths=get_paths(param,smbconf=smbconf)
936     paths.setup = setup_dir
937     # Guess all the needed names (variables in fact) from the current
938     # provision.
939
940     names = find_provision_key_parameters(param, creds, session, paths, smbconf)
941     if not sanitychecks(creds,session,names,paths):
942         message(SIMPLE, "Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision")
943         sys.exit(1)
944     # Let's see them
945     print_provision_key_parameters(names)
946     # With all this information let's create a fresh new provision used as reference
947     message(SIMPLE, "Creating a reference provision")
948     provisiondir = tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
949     newprovision(names, setup_dir, creds, session, smbconf, provisiondir, provision_logger)
950     # Get file paths of this new provision
951     newpaths = get_paths(param, targetdir=provisiondir)
952     populate_backlink(newpaths, creds, session,names.schemadn)
953     populate_dnsyntax(newpaths, creds, session,names.schemadn)
954     # Check the difference
955     update_basesamdb(newpaths, paths, names)
956
957     if opts.full:
958         update_samdb(newpaths, paths, creds, session, names)
959     update_secrets(newpaths, paths, creds, session)
960     update_privilege(newpaths, paths)
961     update_machine_account_password(paths, creds, session, names)
962     # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
963     # to recreate them with the good form but with system account and then give the ownership to admin ...
964     admin_session_info = admin_session(lp, str(names.domainsid))
965     message(SIMPLE, "Updating SD")
966     update_sd(paths, creds, session,names)
967     update_sd(paths, creds, admin_session_info, names)
968     check_updated_sd(newpaths, paths, creds, session, names)
969     updateOEMInfo(paths,creds,session,names)
970     message(SIMPLE, "Upgrade finished !")
971     # remove reference provision now that everything is done !
972     shutil.rmtree(provisiondir)