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
.misc
import messageEltFlagToString
50 from samba
.provision
import ProvisionNames
,provision_paths_from_lp
,find_setup_dir
,FILL_FULL
,provision
, get_domain_descriptor
, get_config_descriptor
, secretsdb_self_join
51 from samba
.provisionexceptions
import ProvisioningError
52 from samba
.schema
import get_dnsyntax_attributes
, get_linked_attributes
, Schema
, get_schema_descriptor
53 from samba
.dcerpc
import misc
, security
54 from samba
.ndr
import ndr_pack
, ndr_unpack
55 from samba
.dcerpc
.misc
import SEC_CHAN_BDC
57 replace
=2^ldb
.FLAG_MOD_REPLACE
58 add
=2^ldb
.FLAG_MOD_ADD
59 delete
=2^ldb
.FLAG_MOD_DELETE
61 #Errors are always logged
70 # Attributes that are never copied from the reference provision (even if they
71 # do not exist in the destination object).
72 # This is most probably because they are populated automatcally when object is
74 hashAttrNotCopied
= { "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,\
75 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,\
76 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,\
77 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,\
78 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,\
79 "maxPwdAge":1, "mail":1, "secret":1,"possibleInferiors":1, "sAMAccountType":1}
81 # Usually for an object that already exists we do not overwrite attributes as
82 # they might have been changed for good reasons. Anyway for a few of them it's
83 # mandatory to replace them otherwise the provision will be broken somehow.
84 hashOverwrittenAtt
= { "prefixMap": replace
, "systemMayContain": replace
,"systemOnly":replace
, "searchFlags":replace
,\
85 "mayContain":replace
, "systemFlags":replace
,"description":replace
,
86 "oEMInformation":replace
, "operatingSystemVersion":replace
, "adminPropertyPages":replace
,
87 "defaultSecurityDescriptor": replace
,"wellKnownObjects":replace
,"privilege":delete
}
90 def define_what_to_log(opts
):
94 if opts
.debugchangesd
:
95 what
= what | CHANGESD
98 if opts
.debugprovision
:
99 what
= what | PROVISION
101 what
= what | CHANGEALL
105 parser
= optparse
.OptionParser("provision [options]")
106 sambaopts
= options
.SambaOptions(parser
)
107 parser
.add_option_group(sambaopts
)
108 parser
.add_option_group(options
.VersionOptions(parser
))
109 credopts
= options
.CredentialsOptions(parser
)
110 parser
.add_option_group(credopts
)
111 parser
.add_option("--setupdir", type="string", metavar
="DIR",
112 help="directory with setup files")
113 parser
.add_option("--debugprovision", help="Debug provision", action
="store_true")
114 parser
.add_option("--debugguess", help="Print information on what is different but won't be changed", action
="store_true")
115 parser
.add_option("--debugchange", help="Print information on what is different but won't be changed", action
="store_true")
116 parser
.add_option("--debugchangesd", help="Print information security descriptors differences", action
="store_true")
117 parser
.add_option("--debugall", help="Print all available information (very verbose)", action
="store_true")
118 parser
.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action
="store_true")
120 opts
= parser
.parse_args()[0]
122 whatToLog
= define_what_to_log(opts
)
124 def messageprovision(text
):
125 """print a message if quiet is not set."""
126 if opts
.debugprovision
or opts
.debugall
:
129 def message(what
,text
):
130 """print a message if quiet is not set."""
131 if (whatToLog
& what
) or (what
<= 0 ):
134 if len(sys
.argv
) == 1:
135 opts
.interactive
= True
136 lp
= sambaopts
.get_loadparm()
137 smbconf
= lp
.configfile
139 creds
= credopts
.get_credentials(lp
)
140 creds
.set_kerberos_state(DONT_USE_KERBEROS
)
141 setup_dir
= opts
.setupdir
142 if setup_dir
is None:
143 setup_dir
= find_setup_dir()
145 session
= system_session()
147 # simple helper to allow back and forth rename
148 def identic_rename(ldbobj
,dn
):
149 (before
,sep
,after
)=str(dn
).partition('=')
150 ldbobj
.rename(dn
,ldb
.Dn(ldbobj
,"%s=foo%s"%(before
,after
)))
151 ldbobj
.rename(ldb
.Dn(ldbobj
,"%s=foo%s"%(before
,after
)),dn
)
153 # Create an array of backlinked attributes
154 def populate_backlink(newpaths
,creds
,session
,schemadn
):
155 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
156 backlinked
.extend(get_linked_attributes(ldb
.Dn(newsam_ldb
,str(schemadn
)),newsam_ldb
).values())
158 # Get Paths for important objects (ldb, keytabs ...)
159 def get_paths(targetdir
=None,smbconf
=None):
160 if targetdir
is not None:
161 if (not os
.path
.exists(os
.path
.join(targetdir
, "etc"))):
162 os
.makedirs(os
.path
.join(targetdir
, "etc"))
163 smbconf
= os
.path
.join(targetdir
, "etc", "smb.conf")
165 smbconf
= param
.default_path()
167 if not os
.path
.exists(smbconf
):
168 message(ERROR
,"Unable to find smb.conf ..")
172 lp
= param
.LoadParm()
174 # Normally we need the domain name for this function but for our needs it's
176 paths
= provision_paths_from_lp(lp
,"foo")
179 # This function guesses (fetches) informations needed to make a fresh provision
180 # from the current provision
181 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
182 def guess_names_from_current_provision(credentials
,session_info
,paths
):
183 lp
= param
.LoadParm()
184 lp
.load(paths
.smbconf
)
185 names
= ProvisionNames()
186 # NT domain, kerberos realm, root dn, domain dn, domain dns name
187 names
.domain
= string
.upper(lp
.get("workgroup"))
188 names
.realm
= lp
.get("realm")
189 basedn
= "DC=" + names
.realm
.replace(".",",DC=")
190 names
.dnsdomain
= names
.realm
191 names
.realm
= string
.upper(names
.realm
)
193 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session_info
, credentials
=credentials
,lp
=lp
, options
=["modules:samba_secrets"])
194 # Get the netbiosname first (could be obtained from smb.conf in theory)
195 attrs
= ["sAMAccountName"]
196 res
= secrets_ldb
.search(expression
="(flatname=%s)"%names
.domain
,base
="CN=Primary Domains", scope
=SCOPE_SUBTREE
, attrs
=attrs
)
197 names
.netbiosname
= str(res
[0]["sAMAccountName"]).replace("$","")
199 names
.smbconf
= smbconf
200 # It's important here to let ldb load with the old module or it's quite
201 # certain that the LDB won't load ...
202 samdb
= Ldb(paths
.samdb
, session_info
=session_info
,
203 credentials
=credentials
, lp
=lp
, options
=["modules:samba_dsdb"])
205 # That's a bit simplistic but it's ok as long as we have only 3
207 attrs2
= ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
208 current
= samdb
.search(expression
="(objectClass=*)",base
="", scope
=SCOPE_BASE
, attrs
=attrs2
)
210 names
.configdn
= current
[0]["configurationNamingContext"]
211 configdn
= str(names
.configdn
)
212 names
.schemadn
= current
[0]["schemaNamingContext"]
213 if not (ldb
.Dn(samdb
, basedn
) == (ldb
.Dn(samdb
, current
[0]["defaultNamingContext"][0]))):
214 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths
.samdb
, str(current
[0]["defaultNamingContext"][0]), paths
.smbconf
, basedn
)))
216 names
.domaindn
=current
[0]["defaultNamingContext"]
217 names
.rootdn
=current
[0]["rootDomainNamingContext"]
220 res3
= samdb
.search(expression
="(objectClass=*)",base
="CN=Sites,"+configdn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs3
)
221 names
.sitename
= str(res3
[0]["cn"])
223 # dns hostname and server dn
224 attrs4
= ["dNSHostName"]
225 res4
= samdb
.search(expression
="(CN=%s)"%names
.netbiosname
,base
="OU=Domain Controllers,"+basedn
, \
226 scope
=SCOPE_ONELEVEL
, attrs
=attrs4
)
227 names
.hostname
= str(res4
[0]["dNSHostName"]).replace("."+names
.dnsdomain
,"")
229 server_res
= samdb
.search(expression
="serverReference=%s"%res4[0].dn
, attrs
=[], base
=configdn
)
230 names
.serverdn
= server_res
[0].dn
232 # invocation id/objectguid
233 res5
= samdb
.search(expression
="(objectClass=*)",base
="CN=NTDS Settings,%s" % str(names
.serverdn
), scope
=SCOPE_BASE
, attrs
=["invocationID","objectGUID"])
234 names
.invocation
= str(ndr_unpack( misc
.GUID
,res5
[0]["invocationId"][0]))
235 names
.ntdsguid
= str(ndr_unpack( misc
.GUID
,res5
[0]["objectGUID"][0]))
238 attrs6
= ["objectGUID", "objectSid","msDS-Behavior-Version" ]
239 res6
= samdb
.search(expression
="(objectClass=*)",base
=basedn
, scope
=SCOPE_BASE
, attrs
=attrs6
)
240 names
.domainguid
= str(ndr_unpack( misc
.GUID
,res6
[0]["objectGUID"][0]))
241 names
.domainsid
= ndr_unpack( security
.dom_sid
,res6
[0]["objectSid"][0])
242 if res6
[0].get("msDS-Behavior-Version") == None or int(res6
[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000
:
243 names
.domainlevel
= DS_DOMAIN_FUNCTION_2000
245 names
.domainlevel
= int(res6
[0]["msDS-Behavior-Version"][0])
248 attrs7
= ["cn","displayName"]
249 res7
= samdb
.search(expression
="(displayName=Default Domain Policy)",base
="CN=Policies,CN=System,"+basedn
, \
250 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
251 names
.policyid
= str(res7
[0]["cn"]).replace("{","").replace("}","")
253 attrs8
= ["cn","displayName"]
254 res8
= samdb
.search(expression
="(displayName=Default Domain Controllers Policy)",base
="CN=Policies,CN=System,"+basedn
, \
255 scope
=SCOPE_ONELEVEL
, attrs
=attrs7
)
257 names
.policyid_dc
= str(res8
[0]["cn"]).replace("{","").replace("}","")
259 names
.policyid_dc
= None
265 def print_names(names
):
266 message(GUESS
, "rootdn :"+str(names
.rootdn
))
267 message(GUESS
, "configdn :"+str(names
.configdn
))
268 message(GUESS
, "schemadn :"+str(names
.schemadn
))
269 message(GUESS
, "serverdn :"+str(names
.serverdn
))
270 message(GUESS
, "netbiosname :"+names
.netbiosname
)
271 message(GUESS
, "defaultsite :"+names
.sitename
)
272 message(GUESS
, "dnsdomain :"+names
.dnsdomain
)
273 message(GUESS
, "hostname :"+names
.hostname
)
274 message(GUESS
, "domain :"+names
.domain
)
275 message(GUESS
, "realm :"+names
.realm
)
276 message(GUESS
, "invocationid:"+names
.invocation
)
277 message(GUESS
, "policyguid :"+names
.policyid
)
278 message(GUESS
, "policyguiddc:"+str(names
.policyid_dc
))
279 message(GUESS
, "domainsid :"+str(names
.domainsid
))
280 message(GUESS
, "domainguid :"+names
.domainguid
)
281 message(GUESS
, "ntdsguid :"+names
.ntdsguid
)
282 message(GUESS
, "domainlevel :"+str(names
.domainlevel
))
284 # Create a fresh new reference provision
285 # This provision will be the reference for knowing what has changed in the
286 # since the latest upgrade in the current provision
287 def newprovision(names
,setup_dir
,creds
,session
,smbconf
):
288 message(SIMPLE
, "Creating a reference provision")
289 provdir
=tempfile
.mkdtemp(dir=paths
.private_dir
, prefix
="referenceprovision")
290 if os
.path
.isdir(provdir
):
292 logstd
=os
.path
.join(provdir
,"log.std")
293 os
.chdir(os
.path
.join(setup_dir
,".."))
296 sys
.stderr
= open("%s/provision.log"%provdir
, "w")
297 message(PROVISION
, "Reference provision stored in %s"%provdir
)
298 message(PROVISION
, "STDERR message of provision will be logged in %s/provision.log"%provdir
)
299 sys
.stderr
= open("/dev/stdout", "w")
300 provision(setup_dir
, messageprovision
,
301 session
, creds
, smbconf
=smbconf
, targetdir
=provdir
,
302 samdb_fill
=FILL_FULL
, realm
=names
.realm
, domain
=names
.domain
,
303 domainguid
=names
.domainguid
, domainsid
=str(names
.domainsid
),ntdsguid
=names
.ntdsguid
,
304 policyguid
=names
.policyid
,policyguid_dc
=names
.policyid_dc
,hostname
=names
.netbiosname
,
305 hostip
=None, hostip6
=None,
306 invocationid
=names
.invocation
, adminpass
=None,
307 krbtgtpass
=None, machinepass
=None,
308 dnspass
=None, root
=None, nobody
=None,
309 wheel
=None, users
=None,
310 serverrole
="domain controller",
311 ldap_backend_extra_port
=None,
318 dom_for_fun_level
=names
.domainlevel
,
319 ldap_dryrun_mode
=None,useeadb
=True)
322 # This function sorts two DNs in the lexicographical order and put higher level
324 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller
325 # (-1) as it has less level
327 p
= re
.compile(r
'(?<!\\),')
328 tab1
= p
.split(str(x
))
329 tab2
= p
.split(str(y
))
331 if (len(tab1
) > len(tab2
)):
333 elif (len(tab1
) < len(tab2
)):
340 # Note: python range go up to upper limit but do not include it
341 for i
in range(0,min):
342 ret
=cmp(tab1
[len1
-i
],tab2
[len2
-i
])
348 message(ERROR
,"PB PB PB"+space
.join(tab1
)+" / "+space
.join(tab2
))
355 # Check for security descriptors modifications return 1 if it is and 0 otherwise
356 # it also populate hash structure for later use in the upgrade process
357 def handle_security_desc(ischema
,att
,msgElt
,hashallSD
,old
,new
):
358 if ischema
== 1 and att
== "defaultSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
360 hashSD
["oldSD"] = old
[0][att
]
361 hashSD
["newSD"] = new
[0][att
]
362 hashallSD
[str(old
[0].dn
)] = hashSD
364 if att
== "nTSecurityDescriptor" and msgElt
.flags() == ldb
.FLAG_MOD_REPLACE
:
367 hashSD
["oldSD"] = ndr_unpack(security
.descriptor
,str(old
[0][att
]))
368 hashSD
["newSD"] = ndr_unpack(security
.descriptor
,str(new
[0][att
]))
369 hashallSD
[str(old
[0].dn
)] = hashSD
373 # Handle special cases ... That's when we want to update a particular attribute
374 # only, e.g. if it has a certain value or if it's for a certain object or
376 # It can be also if we want to do a merge of value instead of a simple replace
377 def handle_special_case(att
,delta
,new
,old
,ischema
):
378 flag
= delta
.get(att
).flags()
379 if (att
== "gPLink" or att
== "gPCFileSysPath") and flag
== ldb
.FLAG_MOD_REPLACE
and str(new
[0].dn
).lower() == str(old
[0].dn
).lower():
382 if att
== "forceLogoff":
383 ref
=0x8000000000000000
384 oldval
=int(old
[0][att
][0])
385 newval
=int(new
[0][att
][0])
386 ref
== old
and ref
== abs(new
)
388 if (att
== "adminDisplayName" or att
== "adminDescription") and ischema
:
390 if (str(old
[0].dn
) == "CN=Samba4-Local-Domain,%s"%(str(names
.schemadn
)) and att
== "defaultObjectCategory" and flag
== ldb
.FLAG_MOD_REPLACE
):
392 # 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):
394 if (str(old
[0].dn
) == "CN=Title,%s"%(str(names
.schemadn
)) and att
== "rangeUpper" and flag
== ldb
.FLAG_MOD_REPLACE
):
396 if ( (att
== "member" or att
== "servicePrincipalName") and flag
== ldb
.FLAG_MOD_REPLACE
):
401 for elem
in old
[0][att
]:
403 newval
.append(str(elem
))
405 for elem
in new
[0][att
]:
406 if not hash.has_key(str(elem
)):
408 newval
.append(str(elem
))
410 delta
[att
] = ldb
.MessageElement(newval
, ldb
.FLAG_MOD_REPLACE
, att
)
414 if (str(old
[0].dn
) == "%s"%(str(names
.rootdn
)) and att
== "subRefs" and flag
== ldb
.FLAG_MOD_REPLACE
):
416 if str(delta
.dn
).endswith("CN=DisplaySpecifiers,%s"%names
.configdn
):
420 def update_secrets(newpaths
,paths
,creds
,session
):
421 message(SIMPLE
,"update secrets.ldb")
422 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
423 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_secrets"])
424 reference
= newsecrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
425 current
= secrets_ldb
.search(expression
="dn=@MODULES",base
="", scope
=SCOPE_SUBTREE
)
426 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
427 delta
.dn
= current
[0].dn
428 secrets_ldb
.modify(delta
)
430 newsecrets_ldb
= Ldb(newpaths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
431 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
432 reference
= newsecrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
433 current
= secrets_ldb
.search(expression
="objectClass=top",base
="", scope
=SCOPE_SUBTREE
,attrs
=["dn"])
439 empty
= ldb
.Message()
440 for i
in range(0,len(reference
)):
441 hash_new
[str(reference
[i
]["dn"]).lower()] = reference
[i
]["dn"]
443 # Create a hash for speeding the search of existing object in the
445 for i
in range(0,len(current
)):
446 hash[str(current
[i
]["dn"]).lower()] = current
[i
]["dn"]
448 for k
in hash_new
.keys():
449 if not hash.has_key(k
):
450 listMissing
.append(hash_new
[k
])
452 listPresent
.append(hash_new
[k
])
453 for entry
in listMissing
:
454 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
455 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
456 delta
= secrets_ldb
.msg_diff(empty
,reference
[0])
457 for att
in hashAttrNotCopied
.keys():
459 message(CHANGE
,"Entry %s is missing from secrets.ldb"%reference
[0].dn
)
461 message(CHANGE
," Adding attribute %s"%att
)
462 delta
.dn
= reference
[0].dn
463 secrets_ldb
.add(delta
)
465 for entry
in listPresent
:
466 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
467 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
468 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
470 for att
in hashAttrNotCopied
.keys():
476 message(CHANGE
,"Found attribute name on %s, must rename the DN "%(current
[0].dn
))
477 identic_rename(secrets_ldb
,reference
[0].dn
)
482 for entry
in listPresent
:
483 reference
= newsecrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
484 current
= secrets_ldb
.search(expression
="dn=%s"%entry
,base
="", scope
=SCOPE_SUBTREE
)
485 delta
= secrets_ldb
.msg_diff(current
[0],reference
[0])
487 for att
in hashAttrNotCopied
.keys():
492 message(CHANGE
," Adding/Changing attribute %s to %s"%(att
,current
[0].dn
))
494 delta
.dn
= current
[0].dn
495 secrets_ldb
.modify(delta
)
498 # Check difference between the current provision and the reference provision.
499 # It looks for all objects which base DN is name. If ischema is "false" then
500 # the scan is done in cross partition mode.
501 # If "ischema" is true, then special handling is done for dealing with schema
502 def check_diff_name(newpaths
,paths
,creds
,session
,basedn
,names
,ischema
):
510 # Connect to the reference provision and get all the attribute in the
511 # partition referred by name
512 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
513 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
514 sam_ldb
.transaction_start()
516 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
517 current
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"])
519 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
520 current
= sam_ldb
.search(expression
="objectClass=*",base
=basedn
, scope
=SCOPE_SUBTREE
,attrs
=["dn"],controls
=["search_options:1:2"])
522 sam_ldb
.transaction_commit()
523 # Create a hash for speeding the search of new object
524 for i
in range(0,len(reference
)):
525 hash_new
[str(reference
[i
]["dn"]).lower()] = reference
[i
]["dn"]
527 # Create a hash for speeding the search of existing object in the
529 for i
in range(0,len(current
)):
530 hash[str(current
[i
]["dn"]).lower()] = current
[i
]["dn"]
532 for k
in hash_new
.keys():
533 if not hash.has_key(k
):
534 listMissing
.append(hash_new
[k
])
536 listPresent
.append(hash_new
[k
])
538 # Sort the missing object in order to have object of the lowest level
539 # first (which can be containers for higher level objects)
540 listMissing
.sort(dn_sort
)
541 listPresent
.sort(dn_sort
)
544 # The following lines (up to the for loop) is to load the up to
545 # date schema into our current LDB
546 # a complete schema is needed as the insertion of attributes
547 # and class is done against it
548 # and the schema is self validated
549 # The double ldb open and schema validation is taken from the
550 # initial provision script
551 # it's not certain that it is really needed ....
552 sam_ldb
= Ldb(session_info
=session
, credentials
=creds
, lp
=lp
)
553 schema
= Schema(setup_path
, names
.domainsid
, schemadn
=basedn
, serverdn
=str(names
.serverdn
))
554 # Load the schema from the one we computed earlier
555 sam_ldb
.set_schema_from_ldb(schema
.ldb
)
556 # And now we can connect to the DB - the schema won't be loaded
558 sam_ldb
.connect(paths
.samdb
)
560 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
, options
=["modules:samba_dsdb"])
562 sam_ldb
.transaction_start()
564 empty
= ldb
.Message()
565 message(SIMPLE
,"There are %d missing objects"%(len(listMissing
)))
566 for dn
in listMissing
:
567 reference
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
568 delta
= sam_ldb
.msg_diff(empty
,reference
[0])
569 for att
in hashAttrNotCopied
.keys():
571 for att
in backlinked
:
575 sam_ldb
.add(delta
,["relax:0"])
578 for dn
in listPresent
:
579 reference
= newsam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
580 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
581 if ((str(current
[0].dn
) != str(reference
[0].dn
)) and (str(current
[0].dn
).upper() == str(reference
[0].dn
).upper())):
582 message(CHANGE
,"Name are the same but case change, let's rename %s to %s"%(str(current
[0].dn
),str(reference
[0].dn
)))
583 identic_rename(sam_ldb
,reference
[0].dn
)
584 current
= sam_ldb
.search(expression
="dn=%s"%(str(dn
)),base
=basedn
, scope
=SCOPE_SUBTREE
,controls
=["search_options:1:2"])
586 delta
= sam_ldb
.msg_diff(current
[0],reference
[0])
587 for att
in hashAttrNotCopied
.keys():
589 for att
in backlinked
:
591 delta
.remove("parentGUID")
595 msgElt
= delta
.get(att
)
601 if handle_security_desc(ischema
,att
,msgElt
,hashallSD
,current
,reference
):
604 if (not hashOverwrittenAtt
.has_key(att
) or not (hashOverwrittenAtt
.get(att
)&2^msgElt
.flags())):
605 if handle_special_case(att
,delta
,reference
,current
,ischema
)==0 and msgElt
.flags()!=ldb
.FLAG_MOD_ADD
:
609 dump_denied_change(dn
,att
,messageEltFlagToString(msgElt
.flags()),current
[0][att
],reference
[0][att
])
611 dump_denied_change(dn
,att
,messageEltFlagToString(msgElt
.flags()),current
[0][att
],None)
614 if len(delta
.items()) >1:
615 attributes
=",".join(delta
.keys())
616 message(CHANGE
,"%s is different from the reference one, changed attributes: %s"%(dn
,attributes
))
617 changed
= changed
+ 1
618 sam_ldb
.modify(delta
)
620 sam_ldb
.transaction_commit()
621 message(SIMPLE
,"There are %d changed objects"%(changed))
624 # Check that SD are correct
625 def check_updated_sd(newpaths
,paths
,creds
,session
,names
):
626 newsam_ldb
= Ldb(newpaths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
627 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
628 reference
= newsam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
629 current
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
631 for i
in range(0,len(reference
)):
632 hash_new
[str(reference
[i
]["dn"]).lower()] = ndr_unpack(security
.descriptor
,str(reference
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
634 for i
in range(0,len(current
)):
635 key
= str(current
[i
]["dn"]).lower()
636 if hash_new
.has_key(key
):
637 sddl
= ndr_unpack(security
.descriptor
,str(current
[i
]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
638 if sddl
!= hash_new
[key
]:
639 print "%s new sddl/sddl in ref"%key
640 print "%s\n%s"%(sddl
,hash_new
[key
])
642 # Simple update method for updating the SD that rely on the fact that nobody
643 # should have modified the SD
644 # This assumption is safe right now (alpha9) but should be removed asap
645 def update_sd(paths
,creds
,session
,names
):
646 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
,options
=["modules:samba_dsdb"])
647 sam_ldb
.transaction_start()
648 # First update the SD for the rootdn
649 sam_ldb
.set_session_info(session
)
650 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), 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_domain_descriptor(names
.domainsid
)
654 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
655 sam_ldb
.modify(delta
,["recalculate_sd:0"])
657 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.configdn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
658 delta
= ldb
.Message()
659 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
660 descr
= get_config_descriptor(names
.domainsid
)
661 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
662 sam_ldb
.modify(delta
,["recalculate_sd:0"])
664 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.schemadn
), scope
=SCOPE_BASE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
665 delta
= ldb
.Message()
666 delta
.dn
= ldb
.Dn(sam_ldb
,str(res
[0]["dn"]))
667 descr
= get_schema_descriptor(names
.domainsid
)
668 delta
["nTSecurityDescriptor"] = ldb
.MessageElement( descr
,ldb
.FLAG_MOD_REPLACE
,"nTSecurityDescriptor" )
669 sam_ldb
.modify(delta
,["recalculate_sd:0"])
673 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","whenCreated"],controls
=["search_options:1:2"])
675 if not (str(obj
["dn"]) == str(names
.rootdn
) or
676 str(obj
["dn"]) == str(names
.configdn
) or \
677 str(obj
["dn"]) == str(names
.schemadn
)):
678 hash[str(obj
["dn"])] = obj
["whenCreated"]
680 listkeys
= hash.keys()
681 listkeys
.sort(dn_sort
)
685 delta
= ldb
.Message()
686 delta
.dn
= ldb
.Dn(sam_ldb
,key
)
687 delta
["whenCreated"] = ldb
.MessageElement( hash[key
],ldb
.FLAG_MOD_REPLACE
,"whenCreated" )
688 sam_ldb
.modify(delta
,["recalculate_sd:0"])
690 sam_ldb
.transaction_cancel()
691 res
= sam_ldb
.search(expression
="objectClass=*",base
=str(names
.rootdn
), scope
=SCOPE_SUBTREE
,attrs
=["dn","nTSecurityDescriptor"],controls
=["search_options:1:2"])
692 print "bad stuff" +ndr_unpack(security
.descriptor
,str(res
[0]["nTSecurityDescriptor"])).as_sddl(names
.domainsid
)
694 sam_ldb
.transaction_commit()
697 for root
, dirs
, files
in os
.walk(topdir
, topdown
=False):
699 os
.remove(os
.path
.join(root
, name
))
701 os
.rmdir(os
.path
.join(root
, name
))
705 def update_basesamdb(newpaths
,paths
,names
):
706 message(SIMPLE
,"Copy samdb")
707 shutil
.copy(newpaths
.samdb
,paths
.samdb
)
709 message(SIMPLE
,"Update partitions filename if needed")
710 schemaldb
=os
.path
.join(paths
.private_dir
,"schema.ldb")
711 configldb
=os
.path
.join(paths
.private_dir
,"configuration.ldb")
712 usersldb
=os
.path
.join(paths
.private_dir
,"users.ldb")
713 samldbdir
=os
.path
.join(paths
.private_dir
,"sam.ldb.d")
715 if not os
.path
.isdir(samldbdir
):
717 os
.chmod(samldbdir
,0700)
718 if os
.path
.isfile(schemaldb
):
719 shutil
.copy(schemaldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.schemadn
).upper()))
721 if os
.path
.isfile(usersldb
):
722 shutil
.copy(usersldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.rootdn
).upper()))
724 if os
.path
.isfile(configldb
):
725 shutil
.copy(configldb
,os
.path
.join(samldbdir
,"%s.ldb"%str
(names
.configdn
).upper()))
728 def update_privilege(newpaths
,paths
):
729 message(SIMPLE
,"Copy privilege")
730 shutil
.copy(os
.path
.join(newpaths
.private_dir
,"privilege.ldb"),os
.path
.join(paths
.private_dir
,"privilege.ldb"))
732 # For each partition check the differences
733 def update_samdb(newpaths
,paths
,creds
,session
,names
):
735 message(SIMPLE
, "Doing schema update")
736 hashdef
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.schemadn
),names
,1)
737 message(SIMPLE
,"Done with schema update")
738 message(SIMPLE
,"Scanning whole provision for updates and additions")
739 hashSD
= check_diff_name(newpaths
,paths
,creds
,session
,str(names
.rootdn
),names
,0)
740 message(SIMPLE
,"Done with scanning")
742 def update_machine_account_password(paths
,creds
,session
,names
):
744 secrets_ldb
= Ldb(paths
.secrets
, session_info
=session
, credentials
=creds
,lp
=lp
)
745 secrets_ldb
.transaction_start()
746 secrets_msg
= secrets_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=["secureChannelType"])
747 sam_ldb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
748 sam_ldb
.transaction_start()
749 if int(secrets_msg
[0]["secureChannelType"][0]) == SEC_CHAN_BDC
:
750 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
), attrs
=[])
751 assert(len(res
) == 1)
753 msg
= ldb
.Message(res
[0].dn
)
754 machinepass
= glue
.generate_random_str(12)
755 msg
["userPassword"] = ldb
.MessageElement(machinepass
, ldb
.FLAG_MOD_REPLACE
, "userPassword")
758 res
= sam_ldb
.search(expression
=("samAccountName=%s$" % names
.netbiosname
),
759 attrs
=["msDs-keyVersionNumber"])
760 assert(len(res
) == 1)
761 kvno
= int(str(res
[0]["msDs-keyVersionNumber"]))
763 secretsdb_self_join(secrets_ldb
, domain
=names
.domain
,
765 domainsid
=names
.domainsid
,
766 dnsdomain
=names
.dnsdomain
,
767 netbiosname
=names
.netbiosname
,
768 machinepass
=machinepass
,
769 key_version_number
=kvno
,
770 secure_channel_type
=int(secrets_msg
[0]["secureChannelType"][0]))
771 sam_ldb
.transaction_prepare_commit()
772 secrets_ldb
.transaction_prepare_commit()
773 sam_ldb
.transaction_commit()
774 secrets_ldb
.transaction_commit()
776 secrets_ldb
.transaction_cancel()
778 # From here start the big steps of the program
779 # First get files paths
780 paths
=get_paths(smbconf
=smbconf
)
781 paths
.setup
= setup_dir
782 def setup_path(file):
783 return os
.path
.join(setup_dir
, file)
784 # Guess all the needed names (variables in fact) from the current
786 names
= guess_names_from_current_provision(creds
,session
,paths
)
789 # With all this information let's create a fresh new provision used as reference
790 provisiondir
= newprovision(names
,setup_dir
,creds
,session
,smbconf
)
791 # Get file paths of this new provision
792 newpaths
= get_paths(targetdir
=provisiondir
)
793 populate_backlink(newpaths
,creds
,session
,names
.schemadn
)
794 # Check the difference
795 update_basesamdb(newpaths
,paths
,names
)
798 update_samdb(newpaths
,paths
,creds
,session
,names
)
799 update_secrets(newpaths
,paths
,creds
,session
)
800 update_privilege(newpaths
,paths
)
801 update_machine_account_password(paths
,creds
,session
,names
)
802 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
803 # to recreate them with the good form but with system account and then give the ownership to admin ...
804 admin_session_info
= admin_session(lp
, str(names
.domainsid
))
805 message(SIMPLE
,"Updating SD")
806 update_sd(paths
,creds
,session
,names
)
807 update_sd(paths
,creds
,admin_session_info
,names
)
808 check_updated_sd(newpaths
,paths
,creds
,session
,names
)
809 message(SIMPLE
,"Upgrade finished !")
810 # remove reference provision now that everything is done !