2 // caspol.cs: Code Access Security Policy Tool
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
11 using System
.Collections
;
13 using System
.Reflection
;
14 using System
.Security
;
15 using System
.Security
.Cryptography
;
16 using System
.Security
.Cryptography
.X509Certificates
;
17 using System
.Security
.Permissions
;
18 using System
.Security
.Policy
;
21 using Mono
.Security
.Cryptography
;
23 [assembly
: AssemblyTitle ("Mono CasPol")]
24 [assembly
: AssemblyDescription ("Command line tool to modify Code Access Security policies.")]
26 namespace Mono
.Tools
{
28 class CustomMembershipCondition
: IMembershipCondition
{
32 public CustomMembershipCondition (SecurityElement se
)
37 public bool Check (Evidence evidence
)
42 public IMembershipCondition
Copy ()
44 return new CustomMembershipCondition (_se
);
47 public void FromXml (SecurityElement e
)
52 public SecurityElement
ToXml ()
57 public void FromXml (SecurityElement e
, PolicyLevel level
)
62 public SecurityElement
ToXml (PolicyLevel level
)
70 static ArrayList _levels
;
72 static private void Help ()
74 Console
.WriteLine ("Usage: caspol [options] [arguments] ...{0}", Environment
.NewLine
);
77 // (to be) Stored Options
78 static bool PolicyChangesConfirmation
= true;
80 static bool forcePolicyChanges
= false;
81 static bool policyLevelDefault
= true;
83 static void PrintGlobalInfo ()
85 Console
.WriteLine ("Security: {0}", SecurityManager
.SecurityEnabled
);
86 Console
.WriteLine ("Execution check: {0}", SecurityManager
.CheckExecutionRights
);
87 Console
.WriteLine ("Policy changes confirmation: {0}", PolicyChangesConfirmation
);
90 static bool Confirm ()
92 if (PolicyChangesConfirmation
) {
93 Console
.WriteLine ("WARNING: This action will modify the specified security policy!");
94 Console
.WriteLine ("Do you want to change the policy ?");
95 string answer
= Console
.ReadLine ();
96 switch (answer
.ToUpper ()) {
101 Console
.WriteLine ("Change aborted!");
108 static string Policies (string prefix
)
110 StringBuilder sb
= new StringBuilder (prefix
);
111 PolicyLevel pl
= null;
112 for (int i
= 0; i
< Levels
.Count
- 1; i
++) {
113 pl
= (PolicyLevel
)Levels
[i
];
114 sb
.AppendFormat ("{0}, ", pl
.Label
);
116 pl
= (PolicyLevel
)Levels
[Levels
.Count
- 1];
117 sb
.Append (pl
.Label
);
119 sb
.Append (" policy level");
120 if (Levels
.Count
> 1)
123 return sb
.ToString ();
126 // In Fx 1.0/1.1 there is not direct way to load a XML file
127 // into a SecurityElement so we use SecurityParser from
128 // Mono.Security.dll.
129 static SecurityElement
LoadXml (string filename
)
131 if (!File
.Exists (filename
)) {
132 Console
.WriteLine ("Couldn't not find '{0}'.", filename
);
137 using (StreamReader sr
= new StreamReader (filename
)) {
138 xml
= sr
.ReadToEnd ();
141 // actually this use the SecurityParser (on the Mono
142 // runtime) in corlib do to the job - but it remove
143 // the dependency on Mono.Security.dll
144 SecurityElement se
= SecurityElement
.FromString (xml
);
148 static PermissionSet
LoadPermissions (string filename
)
150 SecurityElement se
= LoadXml (filename
);
154 PermissionSet ps
= new PermissionSet (PermissionState
.None
);
156 if (se
.Attribute ("class").IndexOf ("System.Security.NamedPermissionSet") == -1)
158 // now we know it's a NamedPermissionSet
159 return (PermissionSet
) new NamedPermissionSet (se
.Attribute ("Name"), ps
);
162 static StrongName
GetStrongName (string filename
)
165 AssemblyName an
= AssemblyName
.GetAssemblyName (filename
);
166 byte [] pk
= an
.GetPublicKey ();
167 return new StrongName (new StrongNamePublicKeyBlob (pk
), an
.Name
, an
.Version
);
169 catch (FileNotFoundException
) {
170 Console
.WriteLine ("Couldn't find assembly '{0}'.", filename
);
175 static Assembly
GetAssembly (string filename
)
178 AssemblyName an
= AssemblyName
.GetAssemblyName (filename
);
179 return Assembly
.Load (an
);
181 catch (FileNotFoundException
) {
182 Console
.WriteLine ("Couldn't find assembly '{0}'.", filename
);
187 static Evidence
GetAssemblyEvidences (string filename
)
189 return GetAssembly (filename
).Evidence
;
192 static bool OnOff (string value, ref bool on
)
194 switch (value.ToUpper ()) {
207 static bool SaveSettings ()
209 Console
.WriteLine ("TODO - where to save those settings ?");
216 static void ShowCodeGroup (CodeGroup cg
, string prefix
)
218 Console
.WriteLine ("{0}. {1}: {2}", prefix
, cg
.MembershipCondition
, cg
.PermissionSetName
);
219 for (int i
=0; i
< cg
.Children
.Count
; i
++) {
220 ShowCodeGroup ((CodeGroup
)cg
.Children
[i
], " " + prefix
+ "." + (i
+ 1));
226 static void ListCodeGroups ()
230 foreach (PolicyLevel pl
in Levels
) {
231 Console
.WriteLine ("{0}Level: {1}{0}", Environment
.NewLine
, pl
.Label
);
233 Console
.WriteLine ("Code Groups:{0}", Environment
.NewLine
);
234 ShowCodeGroup (pl
.RootCodeGroup
, "1");
238 static void ShowDescription (CodeGroup cg
, string prefix
)
240 Console
.WriteLine ("{0}. {1}: {2}", prefix
, cg
.Name
, cg
.Description
);
241 for (int i
= 0; i
< cg
.Children
.Count
; i
++) {
242 ShowDescription ((CodeGroup
)cg
.Children
[i
], " " + prefix
+ "." + (i
+ 1));
248 static void ListDescriptions ()
252 foreach (PolicyLevel pl
in Levels
) {
253 Console
.WriteLine ("{0}Level: {1}{0}", Environment
.NewLine
, pl
.Label
);
255 Console
.WriteLine ("Code Groups:{0}", Environment
.NewLine
);
256 ShowDescription (pl
.RootCodeGroup
, "1");
262 static void ListPermissionSets ()
266 foreach (PolicyLevel pl
in Levels
) {
267 Console
.WriteLine ("{0}Level: {1}{0}", Environment
.NewLine
, pl
.Label
);
269 Console
.WriteLine ("Named Permission Sets:{0}", Environment
.NewLine
);
271 foreach (NamedPermissionSet nps
in pl
.NamedPermissionSets
) {
272 Console
.WriteLine ("{0}. {1} ({2}) = {3}{4}",
273 n
++, nps
.Name
, nps
.Description
, Environment
.NewLine
, nps
);
280 static void ListFullTrust ()
284 foreach (PolicyLevel pl
in Levels
) {
285 Console
.WriteLine ("{0}Level: {1}{0}", Environment
.NewLine
, pl
.Label
);
287 Console
.WriteLine ("Full Trust Assemblies:{0}", Environment
.NewLine
);
289 foreach (StrongNameMembershipCondition snmc
in pl
.FullTrustAssemblies
) {
290 Console
.WriteLine ("{0}. {1} = {2}{3}",
291 n
++, snmc
.Name
, Environment
.NewLine
, snmc
);
296 static void ShowResolveGroup (PolicyLevel pl
, Evidence e
)
298 Console
.WriteLine ("{0}Level: {1}{0}", Environment
.NewLine
, pl
.Label
);
299 CodeGroup cg
= pl
.ResolveMatchingCodeGroups (e
);
300 Console
.WriteLine ("Code Groups:{0}", Environment
.NewLine
);
301 ShowCodeGroup (cg
, "1");
302 Console
.WriteLine ();
306 // -resolvegroup assemblyname
307 static bool ResolveGroup (string assemblyname
)
309 Evidence ev
= GetAssemblyEvidences (assemblyname
);
313 if (policyLevelDefault
) {
314 // different "default" here
315 IEnumerator e
= SecurityManager
.PolicyHierarchy ();
316 while (e
.MoveNext ()) {
317 PolicyLevel pl
= (PolicyLevel
)e
.Current
;
318 ShowResolveGroup (pl
, ev
);
321 // use the user specified levels
322 foreach (PolicyLevel pl
in Levels
) {
323 ShowResolveGroup (pl
, ev
);
330 // -resolveperm assemblyname
331 static bool ResolvePermissions (string assemblyname
)
333 Evidence ev
= GetAssemblyEvidences (assemblyname
);
337 PermissionSet ps
= null;
338 Console
.WriteLine ();
339 if (policyLevelDefault
) {
340 // different "default" here
341 IEnumerator e
= SecurityManager
.PolicyHierarchy ();
342 while (e
.MoveNext ()) {
343 PolicyLevel pl
= (PolicyLevel
)e
.Current
;
344 Console
.WriteLine ("Resolving {0} level", pl
.Label
);
346 ps
= pl
.Resolve (ev
).PermissionSet
;
348 ps
= ps
.Intersect (pl
.Resolve (ev
).PermissionSet
);
351 // use the user specified levels
352 foreach (PolicyLevel pl
in Levels
) {
353 Console
.WriteLine ("Resolving {0} level", pl
.Label
);
355 ps
= pl
.Resolve (ev
).PermissionSet
;
357 ps
= ps
.Intersect (pl
.Resolve (ev
).PermissionSet
);
363 IEnumerator ee
= ev
.GetHostEnumerator ();
364 while (ee
.MoveNext ()) {
365 IIdentityPermissionFactory ipf
= (ee
.Current
as IIdentityPermissionFactory
);
367 IPermission p
= ipf
.CreateIdentityPermission (ev
);
368 ps
.AddPermission (p
);
372 Console
.WriteLine ("{0}Grant:{0}{1}", Environment
.NewLine
, ps
.ToXml ().ToString ());
377 // -addpset namedxmlfile
379 // -addpset xmlfile name
380 static bool AddPermissionSet (string [] args
, ref int i
)
382 // two syntax - so we first load the XML file and
383 // if it's not a named XML file, then we use the next
384 // parameter as it's name
385 string xmlfile
= args
[++i
];
386 PermissionSet ps
= LoadPermissions (xmlfile
);
387 if ((ps
== null) || !Confirm ())
390 NamedPermissionSet nps
= null;
391 if (ps
is NamedPermissionSet
) {
392 nps
= (NamedPermissionSet
)ps
;
394 nps
= new NamedPermissionSet (args
[++i
], ps
);
397 foreach (PolicyLevel pl
in Levels
) {
398 pl
.AddNamedPermissionSet (nps
);
399 SecurityManager
.SavePolicyLevel (pl
);
404 // -cp xmlfile psetname
405 // -chgpset xmlfile psetname
406 static bool ChangePermissionSet (string[] args
, ref int i
)
408 string xmlfile
= args
[++i
];
409 PermissionSet ps
= LoadPermissions (xmlfile
);
413 bool confirmed
= false;
414 string psname
= args
[++i
];
416 foreach (PolicyLevel pl
in Levels
) {
417 if (pl
.GetNamedPermissionSet (psname
) == null) {
418 Console
.WriteLine ("Couldn't find '{0}' permission set in policy.", psname
);
420 } else if (confirmed
|| Confirm ()) {
421 confirmed
= true; // only ask once
422 pl
.ChangeNamedPermissionSet (psname
, ps
);
423 SecurityManager
.SavePolicyLevel (pl
);
432 static bool RemovePermissionSet (string psname
)
434 bool confirmed
= false;
436 foreach (PolicyLevel pl
in Levels
) {
437 PermissionSet ps
= pl
.GetNamedPermissionSet (psname
);
439 Console
.WriteLine ("Couldn't find '{0}' permission set in policy.", psname
);
441 } else if (confirmed
|| Confirm ()) {
442 confirmed
= true; // only ask once
443 pl
.RemoveNamedPermissionSet (psname
);
444 SecurityManager
.SavePolicyLevel (pl
);
445 Console
.WriteLine ("Permission set '{0}' removed from policy.", psname
);
453 // -addfulltrust assemblyname
454 static bool AddFullTrust (string aname
)
456 StrongName sn
= GetStrongName (aname
);
457 if ((sn
== null) || !Confirm ())
460 foreach (PolicyLevel pl
in Levels
) {
461 pl
.AddFullTrustAssembly (sn
);
467 // -remfulltrust assemblyname
468 static bool RemoveFullTrust (string aname
)
470 StrongName sn
= GetStrongName (aname
);
471 if ((sn
== null) || !Confirm ())
474 foreach (PolicyLevel pl
in Levels
) {
475 pl
.RemoveFullTrustAssembly (sn
);
481 static CodeGroup
FindCodeGroupByName (string name
, ref CodeGroup parent
)
483 for (int i
= 0; i
< parent
.Children
.Count
; i
++) {
484 CodeGroup child
= (CodeGroup
)parent
.Children
[i
];
485 if (child
.Name
== name
) {
488 CodeGroup cg
= FindCodeGroupByName (name
, ref child
);
496 static CodeGroup
FindCodeGroupByLabel (string label
, string current
, ref CodeGroup parent
)
498 for (int i
=0; i
< parent
.Children
.Count
; i
++) {
499 CodeGroup child
= (CodeGroup
)parent
.Children
[i
];
500 string temp
= String
.Concat (current
, ".", (i
+ 1).ToString ());
501 if ((label
== temp
) || (label
== temp
+ ".")) {
503 } else if (label
.StartsWith (temp
)) {
504 CodeGroup cg
= FindCodeGroupByLabel (label
, temp
, ref child
);
512 static CodeGroup
FindCodeGroup (string name
, ref CodeGroup parent
, ref PolicyLevel pl
)
518 // - labels starts with numbers (e.g. 1.2.1)
519 // - names cannot start with numbers (A-Z, 0-9 and _)
520 bool label
= Char
.IsDigit (name
, 0);
523 // - we can't remove the root code group
524 // - we remove only one group (e.g. name)
525 for (int i
=0; i
< Levels
.Count
; i
++) {
526 pl
= (PolicyLevel
) Levels
[i
];
527 parent
= pl
.RootCodeGroup
;
530 cg
= FindCodeGroupByLabel (name
, "1", ref parent
);
532 cg
= FindCodeGroupByName (name
, ref parent
);
537 Console
.WriteLine ("CodeGroup with {0} '{1}' was not found!",
538 label
? "label" : "name", name
);
543 static IMembershipCondition
ProcessCustomMembership (string filename
)
545 SecurityElement se
= LoadXml (filename
);
548 return new CustomMembershipCondition (se
);
551 // -hash algo -hex hash
552 // -hash algo -file assemblyname
553 static IMembershipCondition
ProcessHashMembership (string[] args
, ref int i
)
555 HashAlgorithm ha
= HashAlgorithm
.Create (args
[++i
]);
556 byte [] value = null;
557 switch (args
[++i
]) {
559 value = CryptoConvert
.FromHex (args
[++i
]);
562 Hash hash
= new Hash (GetAssembly (args
[++i
]));
563 value = hash
.GenerateHash (ha
);
568 return new HashMembershipCondition (ha
, value);
571 // -pub -cert certificate
572 // -pub -file signedfile
574 static IMembershipCondition
ProcessPublisherMembership (string[] args
, ref int i
)
576 X509Certificate cert
= null;
577 switch (args
[++i
]) {
579 cert
= X509Certificate
.CreateFromCertFile (args
[++i
]);
582 cert
= X509Certificate
.CreateFromSignedFile (args
[++i
]);
585 byte[] raw
= CryptoConvert
.FromHex (args
[++i
]);
586 cert
= new X509Certificate (raw
);
591 return new PublisherMembershipCondition (cert
);
594 // -strong -file filename [name | -noname] [version | -noversion]
595 static IMembershipCondition
ProcessStrongNameMembership (string[] args
, ref int i
)
597 if (args
[++i
] != "-file") {
598 Console
.WriteLine ("Missing -file parameter.");
602 StrongName sn
= GetStrongName (args
[++i
]);
604 string name
= args
[++i
];
605 if (name
== "-noname")
609 string version
= args
[++i
];
610 if (version
!= "-noversion")
611 v
= new Version (version
);
613 return new StrongNameMembershipCondition (sn
.PublicKey
, name
, v
);
616 static bool ProcessCodeGroup (CodeGroup cg
, string[] args
, ref int i
)
618 IMembershipCondition mship
= null;
619 for (; i
< args
.Length
; i
++) {
620 switch (args
[++i
]) {
622 cg
.MembershipCondition
= new AllMembershipCondition ();
625 cg
.MembershipCondition
= new ApplicationDirectoryMembershipCondition ();
628 mship
= ProcessCustomMembership (args
[++i
]);
631 cg
.MembershipCondition
= mship
;
634 mship
= ProcessHashMembership (args
, ref i
);
637 cg
.MembershipCondition
= mship
;
640 mship
= ProcessPublisherMembership (args
, ref i
);
643 cg
.MembershipCondition
= mship
;
646 cg
.MembershipCondition
= new SiteMembershipCondition (args
[++i
]);
649 mship
= ProcessStrongNameMembership (args
, ref i
);
652 cg
.MembershipCondition
= mship
;
655 cg
.MembershipCondition
= new UrlMembershipCondition (args
[++i
]);
658 SecurityZone zone
= (SecurityZone
) Enum
.Parse (typeof (SecurityZone
), args
[++i
]);
659 cg
.MembershipCondition
= new ZoneMembershipCondition (zone
);
664 cg
.Description
= args
[++i
];
667 bool exclusive
= false;
668 if (OnOff (args
[++i
], ref exclusive
)) {
670 cg
.PolicyStatement
.Attributes
|= PolicyStatementAttribute
.Exclusive
;
677 if (OnOff (args
[++i
], ref final
)) {
679 cg
.PolicyStatement
.Attributes
|= PolicyStatementAttribute
.LevelFinal
;
686 cg
.Name
= args
[++i
];
696 // -ag label|name membership psetname flag
697 // -addgroup label|name membership psetname flag
698 static bool AddCodeGroup (string[] args
, ref int i
)
700 string name
= args
[++i
];
702 PolicyLevel pl
= null;
703 CodeGroup parent
= null;
704 CodeGroup cg
= FindCodeGroup (name
, ref parent
, ref pl
);
705 if ((pl
== null) || (parent
== null) || (cg
== null))
708 UnionCodeGroup child
= new UnionCodeGroup (
709 new AllMembershipCondition (),
710 new PolicyStatement (new PermissionSet (PermissionState
.Unrestricted
)));
711 if (!ProcessCodeGroup (child
, args
, ref i
))
715 SecurityManager
.SavePolicyLevel (pl
);
716 Console
.WriteLine ("CodeGroup '{0}' added in {1} policy level.",
721 // -cg label|name membership|psetname|flag
722 // -chggroup label|name membership|psetname|flag
723 static bool ChangeCodeGroup (string[] args
, ref int i
)
725 string name
= args
[++i
];
727 PolicyLevel pl
= null;
728 CodeGroup parent
= null;
729 CodeGroup cg
= FindCodeGroup (name
, ref parent
, ref pl
);
730 if ((pl
== null) || (parent
== null) || (cg
== null))
733 if (!ProcessCodeGroup (cg
, args
, ref i
))
736 SecurityManager
.SavePolicyLevel (pl
);
737 Console
.WriteLine ("CodeGroup '{0}' modified in {1} policy level.",
743 // -remgroup label|name
744 static bool RemoveCodeGroup (string name
)
746 PolicyLevel pl
= null;
747 CodeGroup parent
= null;
748 CodeGroup cg
= FindCodeGroup (name
, ref parent
, ref pl
);
749 if ((pl
== null) || (parent
== null) || (cg
== null))
755 parent
.RemoveChild (cg
);
756 SecurityManager
.SavePolicyLevel (pl
);
757 Console
.WriteLine ("CodeGroup '{0}' removed from {1} policy level.",
764 static void Recover ()
766 // no confirmation required to recover
767 foreach (PolicyLevel pl
in Levels
) {
769 SecurityManager
.SavePolicyLevel (pl
);
777 Console
.WriteLine (Policies ("Resetting "));
779 foreach (PolicyLevel pl
in Levels
) {
781 SecurityManager
.SavePolicyLevel (pl
);
790 static bool Security (string value)
793 if (!OnOff (value, ref on
))
795 SecurityManager
.SecurityEnabled
= on
;
796 return SaveSettings ();
801 static bool Execution (string value)
804 if (!OnOff (value, ref on
))
806 SecurityManager
.CheckExecutionRights
= on
;
807 return SaveSettings ();
812 static bool BuildCache ()
819 // -polchgprompt on|off
820 static bool PolicyChangePrompt (string value)
823 if (!OnOff (value, ref on
))
825 PolicyChangesConfirmation
= on
;
826 return SaveSettings ();
830 // Policy Levels Internal Management
832 static PolicyLevel levelEnterprise
;
833 static PolicyLevel levelMachine
;
834 static PolicyLevel levelUser
;
836 static void BuildLevels ()
838 IEnumerator e
= SecurityManager
.PolicyHierarchy ();
840 levelEnterprise
= (PolicyLevel
) e
.Current
;
842 levelMachine
= (PolicyLevel
) e
.Current
;
844 levelUser
= (PolicyLevel
) e
.Current
;
847 static PolicyLevel Enterprise
{
849 if (levelEnterprise
== null)
851 return levelEnterprise
;
855 static PolicyLevel Machine
{
857 if (levelMachine
== null)
863 static PolicyLevel User
{
865 if (levelUser
== null)
871 static ArrayList Levels
{
874 _levels
= new ArrayList (3);
879 static bool ProcessInstruction (string[] args
, ref int i
)
881 for (; i
< args
.Length
; i
++) {
885 PolicyChangesConfirmation
= false;
889 forcePolicyChanges
= true;
900 policyLevelDefault
= false;
902 Levels
.Add (Enterprise
);
903 Levels
.Add (Machine
);
908 policyLevelDefault
= false;
910 Levels
.Add (Enterprise
);
911 Levels
.Add (Machine
);
912 Levels
.Add (SecurityManager
.LoadPolicyLevelFromFile (args
[++i
], PolicyLevelType
.User
));
916 policyLevelDefault
= false;
918 Levels
.Add (SecurityManager
.LoadPolicyLevelFromFile (args
[++i
], PolicyLevelType
.User
));
922 policyLevelDefault
= false;
924 Levels
.Add (Enterprise
);
928 policyLevelDefault
= false;
930 Levels
.Add (Machine
);
934 policyLevelDefault
= false;
944 case "-listdescription":
949 ListPermissionSets ();
952 case "-listfulltrust":
958 Console
.WriteLine ();
959 ListPermissionSets ();
960 Console
.WriteLine ();
965 case "-resolvegroup":
966 if (!ResolveGroup (args
[++i
]))
971 if (!ResolvePermissions (args
[++i
]))
977 if (!AddPermissionSet (args
, ref i
))
982 if (!ChangePermissionSet (args
, ref i
))
987 if (!RemovePermissionSet (args
[++i
]))
992 case "-addfulltrust":
993 if (!AddFullTrust (args
[++i
]))
997 case "-remfulltrust":
998 if (!RemoveFullTrust (args
[++i
]))
1004 if (!AddCodeGroup (args
, ref i
))
1009 if (!ChangeCodeGroup (args
, ref i
))
1014 if (!RemoveCodeGroup (args
[++i
]))
1030 if (!Security (args
[++i
]))
1035 if (!Execution (args
[++i
]))
1044 case "-polchgprompt":
1045 if (!PolicyChangePrompt (args
[++i
]))
1050 Console
.WriteLine ("*** unknown argument {0} ***", args
[i
]);
1053 Console
.WriteLine ();
1058 static void SetDefaultPolicyLevel ()
1060 // default is User for normal users and Machine for
1061 // administrators. Here we define an administrator as
1062 // someone who can write to the Machine policy files
1064 using (FileStream fs
= File
.OpenWrite (Machine
.StoreLocation
)) {
1067 Levels
.Add (Machine
);
1072 // some actions, like resolves, use a different default (all)
1073 policyLevelDefault
= true;
1077 static int Main (string[] args
)
1079 Console
.WriteLine (new AssemblyInfo ().ToString ());
1080 if (args
.Length
== 0) {
1086 // set default level (when none is specified
1087 // by command line options)
1088 SetDefaultPolicyLevel ();
1090 // process instructions (i.e. multiple
1091 // instructions can be chained)
1092 for (int i
=0; i
< args
.Length
; i
++) {
1093 if (!ProcessInstruction (args
, ref i
))
1097 catch (Exception e
) {
1098 Console
.WriteLine ("Error: " + e
.ToString ());
1102 Console
.WriteLine ("Success");