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
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
45 import samba
.getopt
as options
46 from samba
.samdb
import SamDB
47 from samba
import param
48 from samba
import glue
49 from samba
.provision
import ProvisionNames
,provision_paths_from_lp
,find_setup_dir
,FILL_FULL
,provision
, get_domain_descriptor
, get_config_descriptor
, secretsdb_self_join
50 from samba
.provisionexceptions
import ProvisioningError
51 from samba
.schema
import get_dnsyntax_attributes
, get_linked_attributes
, Schema
, get_schema_descriptor
52 from samba
.dcerpc
import misc
, security
53 from samba
.ndr
import ndr_pack
, ndr_unpack
54 from samba
.dcerpc
.misc
import SEC_CHAN_BDC
56 replace
=2^ldb
.FLAG_MOD_REPLACE
57 add
=2^ldb
.FLAG_MOD_ADD
58 delete
=2^ldb
.FLAG_MOD_DELETE
60 #Errors are always logged
69 # Attributes that not copied from the reference provision even if they do not exists in the destination object
70 # This is most probably because they are populated automatcally when object is created
71 hashAttrNotCopied
= { "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,\
72 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,\
73 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,\
74 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,\
75 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,\
76 "maxPwdAge":1, "mail":1, "secret":1,"possibleInferiors":1, "sAMAccountType":1}
78 # Usually for an object that already exists we do not overwrite attributes as they might have been changed for good
79 # reasons. Anyway for a few of thems it's mandatory to replace them otherwise the provision will be broken somehow.
80 hashOverwrittenAtt
= { "prefixMap": replace
, "systemMayContain": replace
,"systemOnly":replace
, "searchFlags":replace
,\
81 "mayContain":replace
, "systemFlags":replace
,
82 "oEMInformation":replace
, "operatingSystemVersion":replace
, "adminPropertyPages":replace
,
83 "defaultSecurityDescriptor": replace
}
86 def define_what_to_log(opts
):
90 if opts
.debugchangesd
:
91 what
= what | CHANGESD
94 if opts
.debugprovision
:
95 what
= what | PROVISION
97 what
= what | CHANGEALL
101 parser
= optparse
.OptionParser("provision [options]")
102 sambaopts
= options
.SambaOptions(parser
)
103 parser
.add_option_group(sambaopts
)
104 parser
.add_option_group(options
.VersionOptions(parser
))
105 credopts
= options
.CredentialsOptions(parser
)
106 parser
.add_option_group(credopts
)
107 parser
.add_option("--setupdir", type="string", metavar
="DIR",
108 help="directory with setup files")
109 parser
.add_option("--debugprovision", help="Debug provision", action
="store_true")
110 parser
.add_option("--debugguess", help="Print information on what is different but won't be changed", action
="store_true")
111 parser
.add_option("--debugchange", help="Print information on what is different but won't be changed", action
="store_true")
112 parser
.add_option("--debugchangesd", help="Print information security descriptors differences", action
="store_true")
113 parser
.add_option("--debugall", help="Print all available information (very verbose)", action
="store_true")
114 parser
.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action
="store_true")
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","msDS-Behavior-Version" ]
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
= ndr_unpack( security
.dom_sid
,res6
[0]["objectSid"][0])
228 if res6
[0].get("msDS-Behavior-Version") == None or int(res6
[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000
:
229 names
.domainlevel
= DS_DOMAIN_FUNCTION_2000
231 names
.domainlevel
= int(res6
[0]["msDS-Behavior-Version"][0])
234 attrs7
= ["cn","displayName"]
235 res7
= samdb
.search(expression
="(displayName=Default Domain Policy)",base
="CN=Policies,CN=System,"+basedn
, \
236 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
237 names
.policyid
= str(res7
[0]["cn"]).replace("{","").replace("}","")
239 attrs8
= ["cn","displayName"]
240 res8
= samdb
.search(expression
="(displayName=Default Domain Controllers Policy)",base
="CN=Policies,CN=System,"+basedn
, \
241 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
243 names
.policyid_dc
= str(res8
[0]["cn"]).replace("{","").replace("}","")
245 names
.policyid_dc
= None
251 def print_names(names
):
252 message(GUESS
, "rootdn :"+str(names
.rootdn
))
253 message(GUESS
, "configdn :"+str(names
.configdn
))
254 message(GUESS
, "schemadn :"+str(names
.schemadn
))
255 message(GUESS
, "serverdn :"+str(names
.serverdn
))
256 message(GUESS
, "netbiosname :"+names
.netbiosname
)
257 message(GUESS
, "defaultsite :"+names
.sitename
)
258 message(GUESS
, "dnsdomain :"+names
.dnsdomain
)
259 message(GUESS
, "hostname :"+names
.hostname
)
260 message(GUESS
, "domain :"+names
.domain
)
261 message(GUESS
, "realm :"+names
.realm
)
262 message(GUESS
, "invocationid:"+names
.invocation
)
263 message(GUESS
, "policyguid :"+names
.policyid
)
264 message(GUESS
, "policyguiddc:"+str(names
.policyid_dc
))
265 message(GUESS
, "domainsid :"+str(names
.domainsid
))
266 message(GUESS
, "domainguid :"+names
.domainguid
)
267 message(GUESS
, "ntdsguid :"+names
.ntdsguid
)
268 message(GUESS
, "domainlevel :"+str(names
.domainlevel
))
270 # Create a fresh new reference provision
271 # This provision will be the reference for knowing what has changed in the
272 # since the latest upgrade in the current provision
273 def newprovision(names
,setup_dir
,creds
,session
,smbconf
):
274 message(SIMPLE
, "Creating a reference provision")
275 provdir
=tempfile
.mkdtemp(dir=paths
.private_dir
, prefix
="referenceprovision")
276 if os
.path
.isdir(provdir
):
278 logstd
=os
.path
.join(provdir
,"log.std")
279 os
.chdir(os
.path
.join(setup_dir
,".."))
282 sys
.stderr
= open("%s/provision.log"%provdir
, "w")
283 message(PROVISION
, "Reference provision stored in %s"%provdir
)
284 message(PROVISION
, "STDERR message of provision will be logged in %s/provision.log"%provdir
)
285 sys
.stderr
= open("/dev/stdout", "w")
286 provision(setup_dir
, messageprovision
,
287 session
, creds
, smbconf
=smbconf
, targetdir
=provdir
,
288 samdb_fill
=FILL_FULL
, realm
=names
.realm
, domain
=names
.domain
,
289 domainguid
=names
.domainguid
, domainsid
=str(names
.domainsid
),ntdsguid
=names
.ntdsguid
,
290 policyguid
=names
.policyid
,policyguid_dc
=names
.policyid_dc
,hostname
=names
.netbiosname
,
291 hostip
=None, hostip6
=None,
292 invocationid
=names
.invocation
, adminpass
=None,
293 krbtgtpass
=None, machinepass
=None,
294 dnspass
=None, root
=None, nobody
=None,
295 wheel
=None, users
=None,
296 serverrole
="domain controller",
297 ldap_backend_extra_port
=None,
304 dom_for_fun_level
=names
.domainlevel
,
305 ldap_dryrun_mode
=None)
308 # This function sorts two dn in the lexicographical order and put higher level DN before
309 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller (-1) as it has less
312 p
= re
.compile(r
'(?<!\\),')
313 tab1
= p
.split(str(x
))
314 tab2
= p
.split(str(y
))
316 if (len(tab1
) > len(tab2
)):
318 elif (len(tab1
) < len(tab2
)):
325 # Note: python range go up to upper limit but do not include it
326 for i
in range(0,min):
327 ret
=cmp(tab1
[len1
-i
],tab2
[len2
-i
])
333 message(ERROR
,"PB PB PB"+space
.join(tab1
)+" / "+space
.join(tab2
))
340 # check from security descriptors modifications return 1 if it is 0 otherwise
341 # it also populate hash structure for later use in the upgrade process
342 def handle_security_desc(ischema
,att
,msgElt
,hashallSD
,old
,new
):
343 if ischema
== 1 and att
== "defaultSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
345 hashSD
["oldSD"] = old
[0][att
]
346 hashSD
["newSD"] = new
[0][att
]
347 hashallSD
[str(old
[0].dn
)] = hashSD
349 if att
== "nTSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
352 hashSD
["oldSD"] = ndr_unpack(security
.descriptor
,str(old
[0][att
]))
353 hashSD
["newSD"] = ndr_unpack(security
.descriptor
,str(new
[0][att
]))
354 hashallSD
[str(old
[0].dn
)] = hashSD
358 # Hangle special cases ... That's when we want to update an attribute only
359 # if it has a certain value or if it's for a certain object or
361 # It can be also if we want to do a merge of value instead of a simple replace
362 def handle_special_case(att
,delta
,new
,old
,ischema
):
363 flag
= delta
.get(att
).flags()
364 if (att
== "gPLink" or att
== "gPCFileSysPath") and flag
== ldb
.FLAG_MOD_REPLACE
and str(new
[0].dn
).lower() == str(old
[0].dn
).lower():
367 if att
== "forceLogoff":
368 ref
=0x8000000000000000
369 oldval
=int(old
[0][att
][0])
370 newval
=int(new
[0][att
][0])
371 ref
== old
and ref
== abs(new
)
373 if (att
== "adminDisplayName" or att
== "adminDescription") and ischema
:
375 if (str(old
[0].dn
) == "CN=Samba4-Local-Domain,%s"%(str(names
.schemadn
)) and att
== "defaultObjectCategory" and flag
== ldb
.FLAG_MOD_REPLACE
):
377 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
):
379 if (str(old
[0].dn
) == "CN=Title,%s"%(str(names
.schemadn
)) and att
== "rangeUpper" and flag
== ldb
.FLAG_MOD_REPLACE
):
381 if ( (att
== "member" or att
== "servicePrincipalName") and flag
== ldb
.FLAG_MOD_REPLACE
):
386 for elem
in old
[0][att
]:
388 newval
.append(str(elem
))
390 for elem
in new
[0][att
]:
391 if not hash.has_key(str(elem
)):
393 newval
.append(str(elem
))
395 delta
[att
] = ldb
.MessageElement(newval
, ldb
.FLAG_MOD_REPLACE
, att
)
399 if (str(old
[0].dn
) == "%s"%(str(names
.rootdn
)) and att
== "subRefs" and flag
== ldb
.FLAG_MOD_REPLACE
):
401 if str(delta
.dn
).endswith("CN=DisplaySpecifiers,%s"%names
.configdn
):
405 def update_secrets(newpaths
,paths
,creds
,session
):
406 message(SIMPLE
,"update secrets.ldb")
407 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
408 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_secrets"])
409 res
= newsecrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
410 res2
= secrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
411 delta
= secrets_ldb
.msg_diff(res2
[0],res
[0])
412 delta
.dn
= res2
[0].dn
413 secrets_ldb
.modify(delta
)
415 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
416 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
417 res
= newsecrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
418 res2
= secrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
424 empty
= ldb
.Message()
425 for i
in range(0,len(res
)):
426 hash_new
[str(res
[i
]["dn"]).lower()] = res
[i
]["dn"]
428 # Create a hash for speeding the search of existing object in the current provision
429 for i
in range(0,len(res2
)):
430 hash[str(res2
[i
]["dn"]).lower()] = res2
[i
]["dn"]
432 for k
in hash_new
.keys():
433 if not hash.has_key(k
):
434 listMissing
.append(hash_new
[k
])
436 listPresent
.append(hash_new
[k
])
437 for entry
in listMissing
:
438 res
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
439 res2
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
440 delta
= secrets_ldb
.msg_diff(empty
,res
[0])
441 for att
in hashAttrNotCopied
.keys():
443 message(CHANGE
,"Entry %s is missing from secrets.ldb"%res[0].dn
)
445 message(CHANGE
," Adding attribute %s"%att
)
447 secrets_ldb
.add(delta
)
449 for entry
in listPresent
:
450 res
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
451 res2
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
452 delta
= secrets_ldb
.msg_diff(res2
[0],res
[0])
454 for att
in hashAttrNotCopied
.keys():
460 message(CHANGE
,"Found attribute name on %s, must rename the DN "%(res2
[0].dn
))
461 secrets_ldb
.rename(res2
[0].dn
,ldb
.Dn(secrets_ldb
,"%sfoo"%str
(res2
[0].dn
)))
462 secrets_ldb
.rename(ldb
.Dn(secrets_ldb
,"%sfoo"%str
(res2
[0].dn
)),res2
[0].dn
)
467 for entry
in listPresent
:
468 res
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
469 res2
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
470 delta
= secrets_ldb
.msg_diff(res2
[0],res
[0])
472 for att
in hashAttrNotCopied
.keys():
477 message(CHANGE
," Adding/Changing attribute %s to %s"%(att
,res2
[0].dn
))
479 delta
.dn
= res2
[0].dn
480 secrets_ldb
.modify(delta
)
483 # Check difference between the current provision and the reference provision.
484 # It looks for all object which base DN is name if ischema is false then scan is done in
485 # cross partition mode.
486 # If ischema is true, then special handling is done for dealing with schema
487 def check_diff_name(newpaths
,paths
,creds
,session
,basedn
,names
,ischema
):
495 # Connect to the reference provision and get all the attribute in the partition referred by name
496 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
497 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
499 res
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
500 res2
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
502 res
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
503 res2
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
505 # Create a hash for speeding the search of new object
506 for i
in range(0,len(res
)):
507 hash_new
[str(res
[i
]["dn"]).lower()] = res
[i
]["dn"]
509 # Create a hash for speeding the search of existing object in the current provision
510 for i
in range(0,len(res2
)):
511 hash[str(res2
[i
]["dn"]).lower()] = res2
[i
]["dn"]
513 for k
in hash_new
.keys():
514 if not hash.has_key(k
):
515 listMissing
.append(hash_new
[k
])
517 listPresent
.append(hash_new
[k
])
519 # Sort the missing object in order to have object of the lowest level first (which can be
520 # containers for higher level objects)
521 listMissing
.sort(dn_sort
)
522 listPresent
.sort(dn_sort
)
525 # The following lines (up to the for loop) is to load the up to date schema into our current LDB
526 # a complete schema is needed as the insertion of attributes and class is done against it
527 # and the schema is self validated
528 # The double ldb open and schema validation is taken from the initial provision script
529 # it's not certain that it is really needed ....
530 sam_ldb
= Ldb(session_info
=session
, credentials
=creds
, lp
=lp
)
531 schema
= Schema(setup_path
, names
.domainsid
, schemadn
=basedn
, serverdn
=str(names
.serverdn
))
532 # Load the schema from the one we computed earlier
533 sam_ldb
.set_schema_from_ldb(schema
.ldb
)
534 # And now we can connect to the DB - the schema won't be loaded from the DB
535 sam_ldb
.connect(paths
.samdb
)
536 sam_ldb
.transaction_start()
538 sam_ldb
.transaction_start()
540 empty
= ldb
.Message()
541 message(SIMPLE
,"There are %d missing objects"%(len(listMissing
)))
542 for dn
in listMissing
:
543 res
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
544 delta
= sam_ldb
.msg_diff(empty
,res
[0])
545 for att
in hashAttrNotCopied
.keys():
547 for att
in backlinked
:
551 sam_ldb
.add(delta
,["relax:0"])
554 for dn
in listPresent
:
555 res
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
556 res2
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
557 delta
= sam_ldb
.msg_diff(res2
[0],res
[0])
558 for att
in hashAttrNotCopied
.keys():
560 for att
in backlinked
:
562 delta
.remove("parentGUID")
565 msgElt
= delta
.get(att
)
568 if handle_security_desc(ischema
,att
,msgElt
,hashallSD
,res2
,res
):
571 if (not hashOverwrittenAtt
.has_key(att
) or not (hashOverwrittenAtt
.get(att
)&2^msgElt
.flags())):
572 if handle_special_case(att
,delta
,res
,res2
,ischema
)==0 and msgElt
.flags()!=ldb
.FLAG_MOD_ADD
:
575 message(CHANGE
, "dn= "+str(dn
)+ " "+att
+ " with flag "+str(msgElt
.flags())+ " is not allowed to be changed/removed, I discard this change ...")
576 for e
in range(0,len(res2
[0][att
])):
577 message(CHANGE
,"old %d : %s"%(i
,str(res2
[0][att
][e
])))
578 if msgElt
.flags() == 2:
580 for e
in range(0,len(res
[0][att
])):
581 message(CHANGE
,"new %d : %s"%(i
,str(res
[0][att
][e
])))
584 if len(delta
.items()) >1:
585 attributes
=",".join(delta
.keys())
586 message(CHANGE
,"%s is different from the reference one, changed attributes: %s"%(dn
,attributes
))
587 changed
= changed
+ 1
588 sam_ldb
.modify(delta
)
590 sam_ldb
.transaction_commit()
591 message(SIMPLE
,"There are %d changed objects"%(changed))
594 # Check that SD are correct
595 def check_updated_sd(newpaths
,paths
,creds
,session
,names
):
596 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
597 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
598 res
= newsam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
599 res2
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
601 for i
in range(0,len(res
)):
602 hash_new
[str(res
[i
]["dn"]).lower()] = ndr_unpack(security
.descriptor
,str(res
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
604 for i
in range(0,len(res2
)):
605 key
= str(res2
[i
]["dn"]).lower()
606 if hash_new
.has_key(key
):
607 sddl
= ndr_unpack(security
.descriptor
,str(res2
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
608 if sddl
!= hash_new
[key
]:
609 print "%s new sddl/sddl in ref"%key
610 print "%s\n%s"%(sddl
,hash_new
[key
])
612 # Simple update method for updating the SD that rely on the fact that nobody should have modified the SD
613 # This assumption is safe right now (alpha9) but should be removed asap
614 def update_sd(paths
,creds
,session
,names
):
615 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
,options
=["modules:samba_dsdb"])
616 sam_ldb
.transaction_start()
617 # First update the SD for the rootdn
618 sam_ldb
.set_session_info(session
)
619 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
620 delta
= ldb
.Message()
621 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
622 descr
= get_domain_descriptor(names
.domainsid
)
623 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
624 sam_ldb
.modify(delta
,["recalculate_sd:0"])
626 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.configdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
627 delta
= ldb
.Message()
628 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
629 descr
= get_config_descriptor(names
.domainsid
)
630 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
631 sam_ldb
.modify(delta
,["recalculate_sd:0"])
633 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.schemadn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
634 delta
= ldb
.Message()
635 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
636 descr
= get_schema_descriptor(names
.domainsid
)
637 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
638 sam_ldb
.modify(delta
,["recalculate_sd:0"])
642 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
644 if not (str(obj
["dn"]) == str(names
.rootdn
) or
645 str(obj
["dn"]) == str(names
.configdn
) or \
646 str(obj
["dn"]) == str(names
.schemadn
)):
647 hash[str(obj
["dn"])] = obj
["whenCreated"]
649 listkeys
= hash.keys()
650 listkeys
.sort(dn_sort
)
654 delta
= ldb
.Message()
655 delta
.dn
= ldb
.Dn(sam_ldb
,key
)
656 delta
["whenCreated"] = ldb
.MessageElement( hash[key
],ldb
.FLAG_MOD_REPLACE
,"whenCreated" )
657 sam_ldb
.modify(delta
,["recalculate_sd:0"])
659 sam_ldb
.transaction_cancel()
660 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
661 print "bad stuff" +ndr_unpack(security
.descriptor
,str(res
[0]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
663 sam_ldb
.transaction_commit()
666 for root
, dirs
, files
in os
.walk(topdir
, topdown
=False):
668 os
.remove(os
.path
.join(root
, name
))
670 os
.rmdir(os
.path
.join(root
, name
))
674 def update_basesamdb(newpaths
,paths
,names
):
675 message(SIMPLE
,"Copy samdb")
676 shutil
.copy(newpaths
.samdb
,paths
.samdb
)
678 message(SIMPLE
,"Update partitions filename if needed")
679 schemaldb
=os
.path
.join(paths
.private_dir
,"schema.ldb")
680 configldb
=os
.path
.join(paths
.private_dir
,"configuration.ldb")
681 usersldb
=os
.path
.join(paths
.private_dir
,"users.ldb")
682 samldbdir
=os
.path
.join(paths
.private_dir
,"sam.ldb.d")
684 if not os
.path
.isdir(samldbdir
):
686 os
.chmod(samldbdir
,0700)
687 if os
.path
.isfile(schemaldb
):
688 shutil
.copy(schemaldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.schemadn
).upper()))
690 if os
.path
.isfile(usersldb
):
691 shutil
.copy(usersldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.rootdn
).upper()))
693 if os
.path
.isfile(configldb
):
694 shutil
.copy(configldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.configdn
).upper()))
697 def update_privilege(newpaths
,paths
):
698 message(SIMPLE
,"Copy privilege")
699 shutil
.copy(os
.path
.join(newpaths
.private_dir
,"privilege.ldb"),os
.path
.join(paths
.private_dir
,"privilege.ldb"))
701 # For each partition check the differences
702 def update_samdb(newpaths
,paths
,creds
,session
,names
):
704 message(SIMPLE
, "Doing schema update")
705 hashdef
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.schemadn
),names
,1)
706 message(SIMPLE
,"Done with schema update")
707 message(SIMPLE
,"Scanning whole provision for updates and additions")
708 hashSD
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.rootdn
),names
,0)
709 message(SIMPLE
,"Done with scanning")
711 def update_machine_account_password(paths
,creds
,session
,names
):
713 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
714 secrets_ldb
.transaction_start()
715 secrets_msg
= secrets_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=["secureChannelType"])
716 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
717 sam_ldb
.transaction_start()
718 if int(secrets_msg
[0]["secureChannelType"][0]) == SEC_CHAN_BDC
:
719 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=[])
720 assert(len(res
) == 1)
722 msg
= ldb
.Message(res
[0].dn
)
723 machinepass
= glue
.generate_random_str(12)
724 msg
["userPassword"] = ldb
.MessageElement(machinepass
, ldb
.FLAG_MOD_REPLACE
, "userPassword")
727 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
),
728 attrs
=["msDs-keyVersionNumber"])
729 assert(len(res
) == 1)
730 kvno
= int(str(res
[0]["msDs-keyVersionNumber"]))
732 secretsdb_self_join(secrets_ldb
, domain
=names
.domain
,
734 domainsid
=names
.domainsid
,
735 dnsdomain
=names
.dnsdomain
,
736 netbiosname
=names
.netbiosname
,
737 machinepass
=machinepass
,
738 key_version_number
=kvno
,
739 secure_channel_type
=int(secrets_msg
[0]["secureChannelType"][0]))
740 sam_ldb
.transaction_prepare_commit()
741 secrets_ldb
.transaction_prepare_commit()
742 sam_ldb
.transaction_commit()
743 secrets_ldb
.transaction_commit()
745 secrets_ldb
.transaction_cancel()
747 # From here start the big steps of the program
748 # First get files paths
749 paths
=get_paths(smbconf
=smbconf
)
750 paths
.setup
= setup_dir
751 def setup_path(file):
752 return os
.path
.join(setup_dir
, file)
753 # Guess all the needed names (variables in fact) from the current
755 names
= guess_names_from_current_provision(creds
,session
,paths
)
758 # With all this information let's create a fresh new provision used as reference
759 provisiondir
= newprovision(names
,setup_dir
,creds
,session
,smbconf
)
760 # Get file paths of this new provision
761 newpaths
= get_paths(targetdir
=provisiondir
)
762 populate_backlink(newpaths
,creds
,session
,names
.schemadn
)
763 # Check the difference
764 update_basesamdb(newpaths
,paths
,names
)
765 update_secrets(newpaths
,paths
,creds
,session
)
766 update_privilege(newpaths
,paths
)
767 update_machine_account_password(paths
,creds
,session
,names
)
770 update_samdb(newpaths
,paths
,creds
,session
,names
)
771 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
772 # to recreate them with the good form but with system account and then give the ownership to admin ...
773 admin_session_info
= admin_session(lp
, str(names
.domainsid
))
774 message(SIMPLE
,"Updating SD")
775 update_sd(paths
,creds
,session
,names
)
776 update_sd(paths
,creds
,admin_session_info
,names
)
777 check_updated_sd(newpaths
,paths
,creds
,session
,names
)
778 message(SIMPLE
,"Upgrade finished !")
779 # remove reference provision now that everything is done !