Merge pull request #3142 from henricm/fix-for-win-mono_string_to_utf8
[mono.git] / mcs / class / corlib / System.Security.Policy / PolicyLevel.cs
1 //
2 // System.Security.Policy.PolicyLevel.cs
3 //
4 // Authors:
5 //      Nick Drochak (ndrochak@gol.com)
6 //      Duncan Mak (duncan@ximian.com)
7 //      Sebastien Pouliot  <sebastien@ximian.com>
8 //
9 // (C) 2001 Nick Drochak
10 // (C) 2003 Duncan Mak, Ximian Inc.
11 // Portions (C) 2004 Motus Technologies Inc. (http://www.motus.com)
12 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System.Collections; // for IList
35 using System.Globalization;
36 using System.IO;
37 using System.Reflection;
38 using System.Runtime.InteropServices;
39 using System.Security.Permissions;
40
41 using Mono.Xml;
42
43 namespace System.Security.Policy {
44
45         [Serializable]
46         [ComVisible (true)]
47         public sealed class PolicyLevel {
48
49                 string label;
50                 CodeGroup root_code_group;
51                 private ArrayList full_trust_assemblies;
52                 private ArrayList named_permission_sets;
53                 private string _location;
54                 private PolicyLevelType _type;
55                 private Hashtable fullNames;
56                 private SecurityElement xml;
57
58                 internal PolicyLevel (string label, PolicyLevelType type)
59                 {
60                         this.label = label;
61                         _type = type;
62                         full_trust_assemblies = new ArrayList ();
63                         named_permission_sets = new ArrayList ();
64                 }
65
66                 internal void LoadFromFile (string filename)
67                 {
68                         try {
69                                 // check for policy file
70                                 if (!File.Exists (filename)) {
71                                         // if it doesn't exist use the default configuration (like Fx 2.0)
72                                         // ref: http://blogs.msdn.com/shawnfa/archive/2004/04/21/117833.aspx
73                                         string defcfg = filename + ".default";
74                                         if (File.Exists (defcfg)) {
75                                                 // create policy from default file
76                                                 File.Copy (defcfg, filename);
77                                         }
78                                 }
79                                 // load security policy configuration
80                                 if (File.Exists (filename)) {
81                                         using (StreamReader sr = File.OpenText (filename)) {
82                                                 xml = FromString (sr.ReadToEnd ());
83                                         }
84                                         try {
85                                                 SecurityManager.ResolvingPolicyLevel = this;
86                                                 FromXml (xml);
87                                         }
88                                         finally {
89                                                 SecurityManager.ResolvingPolicyLevel = this;
90                                         }
91                                 } else {
92                                         CreateDefaultFullTrustAssemblies ();
93                                         CreateDefaultNamedPermissionSets ();
94                                         CreateDefaultLevel (_type);
95                                         Save ();
96                                 }
97                         }
98                         catch {
99                                 // this can fail in many ways including...
100                                 // * can't lookup policy (path discovery);
101                                 // * can't copy default file to policy
102                                 // * can't read policy file;
103                                 // * can't decode policy file
104                                 // * can't save hardcoded policy to filename
105                         }
106                         finally {
107                                 _location = filename;
108                         }
109                 }
110
111                 internal void LoadFromString (string xml) 
112                 {
113                         FromXml (FromString (xml));
114                 }
115
116                 private SecurityElement FromString (string xml) 
117                 {
118                         SecurityParser parser = new SecurityParser ();
119                         parser.LoadXml (xml);
120                         // configuration / mscorlib / security / policy / PolicyLevel
121                         SecurityElement configuration = parser.ToXml ();
122                         if (configuration.Tag != "configuration")
123                                 throw new ArgumentException (Locale.GetText ("missing <configuration> root element"));
124                         SecurityElement mscorlib = (SecurityElement) configuration.Children [0];
125                         if (mscorlib.Tag != "mscorlib")
126                                 throw new ArgumentException (Locale.GetText ("missing <mscorlib> tag"));
127                         SecurityElement security = (SecurityElement) mscorlib.Children [0];
128                         if (security.Tag != "security")
129                                 throw new ArgumentException (Locale.GetText ("missing <security> tag"));
130                         SecurityElement policy = (SecurityElement) security.Children [0];
131                         if (policy.Tag != "policy")
132                                 throw new ArgumentException (Locale.GetText ("missing <policy> tag"));
133                         SecurityElement policyLevel = (SecurityElement) policy.Children [0];
134                         return policyLevel;
135                 }
136
137                 // properties
138
139                 [Obsolete ("All GACed assemblies are now fully trusted and all permissions now succeed on fully trusted code.")]
140                 public IList FullTrustAssemblies {
141                         get { return full_trust_assemblies; }
142                 }
143
144                 public string Label {
145                         get { return label; }
146                 }
147
148                 public IList NamedPermissionSets {
149                         get { return named_permission_sets; }
150                 }
151
152                 public CodeGroup RootCodeGroup {
153                         get { return root_code_group; }
154                         set { 
155                                 if (value == null)
156                                         throw new ArgumentNullException ("value");
157                                 root_code_group = value; 
158                         }
159                 }
160
161                 public string StoreLocation {
162                         get { return _location; }
163                 }
164
165                 [ComVisible (false)]
166                 public PolicyLevelType Type {
167                         get { return _type; }
168                 }
169
170                 // methods
171
172                 [Obsolete ("All GACed assemblies are now fully trusted and all permissions now succeed on fully trusted code.")]
173                 public void AddFullTrustAssembly (StrongName sn)
174                 {
175                         if (sn == null)
176                                 throw new ArgumentNullException ("sn");
177
178                         StrongNameMembershipCondition snMC = new StrongNameMembershipCondition(
179                                 sn.PublicKey, sn.Name, sn.Version);
180
181                         AddFullTrustAssembly (snMC);
182                 }
183
184                 [Obsolete ("All GACed assemblies are now fully trusted and all permissions now succeed on fully trusted code.")]
185                 public void AddFullTrustAssembly (StrongNameMembershipCondition snMC)
186                 {
187                         if (snMC == null)
188                                 throw new ArgumentNullException ("snMC");
189                         
190                         foreach (StrongNameMembershipCondition sn in full_trust_assemblies) {
191                                 if (sn.Equals (snMC)) {
192                                         throw new ArgumentException (Locale.GetText ("sn already has full trust."));
193                                 }
194                         }
195                         full_trust_assemblies.Add (snMC);
196                 }
197
198                 public void AddNamedPermissionSet (NamedPermissionSet permSet)
199                 {
200                         if (permSet == null)
201                                 throw new ArgumentNullException ("permSet");
202
203                         foreach (NamedPermissionSet n in named_permission_sets) {
204                                 if (permSet.Name == n.Name) {
205                                         throw new ArgumentException (
206                                                 Locale.GetText ("This NamedPermissionSet is the same an existing NamedPermissionSet."));
207                                 }
208                         }
209                         named_permission_sets.Add (permSet.Copy ());
210                 }
211
212                 public NamedPermissionSet ChangeNamedPermissionSet (string name, PermissionSet pSet)
213                 {
214                         if (name == null)
215                                 throw new ArgumentNullException ("name");
216                         if (pSet == null)
217                                 throw new ArgumentNullException ("pSet");
218                         if (DefaultPolicies.ReservedNames.IsReserved (name))
219                                 throw new ArgumentException (Locale.GetText ("Reserved name"));
220
221                         foreach (NamedPermissionSet n in named_permission_sets) {
222                                 if (name == n.Name) {
223                                         named_permission_sets.Remove (n);
224                                         AddNamedPermissionSet (new NamedPermissionSet (name, pSet));
225                                         return n;
226                                 }
227                         }
228                         throw new ArgumentException (Locale.GetText ("PermissionSet not found"));
229                 }
230
231                 public static PolicyLevel CreateAppDomainLevel ()
232                 {
233                         UnionCodeGroup cg = new UnionCodeGroup (new AllMembershipCondition (), new PolicyStatement (DefaultPolicies.FullTrust));
234                         cg.Name = "All_Code";
235                         PolicyLevel pl = new PolicyLevel ("AppDomain", PolicyLevelType.AppDomain);
236                         pl.RootCodeGroup = cg;
237                         pl.Reset ();
238                         return pl;
239                 }
240
241
242                 public void FromXml (SecurityElement e)
243                 {
244                         if (e == null)
245                                 throw new ArgumentNullException ("e");
246 // MS doesn't throw an exception for this case
247 //                      if (e.Tag != "PolicyLevel")
248 //                              throw new ArgumentException (Locale.GetText ("Invalid XML"));
249
250                         SecurityElement sc = e.SearchForChildByTag ("SecurityClasses");
251                         if ((sc != null) && (sc.Children != null) && (sc.Children.Count > 0)) {
252                                 fullNames = new Hashtable (sc.Children.Count);
253                                 foreach (SecurityElement se in sc.Children) {
254                                         fullNames.Add (se.Attributes ["Name"], se.Attributes ["Description"]);
255                                 }
256                         }
257
258                         SecurityElement fta = e.SearchForChildByTag ("FullTrustAssemblies");
259                         if ((fta != null) && (fta.Children != null) && (fta.Children.Count > 0)) {
260                                 full_trust_assemblies.Clear ();
261                                 foreach (SecurityElement se in fta.Children) {
262                                         if (se.Tag != "IMembershipCondition")
263                                                 throw new ArgumentException (Locale.GetText ("Invalid XML"));
264                                         string className = se.Attribute ("class");
265                                         if (className.IndexOf ("StrongNameMembershipCondition") < 0)
266                                                 throw new ArgumentException (Locale.GetText ("Invalid XML - must be StrongNameMembershipCondition"));
267                                         // we directly use StrongNameMembershipCondition
268                                         full_trust_assemblies.Add (new StrongNameMembershipCondition (se));
269                                 }
270                         }
271
272                         SecurityElement cg = e.SearchForChildByTag ("CodeGroup");
273                         if ((cg != null) && (cg.Children != null) && (cg.Children.Count > 0)) {
274                                 root_code_group = CodeGroup.CreateFromXml (cg, this);
275                         } else {
276                                 throw new ArgumentException (Locale.GetText ("Missing Root CodeGroup"));
277                         }
278
279                         SecurityElement nps = e.SearchForChildByTag ("NamedPermissionSets");
280                         if ((nps != null) && (nps.Children != null) && (nps.Children.Count > 0)) {
281                                 named_permission_sets.Clear ();
282                                 foreach (SecurityElement se in nps.Children) {
283                                         NamedPermissionSet n = new NamedPermissionSet ();
284                                         n.Resolver = this;
285                                         n.FromXml (se);
286                                         named_permission_sets.Add (n);
287                                 }
288                         }
289                 }
290
291                 public NamedPermissionSet GetNamedPermissionSet (string name)
292                 {
293                         if (name == null)
294                                 throw new ArgumentNullException ("name");
295
296                         foreach (NamedPermissionSet n in named_permission_sets) {
297                                 if (n.Name == name)
298                                         return (NamedPermissionSet) n.Copy ();
299                         }
300                         return null;
301                 }
302
303                 public void Recover ()
304                 {
305                         if (_location == null) {
306                                 string msg = Locale.GetText ("Only file based policies may be recovered.");
307                                 throw new PolicyException (msg);
308                         }
309
310                         string backup = _location + ".backup";
311                         if (!File.Exists (backup)) {
312                                 string msg = Locale.GetText ("No policy backup exists.");
313                                 throw new PolicyException (msg);
314                         }
315
316                         try {
317                                 File.Copy (backup, _location, true);
318                         }
319                         catch (Exception e) {
320                                 string msg = Locale.GetText ("Couldn't replace the policy file with it's backup.");
321                                 throw new PolicyException (msg, e);
322                         }
323                 }
324
325                 [Obsolete ("All GACed assemblies are now fully trusted and all permissions now succeed on fully trusted code.")]
326                 public void RemoveFullTrustAssembly (StrongName sn)
327                 {
328                         if (sn == null)
329                                 throw new ArgumentNullException ("sn");
330
331                         StrongNameMembershipCondition s = new StrongNameMembershipCondition (sn.PublicKey, sn.Name, sn.Version);
332                         RemoveFullTrustAssembly (s);
333                 }
334
335                 [Obsolete ("All GACed assemblies are now fully trusted and all permissions now succeed on fully trusted code.")]
336                 public void RemoveFullTrustAssembly (StrongNameMembershipCondition snMC)
337                 {
338                         if (snMC == null)
339                                 throw new ArgumentNullException ("snMC");
340
341                         if (((IList) full_trust_assemblies).Contains (snMC))
342                                 ((IList) full_trust_assemblies).Remove (snMC);
343
344                         else
345                                 throw new ArgumentException (
346                                         Locale.GetText ("sn does not have full trust."));
347                 }
348
349                 public NamedPermissionSet RemoveNamedPermissionSet (NamedPermissionSet permSet)
350                 {
351                         if (permSet == null)
352                                 throw new ArgumentNullException ("permSet");
353
354                         return RemoveNamedPermissionSet (permSet.Name);
355                 }
356
357                 public NamedPermissionSet RemoveNamedPermissionSet (string name)
358                 {
359                         if (name == null)
360                                 throw new ArgumentNullException ("name");
361                         if (DefaultPolicies.ReservedNames.IsReserved (name))
362                                 throw new ArgumentException (Locale.GetText ("Reserved name"));
363
364                         foreach (NamedPermissionSet nps in named_permission_sets) {
365                                 if (name == nps.Name) {
366                                         named_permission_sets.Remove (nps);
367                                         return nps;
368                                 }
369                         }
370                         string msg = String.Format (Locale.GetText ("Name '{0}' cannot be found."), name);
371                         throw new ArgumentException (msg, "name");
372                 }
373
374                 public void Reset ()
375                 {
376                         if (fullNames != null)
377                                 fullNames.Clear ();
378
379                         if (_type != PolicyLevelType.AppDomain) {
380                                 full_trust_assemblies.Clear ();
381                                 named_permission_sets.Clear ();
382
383                                 // because the policy doesn't exist LoadFromFile will try to
384                                 // 1. use the .default file if existing (like Fx 2.0 does); or
385                                 // 2. use the hard-coded default values
386                                 // and recreate a policy file
387                                 if ((_location != null) && (File.Exists (_location))) {
388                                         try {
389                                                 File.Delete (_location);
390                                         }
391                                         catch {}
392                                 }
393                                 LoadFromFile (_location);
394                         } else {
395                                 CreateDefaultFullTrustAssemblies ();
396                                 CreateDefaultNamedPermissionSets ();
397                         }
398                 }
399
400                 public PolicyStatement Resolve (Evidence evidence)
401                 {
402                         if (evidence == null)
403                                 throw new ArgumentNullException ("evidence");
404
405                         PolicyStatement ps = root_code_group.Resolve (evidence);
406                         return ((ps != null) ? ps : PolicyStatement.Empty ());
407                 }
408
409                 public CodeGroup ResolveMatchingCodeGroups (Evidence evidence)
410                 {
411                         if (evidence == null)
412                                 throw new ArgumentNullException ("evidence");
413
414                         CodeGroup cg = root_code_group.ResolveMatchingCodeGroups (evidence);
415                         return ((cg != null) ? cg : null);
416                 }
417
418                 public SecurityElement ToXml ()
419                 {
420                         Hashtable fullNames = new Hashtable ();
421                         // only StrongNameMembershipCondition so no need to loop
422                         if (full_trust_assemblies.Count > 0) {
423                                 if (!fullNames.Contains ("StrongNameMembershipCondition")) {
424                                         fullNames.Add ("StrongNameMembershipCondition", typeof (StrongNameMembershipCondition).FullName);
425                                 }
426                         }
427                         
428                         SecurityElement namedPSs = new SecurityElement ("NamedPermissionSets");
429                         foreach (NamedPermissionSet nps in named_permission_sets) {
430                                 SecurityElement se = nps.ToXml ();
431                                 object objectClass = se.Attributes ["class"];
432                                 if (!fullNames.Contains (objectClass)) {
433                                         fullNames.Add (objectClass, nps.GetType ().FullName);
434                                 }
435                                 namedPSs.AddChild (se);
436                         }
437
438                         SecurityElement fta = new SecurityElement ("FullTrustAssemblies");
439                         foreach (StrongNameMembershipCondition snmc in full_trust_assemblies) {
440                                 fta.AddChild (snmc.ToXml (this));
441                         }
442
443                         SecurityElement security_classes = new SecurityElement ("SecurityClasses");
444                         if (fullNames.Count > 0) {
445                                 foreach (DictionaryEntry de in fullNames) {
446                                         SecurityElement sc = new SecurityElement ("SecurityClass");
447                                         sc.AddAttribute ("Name", (string)de.Key);
448                                         sc.AddAttribute ("Description", (string)de.Value);
449                                         security_classes.AddChild (sc);
450                                 }
451                         }
452
453                         SecurityElement element = new SecurityElement (typeof (System.Security.Policy.PolicyLevel).Name);
454                         element.AddAttribute ("version", "1");
455                         element.AddChild (security_classes);
456                         element.AddChild (namedPSs);
457                         if (root_code_group != null) {
458                                 element.AddChild (root_code_group.ToXml (this));
459                         }
460                         element.AddChild (fta);
461
462                         return element;
463                 }
464
465                 // internal stuff
466
467                 // NOTE: Callers are expected to check for ControlPolicy
468                 internal void Save ()
469                 {
470                         if (_type == PolicyLevelType.AppDomain) {
471                                 throw new PolicyException (Locale.GetText (
472                                         "Can't save AppDomain PolicyLevel"));
473                         }
474
475                         if (_location != null) {
476                                 try {
477                                         if (File.Exists (_location)) {
478                                                 File.Copy (_location, _location + ".backup", true);
479                                         }
480                                 }
481                                 catch (Exception) {
482                                 }
483                                 finally {
484                                         using (StreamWriter sw = new StreamWriter (_location)) {
485                                                 sw.Write (ToXml ().ToString ());
486                                                 sw.Close ();
487                                         }
488                                 }
489                         }
490                 }
491
492                 // Hardcode defaults in case 
493                 // (a) the specified policy file doesn't exists; and
494                 // (b) no corresponding default policy file exists
495                 internal void CreateDefaultLevel (PolicyLevelType type) 
496                 {
497                         PolicyStatement psu = new PolicyStatement (DefaultPolicies.FullTrust);
498
499                         switch (type) {
500                         case PolicyLevelType.Machine:
501                                 // by default all stuff is in the machine policy...
502                                 PolicyStatement psn = new PolicyStatement (DefaultPolicies.Nothing);
503                                 root_code_group = new UnionCodeGroup (new AllMembershipCondition (), psn);
504                                 root_code_group.Name = "All_Code";
505
506                                 UnionCodeGroup myComputerZone = new UnionCodeGroup (new ZoneMembershipCondition (SecurityZone.MyComputer), psu);
507                                 myComputerZone.Name = "My_Computer_Zone";
508                                 // TODO: strongname code group for ECMA and MS keys
509                                 root_code_group.AddChild (myComputerZone);
510
511                                 UnionCodeGroup localIntranetZone = new UnionCodeGroup (new ZoneMembershipCondition (SecurityZone.Intranet), 
512                                         new PolicyStatement (DefaultPolicies.LocalIntranet));
513                                 localIntranetZone.Name = "LocalIntranet_Zone";
514                                 // TODO: same site / same directory
515                                 root_code_group.AddChild (localIntranetZone);
516
517                                 PolicyStatement psi = new PolicyStatement (DefaultPolicies.Internet);
518                                 UnionCodeGroup internetZone = new UnionCodeGroup (new ZoneMembershipCondition (SecurityZone.Internet), psi);
519                                 internetZone.Name = "Internet_Zone";
520                                 // TODO: same site
521                                 root_code_group.AddChild (internetZone);
522
523                                 UnionCodeGroup restrictedZone = new UnionCodeGroup (new ZoneMembershipCondition (SecurityZone.Untrusted), psn);
524                                 restrictedZone.Name = "Restricted_Zone";
525                                 root_code_group.AddChild (restrictedZone);
526
527                                 UnionCodeGroup trustedZone = new UnionCodeGroup (new ZoneMembershipCondition (SecurityZone.Trusted), psi);
528                                 trustedZone.Name = "Trusted_Zone";
529                                 // TODO: same site
530                                 root_code_group.AddChild (trustedZone);
531                                 break;
532                         case PolicyLevelType.User:
533                         case PolicyLevelType.Enterprise:
534                         case PolicyLevelType.AppDomain:
535                                 // while the other policies don't restrict anything
536                                 root_code_group = new UnionCodeGroup (new AllMembershipCondition (), psu); 
537                                 root_code_group.Name = "All_Code";
538                                 break;
539                         }
540                 }
541
542                 internal void CreateDefaultFullTrustAssemblies () 
543                 {
544                         // (default) assemblies that are fully trusted during policy resolution
545                         full_trust_assemblies.Clear ();
546                         full_trust_assemblies.Add (DefaultPolicies.FullTrustMembership ("mscorlib", DefaultPolicies.Key.Ecma));
547                         full_trust_assemblies.Add (DefaultPolicies.FullTrustMembership ("System", DefaultPolicies.Key.Ecma));
548                         full_trust_assemblies.Add (DefaultPolicies.FullTrustMembership ("System.Data", DefaultPolicies.Key.Ecma));
549                         full_trust_assemblies.Add (DefaultPolicies.FullTrustMembership ("System.DirectoryServices", DefaultPolicies.Key.MsFinal));
550                         full_trust_assemblies.Add (DefaultPolicies.FullTrustMembership ("System.Drawing", DefaultPolicies.Key.MsFinal));
551                         full_trust_assemblies.Add (DefaultPolicies.FullTrustMembership ("System.Messaging", DefaultPolicies.Key.MsFinal));
552                         full_trust_assemblies.Add (DefaultPolicies.FullTrustMembership ("System.ServiceProcess", DefaultPolicies.Key.MsFinal));
553                 }
554
555                 internal void CreateDefaultNamedPermissionSets () 
556                 {
557                         named_permission_sets.Clear ();
558                         try {
559                                 SecurityManager.ResolvingPolicyLevel = this;
560                                 named_permission_sets.Add (DefaultPolicies.LocalIntranet);
561                                 named_permission_sets.Add (DefaultPolicies.Internet);
562                                 named_permission_sets.Add (DefaultPolicies.SkipVerification);
563                                 named_permission_sets.Add (DefaultPolicies.Execution);
564                                 named_permission_sets.Add (DefaultPolicies.Nothing);
565                                 named_permission_sets.Add (DefaultPolicies.Everything);
566                                 named_permission_sets.Add (DefaultPolicies.FullTrust);
567                         }
568                         finally {
569                                 SecurityManager.ResolvingPolicyLevel = null;
570                         }
571                 }
572
573                 internal string ResolveClassName (string className)
574                 {
575                         if (fullNames != null) {
576                                 object name = fullNames [className];
577                                 if (name != null)
578                                         return (string) name;
579                         }
580                         return className;
581                 }
582
583                 internal bool IsFullTrustAssembly (Assembly a)
584                 {
585                         AssemblyName an = a.GetName ();
586                         StrongNamePublicKeyBlob snpkb = new StrongNamePublicKeyBlob (an.GetPublicKey ());
587                         StrongNameMembershipCondition snMC = new StrongNameMembershipCondition (snpkb, an.Name, an.Version);
588                         foreach (StrongNameMembershipCondition sn in full_trust_assemblies) {
589                                 if (sn.Equals (snMC)) {
590                                         return true;
591                                 }
592                         }
593                         return false;
594                 }
595         }
596 }