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()
565 while len(listMissing
) > 0:
568 empty
= ldb
.Message()
569 message(SIMPLE
,"There are still %d objects missing"%(len(listMissing
)))
571 for dn
in listMissing
:
572 reference
= newsam_ldb
.search(expression
="dn=%s" % (str(dn
)),
573 base
=basedn
, scope
=SCOPE_SUBTREE
,
574 controls
=["search_options:1:2"])
575 delta
= sam_ldb
.msg_diff(empty
,reference
[0])
576 for att
in hashAttrNotCopied
.keys():
578 for att
in backlinked
:
583 sam_ldb
.add(delta
,["relax:0"])
584 # This is needed here since otherwise the
585 # "replmd_meta_data" module doesn't see the
587 sam_ldb
.transaction_commit()
588 sam_ldb
.transaction_start()
589 except LdbError
, (num
, msg
):
590 # An exception can happen if a linked object
591 # doesn't exist which can happen if it is also
595 listMissing2
.append(dn
)
597 if len(listMissing2
) == len(listMissing
):
598 # We couldn't add any object in this iteration ->
599 # we have to resign and hope that the user manually
602 message(ERROR
, "The script isn't capable to do the upgrade fully automatically!")
603 message(ERROR
, "Often this happens when important system objects moved their location. Please look for them (for example doable using the displayed 'sAMAccountName' attribute), backup if personally changed and remove them.")
604 message(ERROR
, "Reinvoke this script and reapply eventual modifications done before. It is possible to get this error more than once (for each problematic object).")
606 raise LdbError(err_num
, err_msg
)
608 listMissing
= listMissing2
611 for dn
in listPresent
:
612 reference
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
613 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
614 if ((str(current
[0].dn
) != str(reference
[0].dn
)) and (str(current
[0].dn
).upper() == str(reference
[0].dn
).upper())):
615 message(CHANGE
,"Name are the same but case change, let's rename %s to %s"%(str(current
[0].dn
),str(reference
[0].dn
)))
616 identic_rename(sam_ldb
,reference
[0].dn
)
617 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
619 delta
= sam_ldb
.msg_diff(current
[0],reference
[0])
620 for att
in hashAttrNotCopied
.keys():
622 for att
in backlinked
:
624 delta
.remove("parentGUID")
628 msgElt
= delta
.get(att
)
634 if handle_security_desc(ischema
,att
,msgElt
,hashallSD
,current
,reference
):
637 if (not hashOverwrittenAtt
.has_key(att
) or not (hashOverwrittenAtt
.get(att
)&2^msgElt
.flags())):
638 if handle_special_case(att
,delta
,reference
,current
,ischema
)==0 and msgElt
.flags()!=ldb
.FLAG_MOD_ADD
:
641 message(CHANGE
, "dn= "+str(dn
)+ " "+att
+ " with flag "+str(msgElt
.flags())+ " is not allowed to be changed/removed, I discard this change ...")
642 for e
in range(0,len(current
[0][att
])):
643 message(CHANGE
,"old %d : %s"%(i
,str(current
[0][att
][e
])))
644 if msgElt
.flags() == 2:
646 for e
in range(0,len(reference
[0][att
])):
647 message(CHANGE
,"new %d : %s"%(i
,str(reference
[0][att
][e
])))
650 if len(delta
.items()) >1:
651 attributes
=",".join(delta
.keys())
652 message(CHANGE
,"%s is different from the reference one, changed attributes: %s"%(dn
,attributes
))
653 changed
= changed
+ 1
654 sam_ldb
.modify(delta
)
656 sam_ldb
.transaction_commit()
657 message(SIMPLE
,"There are %d changed objects"%(changed))
660 # Check that SD are correct
661 def check_updated_sd(newpaths
,paths
,creds
,session
,names
):
662 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
663 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
664 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
665 current
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
667 for i
in range(0,len(reference
)):
668 hash_new
[str(reference
[i
]["dn"]).lower()] = ndr_unpack(security
.descriptor
,str(reference
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
670 for i
in range(0,len(current
)):
671 key
= str(current
[i
]["dn"]).lower()
672 if hash_new
.has_key(key
):
673 sddl
= ndr_unpack(security
.descriptor
,str(current
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
674 if sddl
!= hash_new
[key
]:
675 print "%s new sddl/sddl in ref"%key
676 print "%s\n%s"%(sddl
,hash_new
[key
])
678 # Simple update method for updating the SD that rely on the fact that nobody
679 # should have modified the SD
680 # This assumption is safe right now (alpha9) but should be removed asap
681 def update_sd(paths
,creds
,session
,names
):
682 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
,options
=["modules:samba_dsdb"])
683 sam_ldb
.transaction_start()
684 # First update the SD for the rootdn
685 sam_ldb
.set_session_info(session
)
686 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
687 delta
= ldb
.Message()
688 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
689 descr
= get_domain_descriptor(names
.domainsid
)
690 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
691 sam_ldb
.modify(delta
,["recalculate_sd:0"])
693 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.configdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
694 delta
= ldb
.Message()
695 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
696 descr
= get_config_descriptor(names
.domainsid
)
697 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
698 sam_ldb
.modify(delta
,["recalculate_sd:0"])
700 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.schemadn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
701 delta
= ldb
.Message()
702 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
703 descr
= get_schema_descriptor(names
.domainsid
)
704 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
705 sam_ldb
.modify(delta
,["recalculate_sd:0"])
709 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
711 if not (str(obj
["dn"]) == str(names
.rootdn
) or
712 str(obj
["dn"]) == str(names
.configdn
) or \
713 str(obj
["dn"]) == str(names
.schemadn
)):
714 hash[str(obj
["dn"])] = obj
["whenCreated"]
716 listkeys
= hash.keys()
717 listkeys
.sort(dn_sort
)
721 delta
= ldb
.Message()
722 delta
.dn
= ldb
.Dn(sam_ldb
,key
)
723 delta
["whenCreated"] = ldb
.MessageElement( hash[key
],ldb
.FLAG_MOD_REPLACE
,"whenCreated" )
724 sam_ldb
.modify(delta
,["recalculate_sd:0"])
726 sam_ldb
.transaction_cancel()
727 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
728 print "bad stuff" +ndr_unpack(security
.descriptor
,str(res
[0]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
730 sam_ldb
.transaction_commit()
733 for root
, dirs
, files
in os
.walk(topdir
, topdown
=False):
735 os
.remove(os
.path
.join(root
, name
))
737 os
.rmdir(os
.path
.join(root
, name
))
741 def update_basesamdb(newpaths
,paths
,names
):
742 message(SIMPLE
,"Copy samdb")
743 shutil
.copy(newpaths
.samdb
,paths
.samdb
)
745 message(SIMPLE
,"Update partitions filename if needed")
746 schemaldb
=os
.path
.join(paths
.private_dir
,"schema.ldb")
747 configldb
=os
.path
.join(paths
.private_dir
,"configuration.ldb")
748 usersldb
=os
.path
.join(paths
.private_dir
,"users.ldb")
749 samldbdir
=os
.path
.join(paths
.private_dir
,"sam.ldb.d")
751 if not os
.path
.isdir(samldbdir
):
753 os
.chmod(samldbdir
,0700)
754 if os
.path
.isfile(schemaldb
):
755 shutil
.copy(schemaldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.schemadn
).upper()))
757 if os
.path
.isfile(usersldb
):
758 shutil
.copy(usersldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.rootdn
).upper()))
760 if os
.path
.isfile(configldb
):
761 shutil
.copy(configldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.configdn
).upper()))
764 def update_privilege(newpaths
,paths
):
765 message(SIMPLE
,"Copy privilege")
766 shutil
.copy(os
.path
.join(newpaths
.private_dir
,"privilege.ldb"),os
.path
.join(paths
.private_dir
,"privilege.ldb"))
768 # For each partition check the differences
769 def update_samdb(newpaths
,paths
,creds
,session
,names
):
771 message(SIMPLE
, "Doing schema update")
772 hashdef
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.schemadn
),names
,1)
773 message(SIMPLE
,"Done with schema update")
774 message(SIMPLE
,"Scanning whole provision for updates and additions")
775 hashSD
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.rootdn
),names
,0)
776 message(SIMPLE
,"Done with scanning")
778 def update_machine_account_password(paths
,creds
,session
,names
):
780 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
781 secrets_ldb
.transaction_start()
782 secrets_msg
= secrets_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=["secureChannelType"])
783 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
784 sam_ldb
.transaction_start()
785 if int(secrets_msg
[0]["secureChannelType"][0]) == SEC_CHAN_BDC
:
786 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=[])
787 assert(len(res
) == 1)
789 msg
= ldb
.Message(res
[0].dn
)
790 machinepass
= glue
.generate_random_str(12)
791 msg
["userPassword"] = ldb
.MessageElement(machinepass
, ldb
.FLAG_MOD_REPLACE
, "userPassword")
794 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
),
795 attrs
=["msDs-keyVersionNumber"])
796 assert(len(res
) == 1)
797 kvno
= int(str(res
[0]["msDs-keyVersionNumber"]))
799 secretsdb_self_join(secrets_ldb
, domain
=names
.domain
,
801 domainsid
=names
.domainsid
,
802 dnsdomain
=names
.dnsdomain
,
803 netbiosname
=names
.netbiosname
,
804 machinepass
=machinepass
,
805 key_version_number
=kvno
,
806 secure_channel_type
=int(secrets_msg
[0]["secureChannelType"][0]))
807 sam_ldb
.transaction_prepare_commit()
808 secrets_ldb
.transaction_prepare_commit()
809 sam_ldb
.transaction_commit()
810 secrets_ldb
.transaction_commit()
812 secrets_ldb
.transaction_cancel()
814 # From here start the big steps of the program
815 # First get files paths
816 paths
=get_paths(smbconf
=smbconf
)
817 paths
.setup
= setup_dir
818 def setup_path(file):
819 return os
.path
.join(setup_dir
, file)
820 # Guess all the needed names (variables in fact) from the current
822 names
= guess_names_from_current_provision(creds
,session
,paths
)
825 # With all this information let's create a fresh new provision used as reference
826 provisiondir
= newprovision(names
,setup_dir
,creds
,session
,smbconf
)
827 # Get file paths of this new provision
828 newpaths
= get_paths(targetdir
=provisiondir
)
829 populate_backlink(newpaths
,creds
,session
,names
.schemadn
)
830 # Check the difference
831 update_basesamdb(newpaths
,paths
,names
)
834 update_samdb(newpaths
,paths
,creds
,session
,names
)
835 update_secrets(newpaths
,paths
,creds
,session
)
836 update_privilege(newpaths
,paths
)
837 update_machine_account_password(paths
,creds
,session
,names
)
838 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
839 # to recreate them with the good form but with system account and then give the ownership to admin ...
840 admin_session_info
= admin_session(lp
, str(names
.domainsid
))
841 message(SIMPLE
,"Updating SD")
842 update_sd(paths
,creds
,session
,names
)
843 update_sd(paths
,creds
,admin_session_info
,names
)
844 check_updated_sd(newpaths
,paths
,creds
,session
,names
)
845 message(SIMPLE
,"Upgrade finished !")
846 # remove reference provision now that everything is done !