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/>.
34 # Find right directory when running from source tree
35 sys
.path
.insert(0, "bin/python")
37 from base64
import b64encode
40 from samba
.credentials
import DONT_USE_KERBEROS
41 from samba
.auth
import system_session
, admin_session
43 from ldb
import SCOPE_SUBTREE
, SCOPE_ONELEVEL
, SCOPE_BASE
, LdbError
45 import samba
.getopt
as options
46 from samba
.samdb
import SamDB
47 from samba
import param
48 from samba
.provision
import ProvisionNames
,provision_paths_from_lp
,find_setup_dir
,FILL_FULL
,provision
49 from samba
.provisionexceptions
import ProvisioningError
50 from samba
.schema
import get_dnsyntax_attributes
, get_linked_attributes
, Schema
51 from samba
.dcerpc
import misc
, security
52 from samba
.ndr
import ndr_pack
, ndr_unpack
53 from samba
.dcerpc
.misc
import SEC_CHAN_BDC
55 replace
=2^ldb
.FLAG_MOD_REPLACE
56 add
=2^ldb
.FLAG_MOD_ADD
57 delete
=2^ldb
.FLAG_MOD_DELETE
59 #Errors are always logged
68 # Attributes that not copied from the reference provision even if they do not exists in the destination object
69 # This is most probably because they are populated automatcally when object is created
70 hashAttrNotCopied
= { "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,\
71 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,\
72 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,\
73 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,\
74 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,\
75 "maxPwdAge":1, "mail":1, "secret":1}
77 # Usually for an object that already exists we do not overwrite attributes as they might have been changed for good
78 # reasons. Anyway for a few of thems it's mandatory to replace them otherwise the provision will be broken somehow.
79 hashOverwrittenAtt
= { "prefixMap": replace
, "systemMayContain": replace
,"systemOnly":replace
, "searchFlags":replace
,\
80 "mayContain":replace
, "systemFlags":replace
,
81 "oEMInformation":replace
, "operatingSystemVersion":replace
, "adminPropertyPages":1,"possibleInferiors":replace
+delete
}
84 def define_what_to_log(opts
):
88 if opts
.debugchangesd
:
89 what
= what | CHANGESD
92 if opts
.debugprovision
:
93 what
= what | PROVISION
95 what
= what | CHANGEALL
99 parser
= optparse
.OptionParser("provision [options]")
100 sambaopts
= options
.SambaOptions(parser
)
101 parser
.add_option_group(sambaopts
)
102 parser
.add_option_group(options
.VersionOptions(parser
))
103 credopts
= options
.CredentialsOptions(parser
)
104 parser
.add_option_group(credopts
)
105 parser
.add_option("--setupdir", type="string", metavar
="DIR",
106 help="directory with setup files")
107 parser
.add_option("--debugprovision", help="Debug provision", action
="store_true")
108 parser
.add_option("--debugguess", help="Print information on what is different but won't be changed", action
="store_true")
109 parser
.add_option("--debugchange", help="Print information on what is different but won't be changed", action
="store_true")
110 parser
.add_option("--debugchangesd", help="Print information security descriptors differences", action
="store_true")
111 parser
.add_option("--debugall", help="Print all available information (very verbose)", action
="store_true")
112 parser
.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action
="store_true")
113 parser
.add_option("--targetdir", type="string", metavar
="DIR",
114 help="Set target directory")
116 opts
= parser
.parse_args()[0]
118 whatToLog
= define_what_to_log(opts
)
120 def messageprovision(text
):
121 """print a message if quiet is not set."""
122 if opts
.debugprovision
or opts
.debugall
:
125 def message(what
,text
):
126 """print a message if quiet is not set."""
127 if (whatToLog
& what
) or (what
<= 0 ):
130 if len(sys
.argv
) == 1:
131 opts
.interactive
= True
132 lp
= sambaopts
.get_loadparm()
133 smbconf
= lp
.configfile
135 creds
= credopts
.get_credentials(lp
)
136 creds
.set_kerberos_state(DONT_USE_KERBEROS
)
137 setup_dir
= opts
.setupdir
138 if setup_dir
is None:
139 setup_dir
= find_setup_dir()
141 session
= system_session()
143 # Create an array of backlinked attributes
144 def populate_backlink(newpaths
,creds
,session
,schemadn
):
145 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
146 backlinked
.extend(get_linked_attributes(ldb
.Dn(newsam_ldb
,str(schemadn
)),newsam_ldb
).values())
148 # Get Paths for important objects (ldb, keytabs ...)
149 def get_paths(targetdir
=None,smbconf
=None):
150 if targetdir
is not None:
151 if (not os
.path
.exists(os
.path
.join(targetdir
, "etc"))):
152 os
.makedirs(os
.path
.join(targetdir
, "etc"))
153 smbconf
= os
.path
.join(targetdir
, "etc", "smb.conf")
155 smbconf
= param
.default_path()
157 if not os
.path
.exists(smbconf
):
158 message(ERROR
,"Unable to find smb.conf ..")
162 lp
= param
.LoadParm()
164 # Normaly we need the domain name for this function but for our needs it's pointless
165 paths
= provision_paths_from_lp(lp
,"foo")
168 # This function guess(fetch) informations needed to make a fresh provision from the current provision
169 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
170 def guess_names_from_current_provision(credentials
,session_info
,paths
):
171 lp
= param
.LoadParm()
172 lp
.load(paths
.smbconf
)
173 names
= ProvisionNames()
174 # NT domain, kerberos realm, root dn, domain dn, domain dns name
175 names
.domain
= string
.upper(lp
.get("workgroup"))
176 names
.realm
= lp
.get("realm")
177 basedn
= "DC=" + names
.realm
.replace(".",",DC=")
178 names
.dnsdomain
= names
.realm
179 names
.realm
= string
.upper(names
.realm
)
181 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session_info
, credentials
=credentials
,lp
=lp
, options
=["modules:samba_secrets"])
182 # Get the netbiosname first (could be obtained from smb.conf in theory)
183 attrs
= ["sAMAccountName"]
184 res
= secrets_ldb
.search(expression
="(flatname=%s)"%names
.domain
,base
="CN=Primary Domains", scope
=SCOPE_SUBTREE
, attrs
=attrs
)
185 names
.netbiosname
= str(res
[0]["sAMAccountName"]).replace("$","")
187 names
.smbconf
= smbconf
188 #It's important here to let ldb load with the old module or it's quite certain that the LDB won't load ...
189 samdb
= Ldb(paths
.samdb
, session_info
=session_info
,
190 credentials
=credentials
, lp
=lp
, options
=["modules:samba_dsdb"])
192 # That's a bit simplistic but it's ok as long as we have only 3 partitions
193 attrs2
= ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
194 res2
= samdb
.search(expression
="(objectClass=*)",base
="", scope
=SCOPE_BASE
, attrs
=attrs2
)
196 names
.configdn
= res2
[0]["configurationNamingContext"]
197 configdn
= str(names
.configdn
)
198 names
.schemadn
= res2
[0]["schemaNamingContext"]
199 if not (ldb
.Dn(samdb
, basedn
) == (ldb
.Dn(samdb
, res2
[0]["defaultNamingContext"][0]))):
200 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths
.samdb
, str(res2
[0]["defaultNamingContext"][0]), paths
.smbconf
, basedn
)))
202 names
.domaindn
=res2
[0]["defaultNamingContext"]
203 names
.rootdn
=res2
[0]["rootDomainNamingContext"]
206 res3
= samdb
.search(expression
="(objectClass=*)",base
="CN=Sites,"+configdn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs3
)
207 names
.sitename
= str(res3
[0]["cn"])
209 # dns hostname and server dn
210 attrs4
= ["dNSHostName"]
211 res4
= samdb
.search(expression
="(CN=%s)"%names
.netbiosname
,base
="OU=Domain Controllers,"+basedn
, \
212 scope
=SCOPE_ONELEVEL
, attrs
=attrs4
)
213 names
.hostname
= str(res4
[0]["dNSHostName"]).replace("."+names
.dnsdomain
,"")
215 server_res
= samdb
.search(expression
="serverReference=%s"%res4[0].dn
, attrs
=[], base
=configdn
)
216 names
.serverdn
= server_res
[0].dn
218 # invocation id/objectguid
219 res5
= samdb
.search(expression
="(objectClass=*)",base
="CN=NTDS Settings,%s" % str(names
.serverdn
), scope
=SCOPE_BASE
, attrs
=["invocationID","objectGUID"])
220 names
.invocation
= str(ndr_unpack( misc
.GUID
,res5
[0]["invocationId"][0]))
221 names
.ntdsguid
= str(ndr_unpack( misc
.GUID
,res5
[0]["objectGUID"][0]))
224 attrs6
= ["objectGUID", "objectSid", ]
225 res6
= samdb
.search(expression
="(objectClass=*)",base
=basedn
, scope
=SCOPE_BASE
, attrs
=attrs6
)
226 names
.domainguid
= str(ndr_unpack( misc
.GUID
,res6
[0]["objectGUID"][0]))
227 names
.domainsid
= str(ndr_unpack( security
.dom_sid
,res6
[0]["objectSid"][0]))
230 attrs7
= ["cn","displayName"]
231 res7
= samdb
.search(expression
="(displayName=Default Domain Policy)",base
="CN=Policies,CN=System,"+basedn
, \
232 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
233 names
.policyid
= str(res7
[0]["cn"]).replace("{","").replace("}","")
235 attrs8
= ["cn","displayName"]
236 res8
= samdb
.search(expression
="(displayName=Default Domain Controllers Policy)",base
="CN=Policies,CN=System,"+basedn
, \
237 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
239 names
.policyid_dc
= str(res8
[0]["cn"]).replace("{","").replace("}","")
241 names
.policyid_dc
= None
247 def print_names(names
):
248 message(GUESS
, "rootdn :"+str(names
.rootdn
))
249 message(GUESS
, "configdn :"+str(names
.configdn
))
250 message(GUESS
, "schemadn :"+str(names
.schemadn
))
251 message(GUESS
, "serverdn :"+str(names
.serverdn
))
252 message(GUESS
, "netbiosname :"+names
.netbiosname
)
253 message(GUESS
, "defaultsite :"+names
.sitename
)
254 message(GUESS
, "dnsdomain :"+names
.dnsdomain
)
255 message(GUESS
, "hostname :"+names
.hostname
)
256 message(GUESS
, "domain :"+names
.domain
)
257 message(GUESS
, "realm :"+names
.realm
)
258 message(GUESS
, "invocationid:"+names
.invocation
)
259 message(GUESS
, "policyguid :"+names
.policyid
)
260 message(GUESS
, "policyguiddc:"+str(names
.policyid_dc
))
261 message(GUESS
, "domainsid :"+names
.domainsid
)
262 message(GUESS
, "domainguid :"+names
.domainguid
)
263 message(GUESS
, "ntdsguid :"+names
.ntdsguid
)
265 # Create a fresh new reference provision
266 # This provision will be the reference for knowing what has changed in the
267 # since the latest upgrade in the current provision
268 def newprovision(names
,setup_dir
,creds
,session
,smbconf
):
269 message(SIMPLE
, "Creating a reference provision")
270 provdir
=tempfile
.mkdtemp(dir=paths
.private_dir
, prefix
="referenceprovision")
271 if os
.path
.isdir(provdir
):
273 logstd
=os
.path
.join(provdir
,"log.std")
274 os
.chdir(os
.path
.join(setup_dir
,".."))
277 sys
.stderr
= open("%s/provision.log"%provdir
, "w")
278 message(PROVISION
, "Reference provision stored in %s"%provdir
)
279 message(PROVISION
, "STDERR message of provision will be logged in %s/provision.log"%provdir
)
280 sys
.stderr
= open("/dev/stdout", "w")
281 provision(setup_dir
, messageprovision
,
282 session
, creds
, smbconf
=smbconf
, targetdir
=provdir
,
283 samdb_fill
=FILL_FULL
, realm
=names
.realm
, domain
=names
.domain
,
284 domainguid
=names
.domainguid
, domainsid
=names
.domainsid
,ntdsguid
=names
.ntdsguid
,
285 policyguid
=names
.policyid
,policyguid_dc
=names
.policyid_dc
,hostname
=names
.netbiosname
,
286 hostip
=None, hostip6
=None,
287 invocationid
=names
.invocation
, adminpass
=None,
288 krbtgtpass
=None, machinepass
=None,
289 dnspass
=None, root
=None, nobody
=None,
290 wheel
=None, users
=None,
291 serverrole
="domain controller",
292 ldap_backend_extra_port
=None,
299 ldap_dryrun_mode
=None)
302 # This function sorts two dn in the lexicographical order and put higher level DN before
303 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller (-1) as it has less
306 p
= re
.compile(r
'(?<!\\),')
307 tab1
= p
.split(str(x
))
308 tab2
= p
.split(str(y
))
310 if (len(tab1
) > len(tab2
)):
312 elif (len(tab1
) < len(tab2
)):
319 # Note: python range go up to upper limit but do not include it
320 for i
in range(0,min):
321 ret
=cmp(tab1
[len1
-i
],tab2
[len2
-i
])
327 message(ERROR
,"PB PB PB"+space
.join(tab1
)+" / "+space
.join(tab2
))
334 # check from security descriptors modifications return 1 if it is 0 otherwise
335 # it also populate hash structure for later use in the upgrade process
336 def handle_security_desc(ischema
,att
,msgElt
,hashallSD
,old
,new
):
337 if ischema
== 1 and att
== "defaultSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
339 hashSD
["oldSD"] = old
[0][att
]
340 hashSD
["newSD"] = new
[0][att
]
341 hashallSD
[str(old
[0].dn
)] = hashSD
343 if att
== "nTSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
346 hashSD
["oldSD"] = ndr_unpack(security
.descriptor
,str(old
[0][att
]))
347 hashSD
["newSD"] = ndr_unpack(security
.descriptor
,str(new
[0][att
]))
348 hashallSD
[str(old
[0].dn
)] = hashSD
352 # Hangle special cases ... That's when we want to update an attribute only
353 # if it has a certain value or if it's for a certain object or
355 # It can be also if we want to do a merge of value instead of a simple replace
356 def handle_special_case(att
,delta
,new
,old
,ischema
):
357 flag
= delta
.get(att
).flags()
358 if (att
== "gPLink" or att
== "gPCFileSysPath") and flag
== ldb
.FLAG_MOD_REPLACE
and str(new
[0].dn
).lower() == str(old
[0].dn
).lower():
361 if att
== "forceLogoff":
362 ref
=0x8000000000000000
363 oldval
=int(old
[0][att
][0])
364 newval
=int(new
[0][att
][0])
365 ref
== old
and ref
== abs(new
)
367 if (att
== "adminDisplayName" or att
== "adminDescription") and ischema
:
369 if (str(old
[0].dn
) == "CN=Samba4-Local-Domain,%s"%(str(names
.schemadn
)) and att
== "defaultObjectCategory" and flag
== ldb
.FLAG_MOD_REPLACE
):
371 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
):
373 if (str(old
[0].dn
) == "CN=Title,%s"%(str(names
.schemadn
)) and att
== "rangeUpper" and flag
== ldb
.FLAG_MOD_REPLACE
):
375 if ( (att
== "member" or att
== "servicePrincipalName") and flag
== ldb
.FLAG_MOD_REPLACE
):
380 for elem
in old
[0][att
]:
382 newval
.append(str(elem
))
384 for elem
in new
[0][att
]:
385 if not hash.has_key(str(elem
)):
387 newval
.append(str(elem
))
389 delta
[att
] = ldb
.MessageElement(newval
, ldb
.FLAG_MOD_REPLACE
, att
)
393 if (str(old
[0].dn
) == "%s"%(str(names
.rootdn
)) and att
== "subRefs" and flag
== ldb
.FLAG_MOD_REPLACE
):
395 if str(delta
.dn
).endswith("CN=DisplaySpecifiers,%s"%names
.configdn
):
399 def update_secrets(newpaths
,paths
,creds
,session
):
400 message(SIMPLE
,"update secrets.ldb")
401 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
402 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_secrets"])
403 res
= newsecrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
404 res2
= secrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
405 delta
= secrets_ldb
.msg_diff(res2
[0],res
[0])
406 delta
.dn
= res2
[0].dn
407 secrets_ldb
.modify(delta
)
409 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
410 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
411 res
= newsecrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
412 res2
= secrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
418 empty
= ldb
.Message()
419 for i
in range(0,len(res
)):
420 hash_new
[str(res
[i
]["dn"]).lower()] = res
[i
]["dn"]
422 # Create a hash for speeding the search of existing object in the current provision
423 for i
in range(0,len(res2
)):
424 hash[str(res2
[i
]["dn"]).lower()] = res2
[i
]["dn"]
426 for k
in hash_new
.keys():
427 if not hash.has_key(k
):
428 listMissing
.append(hash_new
[k
])
430 listPresent
.append(hash_new
[k
])
431 for entry
in listMissing
:
432 res
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
433 res2
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
434 delta
= secrets_ldb
.msg_diff(empty
,res
[0])
435 for att
in hashAttrNotCopied
.keys():
437 message(CHANGE
,"Entry %s is missing from secrets.ldb"%res[0].dn
)
439 message(CHANGE
," Adding attribute %s"%att
)
441 secrets_ldb
.add(delta
)
443 for entry
in listPresent
:
444 res
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
445 res2
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
446 delta
= secrets_ldb
.msg_diff(res2
[0],res
[0])
448 for att
in hashAttrNotCopied
.keys():
453 message(CHANGE
," Adding/Changing attribute %s to %s"%(att
,res2
[0].dn
))
455 delta
.dn
= res2
[0].dn
456 secrets_ldb
.modify(delta
)
458 # Check difference between the current provision and the reference provision.
459 # It looks for all object which base DN is name if ischema is false then scan is done in
460 # cross partition mode.
461 # If ischema is true, then special handling is done for dealing with schema
462 def check_diff_name(newpaths
,paths
,creds
,session
,basedn
,names
,ischema
):
470 # Connect to the reference provision and get all the attribute in the partition referred by name
471 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
472 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
474 res
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
475 res2
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
477 res
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
478 res2
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
480 # Create a hash for speeding the search of new object
481 for i
in range(0,len(res
)):
482 hash_new
[str(res
[i
]["dn"]).lower()] = res
[i
]["dn"]
484 # Create a hash for speeding the search of existing object in the current provision
485 for i
in range(0,len(res2
)):
486 hash[str(res2
[i
]["dn"]).lower()] = res2
[i
]["dn"]
488 for k
in hash_new
.keys():
489 if not hash.has_key(k
):
490 listMissing
.append(hash_new
[k
])
492 listPresent
.append(hash_new
[k
])
494 # Sort the missing object in order to have object of the lowest level first (which can be
495 # containers for higher level objects)
496 listMissing
.sort(dn_sort
)
497 listPresent
.sort(dn_sort
)
500 # The following lines (up to the for loop) is to load the up to date schema into our current LDB
501 # a complete schema is needed as the insertion of attributes and class is done against it
502 # and the schema is self validated
503 # The double ldb open and schema validation is taken from the initial provision script
504 # it's not certain that it is really needed ....
505 sam_ldb
= Ldb(session_info
=session
, credentials
=creds
, lp
=lp
)
506 schema
= Schema(setup_path
, security
.dom_sid(names
.domainsid
), schemadn
=basedn
, serverdn
=str(names
.serverdn
))
507 # Load the schema from the one we computed earlier
508 sam_ldb
.set_schema_from_ldb(schema
.ldb
)
509 # And now we can connect to the DB - the schema won't be loaded from the DB
510 sam_ldb
.connect(paths
.samdb
)
511 sam_ldb
.transaction_start()
513 sam_ldb
.transaction_start()
515 empty
= ldb
.Message()
516 message(SIMPLE
,"There are %d missing objects"%(len(listMissing
)))
517 for dn
in listMissing
:
518 res
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
519 delta
= sam_ldb
.msg_diff(empty
,res
[0])
520 for att
in hashAttrNotCopied
.keys():
522 for att
in backlinked
:
526 sam_ldb
.add(delta
,["relax:0"])
529 for dn
in listPresent
:
530 res
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
531 res2
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
532 delta
= sam_ldb
.msg_diff(res2
[0],res
[0])
533 for att
in hashAttrNotCopied
.keys():
535 for att
in backlinked
:
537 delta
.remove("parentGUID")
540 msgElt
= delta
.get(att
)
543 if handle_security_desc(ischema
,att
,msgElt
,hashallSD
,res2
,res
):
546 if (not hashOverwrittenAtt
.has_key(att
) or not (hashOverwrittenAtt
.get(att
)&2^msgElt
.flags())):
547 if handle_special_case(att
,delta
,res
,res2
,ischema
)==0 and msgElt
.flags()!=ldb
.FLAG_MOD_ADD
:
550 message(CHANGE
, "dn= "+str(dn
)+ " "+att
+ " with flag "+str(msgElt
.flags())+ " is not allowed to be changed/removed, I discard this change ...")
551 for e
in range(0,len(res2
[0][att
])):
552 message(CHANGE
,"old %d : %s"%(i
,str(res2
[0][att
][e
])))
553 if msgElt
.flags() == 2:
555 for e
in range(0,len(res
[0][att
])):
556 message(CHANGE
,"new %d : %s"%(i
,str(res
[0][att
][e
])))
559 if len(delta
.items()) >1:
560 attributes
=",".join(delta
.keys())
561 message(CHANGE
,"%s is different from the reference one, changed attributes: %s"%(dn
,attributes
))
562 changed
= changed
+ 1
563 sam_ldb
.modify(delta
)
565 sam_ldb
.transaction_commit()
566 message(SIMPLE
,"There are %d changed objects"%(changed))
570 # This function updates SD for AD objects.
571 # As SD in the upgraded provision can be different for various reasons
572 # this function check if an automatic update can be performed and do it
573 # or if it can't be done.
574 def update_sds(diffDefSD
,diffSD
,paths
,creds
,session
,rootdn
,domSIDTxt
):
575 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
576 sam_ldb
.transaction_start()
577 domSID
= security
.dom_sid(domSIDTxt
)
579 admin_session_info
= admin_session(lp
, str(domSID
))
580 system_session_info
= system_session()
582 for dn
in diffSD
.keys():
583 newSD
= diffSD
[dn
]["newSD"].as_sddl(domSID
)
584 oldSD
= diffSD
[dn
]["oldSD"].as_sddl(domSID
)
585 message(CHANGESD
, "ntsecuritydescriptor for %s has changed old %s new %s"%(dn
,oldSD
,diffSD
[dn
]["newSD"].as_sddl(domSID
)))
586 # First let's find the defaultSD for the object which SD is different from the reference one.
587 res
= sam_ldb
.search(expression
="dn=%s"%(dn),base
=rootdn
, scope
=SCOPE_SUBTREE
,attrs
=["objectClass"],controls
=["search_options:1:2"])
588 classObj
= res
[0]["objectClass"][-1]
590 if hashClassSD
.has_key(classObj
):
591 defSD
= hashClassSD
[classObj
]
593 res2
= sam_ldb
.search(expression
="lDAPDisplayName=%s"%(classObj),base
=rootdn
, scope
=SCOPE_SUBTREE
,attrs
=["defaultSecurityDescriptor"],controls
=["search_options:1:2"])
595 defSD
= str(res2
[0]["defaultSecurityDescriptor"])
596 hashClassSD
[classObj
] = defSD
597 # Because somewhere between alpha8 and alpha9 samba4 changed the owner of ACLs in the AD so
598 # we check if it's the case and if so use the "old" owner to see if the ACL is a direct calculation
599 # from the defaultSecurityDescriptor
600 session
= admin_session_info
601 if oldSD
.startswith("O:SYG:BA"):
602 session
= system_session_info
603 descr
= security
.descriptor
.ntsd_from_defaultsd(defSD
, domSID
,session
)
604 if descr
.as_sddl(domSID
) != oldSD
:
605 message(SIMPLE
, "nTSecurity Descriptor for %s do not directly inherit from the defaultSecurityDescriptor and is different from the one of the reference provision, therefor I can't upgrade i")
606 message(SIMPLE
,"Old Descriptor: %s"%(oldSD))
607 message(SIMPLE
,"New Descriptor: %s"%(newSD))
608 if diffDefSD
.has_key(classObj
):
609 # We have a pending modification for the defaultSecurityDescriptor of the class Object of the currently inspected object
610 # and we have a conflict so write down that we won't upgrade this defaultSD for this class object
611 diffDefSD
[classObj
]["noupgrade"]=1
613 # At this point we know that the SD was directly generated from the defaultSecurityDescriptor
614 # so we can take the new SD and replace the old one
616 delta
= ldb
.Message()
617 delta
.dn
= ldb
.Dn(sam_ldb
,dn
)
618 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( ndr_pack(diffSD
[dn
]["newSD"]),ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
619 sam_ldb
.modify(delta
)
621 sam_ldb
.transaction_commit()
622 message(SIMPLE
,"%d nTSecurityDescriptor attribute(s) have been updated"%(upgrade))
623 sam_ldb
.transaction_start()
626 message(CHANGESD
, "DefaultSecurityDescriptor for class object %s has changed"%(dn))
627 if not diffDefSD
[dn
].has_key("noupgrade"):
629 delta
= ldb
.Message()
630 delta
.dn
= ldb
.Dn(sam_ldb
,dn
)
631 delta
["defaultSecurityDescriptor"] = ldb
.MessageElement(diffDefSD
[dn
]["newSD"],ldb
.FLAG_MOD_REPLACE
,"defaultSecurityDescriptor" )
632 sam_ldb
.modify(delta
)
634 message(CHANGESD
,"Not updating the defaultSecurityDescriptor for class object %s as one or more dependant object hasn't been upgraded"%(dn))
636 sam_ldb
.transaction_commit()
637 message(SIMPLE
,"%d defaultSecurityDescriptor attribute(s) have been updated"%(upgrade))
640 for root
, dirs
, files
in os
.walk(topdir
, topdown
=False):
642 os
.remove(os
.path
.join(root
, name
))
644 os
.rmdir(os
.path
.join(root
, name
))
648 def update_basesamdb(newpaths
,paths
,names
):
649 message(SIMPLE
,"Copy samdb")
650 shutil
.copy(newpaths
.samdb
,paths
.samdb
)
652 message(SIMPLE
,"Update partitions filename if needed")
653 schemaldb
=os
.path
.join(paths
.private_dir
,"schema.ldb")
654 configldb
=os
.path
.join(paths
.private_dir
,"configuration.ldb")
655 usersldb
=os
.path
.join(paths
.private_dir
,"users.ldb")
656 samldbdir
=os
.path
.join(paths
.private_dir
,"sam.ldb.d")
658 if not os
.path
.isdir(samldbdir
):
660 os
.chmod(samldbdir
,0700)
661 if os
.path
.isfile(schemaldb
):
662 shutil
.copy(schemaldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.schemadn
).upper()))
664 if os
.path
.isfile(usersldb
):
665 shutil
.copy(usersldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.rootdn
).upper()))
667 if os
.path
.isfile(configldb
):
668 shutil
.copy(configldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.configdn
).upper()))
671 def update_privilege(newpaths
,paths
):
672 message(SIMPLE
,"Copy privilege")
673 shutil
.copy(os
.path
.join(newpaths
.private_dir
,"privilege.ldb"),os
.path
.join(paths
.private_dir
,"privilege.ldb"))
675 # For each partition check the differences
676 def update_samdb(newpaths
,paths
,creds
,session
,names
):
678 message(SIMPLE
, "Doing schema update")
679 hashdef
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.schemadn
),names
,1)
680 message(SIMPLE
,"Done with schema update")
681 message(SIMPLE
,"Scanning whole provision for updates and additions")
682 hashSD
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.rootdn
),names
,0)
683 message(SIMPLE
,"Done with scanning")
684 # update_sds(hashdef,hashSD,paths,creds,session,str(names.rootdn),names.domainsid)
686 def update_machine_account_password(newpaths
,paths
,creds
,session
,names
):
688 secrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
689 secrets_ldb
.transaction_start()
690 secrets_msg
= secrets_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=["secureChannelType"])
691 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
692 if secrets_msg
[0]["secureChannelType"][0] == SEC_CHAN_BDC
:
693 sam_ldb
.transaction_start()
694 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=[])
695 assert(len(res
) == 1)
697 msg
= ldb
.Message(res
[0].dn
)
698 machinepass
= msg
["userPassword"] = glue
.generate_random_str(12)
700 el
.set_flags(ldb
.FLAG_MOD_REPLACE
)
703 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
),
704 attrs
=["msDs-keyVersionNumber"])
705 assert(len(res
) == 1)
706 kvno
= res
[0]["msDs-keyVersionNumber"]
708 secretsdb_self_join(secrets_ldb
, domain
=names
.domain
,
710 dnsdomain
=names
.dnsdomain
,
711 netbiosname
=names
.netbiosname
,
712 machinepass
=machinepass
,
713 key_version_number
=kvno
,
714 secure_channel_type
=secrets_msg
[0]["secureChannelType"])
715 sam_ldb
.transaction_prepare_commit()
716 secrets_ldb
.transaction_prepare_commit()
717 sam_ldb
.transaction_commit()
718 secrets_ldb
.transaction_commit()
720 # From here start the big steps of the program
721 # First get files paths
722 paths
=get_paths(targetdir
=opts
.targetdir
,smbconf
=smbconf
)
723 paths
.setup
= setup_dir
724 def setup_path(file):
725 return os
.path
.join(setup_dir
, file)
726 # Guess all the needed names (variables in fact) from the current
728 names
= guess_names_from_current_provision(creds
,session
,paths
)
731 # With all this information let's create a fresh new provision used as reference
732 provisiondir
= newprovision(names
,setup_dir
,creds
,session
,smbconf
)
733 # Get file paths of this new provision
734 newpaths
= get_paths(targetdir
=provisiondir
)
735 populate_backlink(newpaths
,creds
,session
,names
.schemadn
)
736 # Check the difference
737 update_basesamdb(newpaths
,paths
,names
)
738 update_secrets(newpaths
,paths
,creds
,session
)
739 update_privilege(newpaths
,paths
)
740 update_machine_account_password(newpaths
,paths
,creds
,session
,names
)
742 update_samdb(newpaths
,paths
,creds
,session
,names
)
743 message(SIMPLE
,"Upgrade finished !")
744 # remove reference provision now that everything is done !