upgradeprovision: improve info messages
[Samba/gebeck_regimport.git] / source4 / scripting / bin / upgradeprovision
blob9af8b2d189c39bd517d24a23817e780c0c7e07b3
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.misc import messageEltFlagToString
50 from samba.provision import ProvisionNames,provision_paths_from_lp,find_setup_dir,FILL_FULL,provision, get_domain_descriptor, get_config_descriptor, secretsdb_self_join
51 from samba.provisionexceptions import ProvisioningError
52 from samba.schema import get_dnsyntax_attributes, get_linked_attributes, Schema, get_schema_descriptor
53 from samba.dcerpc import misc, security
54 from samba.ndr import ndr_pack, ndr_unpack
55 from samba.dcerpc.misc import SEC_CHAN_BDC
57 replace=2^ldb.FLAG_MOD_REPLACE
58 add=2^ldb.FLAG_MOD_ADD
59 delete=2^ldb.FLAG_MOD_DELETE
61 #Errors are always logged
62 ERROR = -1
63 SIMPLE = 0x00
64 CHANGE = 0x01
65 CHANGESD = 0x02
66 GUESS = 0x04
67 PROVISION = 0x08
68 CHANGEALL = 0xff
70 # Attributes that are never copied from the reference provision (even if they
71 # do not exist in the destination object).
72 # This is most probably because they are populated automatcally when object is
73 # created
74 hashAttrNotCopied = { "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,\
75 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,\
76 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,\
77 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,\
78 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,\
79 "maxPwdAge":1, "mail":1, "secret":1,"possibleInferiors":1, "sAMAccountType":1}
81 # Usually for an object that already exists we do not overwrite attributes as
82 # they might have been changed for good reasons. Anyway for a few of them it's
83 # mandatory to replace them otherwise the provision will be broken somehow.
84 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,"systemOnly":replace, "searchFlags":replace,\
85 "mayContain":replace, "systemFlags":replace,"description":replace,
86 "oEMInformation":replace, "operatingSystemVersion":replace, "adminPropertyPages":replace,
87 "defaultSecurityDescriptor": replace,"wellKnownObjects":replace,"privilege":delete}
88 backlinked = []
90 def define_what_to_log(opts):
91 what = 0
92 if opts.debugchange:
93 what = what | CHANGE
94 if opts.debugchangesd:
95 what = what | CHANGESD
96 if opts.debugguess:
97 what = what | GUESS
98 if opts.debugprovision:
99 what = what | PROVISION
100 if opts.debugall:
101 what = what | CHANGEALL
102 return what
105 parser = optparse.OptionParser("provision [options]")
106 sambaopts = options.SambaOptions(parser)
107 parser.add_option_group(sambaopts)
108 parser.add_option_group(options.VersionOptions(parser))
109 credopts = options.CredentialsOptions(parser)
110 parser.add_option_group(credopts)
111 parser.add_option("--setupdir", type="string", metavar="DIR",
112 help="directory with setup files")
113 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
114 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
115 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
116 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
117 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
118 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
120 opts = parser.parse_args()[0]
122 whatToLog = define_what_to_log(opts)
124 def messageprovision(text):
125 """print a message if quiet is not set."""
126 if opts.debugprovision or opts.debugall:
127 print text
129 def message(what,text):
130 """print a message if quiet is not set."""
131 if (whatToLog & what) or (what <= 0 ):
132 print text
134 if len(sys.argv) == 1:
135 opts.interactive = True
136 lp = sambaopts.get_loadparm()
137 smbconf = lp.configfile
139 creds = credopts.get_credentials(lp)
140 creds.set_kerberos_state(DONT_USE_KERBEROS)
141 setup_dir = opts.setupdir
142 if setup_dir is None:
143 setup_dir = find_setup_dir()
145 session = system_session()
147 # simple helper to allow back and forth rename
148 def identic_rename(ldbobj,dn):
149 (before,sep,after)=str(dn).partition('=')
150 ldbobj.rename(dn,ldb.Dn(ldbobj,"%s=foo%s"%(before,after)))
151 ldbobj.rename(ldb.Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
153 # Create an array of backlinked attributes
154 def populate_backlink(newpaths,creds,session,schemadn):
155 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
156 backlinked.extend(get_linked_attributes(ldb.Dn(newsam_ldb,str(schemadn)),newsam_ldb).values())
158 # Get Paths for important objects (ldb, keytabs ...)
159 def get_paths(targetdir=None,smbconf=None):
160 if targetdir is not None:
161 if (not os.path.exists(os.path.join(targetdir, "etc"))):
162 os.makedirs(os.path.join(targetdir, "etc"))
163 smbconf = os.path.join(targetdir, "etc", "smb.conf")
164 if smbconf is None:
165 smbconf = param.default_path()
167 if not os.path.exists(smbconf):
168 message(ERROR,"Unable to find smb.conf ..")
169 parser.print_usage()
170 sys.exit(1)
172 lp = param.LoadParm()
173 lp.load(smbconf)
174 # Normally we need the domain name for this function but for our needs it's
175 # pointless
176 paths = provision_paths_from_lp(lp,"foo")
177 return paths
179 # This function guesses (fetches) informations needed to make a fresh provision
180 # from the current provision
181 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
182 def guess_names_from_current_provision(credentials,session_info,paths):
183 lp = param.LoadParm()
184 lp.load(paths.smbconf)
185 names = ProvisionNames()
186 # NT domain, kerberos realm, root dn, domain dn, domain dns name
187 names.domain = string.upper(lp.get("workgroup"))
188 names.realm = lp.get("realm")
189 basedn = "DC=" + names.realm.replace(".",",DC=")
190 names.dnsdomain = names.realm
191 names.realm = string.upper(names.realm)
192 # netbiosname
193 secrets_ldb = Ldb(paths.secrets, session_info=session_info, credentials=credentials,lp=lp, options=["modules:samba_secrets"])
194 # Get the netbiosname first (could be obtained from smb.conf in theory)
195 attrs = ["sAMAccountName"]
196 res = secrets_ldb.search(expression="(flatname=%s)"%names.domain,base="CN=Primary Domains", scope=SCOPE_SUBTREE, attrs=attrs)
197 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
199 names.smbconf = smbconf
200 # It's important here to let ldb load with the old module or it's quite
201 # certain that the LDB won't load ...
202 samdb = Ldb(paths.samdb, session_info=session_info,
203 credentials=credentials, lp=lp, options=["modules:samba_dsdb"])
205 # That's a bit simplistic but it's ok as long as we have only 3
206 # partitions
207 attrs2 = ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
208 current = samdb.search(expression="(objectClass=*)",base="", scope=SCOPE_BASE, attrs=attrs2)
210 names.configdn = current[0]["configurationNamingContext"]
211 configdn = str(names.configdn)
212 names.schemadn = current[0]["schemaNamingContext"]
213 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb, current[0]["defaultNamingContext"][0]))):
214 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths.samdb, str(current[0]["defaultNamingContext"][0]), paths.smbconf, basedn)))
216 names.domaindn=current[0]["defaultNamingContext"]
217 names.rootdn=current[0]["rootDomainNamingContext"]
218 # default site name
219 attrs3 = ["cn"]
220 res3= samdb.search(expression="(objectClass=*)",base="CN=Sites,"+configdn, scope=SCOPE_ONELEVEL, attrs=attrs3)
221 names.sitename = str(res3[0]["cn"])
223 # dns hostname and server dn
224 attrs4 = ["dNSHostName"]
225 res4= samdb.search(expression="(CN=%s)"%names.netbiosname,base="OU=Domain Controllers,"+basedn, \
226 scope=SCOPE_ONELEVEL, attrs=attrs4)
227 names.hostname = str(res4[0]["dNSHostName"]).replace("."+names.dnsdomain,"")
229 server_res = samdb.search(expression="serverReference=%s"%res4[0].dn, attrs=[], base=configdn)
230 names.serverdn = server_res[0].dn
232 # invocation id/objectguid
233 res5 = samdb.search(expression="(objectClass=*)",base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE, attrs=["invocationID","objectGUID"])
234 names.invocation = str(ndr_unpack( misc.GUID,res5[0]["invocationId"][0]))
235 names.ntdsguid = str(ndr_unpack( misc.GUID,res5[0]["objectGUID"][0]))
237 # domain guid/sid
238 attrs6 = ["objectGUID", "objectSid","msDS-Behavior-Version" ]
239 res6 = samdb.search(expression="(objectClass=*)",base=basedn, scope=SCOPE_BASE, attrs=attrs6)
240 names.domainguid = str(ndr_unpack( misc.GUID,res6[0]["objectGUID"][0]))
241 names.domainsid = ndr_unpack( security.dom_sid,res6[0]["objectSid"][0])
242 if res6[0].get("msDS-Behavior-Version") == None or int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
243 names.domainlevel = DS_DOMAIN_FUNCTION_2000
244 else:
245 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
247 # policy guid
248 attrs7 = ["cn","displayName"]
249 res7 = samdb.search(expression="(displayName=Default Domain Policy)",base="CN=Policies,CN=System,"+basedn, \
250 scope=SCOPE_ONELEVEL, attrs=attrs7)
251 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
252 # dc policy guid
253 attrs8 = ["cn","displayName"]
254 res8 = samdb.search(expression="(displayName=Default Domain Controllers Policy)",base="CN=Policies,CN=System,"+basedn, \
255 scope=SCOPE_ONELEVEL, attrs=attrs7)
256 if len(res8) == 1:
257 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
258 else:
259 names.policyid_dc = None
262 return names
264 # Debug a little bit
265 def print_names(names):
266 message(GUESS, "rootdn :"+str(names.rootdn))
267 message(GUESS, "configdn :"+str(names.configdn))
268 message(GUESS, "schemadn :"+str(names.schemadn))
269 message(GUESS, "serverdn :"+str(names.serverdn))
270 message(GUESS, "netbiosname :"+names.netbiosname)
271 message(GUESS, "defaultsite :"+names.sitename)
272 message(GUESS, "dnsdomain :"+names.dnsdomain)
273 message(GUESS, "hostname :"+names.hostname)
274 message(GUESS, "domain :"+names.domain)
275 message(GUESS, "realm :"+names.realm)
276 message(GUESS, "invocationid:"+names.invocation)
277 message(GUESS, "policyguid :"+names.policyid)
278 message(GUESS, "policyguiddc:"+str(names.policyid_dc))
279 message(GUESS, "domainsid :"+str(names.domainsid))
280 message(GUESS, "domainguid :"+names.domainguid)
281 message(GUESS, "ntdsguid :"+names.ntdsguid)
282 message(GUESS, "domainlevel :"+str(names.domainlevel))
284 # Create a fresh new reference provision
285 # This provision will be the reference for knowing what has changed in the
286 # since the latest upgrade in the current provision
287 def newprovision(names,setup_dir,creds,session,smbconf):
288 message(SIMPLE, "Creating a reference provision")
289 provdir=tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
290 if os.path.isdir(provdir):
291 rmall(provdir)
292 logstd=os.path.join(provdir,"log.std")
293 os.chdir(os.path.join(setup_dir,".."))
294 os.mkdir(provdir)
295 os.close(2)
296 sys.stderr = open("%s/provision.log"%provdir, "w")
297 message(PROVISION, "Reference provision stored in %s"%provdir)
298 message(PROVISION, "STDERR message of provision will be logged in %s/provision.log"%provdir)
299 sys.stderr = open("/dev/stdout", "w")
300 provision(setup_dir, messageprovision,
301 session, creds, smbconf=smbconf, targetdir=provdir,
302 samdb_fill=FILL_FULL, realm=names.realm, domain=names.domain,
303 domainguid=names.domainguid, domainsid=str(names.domainsid),ntdsguid=names.ntdsguid,
304 policyguid=names.policyid,policyguid_dc=names.policyid_dc,hostname=names.netbiosname,
305 hostip=None, hostip6=None,
306 invocationid=names.invocation, adminpass=None,
307 krbtgtpass=None, machinepass=None,
308 dnspass=None, root=None, nobody=None,
309 wheel=None, users=None,
310 serverrole="domain controller",
311 ldap_backend_extra_port=None,
312 backend_type=None,
313 ldapadminpass=None,
314 ol_mmr_urls=None,
315 slapd_path=None,
316 setup_ds_path=None,
317 nosync=None,
318 dom_for_fun_level=names.domainlevel,
319 ldap_dryrun_mode=None,useeadb=True)
320 return provdir
322 # This function sorts two DNs in the lexicographical order and put higher level
323 # DN before.
324 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller
325 # (-1) as it has less level
326 def dn_sort(x,y):
327 p = re.compile(r'(?<!\\),')
328 tab1 = p.split(str(x))
329 tab2 = p.split(str(y))
330 min = 0
331 if (len(tab1) > len(tab2)):
332 min = len(tab2)
333 elif (len(tab1) < len(tab2)):
334 min = len(tab1)
335 else:
336 min = len(tab1)
337 len1=len(tab1)-1
338 len2=len(tab2)-1
339 space = " "
340 # Note: python range go up to upper limit but do not include it
341 for i in range(0,min):
342 ret=cmp(tab1[len1-i],tab2[len2-i])
343 if(ret != 0):
344 return ret
345 else:
346 if(i==min-1):
347 if(len1==len2):
348 message(ERROR,"PB PB PB"+space.join(tab1)+" / "+space.join(tab2))
349 if(len1>len2):
350 return 1
351 else:
352 return -1
353 return ret
355 # Check for security descriptors modifications return 1 if it is and 0 otherwise
356 # it also populate hash structure for later use in the upgrade process
357 def handle_security_desc(ischema,att,msgElt,hashallSD,old,new):
358 if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
359 hashSD = {}
360 hashSD["oldSD"] = old[0][att]
361 hashSD["newSD"] = new[0][att]
362 hashallSD[str(old[0].dn)] = hashSD
363 return 0
364 if att == "nTSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
365 if ischema == 0:
366 hashSD = {}
367 hashSD["oldSD"] = ndr_unpack(security.descriptor,str(old[0][att]))
368 hashSD["newSD"] = ndr_unpack(security.descriptor,str(new[0][att]))
369 hashallSD[str(old[0].dn)] = hashSD
370 return 1
371 return 0
373 # Handle special cases ... That's when we want to update a particular attribute
374 # only, e.g. if it has a certain value or if it's for a certain object or
375 # a class of object.
376 # It can be also if we want to do a merge of value instead of a simple replace
377 def handle_special_case(att,delta,new,old,ischema):
378 flag = delta.get(att).flags()
379 if (att == "gPLink" or att == "gPCFileSysPath") and flag == ldb.FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
380 delta.remove(att)
381 return 1
382 if att == "forceLogoff":
383 ref=0x8000000000000000
384 oldval=int(old[0][att][0])
385 newval=int(new[0][att][0])
386 ref == old and ref == abs(new)
387 return 1
388 if (att == "adminDisplayName" or att == "adminDescription") and ischema:
389 return 1
390 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn)) and att == "defaultObjectCategory" and flag == ldb.FLAG_MOD_REPLACE):
391 return 1
392 # 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):
393 # return 1
394 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == ldb.FLAG_MOD_REPLACE):
395 return 1
396 if ( (att == "member" or att == "servicePrincipalName") and flag == ldb.FLAG_MOD_REPLACE):
398 hash = {}
399 newval = []
400 changeDelta=0
401 for elem in old[0][att]:
402 hash[str(elem)]=1
403 newval.append(str(elem))
405 for elem in new[0][att]:
406 if not hash.has_key(str(elem)):
407 changeDelta=1
408 newval.append(str(elem))
409 if changeDelta == 1:
410 delta[att] = ldb.MessageElement(newval, ldb.FLAG_MOD_REPLACE, att)
411 else:
412 delta.remove(att)
413 return 1
414 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == ldb.FLAG_MOD_REPLACE):
415 return 1
416 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
417 return 1
418 return 0
420 def update_secrets(newpaths,paths,creds,session):
421 message(SIMPLE,"update secrets.ldb")
422 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
423 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
424 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
425 current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
426 delta = secrets_ldb.msg_diff(current[0],reference[0])
427 delta.dn = current[0].dn
428 secrets_ldb.modify(delta)
430 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
431 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
432 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
433 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
434 hash_new = {}
435 hash = {}
436 listMissing = []
437 listPresent = []
439 empty = ldb.Message()
440 for i in range(0,len(reference)):
441 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
443 # Create a hash for speeding the search of existing object in the
444 # current provision
445 for i in range(0,len(current)):
446 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
448 for k in hash_new.keys():
449 if not hash.has_key(k):
450 listMissing.append(hash_new[k])
451 else:
452 listPresent.append(hash_new[k])
453 for entry in listMissing:
454 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
455 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
456 delta = secrets_ldb.msg_diff(empty,reference[0])
457 for att in hashAttrNotCopied.keys():
458 delta.remove(att)
459 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
460 for att in delta:
461 message(CHANGE," Adding attribute %s"%att)
462 delta.dn = reference[0].dn
463 secrets_ldb.add(delta)
465 for entry in listPresent:
466 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
467 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
468 delta = secrets_ldb.msg_diff(current[0],reference[0])
470 for att in hashAttrNotCopied.keys():
471 delta.remove(att)
472 for att in delta:
473 i = i + 1
475 if att == "name":
476 message(CHANGE,"Found attribute name on %s, must rename the DN "%(current[0].dn))
477 identic_rename(secrets_ldb,reference[0].dn)
478 else:
479 delta.remove(att)
482 for entry in listPresent:
483 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
484 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
485 delta = secrets_ldb.msg_diff(current[0],reference[0])
487 for att in hashAttrNotCopied.keys():
488 delta.remove(att)
489 for att in delta:
490 i = i + 1
491 if att != "dn":
492 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
494 delta.dn = current[0].dn
495 secrets_ldb.modify(delta)
498 # Check difference between the current provision and the reference provision.
499 # It looks for all objects which base DN is name. If ischema is "false" then
500 # the scan is done in cross partition mode.
501 # If "ischema" is true, then special handling is done for dealing with schema
502 def check_diff_name(newpaths,paths,creds,session,basedn,names,ischema):
503 hash_new = {}
504 hash = {}
505 hashallSD = {}
506 listMissing = []
507 listPresent = []
508 reference = []
509 current = []
510 # Connect to the reference provision and get all the attribute in the
511 # partition referred by name
512 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
513 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
514 sam_ldb.transaction_start()
515 if ischema:
516 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
517 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
518 else:
519 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
520 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
522 sam_ldb.transaction_commit()
523 # Create a hash for speeding the search of new object
524 for i in range(0,len(reference)):
525 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
527 # Create a hash for speeding the search of existing object in the
528 # current provision
529 for i in range(0,len(current)):
530 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
532 for k in hash_new.keys():
533 if not hash.has_key(k):
534 listMissing.append(hash_new[k])
535 else:
536 listPresent.append(hash_new[k])
538 # Sort the missing object in order to have object of the lowest level
539 # first (which can be containers for higher level objects)
540 listMissing.sort(dn_sort)
541 listPresent.sort(dn_sort)
543 if ischema:
544 # The following lines (up to the for loop) is to load the up to
545 # date schema into our current LDB
546 # a complete schema is needed as the insertion of attributes
547 # and class is done against it
548 # and the schema is self validated
549 # The double ldb open and schema validation is taken from the
550 # initial provision script
551 # it's not certain that it is really needed ....
552 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
553 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
554 # Load the schema from the one we computed earlier
555 sam_ldb.set_schema_from_ldb(schema.ldb)
556 # And now we can connect to the DB - the schema won't be loaded
557 # from the DB
558 sam_ldb.connect(paths.samdb)
559 else:
560 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
562 sam_ldb.transaction_start()
564 empty = ldb.Message()
565 message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
566 for dn in listMissing:
567 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
568 delta = sam_ldb.msg_diff(empty,reference[0])
569 for att in hashAttrNotCopied.keys():
570 delta.remove(att)
571 for att in backlinked:
572 delta.remove(att)
573 delta.dn = dn
575 sam_ldb.add(delta,["relax:0"])
577 changed = 0
578 for dn in listPresent:
579 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
580 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
581 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
582 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
583 identic_rename(sam_ldb,reference[0].dn)
584 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
586 delta = sam_ldb.msg_diff(current[0],reference[0])
587 for att in hashAttrNotCopied.keys():
588 delta.remove(att)
589 for att in backlinked:
590 delta.remove(att)
591 delta.remove("parentGUID")
592 nb = 0
594 for att in delta:
595 msgElt = delta.get(att)
596 if att == "dn":
597 continue
598 if att == "name":
599 delta.remove(att)
600 continue
601 if handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
602 delta.remove(att)
603 continue
604 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
605 if handle_special_case(att,delta,reference,current,ischema)==0 and msgElt.flags()!=ldb.FLAG_MOD_ADD:
606 i = 0
607 if opts.debugchange:
608 try:
609 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
610 except:
611 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
612 delta.remove(att)
613 delta.dn = dn
614 if len(delta.items()) >1:
615 attributes=",".join(delta.keys())
616 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
617 changed = changed + 1
618 sam_ldb.modify(delta)
620 sam_ldb.transaction_commit()
621 message(SIMPLE,"There are %d changed objects"%(changed))
622 return hashallSD
624 # Check that SD are correct
625 def check_updated_sd(newpaths,paths,creds,session,names):
626 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
627 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
628 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
629 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
630 hash_new = {}
631 for i in range(0,len(reference)):
632 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
634 for i in range(0,len(current)):
635 key = str(current[i]["dn"]).lower()
636 if hash_new.has_key(key):
637 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
638 if sddl != hash_new[key]:
639 print "%s new sddl/sddl in ref"%key
640 print "%s\n%s"%(sddl,hash_new[key])
642 # Simple update method for updating the SD that rely on the fact that nobody
643 # should have modified the SD
644 # This assumption is safe right now (alpha9) but should be removed asap
645 def update_sd(paths,creds,session,names):
646 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
647 sam_ldb.transaction_start()
648 # First update the SD for the rootdn
649 sam_ldb.set_session_info(session)
650 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
651 delta = ldb.Message()
652 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
653 descr = get_domain_descriptor(names.domainsid)
654 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
655 sam_ldb.modify(delta,["recalculate_sd:0"])
656 # Then the config dn
657 res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
658 delta = ldb.Message()
659 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
660 descr = get_config_descriptor(names.domainsid)
661 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
662 sam_ldb.modify(delta,["recalculate_sd:0"])
663 # Then the schema dn
664 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
665 delta = ldb.Message()
666 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
667 descr = get_schema_descriptor(names.domainsid)
668 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
669 sam_ldb.modify(delta,["recalculate_sd:0"])
671 # Then the rest
672 hash = {}
673 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
674 for obj in res:
675 if not (str(obj["dn"]) == str(names.rootdn) or
676 str(obj["dn"]) == str(names.configdn) or \
677 str(obj["dn"]) == str(names.schemadn)):
678 hash[str(obj["dn"])] = obj["whenCreated"]
680 listkeys = hash.keys()
681 listkeys.sort(dn_sort)
683 for key in listkeys:
684 try:
685 delta = ldb.Message()
686 delta.dn = ldb.Dn(sam_ldb,key)
687 delta["whenCreated"] = ldb.MessageElement( hash[key],ldb.FLAG_MOD_REPLACE,"whenCreated" )
688 sam_ldb.modify(delta,["recalculate_sd:0"])
689 except:
690 sam_ldb.transaction_cancel()
691 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
692 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
693 return
694 sam_ldb.transaction_commit()
696 def rmall(topdir):
697 for root, dirs, files in os.walk(topdir, topdown=False):
698 for name in files:
699 os.remove(os.path.join(root, name))
700 for name in dirs:
701 os.rmdir(os.path.join(root, name))
702 os.rmdir(topdir)
705 def update_basesamdb(newpaths,paths,names):
706 message(SIMPLE,"Copy samdb")
707 shutil.copy(newpaths.samdb,paths.samdb)
709 message(SIMPLE,"Update partitions filename if needed")
710 schemaldb=os.path.join(paths.private_dir,"schema.ldb")
711 configldb=os.path.join(paths.private_dir,"configuration.ldb")
712 usersldb=os.path.join(paths.private_dir,"users.ldb")
713 samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
715 if not os.path.isdir(samldbdir):
716 os.mkdir(samldbdir)
717 os.chmod(samldbdir,0700)
718 if os.path.isfile(schemaldb):
719 shutil.copy(schemaldb,os.path.join(samldbdir,"%s.ldb"%str(names.schemadn).upper()))
720 os.remove(schemaldb)
721 if os.path.isfile(usersldb):
722 shutil.copy(usersldb,os.path.join(samldbdir,"%s.ldb"%str(names.rootdn).upper()))
723 os.remove(usersldb)
724 if os.path.isfile(configldb):
725 shutil.copy(configldb,os.path.join(samldbdir,"%s.ldb"%str(names.configdn).upper()))
726 os.remove(configldb)
728 def update_privilege(newpaths,paths):
729 message(SIMPLE,"Copy privilege")
730 shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb"))
732 # For each partition check the differences
733 def update_samdb(newpaths,paths,creds,session,names):
735 message(SIMPLE, "Doing schema update")
736 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
737 message(SIMPLE,"Done with schema update")
738 message(SIMPLE,"Scanning whole provision for updates and additions")
739 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
740 message(SIMPLE,"Done with scanning")
742 def update_machine_account_password(paths,creds,session,names):
744 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
745 secrets_ldb.transaction_start()
746 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
747 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
748 sam_ldb.transaction_start()
749 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
750 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
751 assert(len(res) == 1)
753 msg = ldb.Message(res[0].dn)
754 machinepass = glue.generate_random_str(12)
755 msg["userPassword"] = ldb.MessageElement(machinepass, ldb.FLAG_MOD_REPLACE, "userPassword")
756 sam_ldb.modify(msg)
758 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
759 attrs=["msDs-keyVersionNumber"])
760 assert(len(res) == 1)
761 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
763 secretsdb_self_join(secrets_ldb, domain=names.domain,
764 realm=names.realm,
765 domainsid=names.domainsid,
766 dnsdomain=names.dnsdomain,
767 netbiosname=names.netbiosname,
768 machinepass=machinepass,
769 key_version_number=kvno,
770 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
771 sam_ldb.transaction_prepare_commit()
772 secrets_ldb.transaction_prepare_commit()
773 sam_ldb.transaction_commit()
774 secrets_ldb.transaction_commit()
775 else:
776 secrets_ldb.transaction_cancel()
778 # From here start the big steps of the program
779 # First get files paths
780 paths=get_paths(smbconf=smbconf)
781 paths.setup = setup_dir
782 def setup_path(file):
783 return os.path.join(setup_dir, file)
784 # Guess all the needed names (variables in fact) from the current
785 # provision.
786 names = guess_names_from_current_provision(creds,session,paths)
787 # Let's see them
788 print_names(names)
789 # With all this information let's create a fresh new provision used as reference
790 provisiondir = newprovision(names,setup_dir,creds,session,smbconf)
791 # Get file paths of this new provision
792 newpaths = get_paths(targetdir=provisiondir)
793 populate_backlink(newpaths,creds,session,names.schemadn)
794 # Check the difference
795 update_basesamdb(newpaths,paths,names)
797 if opts.full:
798 update_samdb(newpaths,paths,creds,session,names)
799 update_secrets(newpaths,paths,creds,session)
800 update_privilege(newpaths,paths)
801 update_machine_account_password(paths,creds,session,names)
802 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
803 # to recreate them with the good form but with system account and then give the ownership to admin ...
804 admin_session_info = admin_session(lp, str(names.domainsid))
805 message(SIMPLE,"Updating SD")
806 update_sd(paths,creds,session,names)
807 update_sd(paths,creds,admin_session_info,names)
808 check_updated_sd(newpaths,paths,creds,session,names)
809 message(SIMPLE,"Upgrade finished !")
810 # remove reference provision now that everything is done !
811 rmall(provisiondir)