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 are never copied from the reference provision (even if they
70 # do not exist in the destination object).
71 # This is most probably because they are populated automatcally when object is
73 hashAttrNotCopied
= { "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,\
74 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,\
75 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,\
76 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,\
77 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,\
78 "maxPwdAge":1, "mail":1, "secret":1,"possibleInferiors":1, "sAMAccountType":1}
80 # Usually for an object that already exists we do not overwrite attributes as
81 # they might have been changed for good reasons. Anyway for a few of them it's
82 # mandatory to replace them otherwise the provision will be broken somehow.
83 hashOverwrittenAtt
= { "prefixMap": replace
, "systemMayContain": replace
,"systemOnly":replace
, "searchFlags":replace
,\
84 "mayContain":replace
, "systemFlags":replace
,"description":replace
,
85 "oEMInformation":replace
, "operatingSystemVersion":replace
, "adminPropertyPages":replace
,
86 "defaultSecurityDescriptor": replace
,"wellKnownObjects":replace
,"privilege":delete
}
89 def define_what_to_log(opts
):
93 if opts
.debugchangesd
:
94 what
= what | CHANGESD
97 if opts
.debugprovision
:
98 what
= what | PROVISION
100 what
= what | CHANGEALL
104 parser
= optparse
.OptionParser("provision [options]")
105 sambaopts
= options
.SambaOptions(parser
)
106 parser
.add_option_group(sambaopts
)
107 parser
.add_option_group(options
.VersionOptions(parser
))
108 credopts
= options
.CredentialsOptions(parser
)
109 parser
.add_option_group(credopts
)
110 parser
.add_option("--setupdir", type="string", metavar
="DIR",
111 help="directory with setup files")
112 parser
.add_option("--debugprovision", help="Debug provision", action
="store_true")
113 parser
.add_option("--debugguess", help="Print information on what is different but won't be changed", action
="store_true")
114 parser
.add_option("--debugchange", help="Print information on what is different but won't be changed", action
="store_true")
115 parser
.add_option("--debugchangesd", help="Print information security descriptors differences", action
="store_true")
116 parser
.add_option("--debugall", help="Print all available information (very verbose)", action
="store_true")
117 parser
.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action
="store_true")
119 opts
= parser
.parse_args()[0]
121 whatToLog
= define_what_to_log(opts
)
123 def messageprovision(text
):
124 """print a message if quiet is not set."""
125 if opts
.debugprovision
or opts
.debugall
:
128 def message(what
,text
):
129 """print a message if quiet is not set."""
130 if (whatToLog
& what
) or (what
<= 0 ):
133 if len(sys
.argv
) == 1:
134 opts
.interactive
= True
135 lp
= sambaopts
.get_loadparm()
136 smbconf
= lp
.configfile
138 creds
= credopts
.get_credentials(lp
)
139 creds
.set_kerberos_state(DONT_USE_KERBEROS
)
140 setup_dir
= opts
.setupdir
141 if setup_dir
is None:
142 setup_dir
= find_setup_dir()
144 session
= system_session()
146 # simple helper to allow back and forth rename
147 def identic_rename(ldbobj
,dn
):
148 (before
,sep
,after
)=str(dn
).partition('=')
149 ldbobj
.rename(dn
,ldb
.Dn(ldbobj
,"%s=foo%s"%(before
,after
)))
150 ldbobj
.rename(ldb
.Dn(ldbobj
,"%s=foo%s"%(before
,after
)),dn
)
152 # Create an array of backlinked attributes
153 def populate_backlink(newpaths
,creds
,session
,schemadn
):
154 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
155 backlinked
.extend(get_linked_attributes(ldb
.Dn(newsam_ldb
,str(schemadn
)),newsam_ldb
).values())
157 # Get Paths for important objects (ldb, keytabs ...)
158 def get_paths(targetdir
=None,smbconf
=None):
159 if targetdir
is not None:
160 if (not os
.path
.exists(os
.path
.join(targetdir
, "etc"))):
161 os
.makedirs(os
.path
.join(targetdir
, "etc"))
162 smbconf
= os
.path
.join(targetdir
, "etc", "smb.conf")
164 smbconf
= param
.default_path()
166 if not os
.path
.exists(smbconf
):
167 message(ERROR
,"Unable to find smb.conf ..")
171 lp
= param
.LoadParm()
173 # Normally we need the domain name for this function but for our needs it's
175 paths
= provision_paths_from_lp(lp
,"foo")
178 # This function guesses (fetches) informations needed to make a fresh provision
179 # from the current provision
180 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
181 def guess_names_from_current_provision(credentials
,session_info
,paths
):
182 lp
= param
.LoadParm()
183 lp
.load(paths
.smbconf
)
184 names
= ProvisionNames()
185 # NT domain, kerberos realm, root dn, domain dn, domain dns name
186 names
.domain
= string
.upper(lp
.get("workgroup"))
187 names
.realm
= lp
.get("realm")
188 basedn
= "DC=" + names
.realm
.replace(".",",DC=")
189 names
.dnsdomain
= names
.realm
190 names
.realm
= string
.upper(names
.realm
)
192 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session_info
, credentials
=credentials
,lp
=lp
, options
=["modules:samba_secrets"])
193 # Get the netbiosname first (could be obtained from smb.conf in theory)
194 attrs
= ["sAMAccountName"]
195 res
= secrets_ldb
.search(expression
="(flatname=%s)"%names
.domain
,base
="CN=Primary Domains", scope
=SCOPE_SUBTREE
, attrs
=attrs
)
196 names
.netbiosname
= str(res
[0]["sAMAccountName"]).replace("$","")
198 names
.smbconf
= smbconf
199 # It's important here to let ldb load with the old module or it's quite
200 # certain that the LDB won't load ...
201 samdb
= Ldb(paths
.samdb
, session_info
=session_info
,
202 credentials
=credentials
, lp
=lp
, options
=["modules:samba_dsdb"])
204 # That's a bit simplistic but it's ok as long as we have only 3
206 attrs2
= ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
207 current
= samdb
.search(expression
="(objectClass=*)",base
="", scope
=SCOPE_BASE
, attrs
=attrs2
)
209 names
.configdn
= current
[0]["configurationNamingContext"]
210 configdn
= str(names
.configdn
)
211 names
.schemadn
= current
[0]["schemaNamingContext"]
212 if not (ldb
.Dn(samdb
, basedn
) == (ldb
.Dn(samdb
, current
[0]["defaultNamingContext"][0]))):
213 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths
.samdb
, str(current
[0]["defaultNamingContext"][0]), paths
.smbconf
, basedn
)))
215 names
.domaindn
=current
[0]["defaultNamingContext"]
216 names
.rootdn
=current
[0]["rootDomainNamingContext"]
219 res3
= samdb
.search(expression
="(objectClass=*)",base
="CN=Sites,"+configdn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs3
)
220 names
.sitename
= str(res3
[0]["cn"])
222 # dns hostname and server dn
223 attrs4
= ["dNSHostName"]
224 res4
= samdb
.search(expression
="(CN=%s)"%names
.netbiosname
,base
="OU=Domain Controllers,"+basedn
, \
225 scope
=SCOPE_ONELEVEL
, attrs
=attrs4
)
226 names
.hostname
= str(res4
[0]["dNSHostName"]).replace("."+names
.dnsdomain
,"")
228 server_res
= samdb
.search(expression
="serverReference=%s"%res4[0].dn
, attrs
=[], base
=configdn
)
229 names
.serverdn
= server_res
[0].dn
231 # invocation id/objectguid
232 res5
= samdb
.search(expression
="(objectClass=*)",base
="CN=NTDS Settings,%s" % str(names
.serverdn
), scope
=SCOPE_BASE
, attrs
=["invocationID","objectGUID"])
233 names
.invocation
= str(ndr_unpack( misc
.GUID
,res5
[0]["invocationId"][0]))
234 names
.ntdsguid
= str(ndr_unpack( misc
.GUID
,res5
[0]["objectGUID"][0]))
237 attrs6
= ["objectGUID", "objectSid","msDS-Behavior-Version" ]
238 res6
= samdb
.search(expression
="(objectClass=*)",base
=basedn
, scope
=SCOPE_BASE
, attrs
=attrs6
)
239 names
.domainguid
= str(ndr_unpack( misc
.GUID
,res6
[0]["objectGUID"][0]))
240 names
.domainsid
= ndr_unpack( security
.dom_sid
,res6
[0]["objectSid"][0])
241 if res6
[0].get("msDS-Behavior-Version") == None or int(res6
[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000
:
242 names
.domainlevel
= DS_DOMAIN_FUNCTION_2000
244 names
.domainlevel
= int(res6
[0]["msDS-Behavior-Version"][0])
247 attrs7
= ["cn","displayName"]
248 res7
= samdb
.search(expression
="(displayName=Default Domain Policy)",base
="CN=Policies,CN=System,"+basedn
, \
249 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
250 names
.policyid
= str(res7
[0]["cn"]).replace("{","").replace("}","")
252 attrs8
= ["cn","displayName"]
253 res8
= samdb
.search(expression
="(displayName=Default Domain Controllers Policy)",base
="CN=Policies,CN=System,"+basedn
, \
254 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
256 names
.policyid_dc
= str(res8
[0]["cn"]).replace("{","").replace("}","")
258 names
.policyid_dc
= None
264 def print_names(names
):
265 message(GUESS
, "rootdn :"+str(names
.rootdn
))
266 message(GUESS
, "configdn :"+str(names
.configdn
))
267 message(GUESS
, "schemadn :"+str(names
.schemadn
))
268 message(GUESS
, "serverdn :"+str(names
.serverdn
))
269 message(GUESS
, "netbiosname :"+names
.netbiosname
)
270 message(GUESS
, "defaultsite :"+names
.sitename
)
271 message(GUESS
, "dnsdomain :"+names
.dnsdomain
)
272 message(GUESS
, "hostname :"+names
.hostname
)
273 message(GUESS
, "domain :"+names
.domain
)
274 message(GUESS
, "realm :"+names
.realm
)
275 message(GUESS
, "invocationid:"+names
.invocation
)
276 message(GUESS
, "policyguid :"+names
.policyid
)
277 message(GUESS
, "policyguiddc:"+str(names
.policyid_dc
))
278 message(GUESS
, "domainsid :"+str(names
.domainsid
))
279 message(GUESS
, "domainguid :"+names
.domainguid
)
280 message(GUESS
, "ntdsguid :"+names
.ntdsguid
)
281 message(GUESS
, "domainlevel :"+str(names
.domainlevel
))
283 # Create a fresh new reference provision
284 # This provision will be the reference for knowing what has changed in the
285 # since the latest upgrade in the current provision
286 def newprovision(names
,setup_dir
,creds
,session
,smbconf
):
287 message(SIMPLE
, "Creating a reference provision")
288 provdir
=tempfile
.mkdtemp(dir=paths
.private_dir
, prefix
="referenceprovision")
289 if os
.path
.isdir(provdir
):
291 logstd
=os
.path
.join(provdir
,"log.std")
292 os
.chdir(os
.path
.join(setup_dir
,".."))
295 sys
.stderr
= open("%s/provision.log"%provdir
, "w")
296 message(PROVISION
, "Reference provision stored in %s"%provdir
)
297 message(PROVISION
, "STDERR message of provision will be logged in %s/provision.log"%provdir
)
298 sys
.stderr
= open("/dev/stdout", "w")
299 provision(setup_dir
, messageprovision
,
300 session
, creds
, smbconf
=smbconf
, targetdir
=provdir
,
301 samdb_fill
=FILL_FULL
, realm
=names
.realm
, domain
=names
.domain
,
302 domainguid
=names
.domainguid
, domainsid
=str(names
.domainsid
),ntdsguid
=names
.ntdsguid
,
303 policyguid
=names
.policyid
,policyguid_dc
=names
.policyid_dc
,hostname
=names
.netbiosname
,
304 hostip
=None, hostip6
=None,
305 invocationid
=names
.invocation
, adminpass
=None,
306 krbtgtpass
=None, machinepass
=None,
307 dnspass
=None, root
=None, nobody
=None,
308 wheel
=None, users
=None,
309 serverrole
="domain controller",
310 ldap_backend_extra_port
=None,
317 dom_for_fun_level
=names
.domainlevel
,
318 ldap_dryrun_mode
=None)
321 # This function sorts two DNs in the lexicographical order and put higher level
323 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller
324 # (-1) as it has less level
326 p
= re
.compile(r
'(?<!\\),')
327 tab1
= p
.split(str(x
))
328 tab2
= p
.split(str(y
))
330 if (len(tab1
) > len(tab2
)):
332 elif (len(tab1
) < len(tab2
)):
339 # Note: python range go up to upper limit but do not include it
340 for i
in range(0,min):
341 ret
=cmp(tab1
[len1
-i
],tab2
[len2
-i
])
347 message(ERROR
,"PB PB PB"+space
.join(tab1
)+" / "+space
.join(tab2
))
354 # Check for security descriptors modifications return 1 if it is and 0 otherwise
355 # it also populate hash structure for later use in the upgrade process
356 def handle_security_desc(ischema
,att
,msgElt
,hashallSD
,old
,new
):
357 if ischema
== 1 and att
== "defaultSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
359 hashSD
["oldSD"] = old
[0][att
]
360 hashSD
["newSD"] = new
[0][att
]
361 hashallSD
[str(old
[0].dn
)] = hashSD
363 if att
== "nTSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
366 hashSD
["oldSD"] = ndr_unpack(security
.descriptor
,str(old
[0][att
]))
367 hashSD
["newSD"] = ndr_unpack(security
.descriptor
,str(new
[0][att
]))
368 hashallSD
[str(old
[0].dn
)] = hashSD
372 # Handle special cases ... That's when we want to update a particular attribute
373 # only, e.g. if it has a certain value or if it's for a certain object or
375 # It can be also if we want to do a merge of value instead of a simple replace
376 def handle_special_case(att
,delta
,new
,old
,ischema
):
377 flag
= delta
.get(att
).flags()
378 if (att
== "gPLink" or att
== "gPCFileSysPath") and flag
== ldb
.FLAG_MOD_REPLACE
and str(new
[0].dn
).lower() == str(old
[0].dn
).lower():
381 if att
== "forceLogoff":
382 ref
=0x8000000000000000
383 oldval
=int(old
[0][att
][0])
384 newval
=int(new
[0][att
][0])
385 ref
== old
and ref
== abs(new
)
387 if (att
== "adminDisplayName" or att
== "adminDescription") and ischema
:
389 if (str(old
[0].dn
) == "CN=Samba4-Local-Domain,%s"%(str(names
.schemadn
)) and att
== "defaultObjectCategory" and flag
== ldb
.FLAG_MOD_REPLACE
):
391 # if (str(old[0].dn) == "CN=S-1-5-11,CN=ForeignSecurityPrincipals,%s"%(str(names.rootdn)) and att == "description" and flag == ldb.FLAG_MOD_DELETE):
393 if (str(old
[0].dn
) == "CN=Title,%s"%(str(names
.schemadn
)) and att
== "rangeUpper" and flag
== ldb
.FLAG_MOD_REPLACE
):
395 if ( (att
== "member" or att
== "servicePrincipalName") and flag
== ldb
.FLAG_MOD_REPLACE
):
400 for elem
in old
[0][att
]:
402 newval
.append(str(elem
))
404 for elem
in new
[0][att
]:
405 if not hash.has_key(str(elem
)):
407 newval
.append(str(elem
))
409 delta
[att
] = ldb
.MessageElement(newval
, ldb
.FLAG_MOD_REPLACE
, att
)
413 if (str(old
[0].dn
) == "%s"%(str(names
.rootdn
)) and att
== "subRefs" and flag
== ldb
.FLAG_MOD_REPLACE
):
415 if str(delta
.dn
).endswith("CN=DisplaySpecifiers,%s"%names
.configdn
):
419 def update_secrets(newpaths
,paths
,creds
,session
):
420 message(SIMPLE
,"update secrets.ldb")
421 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
422 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_secrets"])
423 reference
= newsecrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
424 current
= secrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
425 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
426 delta
.dn
= current
[0].dn
427 secrets_ldb
.modify(delta
)
429 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
430 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
431 reference
= newsecrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
432 current
= secrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
438 empty
= ldb
.Message()
439 for i
in range(0,len(reference
)):
440 hash_new
[str(reference
[i
]["dn"]).lower()] = reference
[i
]["dn"]
442 # Create a hash for speeding the search of existing object in the
444 for i
in range(0,len(current
)):
445 hash[str(current
[i
]["dn"]).lower()] = current
[i
]["dn"]
447 for k
in hash_new
.keys():
448 if not hash.has_key(k
):
449 listMissing
.append(hash_new
[k
])
451 listPresent
.append(hash_new
[k
])
452 for entry
in listMissing
:
453 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
454 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
455 delta
= secrets_ldb
.msg_diff(empty
,reference
[0])
456 for att
in hashAttrNotCopied
.keys():
458 message(CHANGE
,"Entry %s is missing from secrets.ldb"%reference
[0].dn
)
460 message(CHANGE
," Adding attribute %s"%att
)
461 delta
.dn
= reference
[0].dn
462 secrets_ldb
.add(delta
)
464 for entry
in listPresent
:
465 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
466 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
467 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
469 for att
in hashAttrNotCopied
.keys():
475 message(CHANGE
,"Found attribute name on %s, must rename the DN "%(current
[0].dn
))
476 identic_rename(secrets_ldb
,reference
[0].dn
)
481 for entry
in listPresent
:
482 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
483 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
484 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
486 for att
in hashAttrNotCopied
.keys():
491 message(CHANGE
," Adding/Changing attribute %s to %s"%(att
,current
[0].dn
))
493 delta
.dn
= current
[0].dn
494 secrets_ldb
.modify(delta
)
497 # Check difference between the current provision and the reference provision.
498 # It looks for all objects which base DN is name. If ischema is "false" then
499 # the scan is done in cross partition mode.
500 # If "ischema" is true, then special handling is done for dealing with schema
501 def check_diff_name(newpaths
,paths
,creds
,session
,basedn
,names
,ischema
):
509 # Connect to the reference provision and get all the attribute in the
510 # partition referred by name
511 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
512 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
513 sam_ldb
.transaction_start()
515 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
516 current
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
518 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
519 current
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
521 sam_ldb
.transaction_commit()
522 # Create a hash for speeding the search of new object
523 for i
in range(0,len(reference
)):
524 hash_new
[str(reference
[i
]["dn"]).lower()] = reference
[i
]["dn"]
526 # Create a hash for speeding the search of existing object in the
528 for i
in range(0,len(current
)):
529 hash[str(current
[i
]["dn"]).lower()] = current
[i
]["dn"]
531 for k
in hash_new
.keys():
532 if not hash.has_key(k
):
533 listMissing
.append(hash_new
[k
])
535 listPresent
.append(hash_new
[k
])
537 # Sort the missing object in order to have object of the lowest level
538 # first (which can be containers for higher level objects)
539 listMissing
.sort(dn_sort
)
540 listPresent
.sort(dn_sort
)
543 # The following lines (up to the for loop) is to load the up to
544 # date schema into our current LDB
545 # a complete schema is needed as the insertion of attributes
546 # and class is done against it
547 # and the schema is self validated
548 # The double ldb open and schema validation is taken from the
549 # initial provision script
550 # it's not certain that it is really needed ....
551 sam_ldb
= Ldb(session_info
=session
, credentials
=creds
, lp
=lp
)
552 schema
= Schema(setup_path
, names
.domainsid
, schemadn
=basedn
, serverdn
=str(names
.serverdn
))
553 # Load the schema from the one we computed earlier
554 sam_ldb
.set_schema_from_ldb(schema
.ldb
)
555 # And now we can connect to the DB - the schema won't be loaded
557 sam_ldb
.connect(paths
.samdb
)
559 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
561 sam_ldb
.transaction_start()
563 empty
= ldb
.Message()
564 message(SIMPLE
,"There are %d missing objects"%(len(listMissing
)))
565 for dn
in listMissing
:
566 reference
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
567 delta
= sam_ldb
.msg_diff(empty
,reference
[0])
568 for att
in hashAttrNotCopied
.keys():
570 for att
in backlinked
:
574 sam_ldb
.add(delta
,["relax:0"])
577 for dn
in listPresent
:
578 reference
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
579 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
580 if ((str(current
[0].dn
) != str(reference
[0].dn
)) and (str(current
[0].dn
).upper() == str(reference
[0].dn
).upper())):
581 message(CHANGE
,"Name are the same but case change, let's rename %s to %s"%(str(current
[0].dn
),str(reference
[0].dn
)))
582 identic_rename(sam_ldb
,reference
[0].dn
)
583 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
585 delta
= sam_ldb
.msg_diff(current
[0],reference
[0])
586 for att
in hashAttrNotCopied
.keys():
588 for att
in backlinked
:
590 delta
.remove("parentGUID")
594 msgElt
= delta
.get(att
)
600 if handle_security_desc(ischema
,att
,msgElt
,hashallSD
,current
,reference
):
603 if (not hashOverwrittenAtt
.has_key(att
) or not (hashOverwrittenAtt
.get(att
)&2^msgElt
.flags())):
604 if handle_special_case(att
,delta
,reference
,current
,ischema
)==0 and msgElt
.flags()!=ldb
.FLAG_MOD_ADD
:
607 message(CHANGE
, "dn= "+str(dn
)+ " "+att
+ " with flag "+str(msgElt
.flags())+ " is not allowed to be changed/removed, I discard this change ...")
608 for e
in range(0,len(current
[0][att
])):
609 message(CHANGE
,"old %d : %s"%(i
,str(current
[0][att
][e
])))
610 if msgElt
.flags() == 2:
612 for e
in range(0,len(reference
[0][att
])):
613 message(CHANGE
,"new %d : %s"%(i
,str(reference
[0][att
][e
])))
616 if len(delta
.items()) >1:
617 attributes
=",".join(delta
.keys())
618 message(CHANGE
,"%s is different from the reference one, changed attributes: %s"%(dn
,attributes
))
619 changed
= changed
+ 1
620 sam_ldb
.modify(delta
)
622 sam_ldb
.transaction_commit()
623 message(SIMPLE
,"There are %d changed objects"%(changed))
626 # Check that SD are correct
627 def check_updated_sd(newpaths
,paths
,creds
,session
,names
):
628 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
629 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
630 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
631 current
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
633 for i
in range(0,len(reference
)):
634 hash_new
[str(reference
[i
]["dn"]).lower()] = ndr_unpack(security
.descriptor
,str(reference
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
636 for i
in range(0,len(current
)):
637 key
= str(current
[i
]["dn"]).lower()
638 if hash_new
.has_key(key
):
639 sddl
= ndr_unpack(security
.descriptor
,str(current
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
640 if sddl
!= hash_new
[key
]:
641 print "%s new sddl/sddl in ref"%key
642 print "%s\n%s"%(sddl
,hash_new
[key
])
644 # Simple update method for updating the SD that rely on the fact that nobody
645 # should have modified the SD
646 # This assumption is safe right now (alpha9) but should be removed asap
647 def update_sd(paths
,creds
,session
,names
):
648 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
,options
=["modules:samba_dsdb"])
649 sam_ldb
.transaction_start()
650 # First update the SD for the rootdn
651 sam_ldb
.set_session_info(session
)
652 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
653 delta
= ldb
.Message()
654 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
655 descr
= get_domain_descriptor(names
.domainsid
)
656 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
657 sam_ldb
.modify(delta
,["recalculate_sd:0"])
659 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.configdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
660 delta
= ldb
.Message()
661 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
662 descr
= get_config_descriptor(names
.domainsid
)
663 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
664 sam_ldb
.modify(delta
,["recalculate_sd:0"])
666 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.schemadn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
667 delta
= ldb
.Message()
668 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
669 descr
= get_schema_descriptor(names
.domainsid
)
670 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
671 sam_ldb
.modify(delta
,["recalculate_sd:0"])
675 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
677 if not (str(obj
["dn"]) == str(names
.rootdn
) or
678 str(obj
["dn"]) == str(names
.configdn
) or \
679 str(obj
["dn"]) == str(names
.schemadn
)):
680 hash[str(obj
["dn"])] = obj
["whenCreated"]
682 listkeys
= hash.keys()
683 listkeys
.sort(dn_sort
)
687 delta
= ldb
.Message()
688 delta
.dn
= ldb
.Dn(sam_ldb
,key
)
689 delta
["whenCreated"] = ldb
.MessageElement( hash[key
],ldb
.FLAG_MOD_REPLACE
,"whenCreated" )
690 sam_ldb
.modify(delta
,["recalculate_sd:0"])
692 sam_ldb
.transaction_cancel()
693 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
694 print "bad stuff" +ndr_unpack(security
.descriptor
,str(res
[0]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
696 sam_ldb
.transaction_commit()
699 for root
, dirs
, files
in os
.walk(topdir
, topdown
=False):
701 os
.remove(os
.path
.join(root
, name
))
703 os
.rmdir(os
.path
.join(root
, name
))
707 def update_basesamdb(newpaths
,paths
,names
):
708 message(SIMPLE
,"Copy samdb")
709 shutil
.copy(newpaths
.samdb
,paths
.samdb
)
711 message(SIMPLE
,"Update partitions filename if needed")
712 schemaldb
=os
.path
.join(paths
.private_dir
,"schema.ldb")
713 configldb
=os
.path
.join(paths
.private_dir
,"configuration.ldb")
714 usersldb
=os
.path
.join(paths
.private_dir
,"users.ldb")
715 samldbdir
=os
.path
.join(paths
.private_dir
,"sam.ldb.d")
717 if not os
.path
.isdir(samldbdir
):
719 os
.chmod(samldbdir
,0700)
720 if os
.path
.isfile(schemaldb
):
721 shutil
.copy(schemaldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.schemadn
).upper()))
723 if os
.path
.isfile(usersldb
):
724 shutil
.copy(usersldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.rootdn
).upper()))
726 if os
.path
.isfile(configldb
):
727 shutil
.copy(configldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.configdn
).upper()))
730 def update_privilege(newpaths
,paths
):
731 message(SIMPLE
,"Copy privilege")
732 shutil
.copy(os
.path
.join(newpaths
.private_dir
,"privilege.ldb"),os
.path
.join(paths
.private_dir
,"privilege.ldb"))
734 # For each partition check the differences
735 def update_samdb(newpaths
,paths
,creds
,session
,names
):
737 message(SIMPLE
, "Doing schema update")
738 hashdef
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.schemadn
),names
,1)
739 message(SIMPLE
,"Done with schema update")
740 message(SIMPLE
,"Scanning whole provision for updates and additions")
741 hashSD
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.rootdn
),names
,0)
742 message(SIMPLE
,"Done with scanning")
744 def update_machine_account_password(paths
,creds
,session
,names
):
746 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
747 secrets_ldb
.transaction_start()
748 secrets_msg
= secrets_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=["secureChannelType"])
749 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
750 sam_ldb
.transaction_start()
751 if int(secrets_msg
[0]["secureChannelType"][0]) == SEC_CHAN_BDC
:
752 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=[])
753 assert(len(res
) == 1)
755 msg
= ldb
.Message(res
[0].dn
)
756 machinepass
= glue
.generate_random_str(12)
757 msg
["userPassword"] = ldb
.MessageElement(machinepass
, ldb
.FLAG_MOD_REPLACE
, "userPassword")
760 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
),
761 attrs
=["msDs-keyVersionNumber"])
762 assert(len(res
) == 1)
763 kvno
= int(str(res
[0]["msDs-keyVersionNumber"]))
765 secretsdb_self_join(secrets_ldb
, domain
=names
.domain
,
767 domainsid
=names
.domainsid
,
768 dnsdomain
=names
.dnsdomain
,
769 netbiosname
=names
.netbiosname
,
770 machinepass
=machinepass
,
771 key_version_number
=kvno
,
772 secure_channel_type
=int(secrets_msg
[0]["secureChannelType"][0]))
773 sam_ldb
.transaction_prepare_commit()
774 secrets_ldb
.transaction_prepare_commit()
775 sam_ldb
.transaction_commit()
776 secrets_ldb
.transaction_commit()
778 secrets_ldb
.transaction_cancel()
780 # From here start the big steps of the program
781 # First get files paths
782 paths
=get_paths(smbconf
=smbconf
)
783 paths
.setup
= setup_dir
784 def setup_path(file):
785 return os
.path
.join(setup_dir
, file)
786 # Guess all the needed names (variables in fact) from the current
788 names
= guess_names_from_current_provision(creds
,session
,paths
)
791 # With all this information let's create a fresh new provision used as reference
792 provisiondir
= newprovision(names
,setup_dir
,creds
,session
,smbconf
)
793 # Get file paths of this new provision
794 newpaths
= get_paths(targetdir
=provisiondir
)
795 populate_backlink(newpaths
,creds
,session
,names
.schemadn
)
796 # Check the difference
797 update_basesamdb(newpaths
,paths
,names
)
800 update_samdb(newpaths
,paths
,creds
,session
,names
)
801 update_secrets(newpaths
,paths
,creds
,session
)
802 update_privilege(newpaths
,paths
)
803 update_machine_account_password(paths
,creds
,session
,names
)
804 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
805 # to recreate them with the good form but with system account and then give the ownership to admin ...
806 admin_session_info
= admin_session(lp
, str(names
.domainsid
))
807 message(SIMPLE
,"Updating SD")
808 update_sd(paths
,creds
,session
,names
)
809 update_sd(paths
,creds
,admin_session_info
,names
)
810 check_updated_sd(newpaths
,paths
,creds
,session
,names
)
811 message(SIMPLE
,"Upgrade finished !")
812 # remove reference provision now that everything is done !