upgradeprovision: forbid running upgradeprovision when there is more than 1 DC
[Samba/ekacnet.git] / source4 / scripting / bin / upgradeprovision
blobe2ee8d053fe52e42690de4835bf325ca546b153b
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 never=0
58 replace=2^ldb.FLAG_MOD_REPLACE
59 add=2^ldb.FLAG_MOD_ADD
60 delete=2^ldb.FLAG_MOD_DELETE
62 #Errors are always logged
63 ERROR = -1
64 SIMPLE = 0x00
65 CHANGE = 0x01
66 CHANGESD = 0x02
67 GUESS = 0x04
68 PROVISION = 0x08
69 CHANGEALL = 0xff
71 # Attributes that are never copied from the reference provision (even if they
72 # do not exist in the destination object).
73 # This is most probably because they are populated automatcally when object is
74 # created
75 # This also apply to imported object from reference provision
76 hashAttrNotCopied = { "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,
77 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,
78 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
79 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
80 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
81 "maxPwdAge":1, "mail":1, "secret":1,"possibleInferiors":1, "sAMAccountType":1}
83 # Usually for an object that already exists we do not overwrite attributes as
84 # they might have been changed for good reasons. Anyway for a few of them it's
85 # mandatory to replace them otherwise the provision will be broken somehow.
86 hashOverwrittenAtt = { "prefixMap": replace, "systemMayContain": replace,"systemOnly":replace, "searchFlags":replace,
87 "mayContain":replace, "systemFlags":replace,"description":replace,
88 "oEMInformation":replace, "operatingSystemVersion":replace, "adminPropertyPages":replace,
89 "defaultSecurityDescriptor": replace,"wellKnownObjects":replace,"privilege":delete,"groupType":replace,
90 "rIDAvailablePool": never}
93 backlinked = []
94 dn_syntax_att = []
95 def define_what_to_log(opts):
96 what = 0
97 if opts.debugchange:
98 what = what | CHANGE
99 if opts.debugchangesd:
100 what = what | CHANGESD
101 if opts.debugguess:
102 what = what | GUESS
103 if opts.debugprovision:
104 what = what | PROVISION
105 if opts.debugall:
106 what = what | CHANGEALL
107 return what
110 parser = optparse.OptionParser("provision [options]")
111 sambaopts = options.SambaOptions(parser)
112 parser.add_option_group(sambaopts)
113 parser.add_option_group(options.VersionOptions(parser))
114 credopts = options.CredentialsOptions(parser)
115 parser.add_option_group(credopts)
116 parser.add_option("--setupdir", type="string", metavar="DIR",
117 help="directory with setup files")
118 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
119 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
120 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
121 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
122 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
123 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
125 opts = parser.parse_args()[0]
127 whatToLog = define_what_to_log(opts)
129 def messageprovision(text):
130 """print a message if quiet is not set."""
131 if opts.debugprovision or opts.debugall:
132 print text
134 def message(what,text):
135 """print a message if quiet is not set."""
136 if (whatToLog & what) or (what <= 0 ):
137 print text
139 if len(sys.argv) == 1:
140 opts.interactive = True
141 lp = sambaopts.get_loadparm()
142 smbconf = lp.configfile
144 creds = credopts.get_credentials(lp)
145 creds.set_kerberos_state(DONT_USE_KERBEROS)
146 setup_dir = opts.setupdir
147 if setup_dir is None:
148 setup_dir = find_setup_dir()
150 session = system_session()
152 # simple helper to allow back and forth rename
153 def identic_rename(ldbobj,dn):
154 (before,sep,after)=str(dn).partition('=')
155 ldbobj.rename(dn,ldb.Dn(ldbobj,"%s=foo%s"%(before,after)))
156 ldbobj.rename(ldb.Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
158 # Create an array of backlinked attributes
159 def populate_backlink(newpaths,creds,session,schemadn):
160 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
161 linkedAttHash = get_linked_attributes(ldb.Dn(newsam_ldb,str(schemadn)),newsam_ldb)
162 backlinked.extend(linkedAttHash.values())
164 # Create an array of attributes with a dn synthax (2.5.5.1)
165 def populate_dnsyntax(newpaths,creds,session,schemadn):
166 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
167 res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=ldb.Dn(newsam_ldb,str(schemadn)), scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"])
168 for elem in res:
169 dn_syntax_att.append(elem["lDAPDisplayName"])
171 # Get Paths for important objects (ldb, keytabs ...)
172 def get_paths(targetdir=None,smbconf=None):
173 if targetdir is not None:
174 if (not os.path.exists(os.path.join(targetdir, "etc"))):
175 os.makedirs(os.path.join(targetdir, "etc"))
176 smbconf = os.path.join(targetdir, "etc", "smb.conf")
177 if smbconf is None:
178 smbconf = param.default_path()
180 if not os.path.exists(smbconf):
181 message(ERROR,"Unable to find smb.conf ..")
182 parser.print_usage()
183 sys.exit(1)
185 lp = param.LoadParm()
186 lp.load(smbconf)
187 # Normally we need the domain name for this function but for our needs it's
188 # pointless
189 paths = provision_paths_from_lp(lp,"foo")
190 return paths
193 def sanitychecks(credentials,session_info,names,paths):
194 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
195 # First update the SD for the rootdn
196 sam_ldb.set_session_info(session)
197 res = sam_ldb.search(expression="objectClass=ntdsdsa",base=str(names.configdn), scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
198 if len(res) == 0:
199 print "No DC found, your provision is most probalby hardly broken !"
200 return 0
201 elif len(res) != 1:
202 print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
203 domain with more than one DC, please demote the other DC before upgrading"%len(res)
204 return 0
205 else:
206 return 1
209 # This function guesses (fetches) informations needed to make a fresh provision
210 # from the current provision
211 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
212 def guess_names_from_current_provision(credentials,session_info,paths):
213 lp = param.LoadParm()
214 lp.load(paths.smbconf)
215 names = ProvisionNames()
216 # NT domain, kerberos realm, root dn, domain dn, domain dns name
217 names.domain = string.upper(lp.get("workgroup"))
218 names.realm = lp.get("realm")
219 basedn = "DC=" + names.realm.replace(".",",DC=")
220 names.dnsdomain = names.realm
221 names.realm = string.upper(names.realm)
222 # netbiosname
223 secrets_ldb = Ldb(paths.secrets, session_info=session_info, credentials=credentials,lp=lp, options=["modules:samba_secrets"])
224 # Get the netbiosname first (could be obtained from smb.conf in theory)
225 attrs = ["sAMAccountName"]
226 res = secrets_ldb.search(expression="(flatname=%s)"%names.domain,base="CN=Primary Domains", scope=SCOPE_SUBTREE, attrs=attrs)
227 names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
229 names.smbconf = smbconf
230 # It's important here to let ldb load with the old module or it's quite
231 # certain that the LDB won't load ...
232 samdb = Ldb(paths.samdb, session_info=session_info,
233 credentials=credentials, lp=lp, options=["modules:samba_dsdb"])
235 # That's a bit simplistic but it's ok as long as we have only 3
236 # partitions
237 attrs2 = ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
238 current = samdb.search(expression="(objectClass=*)",base="", scope=SCOPE_BASE, attrs=attrs2)
240 names.configdn = current[0]["configurationNamingContext"]
241 configdn = str(names.configdn)
242 names.schemadn = current[0]["schemaNamingContext"]
243 if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb, current[0]["defaultNamingContext"][0]))):
244 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths.samdb, str(current[0]["defaultNamingContext"][0]), paths.smbconf, basedn)))
246 names.domaindn=current[0]["defaultNamingContext"]
247 names.rootdn=current[0]["rootDomainNamingContext"]
248 # default site name
249 attrs3 = ["cn"]
250 res3= samdb.search(expression="(objectClass=*)",base="CN=Sites,"+configdn, scope=SCOPE_ONELEVEL, attrs=attrs3)
251 names.sitename = str(res3[0]["cn"])
253 # dns hostname and server dn
254 attrs4 = ["dNSHostName"]
255 res4= samdb.search(expression="(CN=%s)"%names.netbiosname,base="OU=Domain Controllers,"+basedn, \
256 scope=SCOPE_ONELEVEL, attrs=attrs4)
257 names.hostname = str(res4[0]["dNSHostName"]).replace("."+names.dnsdomain,"")
259 server_res = samdb.search(expression="serverReference=%s"%res4[0].dn, attrs=[], base=configdn)
260 names.serverdn = server_res[0].dn
262 # invocation id/objectguid
263 res5 = samdb.search(expression="(objectClass=*)",base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE, attrs=["invocationID","objectGUID"])
264 names.invocation = str(ndr_unpack( misc.GUID,res5[0]["invocationId"][0]))
265 names.ntdsguid = str(ndr_unpack( misc.GUID,res5[0]["objectGUID"][0]))
267 # domain guid/sid
268 attrs6 = ["objectGUID", "objectSid","msDS-Behavior-Version" ]
269 res6 = samdb.search(expression="(objectClass=*)",base=basedn, scope=SCOPE_BASE, attrs=attrs6)
270 names.domainguid = str(ndr_unpack( misc.GUID,res6[0]["objectGUID"][0]))
271 names.domainsid = ndr_unpack( security.dom_sid,res6[0]["objectSid"][0])
272 if res6[0].get("msDS-Behavior-Version") == None or int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
273 names.domainlevel = DS_DOMAIN_FUNCTION_2000
274 else:
275 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
277 # policy guid
278 attrs7 = ["cn","displayName"]
279 res7 = samdb.search(expression="(displayName=Default Domain Policy)",base="CN=Policies,CN=System,"+basedn, \
280 scope=SCOPE_ONELEVEL, attrs=attrs7)
281 names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
282 # dc policy guid
283 attrs8 = ["cn","displayName"]
284 res8 = samdb.search(expression="(displayName=Default Domain Controllers Policy)",base="CN=Policies,CN=System,"+basedn, \
285 scope=SCOPE_ONELEVEL, attrs=attrs7)
286 if len(res8) == 1:
287 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
288 else:
289 names.policyid_dc = None
292 return names
294 # Debug a little bit
295 def print_names(names):
296 message(GUESS, "rootdn :"+str(names.rootdn))
297 message(GUESS, "configdn :"+str(names.configdn))
298 message(GUESS, "schemadn :"+str(names.schemadn))
299 message(GUESS, "serverdn :"+str(names.serverdn))
300 message(GUESS, "netbiosname :"+names.netbiosname)
301 message(GUESS, "defaultsite :"+names.sitename)
302 message(GUESS, "dnsdomain :"+names.dnsdomain)
303 message(GUESS, "hostname :"+names.hostname)
304 message(GUESS, "domain :"+names.domain)
305 message(GUESS, "realm :"+names.realm)
306 message(GUESS, "invocationid:"+names.invocation)
307 message(GUESS, "policyguid :"+names.policyid)
308 message(GUESS, "policyguiddc:"+str(names.policyid_dc))
309 message(GUESS, "domainsid :"+str(names.domainsid))
310 message(GUESS, "domainguid :"+names.domainguid)
311 message(GUESS, "ntdsguid :"+names.ntdsguid)
312 message(GUESS, "domainlevel :"+str(names.domainlevel))
314 # Create a fresh new reference provision
315 # This provision will be the reference for knowing what has changed in the
316 # since the latest upgrade in the current provision
317 def newprovision(names,setup_dir,creds,session,smbconf):
318 message(SIMPLE, "Creating a reference provision")
319 provdir=tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
320 if os.path.isdir(provdir):
321 rmall(provdir)
322 logstd=os.path.join(provdir,"log.std")
323 os.chdir(os.path.join(setup_dir,".."))
324 os.mkdir(provdir)
325 os.close(2)
326 sys.stderr = open("%s/provision.log"%provdir, "w")
327 message(PROVISION, "Reference provision stored in %s"%provdir)
328 message(PROVISION, "STDERR message of provision will be logged in %s/provision.log"%provdir)
329 sys.stderr = open("/dev/stdout", "w")
330 provision(setup_dir, messageprovision,
331 session, creds, smbconf=smbconf, targetdir=provdir,
332 samdb_fill=FILL_FULL, realm=names.realm, domain=names.domain,
333 domainguid=names.domainguid, domainsid=str(names.domainsid),ntdsguid=names.ntdsguid,
334 policyguid=names.policyid,policyguid_dc=names.policyid_dc,hostname=names.netbiosname,
335 hostip=None, hostip6=None,
336 invocationid=names.invocation, adminpass=None,
337 krbtgtpass=None, machinepass=None,
338 dnspass=None, root=None, nobody=None,
339 wheel=None, users=None,
340 serverrole="domain controller",
341 ldap_backend_extra_port=None,
342 backend_type=None,
343 ldapadminpass=None,
344 ol_mmr_urls=None,
345 slapd_path=None,
346 setup_ds_path=None,
347 nosync=None,
348 dom_for_fun_level=names.domainlevel,
349 ldap_dryrun_mode=None,useeadb=True)
350 return provdir
352 # This function sorts two DNs in the lexicographical order and put higher level
353 # DN before.
354 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller
355 # (-1) as it has less level
356 def dn_sort(x,y):
357 p = re.compile(r'(?<!\\),')
358 tab1 = p.split(str(x))
359 tab2 = p.split(str(y))
360 min = 0
361 if (len(tab1) > len(tab2)):
362 min = len(tab2)
363 elif (len(tab1) < len(tab2)):
364 min = len(tab1)
365 else:
366 min = len(tab1)
367 len1=len(tab1)-1
368 len2=len(tab2)-1
369 space = " "
370 # Note: python range go up to upper limit but do not include it
371 for i in range(0,min):
372 ret=cmp(tab1[len1-i],tab2[len2-i])
373 if(ret != 0):
374 return ret
375 else:
376 if(i==min-1):
377 if(len1==len2):
378 message(ERROR,"PB PB PB"+space.join(tab1)+" / "+space.join(tab2))
379 if(len1>len2):
380 return 1
381 else:
382 return -1
383 return ret
385 # Check for security descriptors modifications return 1 if it is and 0 otherwise
386 # it also populate hash structure for later use in the upgrade process
387 def handle_security_desc(ischema,att,msgElt,hashallSD,old,new):
388 if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
389 hashSD = {}
390 hashSD["oldSD"] = old[0][att]
391 hashSD["newSD"] = new[0][att]
392 hashallSD[str(old[0].dn)] = hashSD
393 return 0
394 if att == "nTSecurityDescriptor" and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
395 if ischema == 0:
396 hashSD = {}
397 hashSD["oldSD"] = ndr_unpack(security.descriptor,str(old[0][att]))
398 hashSD["newSD"] = ndr_unpack(security.descriptor,str(new[0][att]))
399 hashallSD[str(old[0].dn)] = hashSD
400 return 1
401 return 0
403 # Handle special cases ... That's when we want to update a particular attribute
404 # only, e.g. if it has a certain value or if it's for a certain object or
405 # a class of object.
406 # It can be also if we want to do a merge of value instead of a simple replace
407 def handle_special_case(att,delta,new,old,ischema):
408 flag = delta.get(att).flags()
409 if (att == "gPLink" or att == "gPCFileSysPath") and flag == ldb.FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
410 delta.remove(att)
411 return 1
412 if att == "forceLogoff":
413 ref=0x8000000000000000
414 oldval=int(old[0][att][0])
415 newval=int(new[0][att][0])
416 ref == old and ref == abs(new)
417 return 1
418 if (att == "adminDisplayName" or att == "adminDescription") and ischema:
419 return 1
420 if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn)) and att == "defaultObjectCategory" and flag == ldb.FLAG_MOD_REPLACE):
421 return 1
422 # 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):
423 # return 1
424 if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag == ldb.FLAG_MOD_REPLACE):
425 return 1
426 if ( (att == "member" or att == "servicePrincipalName") and flag == ldb.FLAG_MOD_REPLACE):
428 hash = {}
429 newval = []
430 changeDelta=0
431 for elem in old[0][att]:
432 hash[str(elem)]=1
433 newval.append(str(elem))
435 for elem in new[0][att]:
436 if not hash.has_key(str(elem)):
437 changeDelta=1
438 newval.append(str(elem))
439 if changeDelta == 1:
440 delta[att] = ldb.MessageElement(newval, ldb.FLAG_MOD_REPLACE, att)
441 else:
442 delta.remove(att)
443 return 1
444 if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag == ldb.FLAG_MOD_REPLACE):
445 return 1
446 if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
447 return 1
448 return 0
450 def update_secrets(newpaths,paths,creds,session):
451 message(SIMPLE,"update secrets.ldb")
452 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
453 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
454 reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
455 current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
456 delta = secrets_ldb.msg_diff(current[0],reference[0])
457 delta.dn = current[0].dn
458 secrets_ldb.modify(delta)
460 newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
461 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
462 reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
463 current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
464 hash_new = {}
465 hash = {}
466 listMissing = []
467 listPresent = []
469 empty = ldb.Message()
470 for i in range(0,len(reference)):
471 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
473 # Create a hash for speeding the search of existing object in the
474 # current provision
475 for i in range(0,len(current)):
476 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
478 for k in hash_new.keys():
479 if not hash.has_key(k):
480 listMissing.append(hash_new[k])
481 else:
482 listPresent.append(hash_new[k])
483 for entry in listMissing:
484 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
485 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
486 delta = secrets_ldb.msg_diff(empty,reference[0])
487 for att in hashAttrNotCopied.keys():
488 delta.remove(att)
489 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
490 for att in delta:
491 message(CHANGE," Adding attribute %s"%att)
492 delta.dn = reference[0].dn
493 secrets_ldb.add(delta)
495 for entry in listPresent:
496 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
497 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
498 delta = secrets_ldb.msg_diff(current[0],reference[0])
500 for att in hashAttrNotCopied.keys():
501 delta.remove(att)
502 for att in delta:
503 i = i + 1
505 if att == "name":
506 message(CHANGE,"Found attribute name on %s, must rename the DN "%(current[0].dn))
507 identic_rename(secrets_ldb,reference[0].dn)
508 else:
509 delta.remove(att)
512 for entry in listPresent:
513 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
514 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
515 delta = secrets_ldb.msg_diff(current[0],reference[0])
517 for att in hashAttrNotCopied.keys():
518 delta.remove(att)
519 for att in delta:
520 i = i + 1
521 if att != "dn":
522 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
524 delta.dn = current[0].dn
525 secrets_ldb.modify(delta)
527 def dump_denied_change(dn,att,flagtxt,current,reference):
528 message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
529 if att != "objectSid" :
530 i = 0
531 for e in range(0,len(current)):
532 message(CHANGE,"old %d : %s"%(i,str(current[e])))
533 i=i+1
534 if reference != None:
535 i = 0
536 for e in range(0,len(reference)):
537 message(CHANGE,"new %d : %s"%(i,str(reference[e])))
538 i=i+1
539 else:
540 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
541 message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
543 #This function is for doing case by case treatment on special object
545 def handle_special_add(sam_ldb,dn,names):
546 dntoremove=None
547 if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
548 #This entry was misplaced lets remove it if it exists
549 dntoremove="CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
551 if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
552 #This entry was misplaced lets remove it if it exists
553 dntoremove="CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
555 if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
556 #This entry was misplaced lets remove it if it exists
557 dntoremove="CN=Event Log Readers,CN=Users,%s"%names.rootdn
559 if dntoremove != None:
560 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
561 if len(res) > 0:
562 message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
563 sam_ldb.delete(res[0]["dn"])
565 #Check if the one of the dn in the listdn will be created after the current dn
566 #hash is indexed by dn to be created, with each key is associated the creation order
567 #First dn to be created has the creation order 0, second has 1, ...
568 #Index contain the current creation order
569 def check_dn_nottobecreated(hash,index,listdn):
570 if listdn == None:
571 return None
572 for dn in listdn:
573 key = str(dn).lower()
574 if hash.has_key(key) and hash[key] > index:
575 return str(dn)
576 return None
578 #This function tries to add the missing object "dn" if this object depends on some others
579 # the function returns 0, if the object was created 1 is returned
580 def add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hash,index):
581 handle_special_add(sam_ldb,dn,names)
582 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
583 scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
584 empty = ldb.Message()
585 delta = sam_ldb.msg_diff(empty,reference[0])
586 for att in hashAttrNotCopied.keys():
587 delta.remove(att)
588 for att in backlinked:
589 delta.remove(att)
590 depend_on_yettobecreated = None
591 for att in dn_syntax_att:
592 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
593 if depend_on_yet_tobecreated != None:
594 message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
595 %(str(dn),depend_on_yet_tobecreated,str(att)))
596 return 0
597 delta.dn = dn
598 message(CHANGE,"Object %s will be added"%dn)
599 sam_ldb.add(delta,["relax:0"])
600 return 1
602 def gen_dn_index_hash(listMissing):
603 hash = {}
604 for i in range(0,len(listMissing)):
605 hash[str(listMissing[i]).lower()] = i
606 return hash
608 def add_missing_entries(newsam_ldb,sam_ldb,names,basedn,list):
609 listMissing = []
610 listDefered = list
612 while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
613 index = 0
614 listMissing = listDefered
615 listDefered = []
616 hashMissing = gen_dn_index_hash(listMissing)
617 for dn in listMissing:
618 ret = add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
619 index = index + 1
620 if ret == 0:
621 #DN can't be created because it depends on some other DN in the list
622 listDefered.append(dn)
623 if len(listDefered) != 0:
624 raise ProvisioningError("Unable to insert missing elements: circular references")
629 # Check difference between the current provision and the reference provision.
630 # It looks for all objects which base DN is name. If ischema is "false" then
631 # the scan is done in cross partition mode.
632 # If "ischema" is true, then special handling is done for dealing with schema
633 def check_diff_name(newpaths,paths,creds,session,basedn,names,ischema):
634 hash_new = {}
635 hash = {}
636 hashallSD = {}
637 listMissing = []
638 listPresent = []
639 reference = []
640 current = []
641 # Connect to the reference provision and get all the attribute in the
642 # partition referred by name
643 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
644 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
645 sam_ldb.transaction_start()
646 if ischema:
647 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
648 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
649 else:
650 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
651 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
653 sam_ldb.transaction_commit()
654 # Create a hash for speeding the search of new object
655 for i in range(0,len(reference)):
656 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
658 # Create a hash for speeding the search of existing object in the
659 # current provision
660 for i in range(0,len(current)):
661 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
663 for k in hash_new.keys():
664 if not hash.has_key(k):
665 print hash_new[k]
666 listMissing.append(hash_new[k])
667 else:
668 listPresent.append(hash_new[k])
670 # Sort the missing object in order to have object of the lowest level
671 # first (which can be containers for higher level objects)
672 listMissing.sort(dn_sort)
673 listPresent.sort(dn_sort)
675 if ischema:
676 # The following lines (up to the for loop) is to load the up to
677 # date schema into our current LDB
678 # a complete schema is needed as the insertion of attributes
679 # and class is done against it
680 # and the schema is self validated
681 # The double ldb open and schema validation is taken from the
682 # initial provision script
683 # it's not certain that it is really needed ....
684 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
685 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
686 # Load the schema from the one we computed earlier
687 sam_ldb.set_schema_from_ldb(schema.ldb)
688 # And now we can connect to the DB - the schema won't be loaded
689 # from the DB
690 sam_ldb.connect(paths.samdb)
691 else:
692 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
694 sam_ldb.transaction_start()
696 message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
697 add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
698 changed = 0
699 for dn in listPresent:
700 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
701 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
702 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
703 message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
704 identic_rename(sam_ldb,reference[0].dn)
705 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
707 delta = sam_ldb.msg_diff(current[0],reference[0])
708 for att in hashAttrNotCopied.keys():
709 delta.remove(att)
710 for att in backlinked:
711 delta.remove(att)
712 delta.remove("parentGUID")
713 nb = 0
715 for att in delta:
716 msgElt = delta.get(att)
717 if att == "dn":
718 continue
719 if att == "name":
720 delta.remove(att)
721 continue
722 if handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
723 delta.remove(att)
724 continue
725 if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
726 if hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
727 delta.remove(att)
728 continue
729 if handle_special_case(att,delta,reference,current,ischema)==0 and msgElt.flags()!=ldb.FLAG_MOD_ADD:
730 i = 0
731 if opts.debugchange or opts.debugall:
732 try:
733 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
734 except:
735 dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
736 delta.remove(att)
737 delta.dn = dn
738 if len(delta.items()) >1:
739 attributes=",".join(delta.keys())
740 message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
741 changed = changed + 1
742 sam_ldb.modify(delta)
744 sam_ldb.transaction_commit()
745 message(SIMPLE,"There are %d changed objects"%(changed))
746 return hashallSD
748 # Check that SD are correct
749 def check_updated_sd(newpaths,paths,creds,session,names):
750 newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
751 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
752 reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
753 current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
754 hash_new = {}
755 for i in range(0,len(reference)):
756 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
758 for i in range(0,len(current)):
759 key = str(current[i]["dn"]).lower()
760 if hash_new.has_key(key):
761 sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
762 if sddl != hash_new[key]:
763 print "%s new sddl/sddl in ref"%key
764 print "%s\n%s"%(sddl,hash_new[key])
766 # Simple update method for updating the SD that rely on the fact that nobody
767 # should have modified the SD
768 # This assumption is safe right now (alpha9) but should be removed asap
769 def update_sd(paths,creds,session,names):
770 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
771 sam_ldb.transaction_start()
772 # First update the SD for the rootdn
773 sam_ldb.set_session_info(session)
774 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
775 delta = ldb.Message()
776 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
777 descr = get_domain_descriptor(names.domainsid)
778 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
779 sam_ldb.modify(delta,["recalculate_sd:0"])
780 # Then the config dn
781 res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
782 delta = ldb.Message()
783 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
784 descr = get_config_descriptor(names.domainsid)
785 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
786 sam_ldb.modify(delta,["recalculate_sd:0"])
787 # Then the schema dn
788 res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
789 delta = ldb.Message()
790 delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
791 descr = get_schema_descriptor(names.domainsid)
792 delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
793 sam_ldb.modify(delta,["recalculate_sd:0"])
795 # Then the rest
796 hash = {}
797 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
798 for obj in res:
799 if not (str(obj["dn"]) == str(names.rootdn) or
800 str(obj["dn"]) == str(names.configdn) or \
801 str(obj["dn"]) == str(names.schemadn)):
802 hash[str(obj["dn"])] = obj["whenCreated"]
804 listkeys = hash.keys()
805 listkeys.sort(dn_sort)
807 for key in listkeys:
808 try:
809 delta = ldb.Message()
810 delta.dn = ldb.Dn(sam_ldb,key)
811 delta["whenCreated"] = ldb.MessageElement( hash[key],ldb.FLAG_MOD_REPLACE,"whenCreated" )
812 sam_ldb.modify(delta,["recalculate_sd:0"])
813 except:
814 sam_ldb.transaction_cancel()
815 res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
816 print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
817 return
818 sam_ldb.transaction_commit()
820 def rmall(topdir):
821 for root, dirs, files in os.walk(topdir, topdown=False):
822 for name in files:
823 os.remove(os.path.join(root, name))
824 for name in dirs:
825 os.rmdir(os.path.join(root, name))
826 os.rmdir(topdir)
829 def update_basesamdb(newpaths,paths,names):
830 message(SIMPLE,"Copy samdb")
831 shutil.copy(newpaths.samdb,paths.samdb)
833 message(SIMPLE,"Update partitions filename if needed")
834 schemaldb=os.path.join(paths.private_dir,"schema.ldb")
835 configldb=os.path.join(paths.private_dir,"configuration.ldb")
836 usersldb=os.path.join(paths.private_dir,"users.ldb")
837 samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
839 if not os.path.isdir(samldbdir):
840 os.mkdir(samldbdir)
841 os.chmod(samldbdir,0700)
842 if os.path.isfile(schemaldb):
843 shutil.copy(schemaldb,os.path.join(samldbdir,"%s.ldb"%str(names.schemadn).upper()))
844 os.remove(schemaldb)
845 if os.path.isfile(usersldb):
846 shutil.copy(usersldb,os.path.join(samldbdir,"%s.ldb"%str(names.rootdn).upper()))
847 os.remove(usersldb)
848 if os.path.isfile(configldb):
849 shutil.copy(configldb,os.path.join(samldbdir,"%s.ldb"%str(names.configdn).upper()))
850 os.remove(configldb)
852 def update_privilege(newpaths,paths):
853 message(SIMPLE,"Copy privilege")
854 shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb"))
856 # For each partition check the differences
857 def update_samdb(newpaths,paths,creds,session,names):
859 message(SIMPLE, "Doing schema update")
860 hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
861 message(SIMPLE,"Done with schema update")
862 message(SIMPLE,"Scanning whole provision for updates and additions")
863 hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
864 message(SIMPLE,"Done with scanning")
866 def update_machine_account_password(paths,creds,session,names):
868 secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
869 secrets_ldb.transaction_start()
870 secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
871 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
872 sam_ldb.transaction_start()
873 if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
874 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
875 assert(len(res) == 1)
877 msg = ldb.Message(res[0].dn)
878 machinepass = glue.generate_random_str(12)
879 msg["userPassword"] = ldb.MessageElement(machinepass, ldb.FLAG_MOD_REPLACE, "userPassword")
880 sam_ldb.modify(msg)
882 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
883 attrs=["msDs-keyVersionNumber"])
884 assert(len(res) == 1)
885 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
887 secretsdb_self_join(secrets_ldb, domain=names.domain,
888 realm=names.realm,
889 domainsid=names.domainsid,
890 dnsdomain=names.dnsdomain,
891 netbiosname=names.netbiosname,
892 machinepass=machinepass,
893 key_version_number=kvno,
894 secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
895 sam_ldb.transaction_prepare_commit()
896 secrets_ldb.transaction_prepare_commit()
897 sam_ldb.transaction_commit()
898 secrets_ldb.transaction_commit()
899 else:
900 secrets_ldb.transaction_cancel()
902 # From here start the big steps of the program
903 # First get files paths
904 paths=get_paths(smbconf=smbconf)
905 paths.setup = setup_dir
906 def setup_path(file):
907 return os.path.join(setup_dir, file)
908 # Guess all the needed names (variables in fact) from the current
909 # provision.
910 names = guess_names_from_current_provision(creds,session,paths)
911 if not sanitychecks(creds,session,names,paths):
912 print "Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision"
913 sys.exit(1)
914 # Let's see them
915 print_names(names)
916 # With all this information let's create a fresh new provision used as reference
917 provisiondir = newprovision(names,setup_dir,creds,session,smbconf)
918 # Get file paths of this new provision
919 newpaths = get_paths(targetdir=provisiondir)
920 populate_backlink(newpaths,creds,session,names.schemadn)
921 populate_dnsyntax(newpaths,creds,session,names.schemadn)
922 # Check the difference
923 update_basesamdb(newpaths,paths,names)
925 if opts.full:
926 update_samdb(newpaths,paths,creds,session,names)
927 update_secrets(newpaths,paths,creds,session)
928 update_privilege(newpaths,paths)
929 update_machine_account_password(paths,creds,session,names)
930 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
931 # to recreate them with the good form but with system account and then give the ownership to admin ...
932 admin_session_info = admin_session(lp, str(names.domainsid))
933 message(SIMPLE,"Updating SD")
934 update_sd(paths,creds,session,names)
935 update_sd(paths,creds,admin_session_info,names)
936 check_updated_sd(newpaths,paths,creds,session,names)
937 message(SIMPLE,"Upgrade finished !")
938 # remove reference provision now that everything is done !
939 rmall(provisiondir)