s4:provision: use generate_random_password()
[samba.git] / source4 / scripting / bin / upgradeprovision
1 #!/usr/bin/python
2 #
3 # Copyright (C) Matthieu Patou <mat@matws.net> 2009
4 #
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
8 #
9 #
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.
14 #
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.
19 #
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/>.
22
23
24 import getopt
25 import shutil
26 import optparse
27 import os
28 import sys
29 import random
30 import string
31 import re
32 import base64
33 import tempfile
34 # Find right directory when running from source tree
35 sys.path.insert(0, "bin/python")
36
37 from base64 import b64encode
38
39 import samba
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
44 import ldb
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
56
57 never=0
58 replace=2^ldb.FLAG_MOD_REPLACE
59 add=2^ldb.FLAG_MOD_ADD
60 delete=2^ldb.FLAG_MOD_DELETE
61
62 #Errors are always logged
63 ERROR =         -1
64 SIMPLE =        0x00
65 CHANGE =        0x01
66 CHANGESD =      0x02
67 GUESS =         0x04
68 PROVISION =     0x08
69 CHANGEALL =     0xff
70
71 # Attributes that are never copied from the reference provision (even if they
72 # do not exist in the destination object).
73 # This is most probably because they are populated automatcally when object is
74 # created
75 # This also apply to imported object from reference provision
76 hashAttrNotCopied = {   "dn": 1,"whenCreated": 1,"whenChanged": 1,"objectGUID": 1,"replPropertyMetaData": 1,"uSNChanged": 1,
77                                                 "uSNCreated": 1,"parentGUID": 1,"objectCategory": 1,"distinguishedName": 1,
78                                                 "showInAdvancedViewOnly": 1,"instanceType": 1, "cn": 1, "msDS-Behavior-Version":1, "nextRid":1,
79                                                 "nTMixedDomain": 1,"versionNumber":1, "lmPwdHistory":1, "pwdLastSet": 1, "ntPwdHistory":1, "unicodePwd":1,
80                                                 "dBCSPwd":1,"supplementalCredentials":1,"gPCUserExtensionNames":1, "gPCMachineExtensionNames":1,
81                                                 "maxPwdAge":1, "mail":1, "secret":1,"possibleInferiors":1, "sAMAccountType":1}
82
83 # Usually for an object that already exists we do not overwrite attributes as
84 # they might have been changed for good reasons. Anyway for a few of them it's
85 # mandatory to replace them otherwise the provision will be broken somehow.
86 hashOverwrittenAtt = {  "prefixMap": replace, "systemMayContain": replace,"systemOnly":replace, "searchFlags":replace,
87                                                 "mayContain":replace,  "systemFlags":replace,"description":replace,
88                                                 "oEMInformation":replace, "operatingSystemVersion":replace, "adminPropertyPages":replace,
89                                                 "defaultSecurityDescriptor": replace,"wellKnownObjects":replace,"privilege":delete,"groupType":replace,
90                                                 "rIDAvailablePool": never}
91
92
93 backlinked = []
94 dn_syntax_att = []
95 def define_what_to_log(opts):
96         what = 0
97         if opts.debugchange:
98                 what = what | CHANGE
99         if opts.debugchangesd:
100                 what = what | CHANGESD
101         if opts.debugguess:
102                 what = what | GUESS
103         if opts.debugprovision:
104                 what = what | PROVISION
105         if opts.debugall:
106                 what = what | CHANGEALL
107         return what
108
109
110 parser = optparse.OptionParser("provision [options]")
111 sambaopts = options.SambaOptions(parser)
112 parser.add_option_group(sambaopts)
113 parser.add_option_group(options.VersionOptions(parser))
114 credopts = options.CredentialsOptions(parser)
115 parser.add_option_group(credopts)
116 parser.add_option("--setupdir", type="string", metavar="DIR",
117                                         help="directory with setup files")
118 parser.add_option("--debugprovision", help="Debug provision", action="store_true")
119 parser.add_option("--debugguess", help="Print information on what is different but won't be changed", action="store_true")
120 parser.add_option("--debugchange", help="Print information on what is different but won't be changed", action="store_true")
121 parser.add_option("--debugchangesd", help="Print information security descriptors differences", action="store_true")
122 parser.add_option("--debugall", help="Print all available information (very verbose)", action="store_true")
123 parser.add_option("--full", help="Perform full upgrade of the samdb (schema, configuration, new objects, ...", action="store_true")
124
125 opts = parser.parse_args()[0]
126
127 whatToLog = define_what_to_log(opts)
128
129 def messageprovision(text):
130         """print a message if quiet is not set."""
131         if opts.debugprovision or opts.debugall:
132                 print text
133
134 def message(what,text):
135         """print a message if quiet is not set."""
136         if (whatToLog & what) or (what <= 0 ):
137                 print text
138
139 if len(sys.argv) == 1:
140         opts.interactive = True
141 lp = sambaopts.get_loadparm()
142 smbconf = lp.configfile
143
144 creds = credopts.get_credentials(lp)
145 creds.set_kerberos_state(DONT_USE_KERBEROS)
146 setup_dir = opts.setupdir
147 if setup_dir is None:
148     setup_dir = find_setup_dir()
149
150 session = system_session()
151
152 # simple helper to allow back and forth rename
153 def identic_rename(ldbobj,dn):
154         (before,sep,after)=str(dn).partition('=')
155         ldbobj.rename(dn,ldb.Dn(ldbobj,"%s=foo%s"%(before,after)))
156         ldbobj.rename(ldb.Dn(ldbobj,"%s=foo%s"%(before,after)),dn)
157
158 # Create an array of backlinked attributes
159 def populate_backlink(newpaths,creds,session,schemadn):
160         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
161         linkedAttHash = get_linked_attributes(ldb.Dn(newsam_ldb,str(schemadn)),newsam_ldb)
162         backlinked.extend(linkedAttHash.values())
163
164 # Create an array of  attributes with a dn synthax (2.5.5.1)
165 def populate_dnsyntax(newpaths,creds,session,schemadn):
166         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
167         res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=ldb.Dn(newsam_ldb,str(schemadn)), scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"])
168         for elem in res:
169                 dn_syntax_att.append(elem["lDAPDisplayName"])
170
171 # Get Paths for important objects (ldb, keytabs ...)
172 def get_paths(targetdir=None,smbconf=None):
173         if targetdir is not None:
174                 if (not os.path.exists(os.path.join(targetdir, "etc"))):
175                         os.makedirs(os.path.join(targetdir, "etc"))
176                 smbconf = os.path.join(targetdir, "etc", "smb.conf")
177         if smbconf is None:
178                         smbconf = param.default_path()
179
180         if not os.path.exists(smbconf):
181                 message(ERROR,"Unable to find smb.conf ..")
182                 parser.print_usage()
183                 sys.exit(1)
184
185         lp = param.LoadParm()
186         lp.load(smbconf)
187 # Normally we need the domain name for this function but for our needs it's
188 # pointless
189         paths = provision_paths_from_lp(lp,"foo")
190         return paths
191
192
193 def sanitychecks(credentials,session_info,names,paths):
194         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
195         # First update the SD for the rootdn
196         sam_ldb.set_session_info(session)
197         res = sam_ldb.search(expression="objectClass=ntdsdsa",base=str(names.configdn), scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
198         if len(res) == 0:
199                 print "No DC found, your provision is most probalby hardly broken !"
200                 return 0
201         elif len(res) != 1:
202                 print "Found %d domain controllers, for the moment upgradeprovision is not able to handle upgrade on \
203 domain with more than one DC, please demote the other DC before upgrading"%len(res)
204                 return 0
205         else:
206                 return 1
207
208
209 # This function guesses (fetches) informations needed to make a fresh provision
210 # from the current provision
211 # It includes: realm, workgroup, partitions, netbiosname, domain guid, ...
212 def guess_names_from_current_provision(credentials,session_info,paths):
213         lp = param.LoadParm()
214         lp.load(paths.smbconf)
215         names = ProvisionNames()
216         # NT domain, kerberos realm, root dn, domain dn, domain dns name
217         names.domain = string.upper(lp.get("workgroup"))
218         names.realm = lp.get("realm")
219         basedn = "DC=" + names.realm.replace(".",",DC=")
220         names.dnsdomain = names.realm
221         names.realm = string.upper(names.realm)
222         # netbiosname
223         secrets_ldb = Ldb(paths.secrets, session_info=session_info, credentials=credentials,lp=lp, options=["modules:samba_secrets"])
224         # Get the netbiosname first (could be obtained from smb.conf in theory)
225         attrs = ["sAMAccountName"]
226         res = secrets_ldb.search(expression="(flatname=%s)"%names.domain,base="CN=Primary Domains", scope=SCOPE_SUBTREE, attrs=attrs)
227         names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
228
229         names.smbconf = smbconf
230         # It's important here to let ldb load with the old module or it's quite
231         # certain that the LDB won't load ...
232         samdb = Ldb(paths.samdb, session_info=session_info,
233                     credentials=credentials, lp=lp, options=["modules:samba_dsdb"])
234
235         # That's a bit simplistic but it's ok as long as we have only 3
236         # partitions
237         attrs2 = ["defaultNamingContext", "schemaNamingContext","configurationNamingContext","rootDomainNamingContext"]
238         current = samdb.search(expression="(objectClass=*)",base="", scope=SCOPE_BASE, attrs=attrs2)
239
240         names.configdn = current[0]["configurationNamingContext"]
241         configdn = str(names.configdn)
242         names.schemadn = current[0]["schemaNamingContext"]
243         if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb, current[0]["defaultNamingContext"][0]))):
244                 raise ProvisioningError(("basedn in %s (%s) and from %s (%s) is not the same ..." % (paths.samdb, str(current[0]["defaultNamingContext"][0]), paths.smbconf, basedn)))
245
246         names.domaindn=current[0]["defaultNamingContext"]
247         names.rootdn=current[0]["rootDomainNamingContext"]
248         # default site name
249         attrs3 = ["cn"]
250         res3= samdb.search(expression="(objectClass=*)",base="CN=Sites,"+configdn, scope=SCOPE_ONELEVEL, attrs=attrs3)
251         names.sitename = str(res3[0]["cn"])
252
253         # dns hostname and server dn
254         attrs4 = ["dNSHostName"]
255         res4= samdb.search(expression="(CN=%s)"%names.netbiosname,base="OU=Domain Controllers,"+basedn, \
256                                                 scope=SCOPE_ONELEVEL, attrs=attrs4)
257         names.hostname = str(res4[0]["dNSHostName"]).replace("."+names.dnsdomain,"")
258
259         server_res = samdb.search(expression="serverReference=%s"%res4[0].dn, attrs=[], base=configdn)
260         names.serverdn = server_res[0].dn
261
262         # invocation id/objectguid
263         res5 = samdb.search(expression="(objectClass=*)",base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE, attrs=["invocationID","objectGUID"])
264         names.invocation = str(ndr_unpack( misc.GUID,res5[0]["invocationId"][0]))
265         names.ntdsguid = str(ndr_unpack( misc.GUID,res5[0]["objectGUID"][0]))
266
267         # domain guid/sid
268         attrs6 = ["objectGUID", "objectSid","msDS-Behavior-Version" ]
269         res6 = samdb.search(expression="(objectClass=*)",base=basedn, scope=SCOPE_BASE, attrs=attrs6)
270         names.domainguid = str(ndr_unpack( misc.GUID,res6[0]["objectGUID"][0]))
271         names.domainsid = ndr_unpack( security.dom_sid,res6[0]["objectSid"][0])
272         if res6[0].get("msDS-Behavior-Version") == None or int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
273                 names.domainlevel = DS_DOMAIN_FUNCTION_2000
274         else:
275                 names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
276
277         # policy guid
278         attrs7 = ["cn","displayName"]
279         res7 = samdb.search(expression="(displayName=Default Domain Policy)",base="CN=Policies,CN=System,"+basedn, \
280                                                         scope=SCOPE_ONELEVEL, attrs=attrs7)
281         names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
282         # dc policy guid
283         attrs8 = ["cn","displayName"]
284         res8 = samdb.search(expression="(displayName=Default Domain Controllers Policy)",base="CN=Policies,CN=System,"+basedn, \
285                                                         scope=SCOPE_ONELEVEL, attrs=attrs7)
286         if len(res8) == 1:
287                 names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
288         else:
289                 names.policyid_dc = None
290
291
292         return names
293
294 # Debug a little bit
295 def print_names(names):
296         message(GUESS, "rootdn      :"+str(names.rootdn))
297         message(GUESS, "configdn    :"+str(names.configdn))
298         message(GUESS, "schemadn    :"+str(names.schemadn))
299         message(GUESS, "serverdn    :"+str(names.serverdn))
300         message(GUESS, "netbiosname :"+names.netbiosname)
301         message(GUESS, "defaultsite :"+names.sitename)
302         message(GUESS, "dnsdomain   :"+names.dnsdomain)
303         message(GUESS, "hostname    :"+names.hostname)
304         message(GUESS, "domain      :"+names.domain)
305         message(GUESS, "realm       :"+names.realm)
306         message(GUESS, "invocationid:"+names.invocation)
307         message(GUESS, "policyguid  :"+names.policyid)
308         message(GUESS, "policyguiddc:"+str(names.policyid_dc))
309         message(GUESS, "domainsid   :"+str(names.domainsid))
310         message(GUESS, "domainguid  :"+names.domainguid)
311         message(GUESS, "ntdsguid    :"+names.ntdsguid)
312         message(GUESS, "domainlevel :"+str(names.domainlevel))
313
314 # Create a fresh new reference provision
315 # This provision will be the reference for knowing what has changed in the
316 # since the latest upgrade in the current provision
317 def newprovision(names,setup_dir,creds,session,smbconf):
318         message(SIMPLE, "Creating a reference provision")
319         provdir=tempfile.mkdtemp(dir=paths.private_dir, prefix="referenceprovision")
320         if os.path.isdir(provdir):
321                 rmall(provdir)
322         logstd=os.path.join(provdir,"log.std")
323         os.chdir(os.path.join(setup_dir,".."))
324         os.mkdir(provdir)
325         os.close(2)
326         sys.stderr = open("%s/provision.log"%provdir, "w")
327         message(PROVISION, "Reference provision stored in %s"%provdir)
328         message(PROVISION, "STDERR message of provision will be logged in %s/provision.log"%provdir)
329         sys.stderr = open("/dev/stdout", "w")
330         provision(setup_dir, messageprovision,
331                 session, creds, smbconf=smbconf, targetdir=provdir,
332                 samdb_fill=FILL_FULL, realm=names.realm, domain=names.domain,
333                 domainguid=names.domainguid, domainsid=str(names.domainsid),ntdsguid=names.ntdsguid,
334                 policyguid=names.policyid,policyguid_dc=names.policyid_dc,hostname=names.netbiosname,
335                 hostip=None, hostip6=None,
336                 invocationid=names.invocation, adminpass=None,
337                 krbtgtpass=None, machinepass=None,
338                 dnspass=None, root=None, nobody=None,
339                 wheel=None, users=None,
340                 serverrole="domain controller",
341                 ldap_backend_extra_port=None,
342                 backend_type=None,
343                 ldapadminpass=None,
344                 ol_mmr_urls=None,
345                 slapd_path=None,
346                 setup_ds_path=None,
347                 nosync=None,
348                 dom_for_fun_level=names.domainlevel,
349                 ldap_dryrun_mode=None,useeadb=True)
350         return provdir
351
352 # This function sorts two DNs in the lexicographical order and put higher level
353 # DN before.
354 # So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller
355 # (-1) as it has less level
356 def dn_sort(x,y):
357         p = re.compile(r'(?<!\\),')
358         tab1 = p.split(str(x))
359         tab2 = p.split(str(y))
360         min = 0
361         if (len(tab1) > len(tab2)):
362                 min = len(tab2)
363         elif (len(tab1) < len(tab2)):
364                 min = len(tab1)
365         else:
366                 min = len(tab1)
367         len1=len(tab1)-1
368         len2=len(tab2)-1
369         space = " "
370         # Note: python range go up to upper limit but do not include it
371         for i in range(0,min):
372                 ret=cmp(tab1[len1-i],tab2[len2-i])
373                 if(ret != 0):
374                         return ret
375                 else:
376                         if(i==min-1):
377                                 if(len1==len2):
378                                         message(ERROR,"PB PB PB"+space.join(tab1)+" / "+space.join(tab2))
379                                 if(len1>len2):
380                                         return 1
381                                 else:
382                                         return -1
383         return ret
384
385 # Check for security descriptors modifications return 1 if it is and 0 otherwise
386 # it also populate hash structure for later use in the upgrade process
387 def handle_security_desc(ischema,att,msgElt,hashallSD,old,new):
388         if ischema == 1 and att == "defaultSecurityDescriptor"  and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
389                 hashSD = {}
390                 hashSD["oldSD"] = old[0][att]
391                 hashSD["newSD"] = new[0][att]
392                 hashallSD[str(old[0].dn)] = hashSD
393                 return 0
394         if att == "nTSecurityDescriptor"  and msgElt.flags() == ldb.FLAG_MOD_REPLACE:
395                 if ischema == 0:
396                         hashSD = {}
397                         hashSD["oldSD"] =  ndr_unpack(security.descriptor,str(old[0][att]))
398                         hashSD["newSD"] =  ndr_unpack(security.descriptor,str(new[0][att]))
399                         hashallSD[str(old[0].dn)] = hashSD
400                 return 1
401         return 0
402
403 # Handle special cases ... That's when we want to update a particular attribute
404 # only, e.g. if it has a certain value or if it's for a certain object or
405 # a class of object.
406 # It can be also if we want to do a merge of value instead of a simple replace
407 def handle_special_case(att,delta,new,old,ischema):
408         flag = delta.get(att).flags()
409         if (att == "gPLink" or att == "gPCFileSysPath") and flag ==  ldb.FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower():
410                 delta.remove(att)
411                 return 1
412         if att == "forceLogoff":
413                 ref=0x8000000000000000
414                 oldval=int(old[0][att][0])
415                 newval=int(new[0][att][0])
416                 ref == old and ref == abs(new)
417                 return 1
418         if (att == "adminDisplayName" or att == "adminDescription") and ischema:
419                 return 1
420         if (str(old[0].dn) == "CN=Samba4-Local-Domain,%s"%(str(names.schemadn)) and att == "defaultObjectCategory" and flag  == ldb.FLAG_MOD_REPLACE):
421                 return 1
422 #       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):
423 #               return 1
424         if (str(old[0].dn) == "CN=Title,%s"%(str(names.schemadn)) and att == "rangeUpper" and flag  == ldb.FLAG_MOD_REPLACE):
425                 return 1
426         if ( (att == "member" or att == "servicePrincipalName") and flag  == ldb.FLAG_MOD_REPLACE):
427
428                 hash = {}
429                 newval = []
430                 changeDelta=0
431                 for elem in old[0][att]:
432                         hash[str(elem)]=1
433                         newval.append(str(elem))
434
435                 for elem in new[0][att]:
436                         if not hash.has_key(str(elem)):
437                                 changeDelta=1
438                                 newval.append(str(elem))
439                 if changeDelta == 1:
440                         delta[att] = ldb.MessageElement(newval, ldb.FLAG_MOD_REPLACE, att)
441                 else:
442                         delta.remove(att)
443                 return 1
444         if (str(old[0].dn) == "%s"%(str(names.rootdn)) and att == "subRefs" and flag  == ldb.FLAG_MOD_REPLACE):
445                 return 1
446         if str(delta.dn).endswith("CN=DisplaySpecifiers,%s"%names.configdn):
447                 return 1
448         return 0
449
450 def update_secrets(newpaths,paths,creds,session):
451         message(SIMPLE,"update secrets.ldb")
452         newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
453         secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"])
454         reference = newsecrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
455         current = secrets_ldb.search(expression="dn=@MODULES",base="", scope=SCOPE_SUBTREE)
456         delta = secrets_ldb.msg_diff(current[0],reference[0])
457         delta.dn = current[0].dn
458         secrets_ldb.modify(delta)
459
460         newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp)
461         secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
462         reference = newsecrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
463         current = secrets_ldb.search(expression="objectClass=top",base="", scope=SCOPE_SUBTREE,attrs=["dn"])
464         hash_new = {}
465         hash = {}
466         listMissing = []
467         listPresent = []
468
469         empty = ldb.Message()
470         for i in range(0,len(reference)):
471                 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
472
473         # Create a hash for speeding the search of existing object in the
474         # current provision
475         for i in range(0,len(current)):
476                 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
477
478         for k in hash_new.keys():
479                 if not hash.has_key(k):
480                         listMissing.append(hash_new[k])
481                 else:
482                         listPresent.append(hash_new[k])
483         for entry in listMissing:
484                 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
485                 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
486                 delta = secrets_ldb.msg_diff(empty,reference[0])
487                 for att in hashAttrNotCopied.keys():
488                         delta.remove(att)
489                 message(CHANGE,"Entry %s is missing from secrets.ldb"%reference[0].dn)
490                 for att in delta:
491                         message(CHANGE," Adding attribute %s"%att)
492                 delta.dn = reference[0].dn
493                 secrets_ldb.add(delta)
494
495         for entry in listPresent:
496                 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
497                 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
498                 delta = secrets_ldb.msg_diff(current[0],reference[0])
499                 i=0
500                 for att in hashAttrNotCopied.keys():
501                         delta.remove(att)
502                 for att in delta:
503                         i = i + 1
504
505                         if att == "name":
506                                 message(CHANGE,"Found attribute name on  %s, must rename the DN "%(current[0].dn))
507                                 identic_rename(secrets_ldb,reference[0].dn)
508                         else:
509                                 delta.remove(att)
510
511
512         for entry in listPresent:
513                 reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
514                 current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE)
515                 delta = secrets_ldb.msg_diff(current[0],reference[0])
516                 i=0
517                 for att in hashAttrNotCopied.keys():
518                         delta.remove(att)
519                 for att in delta:
520                         i = i + 1
521                         if att != "dn":
522                                 message(CHANGE," Adding/Changing attribute %s to %s"%(att,current[0].dn))
523
524                 delta.dn = current[0].dn
525                 secrets_ldb.modify(delta)
526
527 def dump_denied_change(dn,att,flagtxt,current,reference):
528         message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...")
529         if att != "objectSid" :
530                 i = 0
531                 for e in range(0,len(current)):
532                         message(CHANGE,"old %d : %s"%(i,str(current[e])))
533                         i=i+1
534                 if reference != None:
535                         i = 0
536                         for e in range(0,len(reference)):
537                                         message(CHANGE,"new %d : %s"%(i,str(reference[e])))
538                                         i=i+1
539         else:
540                 message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0])))
541                 message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0])))
542
543 #This function is for doing case by case treatment on special object
544
545 def handle_special_add(sam_ldb,dn,names):
546         dntoremove=None
547         if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower():
548                 #This entry was misplaced lets remove it if it exists
549                 dntoremove="CN=Certificate Service DCOM Access,CN=Users,%s"%names.rootdn
550
551         if str(dn).lower() == ("CN=Cryptographic Operators,CN=Builtin,%s"%names.rootdn).lower():
552                 #This entry was misplaced lets remove it if it exists
553                 dntoremove="CN=Cryptographic Operators,CN=Users,%s"%names.rootdn
554
555         if str(dn).lower() == ("CN=Event Log Readers,CN=Builtin,%s"%names.rootdn).lower():
556                 #This entry was misplaced lets remove it if it exists
557                 dntoremove="CN=Event Log Readers,CN=Users,%s"%names.rootdn
558
559         if dntoremove != None:
560                 res = sam_ldb.search(expression="objectClass=*",base=dntoremove, scope=SCOPE_BASE,attrs=["dn"],controls=["search_options:1:2"])
561                 if len(res) > 0:
562                         message(CHANGE,"Existing object %s must be replaced by %s, removing old object"%(dntoremove,str(dn)))
563                         sam_ldb.delete(res[0]["dn"])
564
565 #Check if the one of the dn in the listdn will be created after the current dn
566 #hash is indexed by dn to be created, with each key is associated the creation order
567 #First dn to be created has the creation order 0, second has 1, ...
568 #Index contain the current creation order
569 def check_dn_nottobecreated(hash,index,listdn):
570         if listdn == None:
571                 return None
572         for dn in listdn:
573                 key = str(dn).lower()
574                 if hash.has_key(key) and hash[key] > index:
575                         return str(dn)
576         return None
577
578 #This function tries to add the missing object "dn" if this object depends on some others
579 # the function returns 0, if the object was created 1 is returned
580 def add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hash,index):
581         handle_special_add(sam_ldb,dn,names)
582         reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn,
583                                         scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
584         empty = ldb.Message()
585         delta = sam_ldb.msg_diff(empty,reference[0])
586         for att in hashAttrNotCopied.keys():
587                 delta.remove(att)
588         for att in backlinked:
589                 delta.remove(att)
590         depend_on_yettobecreated = None
591         for att in dn_syntax_att:
592                 depend_on_yet_tobecreated = check_dn_nottobecreated(hash,index,delta.get(str(att)))
593                 if depend_on_yet_tobecreated != None:
594                         message(CHANGE,"Object %s depends on %s in attribute %s, delaying the creation"
595                                                         %(str(dn),depend_on_yet_tobecreated,str(att)))
596                         return 0
597         delta.dn = dn
598         message(CHANGE,"Object %s will be added"%dn)
599         sam_ldb.add(delta,["relax:0"])
600         return 1
601
602 def gen_dn_index_hash(listMissing):
603         hash = {}
604         for i in range(0,len(listMissing)):
605                 hash[str(listMissing[i]).lower()] = i
606         return hash
607
608 def add_missing_entries(newsam_ldb,sam_ldb,names,basedn,list):
609         listMissing = []
610         listDefered = list
611
612         while(len(listDefered) != len(listMissing) and len(listDefered) > 0):
613                 index = 0
614                 listMissing = listDefered
615                 listDefered = []
616                 hashMissing = gen_dn_index_hash(listMissing)
617                 for dn in listMissing:
618                         ret =  add_missing_object(newsam_ldb,sam_ldb,dn,names,basedn,hashMissing,index)
619                         index = index + 1
620                         if ret == 0:
621                                 #DN can't be created because it depends on some other DN in the list
622                                 listDefered.append(dn)
623         if len(listDefered) != 0:
624                 raise ProvisioningError("Unable to insert missing elements: circular references")
625
626
627
628
629 # Check difference between the current provision and the reference provision.
630 # It looks for all objects which base DN is name. If ischema is "false" then
631 # the scan is done in cross partition mode.
632 # If "ischema" is true, then special handling is done for dealing with schema
633 def check_diff_name(newpaths,paths,creds,session,basedn,names,ischema):
634         hash_new = {}
635         hash = {}
636         hashallSD = {}
637         listMissing = []
638         listPresent = []
639         reference = []
640         current = []
641         # Connect to the reference provision and get all the attribute in the
642         # partition referred by name
643         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
644         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
645         sam_ldb.transaction_start()
646         if ischema:
647                 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
648                 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"])
649         else:
650                 reference = newsam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
651                 current = sam_ldb.search(expression="objectClass=*",base=basedn, scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"])
652
653         sam_ldb.transaction_commit()
654         # Create a hash for speeding the search of new object
655         for i in range(0,len(reference)):
656                 hash_new[str(reference[i]["dn"]).lower()] = reference[i]["dn"]
657
658         # Create a hash for speeding the search of existing object in the
659         # current provision
660         for i in range(0,len(current)):
661                 hash[str(current[i]["dn"]).lower()] = current[i]["dn"]
662
663         for k in hash_new.keys():
664                 if not hash.has_key(k):
665                         print hash_new[k]
666                         listMissing.append(hash_new[k])
667                 else:
668                         listPresent.append(hash_new[k])
669
670         # Sort the missing object in order to have object of the lowest level
671         # first (which can be containers for higher level objects)
672         listMissing.sort(dn_sort)
673         listPresent.sort(dn_sort)
674
675         if ischema:
676                 # The following lines (up to the for loop) is to load the up to
677                 # date schema into our current LDB
678                 # a complete schema is needed as the insertion of attributes
679                 # and class is done against it
680                 # and the schema is self validated
681                 # The double ldb open and schema validation is taken from the
682                 # initial provision script
683                 # it's not certain that it is really needed ....
684                 sam_ldb = Ldb(session_info=session, credentials=creds, lp=lp)
685                 schema = Schema(setup_path, names.domainsid, schemadn=basedn, serverdn=str(names.serverdn))
686                 # Load the schema from the one we computed earlier
687                 sam_ldb.set_schema_from_ldb(schema.ldb)
688                 # And now we can connect to the DB - the schema won't be loaded
689                 # from the DB
690                 sam_ldb.connect(paths.samdb)
691         else:
692                 sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp, options=["modules:samba_dsdb"])
693
694         sam_ldb.transaction_start()
695
696         message(SIMPLE,"There are %d missing objects"%(len(listMissing)))
697         add_missing_entries(newsam_ldb,sam_ldb,names,basedn,listMissing)
698         changed = 0
699         for dn in listPresent:
700                 reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
701                 current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
702                 if ((str(current[0].dn) != str(reference[0].dn)) and (str(current[0].dn).upper() == str(reference[0].dn).upper())):
703                         message(CHANGE,"Name are the same but case change, let's rename %s to %s"%(str(current[0].dn),str(reference[0].dn)))
704                         identic_rename(sam_ldb,reference[0].dn)
705                         current = sam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"])
706
707                 delta = sam_ldb.msg_diff(current[0],reference[0])
708                 for att in hashAttrNotCopied.keys():
709                         delta.remove(att)
710                 for att in backlinked:
711                         delta.remove(att)
712                 delta.remove("parentGUID")
713                 nb = 0
714                 
715                 for att in delta:
716                         msgElt = delta.get(att)
717                         if att == "dn":
718                                 continue
719                         if att == "name":
720                                 delta.remove(att)
721                                 continue
722                         if handle_security_desc(ischema,att,msgElt,hashallSD,current,reference):
723                                 delta.remove(att)
724                                 continue
725                         if (not hashOverwrittenAtt.has_key(att) or not (hashOverwrittenAtt.get(att)&2^msgElt.flags())):
726                                 if  hashOverwrittenAtt.has_key(att) and hashOverwrittenAtt.get(att)==never:
727                                         delta.remove(att)
728                                         continue
729                                 if  handle_special_case(att,delta,reference,current,ischema)==0 and msgElt.flags()!=ldb.FLAG_MOD_ADD:
730                                         i = 0
731                                         if opts.debugchange or opts.debugall:
732                                                 try:
733                                                         dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],reference[0][att])
734                                                 except:
735                                                         dump_denied_change(dn,att,messageEltFlagToString(msgElt.flags()),current[0][att],None)
736                                         delta.remove(att)
737                 delta.dn = dn
738                 if len(delta.items()) >1:
739                         attributes=",".join(delta.keys())
740                         message(CHANGE,"%s is different from the reference one, changed attributes: %s"%(dn,attributes))
741                         changed = changed + 1
742                         sam_ldb.modify(delta)
743
744         sam_ldb.transaction_commit()
745         message(SIMPLE,"There are %d changed objects"%(changed))
746         return hashallSD
747
748 # Check that SD are correct
749 def check_updated_sd(newpaths,paths,creds,session,names):
750         newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp)
751         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
752         reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
753         current = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
754         hash_new = {}
755         for i in range(0,len(reference)):
756                 hash_new[str(reference[i]["dn"]).lower()] = ndr_unpack(security.descriptor,str(reference[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
757
758         for i in range(0,len(current)):
759                 key = str(current[i]["dn"]).lower()
760                 if hash_new.has_key(key):
761                         sddl = ndr_unpack(security.descriptor,str(current[i]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
762                         if sddl != hash_new[key]:
763                                 print "%s new sddl/sddl in ref"%key
764                                 print "%s\n%s"%(sddl,hash_new[key])
765
766 # Simple update method for updating the SD that rely on the fact that nobody
767 # should have modified the SD
768 # This assumption is safe right now (alpha9) but should be removed asap
769 def update_sd(paths,creds,session,names):
770         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"])
771         sam_ldb.transaction_start()
772         # First update the SD for the rootdn
773         sam_ldb.set_session_info(session)
774         res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
775         delta = ldb.Message()
776         delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
777         descr = get_domain_descriptor(names.domainsid)
778         delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
779         sam_ldb.modify(delta,["recalculate_sd:0"])
780         # Then the config dn
781         res = sam_ldb.search(expression="objectClass=*",base=str(names.configdn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
782         delta = ldb.Message()
783         delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
784         descr = get_config_descriptor(names.domainsid)
785         delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
786         sam_ldb.modify(delta,["recalculate_sd:0"])
787         # Then the schema dn
788         res = sam_ldb.search(expression="objectClass=*",base=str(names.schemadn), scope=SCOPE_BASE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
789         delta = ldb.Message()
790         delta.dn = ldb.Dn(sam_ldb,str(res[0]["dn"]))
791         descr = get_schema_descriptor(names.domainsid)
792         delta["nTSecurityDescriptor"] = ldb.MessageElement( descr,ldb.FLAG_MOD_REPLACE,"nTSecurityDescriptor" )
793         sam_ldb.modify(delta,["recalculate_sd:0"])
794
795         # Then the rest
796         hash = {}
797         res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","whenCreated"],controls=["search_options:1:2"])
798         for obj in res:
799                 if not (str(obj["dn"]) == str(names.rootdn) or
800                         str(obj["dn"]) == str(names.configdn) or \
801                         str(obj["dn"]) == str(names.schemadn)):
802                         hash[str(obj["dn"])] = obj["whenCreated"]
803
804         listkeys = hash.keys()
805         listkeys.sort(dn_sort)
806
807         for key in listkeys:
808                 try:
809                         delta = ldb.Message()
810                         delta.dn = ldb.Dn(sam_ldb,key)
811                         delta["whenCreated"] = ldb.MessageElement( hash[key],ldb.FLAG_MOD_REPLACE,"whenCreated" )
812                         sam_ldb.modify(delta,["recalculate_sd:0"])
813                 except:
814                         sam_ldb.transaction_cancel()
815                         res = sam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"])
816                         print "bad stuff" +ndr_unpack(security.descriptor,str(res[0]["nTSecurityDescriptor"])).as_sddl(names.domainsid)
817                         return
818         sam_ldb.transaction_commit()
819
820 def rmall(topdir):
821         for root, dirs, files in os.walk(topdir, topdown=False):
822                 for name in files:
823                         os.remove(os.path.join(root, name))
824                 for name in dirs:
825                         os.rmdir(os.path.join(root, name))
826         os.rmdir(topdir)
827
828
829 def update_basesamdb(newpaths,paths,names):
830         message(SIMPLE,"Copy samdb")
831         shutil.copy(newpaths.samdb,paths.samdb)
832
833         message(SIMPLE,"Update partitions filename if needed")
834         schemaldb=os.path.join(paths.private_dir,"schema.ldb")
835         configldb=os.path.join(paths.private_dir,"configuration.ldb")
836         usersldb=os.path.join(paths.private_dir,"users.ldb")
837         samldbdir=os.path.join(paths.private_dir,"sam.ldb.d")
838
839         if not os.path.isdir(samldbdir):
840                 os.mkdir(samldbdir)
841                 os.chmod(samldbdir,0700)
842         if os.path.isfile(schemaldb):
843                 shutil.copy(schemaldb,os.path.join(samldbdir,"%s.ldb"%str(names.schemadn).upper()))
844                 os.remove(schemaldb)
845         if os.path.isfile(usersldb):
846                 shutil.copy(usersldb,os.path.join(samldbdir,"%s.ldb"%str(names.rootdn).upper()))
847                 os.remove(usersldb)
848         if os.path.isfile(configldb):
849                 shutil.copy(configldb,os.path.join(samldbdir,"%s.ldb"%str(names.configdn).upper()))
850                 os.remove(configldb)
851
852 def update_privilege(newpaths,paths):
853         message(SIMPLE,"Copy privilege")
854         shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb"))
855
856 # For each partition check the differences
857 def update_samdb(newpaths,paths,creds,session,names):
858
859         message(SIMPLE, "Doing schema update")
860         hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1)
861         message(SIMPLE,"Done with schema update")
862         message(SIMPLE,"Scanning whole provision for updates and additions")
863         hashSD = check_diff_name(newpaths,paths,creds,session,str(names.rootdn),names,0)
864         message(SIMPLE,"Done with scanning")
865
866 def update_machine_account_password(paths,creds,session,names):
867
868         secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp)
869         secrets_ldb.transaction_start()
870         secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"])
871         sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp)
872         sam_ldb.transaction_start()
873         if int(secrets_msg[0]["secureChannelType"][0]) == SEC_CHAN_BDC:
874                 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=[])
875                 assert(len(res) == 1)
876
877                 msg = ldb.Message(res[0].dn)
878                 machinepass = glue.generate_random_password(128, 255)
879                 msg["userPassword"] = ldb.MessageElement(machinepass, ldb.FLAG_MOD_REPLACE, "userPassword")
880                 sam_ldb.modify(msg)
881
882                 res = sam_ldb.search(expression=("samAccountName=%s$" % names.netbiosname),
883                                      attrs=["msDs-keyVersionNumber"])
884                 assert(len(res) == 1)
885                 kvno = int(str(res[0]["msDs-keyVersionNumber"]))
886
887                 secretsdb_self_join(secrets_ldb, domain=names.domain,
888                                     realm=names.realm,
889                                     domainsid=names.domainsid,
890                                     dnsdomain=names.dnsdomain,
891                                     netbiosname=names.netbiosname,
892                                     machinepass=machinepass,
893                                     key_version_number=kvno,
894                                     secure_channel_type=int(secrets_msg[0]["secureChannelType"][0]))
895                 sam_ldb.transaction_prepare_commit()
896                 secrets_ldb.transaction_prepare_commit()
897                 sam_ldb.transaction_commit()
898                 secrets_ldb.transaction_commit()
899         else:
900                 secrets_ldb.transaction_cancel()
901
902 # From here start the big steps of the program
903 # First get files paths
904 paths=get_paths(smbconf=smbconf)
905 paths.setup = setup_dir
906 def setup_path(file):
907         return os.path.join(setup_dir, file)
908 # Guess all the needed names (variables in fact) from the current
909 # provision.
910 names = guess_names_from_current_provision(creds,session,paths)
911 if not sanitychecks(creds,session,names,paths):
912         print "Sanity checks for the upgrade fails, checks messages and correct it before rerunning upgradeprovision"
913         sys.exit(1)
914 # Let's see them
915 print_names(names)
916 # With all this information let's create a fresh new provision used as reference
917 provisiondir = newprovision(names,setup_dir,creds,session,smbconf)
918 # Get file paths of this new provision
919 newpaths = get_paths(targetdir=provisiondir)
920 populate_backlink(newpaths,creds,session,names.schemadn)
921 populate_dnsyntax(newpaths,creds,session,names.schemadn)
922 # Check the difference
923 update_basesamdb(newpaths,paths,names)
924
925 if opts.full:
926         update_samdb(newpaths,paths,creds,session,names)
927 update_secrets(newpaths,paths,creds,session)
928 update_privilege(newpaths,paths)
929 update_machine_account_password(paths,creds,session,names)
930 # SD should be created with admin but as some previous acl were so wrong that admin can't modify them we have first
931 # to recreate them with the good form but with system account and then give the ownership to admin ...
932 admin_session_info = admin_session(lp, str(names.domainsid))
933 message(SIMPLE,"Updating SD")
934 update_sd(paths,creds,session,names)
935 update_sd(paths,creds,admin_session_info,names)
936 check_updated_sd(newpaths,paths,creds,session,names)
937 message(SIMPLE,"Upgrade finished !")
938 # remove reference provision now that everything is done !
939 rmall(provisiondir)