s4:upgradeprovision - fix up the script regarding linked attributes
[Samba/fernandojvsilva.git] / source4 / scripting / bin / upgradeprovision
blob23980cd3da9b773ea9ba1aa15c7e28c20875d837
1 #!/usr/bin/python
3 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
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
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.
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.
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/>.
24 import getopt
25 import shutil
26 import optparse
27 import os
28 import sys
29 import random
30 import string
31 import re
32 import base64
33 import tempfile
34 # Find right directory when running from source tree
35 sys.path.insert(0, "bin/python")
37 from base64 import b64encode
39 import samba
40 from samba.credentials import DONT_USE_KERBEROS
41 from samba.auth import system_session, admin_session
42 from samba import Ldb, DS_DOMAIN_FUNCTION_2000, DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008, DS_DC_FUNCTION_2008_R2
43 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError
44 import ldb
45 import samba.getopt as options
46 from samba.samdb import SamDB
47 from samba import param
48 from samba import glue
49 from samba.provision import ProvisionNames,provision_paths_from_lp,find_setup_dir,FILL_FULL,provision, get_domain_descriptor, get_config_descriptor, secretsdb_self_join
50 from samba.provisionexceptions import ProvisioningError
51 from samba.schema import get_dnsyntax_attributes, get_linked_attributes, Schema, get_schema_descriptor
52 from samba.dcerpc import misc, security
53 from samba.ndr import ndr_pack, ndr_unpack
54 from samba.dcerpc.misc import SEC_CHAN_BDC
56 replace=2^ldb.FLAG_MOD_REPLACE
57 add=2^ldb.FLAG_MOD_ADD
58 delete=2^ldb.FLAG_MOD_DELETE
60 #Errors are always logged
61 ERROR = -1
62 SIMPLE = 0x00
63 CHANGE = 0x01
64 CHANGESD = 0x02
65 GUESS = 0x04
66 PROVISION = 0x08
67 CHANGEALL = 0xff
69 # Attributes that are never copied from the reference provision (even if they
70 # do not exist in the destination object).
71 # This is most probably because they are populated automatcally when object is
72 # created
73 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}
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}
87 backlinked = []
89 def define_what_to_log(opts):
90 what = 0
91 if opts.debugchange:
92 what = what | CHANGE
93 if opts.debugchangesd:
94 what = what | CHANGESD
95 if opts.debugguess:
96 what = what | GUESS
97 if opts.debugprovision:
98 what = what | PROVISION
99 if opts.debugall:
100 what = what | CHANGEALL
101 return what
104 parser = optparse.OptionParser("provision [options]")
105 sambaopts = options.SambaOptions(parser)
106 parser.add_option_group(sambaopts)
107 parser.add_option_group(options.VersionOptions(parser))
108 credopts = options.CredentialsOptions(parser)
109 parser.add_option_group(credopts)
110 parser.add_option("--setupdir", type="string", metavar="DIR",
111 help="directory with setup files")
112 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
113 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
114 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
115 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
116 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
117 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
119 opts = parser.parse_args()[0]
121 whatToLog = define_what_to_log(opts)
123 def messageprovision(text):
124 """print a message if quiet is not set."""
125 if opts.debugprovision or opts.debugall:
126 print text
128 def message(what,text):
129 """print a message if quiet is not set."""
130 if (whatToLog & what) or (what <= 0 ):
131 print text
133 if len(sys.argv) == 1:
134 opts.interactive = True
135 lp = sambaopts.get_loadparm()
136 smbconf = lp.configfile
138 creds = credopts.get_credentials(lp)
139 creds.set_kerberos_state(DONT_USE_KERBEROS)
140 setup_dir = opts.setupdir
141 if setup_dir is None:
142 setup_dir = find_setup_dir()
144 session = system_session()
146 # simple helper to allow back and forth rename
147 def identic_rename(ldbobj,dn):
148 (before,sep,after)=str(dn).partition('=')
149 ldbobj.rename(dn,ldb.Dn(ldbobj,"%s=foo%s"%(before,after)))
150 ldbobj.rename(ldb.Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
152 # Create an array of backlinked attributes
153 def populate_backlink(newpaths,creds,session,schemadn):
154 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
155 backlinked.extend(get_linked_attributes(ldb.Dn(newsam_ldb,str(schemadn)),newsam_ldb).values())
157 # Get Paths for important objects (ldb, keytabs ...)
158 def get_paths(targetdir=None,smbconf=None):
159 if targetdir is not None:
160 if (not os.path.exists(os.path.join(targetdir, "etc"))):
161 os.makedirs(os.path.join(targetdir, "etc"))
162 smbconf = os.path.join(targetdir, "etc", "smb.conf")
163 if smbconf is None:
164 smbconf = param.default_path()
166 if not os.path.exists(smbconf):
167 message(ERROR,"Unable to find smb.conf ..")
168 parser.print_usage()
169 sys.exit(1)
171 lp = param.LoadParm()
172 lp.load(smbconf)
173 # Normally we need the domain name for this function but for our needs it's
174 # pointless
175 paths = provision_paths_from_lp(lp,"foo")
176 return paths
178 # This function guesses (fetches) informations needed to make a fresh provision
179 # from the current provision
180 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
181 def guess_names_from_current_provision(credentials,session_info,paths):
182 lp = param.LoadParm()
183 lp.load(paths.smbconf)
184 names = ProvisionNames()
185 # NT domain, kerberos realm, root dn, domain dn, domain dns name
186 names.domain = string.upper(lp.get("workgroup"))
187 names.realm = lp.get("realm")
188 basedn = "DC=" + names.realm.replace(".",",DC=")
189 names.dnsdomain = names.realm
190 names.realm = string.upper(names.realm)
191 # netbiosname
192 secrets_ldb = Ldb(paths.secrets, session_info=session_info, credentials=credentials,lp=lp, options=["modules:samba_secrets"])
193 # Get the netbiosname first (could be obtained from smb.conf in theory)
194 attrs = ["sAMAccountName"]
195 res = secrets_ldb.search(expression="(flatname=%s)"%names.domain,base="CN=Primary Domains", scope=SCOPE_SUBTREE, attrs=attrs)
196 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
198 names.smbconf = smbconf
199 # It's important here to let ldb load with the old module or it's quite
200 # certain that the LDB won't load ...
201 samdb = Ldb(paths.samdb, session_info=session_info,
202 credentials=credentials, lp=lp, options=["modules:samba_dsdb"])
204 # That's a bit simplistic but it's ok as long as we have only 3
205 # partitions
206 attrs2 = ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
207 current = samdb.search(expression="(objectClass=*)",base="", scope=SCOPE_BASE, attrs=attrs2)
209 names.configdn = current[0]["configurationNamingContext"]
210 configdn = str(names.configdn)
211 names.schemadn = current[0]["schemaNamingContext"]
212 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb, current[0]["defaultNamingContext"][0]))):
213 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths.samdb, str(current[0]["defaultNamingContext"][0]), paths.smbconf, basedn)))
215 names.domaindn=current[0]["defaultNamingContext"]
216 names.rootdn=current[0]["rootDomainNamingContext"]
217 # default site name
218 attrs3 = ["cn"]
219 res3= samdb.search(expression="(objectClass=*)",base="CN=Sites,"+configdn, scope=SCOPE_ONELEVEL, attrs=attrs3)
220 names.sitename = str(res3[0]["cn"])
222 # dns hostname and server dn
223 attrs4 = ["dNSHostName"]
224 res4= samdb.search(expression="(CN=%s)"%names.netbiosname,base="OU=Domain Controllers,"+basedn, \
225 scope=SCOPE_ONELEVEL, attrs=attrs4)
226 names.hostname = str(res4[0]["dNSHostName"]).replace("."+names.dnsdomain,"")
228 server_res = samdb.search(expression="serverReference=%s"%res4[0].dn, attrs=[], base=configdn)
229 names.serverdn = server_res[0].dn
231 # invocation id/objectguid
232 res5 = samdb.search(expression="(objectClass=*)",base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE, attrs=["invocationID","objectGUID"])
233 names.invocation = str(ndr_unpack( misc.GUID,res5[0]["invocationId"][0]))
234 names.ntdsguid = str(ndr_unpack( misc.GUID,res5[0]["objectGUID"][0]))
236 # domain guid/sid
237 attrs6 = ["objectGUID", "objectSid","msDS-Behavior-Version" ]
238 res6 = samdb.search(expression="(objectClass=*)",base=basedn, scope=SCOPE_BASE, attrs=attrs6)
239 names.domainguid = str(ndr_unpack( misc.GUID,res6[0]["objectGUID"][0]))
240 names.domainsid = ndr_unpack( security.dom_sid,res6[0]["objectSid"][0])
241 if res6[0].get("msDS-Behavior-Version") == None or int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
242 names.domainlevel = DS_DOMAIN_FUNCTION_2000
243 else:
244 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
246 # policy guid
247 attrs7 = ["cn","displayName"]
248 res7 = samdb.search(expression="(displayName=Default Domain Policy)",base="CN=Policies,CN=System,"+basedn, \
249 scope=SCOPE_ONELEVEL, attrs=attrs7)
250 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
251 # dc policy guid
252 attrs8 = ["cn","displayName"]
253 res8 = samdb.search(expression="(displayName=Default Domain Controllers Policy)",base="CN=Policies,CN=System,"+basedn, \
254 scope=SCOPE_ONELEVEL, attrs=attrs7)
255 if len(res8) == 1:
256 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
257 else:
258 names.policyid_dc = None
261 return names
263 # Debug a little bit
264 def print_names(names):
265 message(GUESS, "rootdn :"+str(names.rootdn))
266 message(GUESS, "configdn :"+str(names.configdn))
267 message(GUESS, "schemadn :"+str(names.schemadn))
268 message(GUESS, "serverdn :"+str(names.serverdn))
269 message(GUESS, "netbiosname :"+names.netbiosname)
270 message(GUESS, "defaultsite :"+names.sitename)
271 message(GUESS, "dnsdomain :"+names.dnsdomain)
272 message(GUESS, "hostname :"+names.hostname)
273 message(GUESS, "domain :"+names.domain)
274 message(GUESS, "realm :"+names.realm)
275 message(GUESS, "invocationid:"+names.invocation)
276 message(GUESS, "policyguid :"+names.policyid)
277 message(GUESS, "policyguiddc:"+str(names.policyid_dc))
278 message(GUESS, "domainsid :"+str(names.domainsid))
279 message(GUESS, "domainguid :"+names.domainguid)
280 message(GUESS, "ntdsguid :"+names.ntdsguid)
281 message(GUESS, "domainlevel :"+str(names.domainlevel))
283 # Create a fresh new reference provision
284 # This provision will be the reference for knowing what has changed in the
285 # since the latest upgrade in the current provision
286 def newprovision(names,setup_dir,creds,session,smbconf):
287 message(SIMPLE, "Creating a reference provision")
288 provdir=tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
289 if os.path.isdir(provdir):
290 rmall(provdir)
291 logstd=os.path.join(provdir,"log.std")
292 os.chdir(os.path.join(setup_dir,".."))
293 os.mkdir(provdir)
294 os.close(2)
295 sys.stderr = open("%s/provision.log"%provdir, "w")
296 message(PROVISION, "Reference provision stored in %s"%provdir)
297 message(PROVISION, "STDERR message of provision will be logged in %s/provision.log"%provdir)
298 sys.stderr = open("/dev/stdout", "w")
299 provision(setup_dir, messageprovision,
300 session, creds, smbconf=smbconf, targetdir=provdir,
301 samdb_fill=FILL_FULL, realm=names.realm, domain=names.domain,
302 domainguid=names.domainguid, domainsid=str(names.domainsid),ntdsguid=names.ntdsguid,
303 policyguid=names.policyid,policyguid_dc=names.policyid_dc,hostname=names.netbiosname,
304 hostip=None, hostip6=None,
305 invocationid=names.invocation, adminpass=None,
306 krbtgtpass=None, machinepass=None,
307 dnspass=None, root=None, nobody=None,
308 wheel=None, users=None,
309 serverrole="domain controller",
310 ldap_backend_extra_port=None,
311 backend_type=None,
312 ldapadminpass=None,
313 ol_mmr_urls=None,
314 slapd_path=None,
315 setup_ds_path=None,
316 nosync=None,
317 dom_for_fun_level=names.domainlevel,
318 ldap_dryrun_mode=None)
319 return provdir
321 # This function sorts two DNs in the lexicographical order and put higher level
322 # DN before.
323 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller
324 # (-1) as it has less level
325 def dn_sort(x,y):
326 p = re.compile(r'(?<!\\),')
327 tab1 = p.split(str(x))
328 tab2 = p.split(str(y))
329 min = 0
330 if (len(tab1) > len(tab2)):
331 min = len(tab2)
332 elif (len(tab1) < len(tab2)):
333 min = len(tab1)
334 else:
335 min = len(tab1)
336 len1=len(tab1)-1
337 len2=len(tab2)-1
338 space = " "
339 # Note: python range go up to upper limit but do not include it
340 for i in range(0,min):
341 ret=cmp(tab1[len1-i],tab2[len2-i])
342 if(ret != 0):
343 return ret
344 else:
345 if(i==min-1):
346 if(len1==len2):
347 message(ERROR,"PB PB PB"+space.join(tab1)+" / "+space.join(tab2))
348 if(len1>len2):
349 return 1
350 else:
351 return -1
352 return ret
354 # Check for security descriptors modifications return 1 if it is and 0 otherwise
355 # it also populate hash structure for later use in the upgrade process
356 def handle_security_desc(ischema,att,msgElt,hashallSD,old,new):
357 if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
358 hashSD = {}
359 hashSD["oldSD"] = old[0][att]
360 hashSD["newSD"] = new[0][att]
361 hashallSD[str(old[0].dn)] = hashSD
362 return 0
363 if att == "nTSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
364 if ischema == 0:
365 hashSD = {}
366 hashSD["oldSD"] = ndr_unpack(security.descriptor,str(old[0][att]))
367 hashSD["newSD"] = ndr_unpack(security.descriptor,str(new[0][att]))
368 hashallSD[str(old[0].dn)] = hashSD
369 return 1
370 return 0
372 # Handle special cases ... That's when we want to update a particular attribute
373 # only, e.g. if it has a certain value or if it's for a certain object or
374 # a class of object.
375 # It can be also if we want to do a merge of value instead of a simple replace
376 def handle_special_case(att,delta,new,old,ischema):
377 flag = delta.get(att).flags()
378 if (att == "gPLink" or att == "gPCFileSysPath") and flag == ldb.FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
379 delta.remove(att)
380 return 1
381 if att == "forceLogoff":
382 ref=0x8000000000000000
383 oldval=int(old[0][att][0])
384 newval=int(new[0][att][0])
385 ref == old and ref == abs(new)
386 return 1
387 if (att == "adminDisplayName" or att == "adminDescription") and ischema:
388 return 1
389 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn)) and att == "defaultObjectCategory" and flag == ldb.FLAG_MOD_REPLACE):
390 return 1
391 # if (str(old[0].dn) == "CN=S-1-5-11,CN=ForeignSecurityPrincipals,%s"%(str(names.rootdn)) and att == "description" and flag == ldb.FLAG_MOD_DELETE):
392 # return 1
393 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == ldb.FLAG_MOD_REPLACE):
394 return 1
395 if ( (att == "member" or att == "servicePrincipalName") and flag == ldb.FLAG_MOD_REPLACE):
397 hash = {}
398 newval = []
399 changeDelta=0
400 for elem in old[0][att]:
401 hash[str(elem)]=1
402 newval.append(str(elem))
404 for elem in new[0][att]:
405 if not hash.has_key(str(elem)):
406 changeDelta=1
407 newval.append(str(elem))
408 if changeDelta == 1:
409 delta[att] = ldb.MessageElement(newval, ldb.FLAG_MOD_REPLACE, att)
410 else:
411 delta.remove(att)
412 return 1
413 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == ldb.FLAG_MOD_REPLACE):
414 return 1
415 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
416 return 1
417 return 0
419 def update_secrets(newpaths,paths,creds,session):
420 message(SIMPLE,"update secrets.ldb")
421 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
422 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
423 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
424 current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
425 delta = secrets_ldb.msg_diff(current[0],reference[0])
426 delta.dn = current[0].dn
427 secrets_ldb.modify(delta)
429 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
430 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
431 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
432 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
433 hash_new = {}
434 hash = {}
435 listMissing = []
436 listPresent = []
438 empty = ldb.Message()
439 for i in range(0,len(reference)):
440 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
442 # Create a hash for speeding the search of existing object in the
443 # current provision
444 for i in range(0,len(current)):
445 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
447 for k in hash_new.keys():
448 if not hash.has_key(k):
449 listMissing.append(hash_new[k])
450 else:
451 listPresent.append(hash_new[k])
452 for entry in listMissing:
453 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
454 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
455 delta = secrets_ldb.msg_diff(empty,reference[0])
456 for att in hashAttrNotCopied.keys():
457 delta.remove(att)
458 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
459 for att in delta:
460 message(CHANGE," Adding attribute %s"%att)
461 delta.dn = reference[0].dn
462 secrets_ldb.add(delta)
464 for entry in listPresent:
465 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
466 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
467 delta = secrets_ldb.msg_diff(current[0],reference[0])
469 for att in hashAttrNotCopied.keys():
470 delta.remove(att)
471 for att in delta:
472 i = i + 1
474 if att == "name":
475 message(CHANGE,"Found attribute name on %s, must rename the DN "%(current[0].dn))
476 identic_rename(secrets_ldb,reference[0].dn)
477 else:
478 delta.remove(att)
481 for entry in listPresent:
482 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
483 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
484 delta = secrets_ldb.msg_diff(current[0],reference[0])
486 for att in hashAttrNotCopied.keys():
487 delta.remove(att)
488 for att in delta:
489 i = i + 1
490 if att != "dn":
491 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
493 delta.dn = current[0].dn
494 secrets_ldb.modify(delta)
497 # Check difference between the current provision and the reference provision.
498 # It looks for all objects which base DN is name. If ischema is "false" then
499 # the scan is done in cross partition mode.
500 # If "ischema" is true, then special handling is done for dealing with schema
501 def check_diff_name(newpaths,paths,creds,session,basedn,names,ischema):
502 hash_new = {}
503 hash = {}
504 hashallSD = {}
505 listMissing = []
506 listPresent = []
507 reference = []
508 current = []
509 # Connect to the reference provision and get all the attribute in the
510 # partition referred by name
511 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
512 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
513 sam_ldb.transaction_start()
514 if ischema:
515 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
516 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
517 else:
518 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
519 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
521 sam_ldb.transaction_commit()
522 # Create a hash for speeding the search of new object
523 for i in range(0,len(reference)):
524 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
526 # Create a hash for speeding the search of existing object in the
527 # current provision
528 for i in range(0,len(current)):
529 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
531 for k in hash_new.keys():
532 if not hash.has_key(k):
533 listMissing.append(hash_new[k])
534 else:
535 listPresent.append(hash_new[k])
537 # Sort the missing object in order to have object of the lowest level
538 # first (which can be containers for higher level objects)
539 listMissing.sort(dn_sort)
540 listPresent.sort(dn_sort)
542 if ischema:
543 # The following lines (up to the for loop) is to load the up to
544 # date schema into our current LDB
545 # a complete schema is needed as the insertion of attributes
546 # and class is done against it
547 # and the schema is self validated
548 # The double ldb open and schema validation is taken from the
549 # initial provision script
550 # it's not certain that it is really needed ....
551 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
552 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
553 # Load the schema from the one we computed earlier
554 sam_ldb.set_schema_from_ldb(schema.ldb)
555 # And now we can connect to the DB - the schema won't be loaded
556 # from the DB
557 sam_ldb.connect(paths.samdb)
558 else:
559 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
561 sam_ldb.transaction_start()
563 err_num = 0
564 err_msg = ""
565 while len(listMissing) > 0:
566 listMissing2 = []
568 empty = ldb.Message()
569 message(SIMPLE,"There are still %d objects missing"%(len(listMissing)))
571 for dn in listMissing:
572 reference = newsam_ldb.search(expression="dn=%s" % (str(dn)),
573 base=basedn, scope=SCOPE_SUBTREE,
574 controls=["search_options:1:2"])
575 delta = sam_ldb.msg_diff(empty,reference[0])
576 for att in hashAttrNotCopied.keys():
577 delta.remove(att)
578 for att in backlinked:
579 delta.remove(att)
580 delta.dn = dn
582 try:
583 sam_ldb.add(delta,["relax:0"])
584 # This is needed here since otherwise the
585 # "replmd_meta_data" module doesn't see the
586 # updated data
587 sam_ldb.transaction_commit()
588 sam_ldb.transaction_start()
589 except LdbError, (num, msg):
590 # An exception can happen if a linked object
591 # doesn't exist which can happen if it is also
592 # to be added
593 err_num = num
594 err_msg = msg
595 listMissing2.append(dn)
597 if len(listMissing2) == len(listMissing):
598 # We couldn't add any object in this iteration ->
599 # we have to resign and hope that the user manually
600 # fixes the damage
602 message(ERROR, "The script isn't capable to do the upgrade fully automatically!")
603 message(ERROR, "Often this happens when important system objects moved their location. Please look for them (for example doable using the displayed 'sAMAccountName' attribute), backup if personally changed and remove them.")
604 message(ERROR, "Reinvoke this script and reapply eventual modifications done before. It is possible to get this error more than once (for each problematic object).")
606 raise LdbError(err_num, err_msg)
608 listMissing = listMissing2
610 changed = 0
611 for dn in listPresent:
612 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
613 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
614 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
615 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
616 identic_rename(sam_ldb,reference[0].dn)
617 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
619 delta = sam_ldb.msg_diff(current[0],reference[0])
620 for att in hashAttrNotCopied.keys():
621 delta.remove(att)
622 for att in backlinked:
623 delta.remove(att)
624 delta.remove("parentGUID")
625 nb = 0
627 for att in delta:
628 msgElt = delta.get(att)
629 if att == "dn":
630 continue
631 if att == "name":
632 delta.remove(att)
633 continue
634 if handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
635 delta.remove(att)
636 continue
637 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
638 if handle_special_case(att,delta,reference,current,ischema)==0 and msgElt.flags()!=ldb.FLAG_MOD_ADD:
639 i = 0
640 if opts.debugchange:
641 message(CHANGE, "dn= "+str(dn)+ " "+att + " with flag "+str(msgElt.flags())+ " is not allowed to be changed/removed, I discard this change ...")
642 for e in range(0,len(current[0][att])):
643 message(CHANGE,"old %d : %s"%(i,str(current[0][att][e])))
644 if msgElt.flags() == 2:
645 i = 0
646 for e in range(0,len(reference[0][att])):
647 message(CHANGE,"new %d : %s"%(i,str(reference[0][att][e])))
648 delta.remove(att)
649 delta.dn = dn
650 if len(delta.items()) >1:
651 attributes=",".join(delta.keys())
652 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
653 changed = changed + 1
654 sam_ldb.modify(delta)
656 sam_ldb.transaction_commit()
657 message(SIMPLE,"There are %d changed objects"%(changed))
658 return hashallSD
660 # Check that SD are correct
661 def check_updated_sd(newpaths,paths,creds,session,names):
662 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
663 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
664 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
665 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
666 hash_new = {}
667 for i in range(0,len(reference)):
668 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
670 for i in range(0,len(current)):
671 key = str(current[i]["dn"]).lower()
672 if hash_new.has_key(key):
673 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
674 if sddl != hash_new[key]:
675 print "%s new sddl/sddl in ref"%key
676 print "%s\n%s"%(sddl,hash_new[key])
678 # Simple update method for updating the SD that rely on the fact that nobody
679 # should have modified the SD
680 # This assumption is safe right now (alpha9) but should be removed asap
681 def update_sd(paths,creds,session,names):
682 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
683 sam_ldb.transaction_start()
684 # First update the SD for the rootdn
685 sam_ldb.set_session_info(session)
686 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
687 delta = ldb.Message()
688 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
689 descr = get_domain_descriptor(names.domainsid)
690 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
691 sam_ldb.modify(delta,["recalculate_sd:0"])
692 # Then the config dn
693 res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
694 delta = ldb.Message()
695 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
696 descr = get_config_descriptor(names.domainsid)
697 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
698 sam_ldb.modify(delta,["recalculate_sd:0"])
699 # Then the schema dn
700 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
701 delta = ldb.Message()
702 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
703 descr = get_schema_descriptor(names.domainsid)
704 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
705 sam_ldb.modify(delta,["recalculate_sd:0"])
707 # Then the rest
708 hash = {}
709 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
710 for obj in res:
711 if not (str(obj["dn"]) == str(names.rootdn) or
712 str(obj["dn"]) == str(names.configdn) or \
713 str(obj["dn"]) == str(names.schemadn)):
714 hash[str(obj["dn"])] = obj["whenCreated"]
716 listkeys = hash.keys()
717 listkeys.sort(dn_sort)
719 for key in listkeys:
720 try:
721 delta = ldb.Message()
722 delta.dn = ldb.Dn(sam_ldb,key)
723 delta["whenCreated"] = ldb.MessageElement( hash[key],ldb.FLAG_MOD_REPLACE,"whenCreated" )
724 sam_ldb.modify(delta,["recalculate_sd:0"])
725 except:
726 sam_ldb.transaction_cancel()
727 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
728 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
729 return
730 sam_ldb.transaction_commit()
732 def rmall(topdir):
733 for root, dirs, files in os.walk(topdir, topdown=False):
734 for name in files:
735 os.remove(os.path.join(root, name))
736 for name in dirs:
737 os.rmdir(os.path.join(root, name))
738 os.rmdir(topdir)
741 def update_basesamdb(newpaths,paths,names):
742 message(SIMPLE,"Copy samdb")
743 shutil.copy(newpaths.samdb,paths.samdb)
745 message(SIMPLE,"Update partitions filename if needed")
746 schemaldb=os.path.join(paths.private_dir,"schema.ldb")
747 configldb=os.path.join(paths.private_dir,"configuration.ldb")
748 usersldb=os.path.join(paths.private_dir,"users.ldb")
749 samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
751 if not os.path.isdir(samldbdir):
752 os.mkdir(samldbdir)
753 os.chmod(samldbdir,0700)
754 if os.path.isfile(schemaldb):
755 shutil.copy(schemaldb,os.path.join(samldbdir,"%s.ldb"%str(names.schemadn).upper()))
756 os.remove(schemaldb)
757 if os.path.isfile(usersldb):
758 shutil.copy(usersldb,os.path.join(samldbdir,"%s.ldb"%str(names.rootdn).upper()))
759 os.remove(usersldb)
760 if os.path.isfile(configldb):
761 shutil.copy(configldb,os.path.join(samldbdir,"%s.ldb"%str(names.configdn).upper()))
762 os.remove(configldb)
764 def update_privilege(newpaths,paths):
765 message(SIMPLE,"Copy privilege")
766 shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb"))
768 # For each partition check the differences
769 def update_samdb(newpaths,paths,creds,session,names):
771 message(SIMPLE, "Doing schema update")
772 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
773 message(SIMPLE,"Done with schema update")
774 message(SIMPLE,"Scanning whole provision for updates and additions")
775 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
776 message(SIMPLE,"Done with scanning")
778 def update_machine_account_password(paths,creds,session,names):
780 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
781 secrets_ldb.transaction_start()
782 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
783 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
784 sam_ldb.transaction_start()
785 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
786 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
787 assert(len(res) == 1)
789 msg = ldb.Message(res[0].dn)
790 machinepass = glue.generate_random_str(12)
791 msg["userPassword"] = ldb.MessageElement(machinepass, ldb.FLAG_MOD_REPLACE, "userPassword")
792 sam_ldb.modify(msg)
794 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
795 attrs=["msDs-keyVersionNumber"])
796 assert(len(res) == 1)
797 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
799 secretsdb_self_join(secrets_ldb, domain=names.domain,
800 realm=names.realm,
801 domainsid=names.domainsid,
802 dnsdomain=names.dnsdomain,
803 netbiosname=names.netbiosname,
804 machinepass=machinepass,
805 key_version_number=kvno,
806 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
807 sam_ldb.transaction_prepare_commit()
808 secrets_ldb.transaction_prepare_commit()
809 sam_ldb.transaction_commit()
810 secrets_ldb.transaction_commit()
811 else:
812 secrets_ldb.transaction_cancel()
814 # From here start the big steps of the program
815 # First get files paths
816 paths=get_paths(smbconf=smbconf)
817 paths.setup = setup_dir
818 def setup_path(file):
819 return os.path.join(setup_dir, file)
820 # Guess all the needed names (variables in fact) from the current
821 # provision.
822 names = guess_names_from_current_provision(creds,session,paths)
823 # Let's see them
824 print_names(names)
825 # With all this information let's create a fresh new provision used as reference
826 provisiondir = newprovision(names,setup_dir,creds,session,smbconf)
827 # Get file paths of this new provision
828 newpaths = get_paths(targetdir=provisiondir)
829 populate_backlink(newpaths,creds,session,names.schemadn)
830 # Check the difference
831 update_basesamdb(newpaths,paths,names)
833 if opts.full:
834 update_samdb(newpaths,paths,creds,session,names)
835 update_secrets(newpaths,paths,creds,session)
836 update_privilege(newpaths,paths)
837 update_machine_account_password(paths,creds,session,names)
838 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
839 # to recreate them with the good form but with system account and then give the ownership to admin ...
840 admin_session_info = admin_session(lp, str(names.domainsid))
841 message(SIMPLE,"Updating SD")
842 update_sd(paths,creds,session,names)
843 update_sd(paths,creds,admin_session_info,names)
844 check_updated_sd(newpaths,paths,creds,session,names)
845 message(SIMPLE,"Upgrade finished !")
846 # remove reference provision now that everything is done !
847 rmall(provisiondir)