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
,"description":replace
,
82 "oEMInformation":replace
, "operatingSystemVersion":replace
, "adminPropertyPages":replace
,
83 "defaultSecurityDescriptor": replace
,"wellKnownObjects":replace
,"privilege":delete
}
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 # simple helper to allow back and forth rename
144 def identic_rename(ldbobj
,dn
):
145 (before
,sep
,after
)=str(dn
).partition('=')
146 ldbobj
.rename(dn
,ldb
.Dn(ldbobj
,"%s=foo%s"%(before
,after
)))
147 ldbobj
.rename(ldb
.Dn(ldbobj
,"%s=foo%s"%(before
,after
)),dn
)
149 # Create an array of backlinked attributes
150 def populate_backlink(newpaths
,creds
,session
,schemadn
):
151 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
152 backlinked
.extend(get_linked_attributes(ldb
.Dn(newsam_ldb
,str(schemadn
)),newsam_ldb
).values())
154 # Get Paths for important objects (ldb, keytabs ...)
155 def get_paths(targetdir
=None,smbconf
=None):
156 if targetdir
is not None:
157 if (not os
.path
.exists(os
.path
.join(targetdir
, "etc"))):
158 os
.makedirs(os
.path
.join(targetdir
, "etc"))
159 smbconf
= os
.path
.join(targetdir
, "etc", "smb.conf")
161 smbconf
= param
.default_path()
163 if not os
.path
.exists(smbconf
):
164 message(ERROR
,"Unable to find smb.conf ..")
168 lp
= param
.LoadParm()
170 # Normaly we need the domain name for this function but for our needs it's pointless
171 paths
= provision_paths_from_lp(lp
,"foo")
174 # This function guess(fetch) informations needed to make a fresh provision from the current provision
175 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
176 def guess_names_from_current_provision(credentials
,session_info
,paths
):
177 lp
= param
.LoadParm()
178 lp
.load(paths
.smbconf
)
179 names
= ProvisionNames()
180 # NT domain, kerberos realm, root dn, domain dn, domain dns name
181 names
.domain
= string
.upper(lp
.get("workgroup"))
182 names
.realm
= lp
.get("realm")
183 basedn
= "DC=" + names
.realm
.replace(".",",DC=")
184 names
.dnsdomain
= names
.realm
185 names
.realm
= string
.upper(names
.realm
)
187 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session_info
, credentials
=credentials
,lp
=lp
, options
=["modules:samba_secrets"])
188 # Get the netbiosname first (could be obtained from smb.conf in theory)
189 attrs
= ["sAMAccountName"]
190 res
= secrets_ldb
.search(expression
="(flatname=%s)"%names
.domain
,base
="CN=Primary Domains", scope
=SCOPE_SUBTREE
, attrs
=attrs
)
191 names
.netbiosname
= str(res
[0]["sAMAccountName"]).replace("$","")
193 names
.smbconf
= smbconf
194 #It's important here to let ldb load with the old module or it's quite certain that the LDB won't load ...
195 samdb
= Ldb(paths
.samdb
, session_info
=session_info
,
196 credentials
=credentials
, lp
=lp
, options
=["modules:samba_dsdb"])
198 # That's a bit simplistic but it's ok as long as we have only 3 partitions
199 attrs2
= ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
200 current
= samdb
.search(expression
="(objectClass=*)",base
="", scope
=SCOPE_BASE
, attrs
=attrs2
)
202 names
.configdn
= current
[0]["configurationNamingContext"]
203 configdn
= str(names
.configdn
)
204 names
.schemadn
= current
[0]["schemaNamingContext"]
205 if not (ldb
.Dn(samdb
, basedn
) == (ldb
.Dn(samdb
, current
[0]["defaultNamingContext"][0]))):
206 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths
.samdb
, str(current
[0]["defaultNamingContext"][0]), paths
.smbconf
, basedn
)))
208 names
.domaindn
=current
[0]["defaultNamingContext"]
209 names
.rootdn
=current
[0]["rootDomainNamingContext"]
212 res3
= samdb
.search(expression
="(objectClass=*)",base
="CN=Sites,"+configdn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs3
)
213 names
.sitename
= str(res3
[0]["cn"])
215 # dns hostname and server dn
216 attrs4
= ["dNSHostName"]
217 res4
= samdb
.search(expression
="(CN=%s)"%names
.netbiosname
,base
="OU=Domain Controllers,"+basedn
, \
218 scope
=SCOPE_ONELEVEL
, attrs
=attrs4
)
219 names
.hostname
= str(res4
[0]["dNSHostName"]).replace("."+names
.dnsdomain
,"")
221 server_res
= samdb
.search(expression
="serverReference=%s"%res4[0].dn
, attrs
=[], base
=configdn
)
222 names
.serverdn
= server_res
[0].dn
224 # invocation id/objectguid
225 res5
= samdb
.search(expression
="(objectClass=*)",base
="CN=NTDS Settings,%s" % str(names
.serverdn
), scope
=SCOPE_BASE
, attrs
=["invocationID","objectGUID"])
226 names
.invocation
= str(ndr_unpack( misc
.GUID
,res5
[0]["invocationId"][0]))
227 names
.ntdsguid
= str(ndr_unpack( misc
.GUID
,res5
[0]["objectGUID"][0]))
230 attrs6
= ["objectGUID", "objectSid","msDS-Behavior-Version" ]
231 res6
= samdb
.search(expression
="(objectClass=*)",base
=basedn
, scope
=SCOPE_BASE
, attrs
=attrs6
)
232 names
.domainguid
= str(ndr_unpack( misc
.GUID
,res6
[0]["objectGUID"][0]))
233 names
.domainsid
= ndr_unpack( security
.dom_sid
,res6
[0]["objectSid"][0])
234 if res6
[0].get("msDS-Behavior-Version") == None or int(res6
[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000
:
235 names
.domainlevel
= DS_DOMAIN_FUNCTION_2000
237 names
.domainlevel
= int(res6
[0]["msDS-Behavior-Version"][0])
240 attrs7
= ["cn","displayName"]
241 res7
= samdb
.search(expression
="(displayName=Default Domain Policy)",base
="CN=Policies,CN=System,"+basedn
, \
242 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
243 names
.policyid
= str(res7
[0]["cn"]).replace("{","").replace("}","")
245 attrs8
= ["cn","displayName"]
246 res8
= samdb
.search(expression
="(displayName=Default Domain Controllers Policy)",base
="CN=Policies,CN=System,"+basedn
, \
247 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
249 names
.policyid_dc
= str(res8
[0]["cn"]).replace("{","").replace("}","")
251 names
.policyid_dc
= None
257 def print_names(names
):
258 message(GUESS
, "rootdn :"+str(names
.rootdn
))
259 message(GUESS
, "configdn :"+str(names
.configdn
))
260 message(GUESS
, "schemadn :"+str(names
.schemadn
))
261 message(GUESS
, "serverdn :"+str(names
.serverdn
))
262 message(GUESS
, "netbiosname :"+names
.netbiosname
)
263 message(GUESS
, "defaultsite :"+names
.sitename
)
264 message(GUESS
, "dnsdomain :"+names
.dnsdomain
)
265 message(GUESS
, "hostname :"+names
.hostname
)
266 message(GUESS
, "domain :"+names
.domain
)
267 message(GUESS
, "realm :"+names
.realm
)
268 message(GUESS
, "invocationid:"+names
.invocation
)
269 message(GUESS
, "policyguid :"+names
.policyid
)
270 message(GUESS
, "policyguiddc:"+str(names
.policyid_dc
))
271 message(GUESS
, "domainsid :"+str(names
.domainsid
))
272 message(GUESS
, "domainguid :"+names
.domainguid
)
273 message(GUESS
, "ntdsguid :"+names
.ntdsguid
)
274 message(GUESS
, "domainlevel :"+str(names
.domainlevel
))
276 # Create a fresh new reference provision
277 # This provision will be the reference for knowing what has changed in the
278 # since the latest upgrade in the current provision
279 def newprovision(names
,setup_dir
,creds
,session
,smbconf
):
280 message(SIMPLE
, "Creating a reference provision")
281 provdir
=tempfile
.mkdtemp(dir=paths
.private_dir
, prefix
="referenceprovision")
282 if os
.path
.isdir(provdir
):
284 logstd
=os
.path
.join(provdir
,"log.std")
285 os
.chdir(os
.path
.join(setup_dir
,".."))
288 sys
.stderr
= open("%s/provision.log"%provdir
, "w")
289 message(PROVISION
, "Reference provision stored in %s"%provdir
)
290 message(PROVISION
, "STDERR message of provision will be logged in %s/provision.log"%provdir
)
291 sys
.stderr
= open("/dev/stdout", "w")
292 provision(setup_dir
, messageprovision
,
293 session
, creds
, smbconf
=smbconf
, targetdir
=provdir
,
294 samdb_fill
=FILL_FULL
, realm
=names
.realm
, domain
=names
.domain
,
295 domainguid
=names
.domainguid
, domainsid
=str(names
.domainsid
),ntdsguid
=names
.ntdsguid
,
296 policyguid
=names
.policyid
,policyguid_dc
=names
.policyid_dc
,hostname
=names
.netbiosname
,
297 hostip
=None, hostip6
=None,
298 invocationid
=names
.invocation
, adminpass
=None,
299 krbtgtpass
=None, machinepass
=None,
300 dnspass
=None, root
=None, nobody
=None,
301 wheel
=None, users
=None,
302 serverrole
="domain controller",
303 ldap_backend_extra_port
=None,
310 dom_for_fun_level
=names
.domainlevel
,
311 ldap_dryrun_mode
=None)
314 # This function sorts two dn in the lexicographical order and put higher level DN before
315 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller (-1) as it has less
318 p
= re
.compile(r
'(?<!\\),')
319 tab1
= p
.split(str(x
))
320 tab2
= p
.split(str(y
))
322 if (len(tab1
) > len(tab2
)):
324 elif (len(tab1
) < len(tab2
)):
331 # Note: python range go up to upper limit but do not include it
332 for i
in range(0,min):
333 ret
=cmp(tab1
[len1
-i
],tab2
[len2
-i
])
339 message(ERROR
,"PB PB PB"+space
.join(tab1
)+" / "+space
.join(tab2
))
346 # check from security descriptors modifications return 1 if it is 0 otherwise
347 # it also populate hash structure for later use in the upgrade process
348 def handle_security_desc(ischema
,att
,msgElt
,hashallSD
,old
,new
):
349 if ischema
== 1 and att
== "defaultSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
351 hashSD
["oldSD"] = old
[0][att
]
352 hashSD
["newSD"] = new
[0][att
]
353 hashallSD
[str(old
[0].dn
)] = hashSD
355 if att
== "nTSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
358 hashSD
["oldSD"] = ndr_unpack(security
.descriptor
,str(old
[0][att
]))
359 hashSD
["newSD"] = ndr_unpack(security
.descriptor
,str(new
[0][att
]))
360 hashallSD
[str(old
[0].dn
)] = hashSD
364 # Hangle special cases ... That's when we want to update an attribute only
365 # if it has a certain value or if it's for a certain object or
367 # It can be also if we want to do a merge of value instead of a simple replace
368 def handle_special_case(att
,delta
,new
,old
,ischema
):
369 flag
= delta
.get(att
).flags()
370 if (att
== "gPLink" or att
== "gPCFileSysPath") and flag
== ldb
.FLAG_MOD_REPLACE
and str(new
[0].dn
).lower() == str(old
[0].dn
).lower():
373 if att
== "forceLogoff":
374 ref
=0x8000000000000000
375 oldval
=int(old
[0][att
][0])
376 newval
=int(new
[0][att
][0])
377 ref
== old
and ref
== abs(new
)
379 if (att
== "adminDisplayName" or att
== "adminDescription") and ischema
:
381 if (str(old
[0].dn
) == "CN=Samba4-Local-Domain,%s"%(str(names
.schemadn
)) and att
== "defaultObjectCategory" and flag
== ldb
.FLAG_MOD_REPLACE
):
383 # 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):
385 if (str(old
[0].dn
) == "CN=Title,%s"%(str(names
.schemadn
)) and att
== "rangeUpper" and flag
== ldb
.FLAG_MOD_REPLACE
):
387 if ( (att
== "member" or att
== "servicePrincipalName") and flag
== ldb
.FLAG_MOD_REPLACE
):
392 for elem
in old
[0][att
]:
394 newval
.append(str(elem
))
396 for elem
in new
[0][att
]:
397 if not hash.has_key(str(elem
)):
399 newval
.append(str(elem
))
401 delta
[att
] = ldb
.MessageElement(newval
, ldb
.FLAG_MOD_REPLACE
, att
)
405 if (str(old
[0].dn
) == "%s"%(str(names
.rootdn
)) and att
== "subRefs" and flag
== ldb
.FLAG_MOD_REPLACE
):
407 if str(delta
.dn
).endswith("CN=DisplaySpecifiers,%s"%names
.configdn
):
411 def update_secrets(newpaths
,paths
,creds
,session
):
412 message(SIMPLE
,"update secrets.ldb")
413 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
414 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_secrets"])
415 reference
= newsecrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
416 current
= secrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
417 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
418 delta
.dn
= current
[0].dn
419 secrets_ldb
.modify(delta
)
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
)
423 reference
= newsecrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
424 current
= secrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
430 empty
= ldb
.Message()
431 for i
in range(0,len(reference
)):
432 hash_new
[str(reference
[i
]["dn"]).lower()] = reference
[i
]["dn"]
434 # Create a hash for speeding the search of existing object in the current provision
435 for i
in range(0,len(current
)):
436 hash[str(current
[i
]["dn"]).lower()] = current
[i
]["dn"]
438 for k
in hash_new
.keys():
439 if not hash.has_key(k
):
440 listMissing
.append(hash_new
[k
])
442 listPresent
.append(hash_new
[k
])
443 for entry
in listMissing
:
444 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
445 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
446 delta
= secrets_ldb
.msg_diff(empty
,reference
[0])
447 for att
in hashAttrNotCopied
.keys():
449 message(CHANGE
,"Entry %s is missing from secrets.ldb"%reference
[0].dn
)
451 message(CHANGE
," Adding attribute %s"%att
)
452 delta
.dn
= reference
[0].dn
453 secrets_ldb
.add(delta
)
455 for entry
in listPresent
:
456 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
457 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
458 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
460 for att
in hashAttrNotCopied
.keys():
466 message(CHANGE
,"Found attribute name on %s, must rename the DN "%(current
[0].dn
))
467 identic_rename(secrets_ldb
,reference
[0].dn
)
472 for entry
in listPresent
:
473 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
474 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
475 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
477 for att
in hashAttrNotCopied
.keys():
482 message(CHANGE
," Adding/Changing attribute %s to %s"%(att
,current
[0].dn
))
484 delta
.dn
= current
[0].dn
485 secrets_ldb
.modify(delta
)
488 # Check difference between the current provision and the reference provision.
489 # It looks for all object which base DN is name if ischema is false then scan is done in
490 # cross partition mode.
491 # If ischema is true, then special handling is done for dealing with schema
492 def check_diff_name(newpaths
,paths
,creds
,session
,basedn
,names
,ischema
):
500 # Connect to the reference provision and get all the attribute in the partition referred by name
501 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
502 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
503 sam_ldb
.transaction_start()
505 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
506 current
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
508 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
509 current
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
511 sam_ldb
.transaction_commit()
512 # Create a hash for speeding the search of new object
513 for i
in range(0,len(reference
)):
514 hash_new
[str(reference
[i
]["dn"]).lower()] = reference
[i
]["dn"]
516 # Create a hash for speeding the search of existing object in the current provision
517 for i
in range(0,len(current
)):
518 hash[str(current
[i
]["dn"]).lower()] = current
[i
]["dn"]
520 for k
in hash_new
.keys():
521 if not hash.has_key(k
):
522 listMissing
.append(hash_new
[k
])
524 listPresent
.append(hash_new
[k
])
526 # Sort the missing object in order to have object of the lowest level first (which can be
527 # containers for higher level objects)
528 listMissing
.sort(dn_sort
)
529 listPresent
.sort(dn_sort
)
532 # The following lines (up to the for loop) is to load the up to date schema into our current LDB
533 # a complete schema is needed as the insertion of attributes and class is done against it
534 # and the schema is self validated
535 # The double ldb open and schema validation is taken from the initial provision script
536 # it's not certain that it is really needed ....
537 sam_ldb
= Ldb(session_info
=session
, credentials
=creds
, lp
=lp
)
538 schema
= Schema(setup_path
, names
.domainsid
, schemadn
=basedn
, serverdn
=str(names
.serverdn
))
539 # Load the schema from the one we computed earlier
540 sam_ldb
.set_schema_from_ldb(schema
.ldb
)
541 # And now we can connect to the DB - the schema won't be loaded from the DB
542 sam_ldb
.connect(paths
.samdb
)
544 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
546 sam_ldb
.transaction_start()
548 empty
= ldb
.Message()
549 message(SIMPLE
,"There are %d missing objects"%(len(listMissing
)))
550 for dn
in listMissing
:
551 reference
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
552 delta
= sam_ldb
.msg_diff(empty
,reference
[0])
553 for att
in hashAttrNotCopied
.keys():
555 for att
in backlinked
:
559 sam_ldb
.add(delta
,["relax:0"])
562 for dn
in listPresent
:
563 reference
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
564 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
565 if ((str(current
[0].dn
) != str(reference
[0].dn
)) and (str(current
[0].dn
).upper() == str(reference
[0].dn
).upper())):
566 message(CHANGE
,"Name are the same but case change, let's rename %s to %s"%(str(current
[0].dn
),str(reference
[0].dn
)))
567 identic_rename(sam_ldb
,reference
[0].dn
)
568 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
570 delta
= sam_ldb
.msg_diff(current
[0],reference
[0])
571 for att
in hashAttrNotCopied
.keys():
573 for att
in backlinked
:
575 delta
.remove("parentGUID")
579 msgElt
= delta
.get(att
)
585 if handle_security_desc(ischema
,att
,msgElt
,hashallSD
,current
,reference
):
588 if (not hashOverwrittenAtt
.has_key(att
) or not (hashOverwrittenAtt
.get(att
)&2^msgElt
.flags())):
589 if handle_special_case(att
,delta
,reference
,current
,ischema
)==0 and msgElt
.flags()!=ldb
.FLAG_MOD_ADD
:
592 message(CHANGE
, "dn= "+str(dn
)+ " "+att
+ " with flag "+str(msgElt
.flags())+ " is not allowed to be changed/removed, I discard this change ...")
593 for e
in range(0,len(current
[0][att
])):
594 message(CHANGE
,"old %d : %s"%(i
,str(current
[0][att
][e
])))
595 if msgElt
.flags() == 2:
597 for e
in range(0,len(reference
[0][att
])):
598 message(CHANGE
,"new %d : %s"%(i
,str(reference
[0][att
][e
])))
601 if len(delta
.items()) >1:
602 attributes
=",".join(delta
.keys())
603 message(CHANGE
,"%s is different from the reference one, changed attributes: %s"%(dn
,attributes
))
604 changed
= changed
+ 1
605 sam_ldb
.modify(delta
)
607 sam_ldb
.transaction_commit()
608 message(SIMPLE
,"There are %d changed objects"%(changed))
611 # Check that SD are correct
612 def check_updated_sd(newpaths
,paths
,creds
,session
,names
):
613 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
614 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
615 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
616 current
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
618 for i
in range(0,len(reference
)):
619 hash_new
[str(reference
[i
]["dn"]).lower()] = ndr_unpack(security
.descriptor
,str(reference
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
621 for i
in range(0,len(current
)):
622 key
= str(current
[i
]["dn"]).lower()
623 if hash_new
.has_key(key
):
624 sddl
= ndr_unpack(security
.descriptor
,str(current
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
625 if sddl
!= hash_new
[key
]:
626 print "%s new sddl/sddl in ref"%key
627 print "%s\n%s"%(sddl
,hash_new
[key
])
629 # Simple update method for updating the SD that rely on the fact that nobody should have modified the SD
630 # This assumption is safe right now (alpha9) but should be removed asap
631 def update_sd(paths
,creds
,session
,names
):
632 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
,options
=["modules:samba_dsdb"])
633 sam_ldb
.transaction_start()
634 # First update the SD for the rootdn
635 sam_ldb
.set_session_info(session
)
636 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
637 delta
= ldb
.Message()
638 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
639 descr
= get_domain_descriptor(names
.domainsid
)
640 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
641 sam_ldb
.modify(delta
,["recalculate_sd:0"])
643 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.configdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
644 delta
= ldb
.Message()
645 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
646 descr
= get_config_descriptor(names
.domainsid
)
647 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
648 sam_ldb
.modify(delta
,["recalculate_sd:0"])
650 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.schemadn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
651 delta
= ldb
.Message()
652 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
653 descr
= get_schema_descriptor(names
.domainsid
)
654 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
655 sam_ldb
.modify(delta
,["recalculate_sd:0"])
659 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
661 if not (str(obj
["dn"]) == str(names
.rootdn
) or
662 str(obj
["dn"]) == str(names
.configdn
) or \
663 str(obj
["dn"]) == str(names
.schemadn
)):
664 hash[str(obj
["dn"])] = obj
["whenCreated"]
666 listkeys
= hash.keys()
667 listkeys
.sort(dn_sort
)
671 delta
= ldb
.Message()
672 delta
.dn
= ldb
.Dn(sam_ldb
,key
)
673 delta
["whenCreated"] = ldb
.MessageElement( hash[key
],ldb
.FLAG_MOD_REPLACE
,"whenCreated" )
674 sam_ldb
.modify(delta
,["recalculate_sd:0"])
676 sam_ldb
.transaction_cancel()
677 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
678 print "bad stuff" +ndr_unpack(security
.descriptor
,str(res
[0]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
680 sam_ldb
.transaction_commit()
683 for root
, dirs
, files
in os
.walk(topdir
, topdown
=False):
685 os
.remove(os
.path
.join(root
, name
))
687 os
.rmdir(os
.path
.join(root
, name
))
691 def update_basesamdb(newpaths
,paths
,names
):
692 message(SIMPLE
,"Copy samdb")
693 shutil
.copy(newpaths
.samdb
,paths
.samdb
)
695 message(SIMPLE
,"Update partitions filename if needed")
696 schemaldb
=os
.path
.join(paths
.private_dir
,"schema.ldb")
697 configldb
=os
.path
.join(paths
.private_dir
,"configuration.ldb")
698 usersldb
=os
.path
.join(paths
.private_dir
,"users.ldb")
699 samldbdir
=os
.path
.join(paths
.private_dir
,"sam.ldb.d")
701 if not os
.path
.isdir(samldbdir
):
703 os
.chmod(samldbdir
,0700)
704 if os
.path
.isfile(schemaldb
):
705 shutil
.copy(schemaldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.schemadn
).upper()))
707 if os
.path
.isfile(usersldb
):
708 shutil
.copy(usersldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.rootdn
).upper()))
710 if os
.path
.isfile(configldb
):
711 shutil
.copy(configldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.configdn
).upper()))
714 def update_privilege(newpaths
,paths
):
715 message(SIMPLE
,"Copy privilege")
716 shutil
.copy(os
.path
.join(newpaths
.private_dir
,"privilege.ldb"),os
.path
.join(paths
.private_dir
,"privilege.ldb"))
718 # For each partition check the differences
719 def update_samdb(newpaths
,paths
,creds
,session
,names
):
721 message(SIMPLE
, "Doing schema update")
722 hashdef
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.schemadn
),names
,1)
723 message(SIMPLE
,"Done with schema update")
724 message(SIMPLE
,"Scanning whole provision for updates and additions")
725 hashSD
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.rootdn
),names
,0)
726 message(SIMPLE
,"Done with scanning")
728 def update_machine_account_password(paths
,creds
,session
,names
):
730 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
731 secrets_ldb
.transaction_start()
732 secrets_msg
= secrets_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=["secureChannelType"])
733 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
734 sam_ldb
.transaction_start()
735 if int(secrets_msg
[0]["secureChannelType"][0]) == SEC_CHAN_BDC
:
736 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=[])
737 assert(len(res
) == 1)
739 msg
= ldb
.Message(res
[0].dn
)
740 machinepass
= glue
.generate_random_str(12)
741 msg
["userPassword"] = ldb
.MessageElement(machinepass
, ldb
.FLAG_MOD_REPLACE
, "userPassword")
744 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
),
745 attrs
=["msDs-keyVersionNumber"])
746 assert(len(res
) == 1)
747 kvno
= int(str(res
[0]["msDs-keyVersionNumber"]))
749 secretsdb_self_join(secrets_ldb
, domain
=names
.domain
,
751 domainsid
=names
.domainsid
,
752 dnsdomain
=names
.dnsdomain
,
753 netbiosname
=names
.netbiosname
,
754 machinepass
=machinepass
,
755 key_version_number
=kvno
,
756 secure_channel_type
=int(secrets_msg
[0]["secureChannelType"][0]))
757 sam_ldb
.transaction_prepare_commit()
758 secrets_ldb
.transaction_prepare_commit()
759 sam_ldb
.transaction_commit()
760 secrets_ldb
.transaction_commit()
762 secrets_ldb
.transaction_cancel()
764 # From here start the big steps of the program
765 # First get files paths
766 paths
=get_paths(smbconf
=smbconf
)
767 paths
.setup
= setup_dir
768 def setup_path(file):
769 return os
.path
.join(setup_dir
, file)
770 # Guess all the needed names (variables in fact) from the current
772 names
= guess_names_from_current_provision(creds
,session
,paths
)
775 # With all this information let's create a fresh new provision used as reference
776 provisiondir
= newprovision(names
,setup_dir
,creds
,session
,smbconf
)
777 # Get file paths of this new provision
778 newpaths
= get_paths(targetdir
=provisiondir
)
779 populate_backlink(newpaths
,creds
,session
,names
.schemadn
)
780 # Check the difference
781 update_basesamdb(newpaths
,paths
,names
)
784 update_samdb(newpaths
,paths
,creds
,session
,names
)
785 update_secrets(newpaths
,paths
,creds
,session
)
786 update_privilege(newpaths
,paths
)
787 update_machine_account_password(paths
,creds
,session
,names
)
788 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
789 # to recreate them with the good form but with system account and then give the ownership to admin ...
790 admin_session_info
= admin_session(lp
, str(names
.domainsid
))
791 message(SIMPLE
,"Updating SD")
792 update_sd(paths
,creds
,session
,names
)
793 update_sd(paths
,creds
,admin_session_info
,names
)
794 check_updated_sd(newpaths
,paths
,creds
,session
,names
)
795 message(SIMPLE
,"Upgrade finished !")
796 # remove reference provision now that everything is done !