//
// caspol.cs: Code Access Security Policy Tool
//
// Author:
//	Sebastien Pouliot  <sebastien@ximian.com>
//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Security.Policy;
using System.Text;

using Mono.Security.Cryptography;

[assembly: AssemblyTitle ("Mono CasPol")]
[assembly: AssemblyDescription ("Command line tool to modify Code Access Security policies.")]

namespace Mono.Tools {

	class CustomMembershipCondition : IMembershipCondition {

		SecurityElement _se;

		public CustomMembershipCondition (SecurityElement se)
		{
			_se = se;
		}

		public bool Check (Evidence evidence)
		{
			return true;
		}

		public IMembershipCondition Copy ()
		{
			return new CustomMembershipCondition (_se);
		}

		public void FromXml (SecurityElement e)
		{
			_se = e;
		}

		public SecurityElement ToXml ()
		{
			return _se;
		}

		public void FromXml (SecurityElement e, PolicyLevel level)
		{
			_se = e;
		}

		public SecurityElement ToXml (PolicyLevel level)
		{
			return _se;
		}
	}

	class CasPol {

		static ArrayList _levels;

		static private void Help () 
		{
			Console.WriteLine ("Usage: caspol [options] [arguments] ...{0}", Environment.NewLine);
		}

		// (to be) Stored Options
		static bool PolicyChangesConfirmation = true;

		static bool forcePolicyChanges = false;
		static bool policyLevelDefault = true;

		static void PrintGlobalInfo ()
		{
			Console.WriteLine ("Security: {0}", SecurityManager.SecurityEnabled);
			Console.WriteLine ("Execution check: {0}", SecurityManager.CheckExecutionRights);
			Console.WriteLine ("Policy changes confirmation: {0}", PolicyChangesConfirmation);
		}

		static bool Confirm ()
		{
			if (PolicyChangesConfirmation) {
				Console.WriteLine ("WARNING: This action will modify the specified security policy!");
				Console.WriteLine ("Do you want to change the policy ?");
				string answer = Console.ReadLine ();
				switch (answer.ToUpper ()) {
				case "YES":
				case "Y":
					return true;
				default:
					Console.WriteLine ("Change aborted!");
					return false;
				}
			}
			return true;
		}

		static string Policies (string prefix)
		{
			StringBuilder sb = new StringBuilder (prefix);
			PolicyLevel pl = null;
			for (int i = 0; i < Levels.Count - 1; i++) {
				pl = (PolicyLevel)Levels [i];
				sb.AppendFormat ("{0}, ", pl.Label);
			}
			pl = (PolicyLevel)Levels [Levels.Count - 1];
			sb.Append (pl.Label);

			sb.Append (" policy level");
			if (Levels.Count > 1)
				sb.Append ("s");

			return sb.ToString ();
		}

		// In Fx 1.0/1.1 there is not direct way to load a XML file 
		// into a SecurityElement so we use SecurityParser from 
		// Mono.Security.dll.
		static SecurityElement LoadXml (string filename)
		{
			if (!File.Exists (filename)) {
				Console.WriteLine ("Couldn't not find '{0}'.", filename);
				return null;
			}

			string xml = null;
			using (StreamReader sr = new StreamReader (filename)) {
				xml = sr.ReadToEnd ();
				sr.Close ();
			}
			// actually this use the SecurityParser (on the Mono 
			// runtime) in corlib do to the job - but it remove 
			// the dependency on Mono.Security.dll
			SecurityElement se = SecurityElement.FromString (xml);
			return se;
		}

		static PermissionSet LoadPermissions (string filename)
		{
			SecurityElement se = LoadXml (filename);
			if (se == null)
				return null;

			PermissionSet ps = new PermissionSet (PermissionState.None);
			ps.FromXml (se);
			if (se.Attribute ("class").IndexOf ("System.Security.NamedPermissionSet") == -1)
				return ps;
			// now we know it's a NamedPermissionSet
			return (PermissionSet) new NamedPermissionSet (se.Attribute ("Name"), ps);
		}

		static StrongName GetStrongName (string filename)
		{
			try {
				AssemblyName an = AssemblyName.GetAssemblyName (filename);
				byte [] pk = an.GetPublicKey ();
				return new StrongName (new StrongNamePublicKeyBlob (pk), an.Name, an.Version);
			}
			catch (FileNotFoundException) {
				Console.WriteLine ("Couldn't find assembly '{0}'.", filename);
				return null;
			}
		}

		static Assembly GetAssembly (string filename)
		{
			try {
				AssemblyName an = AssemblyName.GetAssemblyName (filename);
				return Assembly.Load (an);
			}
			catch (FileNotFoundException) {
				Console.WriteLine ("Couldn't find assembly '{0}'.", filename);
				return null;
			}
		}

		static Evidence GetAssemblyEvidences (string filename)
		{
			return GetAssembly (filename).Evidence;
		}

		static bool OnOff (string value, ref bool on)
		{
			switch (value.ToUpper ()) {
			case "ON":
				on = true;
				break;
			case "OFF":
				on = false;
				break;
			default:
				return false;
			}
			return true;
		}

		static bool SaveSettings ()
		{
			Console.WriteLine ("TODO - where to save those settings ?");
			return false;
		}


		// Actions

		static void ShowCodeGroup (CodeGroup cg, string prefix) 
		{
			Console.WriteLine ("{0}. {1}: {2}", prefix, cg.MembershipCondition, cg.PermissionSetName);
			for (int i=0; i < cg.Children.Count; i++) {
				ShowCodeGroup ((CodeGroup)cg.Children [i], "  " + prefix + "." + (i + 1));
			}
		}

		// -lg
		// -listgroups
		static void ListCodeGroups ()
		{
			PrintGlobalInfo ();

			foreach (PolicyLevel pl in Levels) {
				Console.WriteLine ("{0}Level: {1}{0}", Environment.NewLine, pl.Label);

				Console.WriteLine ("Code Groups:{0}", Environment.NewLine);
				ShowCodeGroup (pl.RootCodeGroup, "1");
			}
		}

		static void ShowDescription (CodeGroup cg, string prefix)
		{
			Console.WriteLine ("{0}. {1}: {2}", prefix, cg.Name, cg.Description);
			for (int i = 0; i < cg.Children.Count; i++) {
				ShowDescription ((CodeGroup)cg.Children [i], "  " + prefix + "." + (i + 1));
			}
		}

		// -ld
		// -listdescription
		static void ListDescriptions ()
		{
			PrintGlobalInfo ();

			foreach (PolicyLevel pl in Levels) {
				Console.WriteLine ("{0}Level: {1}{0}", Environment.NewLine, pl.Label);

				Console.WriteLine ("Code Groups:{0}", Environment.NewLine);
				ShowDescription (pl.RootCodeGroup, "1");
			}
		}

		// -lp
		// -listpset
		static void ListPermissionSets ()
		{
			PrintGlobalInfo ();

			foreach (PolicyLevel pl in Levels) {
				Console.WriteLine ("{0}Level: {1}{0}", Environment.NewLine, pl.Label);

				Console.WriteLine ("Named Permission Sets:{0}", Environment.NewLine);
				int n=1;
				foreach (NamedPermissionSet nps in pl.NamedPermissionSets) {
					Console.WriteLine ("{0}. {1} ({2}) = {3}{4}", 
						n++, nps.Name, nps.Description, Environment.NewLine, nps);
				}
			}
		}

		// -lf
		// -listfulltrust
		static void ListFullTrust ()
		{
			PrintGlobalInfo ();

			foreach (PolicyLevel pl in Levels) {
				Console.WriteLine ("{0}Level: {1}{0}", Environment.NewLine, pl.Label);

				Console.WriteLine ("Full Trust Assemblies:{0}", Environment.NewLine);
				int n = 1;
				foreach (StrongNameMembershipCondition snmc in pl.FullTrustAssemblies) {
					Console.WriteLine ("{0}. {1} = {2}{3}",
						n++, snmc.Name, Environment.NewLine, snmc);
				}
			}
		}

		static void ShowResolveGroup (PolicyLevel pl, Evidence e)
		{
			Console.WriteLine ("{0}Level: {1}{0}", Environment.NewLine, pl.Label);
			CodeGroup cg = pl.ResolveMatchingCodeGroups (e);
			Console.WriteLine ("Code Groups:{0}", Environment.NewLine);
			ShowCodeGroup (cg, "1");
			Console.WriteLine ();
		}

		// -rsg assemblyname
		// -resolvegroup assemblyname
		static bool ResolveGroup (string assemblyname)
		{
			Evidence ev = GetAssemblyEvidences (assemblyname);
			if (ev == null)
				return false;

			if (policyLevelDefault) {
				// different "default" here
				IEnumerator e = SecurityManager.PolicyHierarchy ();
				while (e.MoveNext ()) {
					PolicyLevel pl = (PolicyLevel)e.Current;
					ShowResolveGroup (pl, ev);
				}
			} else {
				// use the user specified levels
				foreach (PolicyLevel pl in Levels) {
					ShowResolveGroup (pl, ev);
				}
			}
			return true;
		}

		// -rsp assemblyname
		// -resolveperm assemblyname
		static bool ResolvePermissions (string assemblyname)
		{
			Evidence ev = GetAssemblyEvidences (assemblyname);
			if (ev == null)
				return false;

			PermissionSet ps = null;
			Console.WriteLine ();
			if (policyLevelDefault)	{
				// different "default" here
				IEnumerator e = SecurityManager.PolicyHierarchy ();
				while (e.MoveNext ()) {
					PolicyLevel pl = (PolicyLevel)e.Current;
					Console.WriteLine ("Resolving {0} level", pl.Label);
					if (ps == null)
						ps = pl.Resolve (ev).PermissionSet;
					else
						ps = ps.Intersect (pl.Resolve (ev).PermissionSet);
				}
			} else {
				// use the user specified levels
				foreach (PolicyLevel pl in Levels) {
					Console.WriteLine ("Resolving {0} level", pl.Label);
					if (ps == null)
						ps = pl.Resolve (ev).PermissionSet;
					else
						ps = ps.Intersect (pl.Resolve (ev).PermissionSet);
				}
			}
			if (ps == null)
				return false;

			IEnumerator ee = ev.GetHostEnumerator ();
			while (ee.MoveNext ()) {
				IIdentityPermissionFactory ipf = (ee.Current as IIdentityPermissionFactory);
				if (ipf != null) {
					IPermission p = ipf.CreateIdentityPermission (ev);
					ps.AddPermission (p);
				}
			}

			Console.WriteLine ("{0}Grant:{0}{1}", Environment.NewLine, ps.ToXml ().ToString ());
			return true;
		}

		// -ap namedxmlfile
		// -addpset namedxmlfile
		// -ap xmlfile name
		// -addpset xmlfile name
		static bool AddPermissionSet (string [] args, ref int i)
		{
			// two syntax - so we first load the XML file and
			// if it's not a named XML file, then we use the next 
			// parameter as it's name
			string xmlfile = args [++i];
			PermissionSet ps = LoadPermissions (xmlfile);
			if ((ps == null) || !Confirm ())
				return false;

			NamedPermissionSet nps = null;
			if (ps is NamedPermissionSet) {
				nps = (NamedPermissionSet)ps;
			} else {
				nps = new NamedPermissionSet (args [++i], ps);
			}

			foreach (PolicyLevel pl in Levels) {
				pl.AddNamedPermissionSet (nps);
				SecurityManager.SavePolicyLevel (pl);
			}
			return true;
		}

		// -cp xmlfile psetname
		// -chgpset xmlfile psetname
		static bool ChangePermissionSet (string[] args, ref int i)
		{
			string xmlfile = args [++i];
			PermissionSet ps = LoadPermissions (xmlfile);
			if (ps == null)
				return false;

			bool confirmed = false;
			string psname = args [++i];

			foreach (PolicyLevel pl in Levels) {
				if (pl.GetNamedPermissionSet (psname) == null) {
					Console.WriteLine ("Couldn't find '{0}' permission set in policy.", psname);
					return false;
				} else if (confirmed || Confirm ()) {
					confirmed = true; // only ask once
					pl.ChangeNamedPermissionSet (psname, ps);
					SecurityManager.SavePolicyLevel (pl);
				} else
					return false;
			}
			return true;
		}

		// -rp psetname
		// -rempset psetname
		static bool RemovePermissionSet (string psname)
		{
			bool confirmed = false;

			foreach (PolicyLevel pl in Levels) {
				PermissionSet ps = pl.GetNamedPermissionSet (psname);
				if (ps == null) {
					Console.WriteLine ("Couldn't find '{0}' permission set in policy.", psname);
					return false;
				} else if (confirmed || Confirm ()) {
					confirmed = true; // only ask once
					pl.RemoveNamedPermissionSet (psname);
					SecurityManager.SavePolicyLevel (pl);
					Console.WriteLine ("Permission set '{0}' removed from policy.", psname);
				} else
					return false;
			}
			return true;
		}

		// -af assemblyname
		// -addfulltrust assemblyname
		static bool AddFullTrust (string aname)
		{
			StrongName sn = GetStrongName (aname);
			if ((sn == null) || !Confirm ())
				return false;

			foreach (PolicyLevel pl in Levels) {
				pl.AddFullTrustAssembly (sn);
			}
			return true;
		}

		// -rf assemblyname
		// -remfulltrust assemblyname
		static bool RemoveFullTrust (string aname)
		{
			StrongName sn = GetStrongName (aname);
			if ((sn == null) || !Confirm ())
				return false;

			foreach (PolicyLevel pl in Levels) {
				pl.RemoveFullTrustAssembly (sn);
			}
			return true;
		}


		static CodeGroup FindCodeGroupByName (string name, ref CodeGroup parent)
		{
			for (int i = 0; i < parent.Children.Count; i++)	{
				CodeGroup child = (CodeGroup)parent.Children [i];
				if (child.Name == name) {
					return child;
				} else {
					CodeGroup cg = FindCodeGroupByName (name, ref child);
					if (cg != null)
						return cg;
				}
			}
			return null;
		}

		static CodeGroup FindCodeGroupByLabel (string label, string current, ref CodeGroup parent)
		{
			for (int i=0; i < parent.Children.Count; i++) {
				CodeGroup child = (CodeGroup)parent.Children [i];
				string temp = String.Concat (current, ".", (i + 1).ToString ());
				if ((label == temp) || (label == temp + ".")) {
					return child;
				} else if (label.StartsWith (temp)) {
					CodeGroup cg = FindCodeGroupByLabel (label, temp, ref child);
					if (cg != null)
						return cg;
				}
			}
			return null;
		}

		static CodeGroup FindCodeGroup (string name, ref CodeGroup parent, ref PolicyLevel pl)
		{
			if (name.Length < 1)
				return null;
			
			// Notes:
			// - labels starts with numbers (e.g. 1.2.1)
			// - names cannot start with numbers (A-Z, 0-9 and _)
			bool label = Char.IsDigit (name, 0);

			// More notes
			// - we can't remove the root code group
			// - we remove only one group (e.g. name)
			for (int i=0; i < Levels.Count; i++) {
				pl = (PolicyLevel) Levels [i];
				parent = pl.RootCodeGroup;
				CodeGroup cg = null;
				if (label)
					cg = FindCodeGroupByLabel (name, "1", ref parent);
				else
					cg = FindCodeGroupByName (name, ref parent);
				
				if (cg != null)
					return cg;
			}
			Console.WriteLine ("CodeGroup with {0} '{1}' was not found!",
				label ? "label" : "name", name);
			return null;
		}

		// -custom xmlfile
		static IMembershipCondition ProcessCustomMembership (string filename)
		{
			SecurityElement se = LoadXml (filename);
			if (se == null)
				return null;
			return new CustomMembershipCondition (se);
		}

		// -hash algo -hex hash
		// -hash algo -file assemblyname
		static IMembershipCondition ProcessHashMembership (string[] args, ref int i)
		{
			HashAlgorithm ha = HashAlgorithm.Create (args [++i]);
			byte [] value = null;
			switch (args [++i]) {
				case "-hex":
					value = CryptoConvert.FromHex (args [++i]);
					break;
				case "-file":
					Hash hash = new Hash (GetAssembly (args [++i]));
					value = hash.GenerateHash (ha);
					break;
				default:
					return null;
			}
			return new HashMembershipCondition (ha, value);
		}

		// -pub -cert certificate
		// -pub -file signedfile
		// -pub -hex rawdata
		static IMembershipCondition ProcessPublisherMembership (string[] args, ref int i)
		{
			X509Certificate cert = null;
			switch (args [++i]) {
				case "-cert":
					cert = X509Certificate.CreateFromCertFile (args [++i]);
					break;
				case "-file":
					cert = X509Certificate.CreateFromSignedFile (args [++i]);
					break;
				case "-hex":
					byte[] raw = CryptoConvert.FromHex (args [++i]);
					cert = new X509Certificate (raw);
					break;
				default:
					return null;
			}
			return new PublisherMembershipCondition (cert);
		}

		// -strong -file filename [name | -noname] [version | -noversion]
		static IMembershipCondition ProcessStrongNameMembership (string[] args, ref int i)
		{
			if (args [++i] != "-file") {
				Console.WriteLine ("Missing -file parameter.");
				return null;
			}
			
			StrongName sn = GetStrongName (args [++i]);

			string name = args [++i];
			if (name == "-noname")
				name = null;

			Version v = null;
			string version = args [++i];
			if (version != "-noversion")
				v = new Version (version);

			return new StrongNameMembershipCondition (sn.PublicKey, name, v);
		}

		static bool ProcessCodeGroup (CodeGroup cg, string[] args, ref int i)
		{
			IMembershipCondition mship = null;
			for (; i < args.Length; i++) {
				switch (args [++i]) {
				case "-all":
					cg.MembershipCondition = new AllMembershipCondition ();
					break;
				case "-appdir":
					cg.MembershipCondition = new ApplicationDirectoryMembershipCondition ();
					break;
				case "-custom":
					mship = ProcessCustomMembership (args [++i]);
					if (mship == null)
						return false;
					cg.MembershipCondition = mship;
					break;
				case "-hash":
					mship = ProcessHashMembership (args, ref i);
					if (mship == null)
						return false;
					cg.MembershipCondition = mship;
					break;
				case "-pub":
					mship = ProcessPublisherMembership (args, ref i);
					if (mship == null)
						return false;
					cg.MembershipCondition = mship;
					break;
				case "-site":
					cg.MembershipCondition = new SiteMembershipCondition (args [++i]);
					break;
				case "-strong":
					mship = ProcessStrongNameMembership (args, ref i);
					if (mship == null)
						return false;
					cg.MembershipCondition = mship;
					break;
				case "-url":
					cg.MembershipCondition = new UrlMembershipCondition (args [++i]);
					break;
				case "-zone":
					SecurityZone zone = (SecurityZone) Enum.Parse (typeof (SecurityZone), args [++i]);
					cg.MembershipCondition = new ZoneMembershipCondition (zone);
					break;

				case "-d":
				case "-description":
					cg.Description = args [++i];
					break;
				case "-exclusive":
					bool exclusive = false;
					if (OnOff (args [++i], ref exclusive)) {
						if (exclusive)
							cg.PolicyStatement.Attributes |= PolicyStatementAttribute.Exclusive;
					}
					else
						return false;
					break;
				case "-levelfinal":
					bool final = false;
					if (OnOff (args [++i], ref final)) {
						if (final)
							cg.PolicyStatement.Attributes |= PolicyStatementAttribute.LevelFinal;
					}
					else
						return false;
					break;
				case "-n":
				case "-name":
					cg.Name = args [++i];
					break;
				default:
					i--;
					break;
				}
			}
			return true;
		}

		// -ag label|name membership psetname flag
		// -addgroup label|name membership psetname flag
		static bool AddCodeGroup (string[] args, ref int i)
		{
			string name = args [++i];

			PolicyLevel pl = null;
			CodeGroup parent = null;
			CodeGroup cg = FindCodeGroup (name, ref parent, ref pl);
			if ((pl == null) || (parent == null) || (cg == null))
				return false;

			UnionCodeGroup child = new UnionCodeGroup (
				new AllMembershipCondition (), 
				new PolicyStatement (new PermissionSet (PermissionState.Unrestricted)));
			if (!ProcessCodeGroup (child, args, ref i))
				return false;

			cg.AddChild (child);
			SecurityManager.SavePolicyLevel (pl);
			Console.WriteLine ("CodeGroup '{0}' added in {1} policy level.",
				cg.Name, pl.Label);
			return true;
		}

		// -cg label|name membership|psetname|flag
		// -chggroup label|name membership|psetname|flag
		static bool ChangeCodeGroup (string[] args, ref int i)
		{
			string name = args [++i];

			PolicyLevel pl = null;
			CodeGroup parent = null;
			CodeGroup cg = FindCodeGroup (name, ref parent, ref pl);
			if ((pl == null) || (parent == null) || (cg == null))
				return false;

			if (!ProcessCodeGroup (cg, args, ref i))
				return false;

			SecurityManager.SavePolicyLevel (pl);
			Console.WriteLine ("CodeGroup '{0}' modified in {1} policy level.",
				cg.Name, pl.Label);
			return true;
		}

		// -rg label|name
		// -remgroup label|name
		static bool RemoveCodeGroup (string name)
		{
			PolicyLevel pl = null;
			CodeGroup parent = null;
			CodeGroup cg = FindCodeGroup (name, ref parent, ref pl);
			if ((pl == null) || (parent == null) || (cg == null))
				return false;

			if (!Confirm ())
				return false;

			parent.RemoveChild (cg);
			SecurityManager.SavePolicyLevel (pl);
			Console.WriteLine ("CodeGroup '{0}' removed from {1} policy level.",
				cg.Name, pl.Label);
			return true;
		}

		// -r
		// -recover
		static void Recover ()
		{
			// no confirmation required to recover
			foreach (PolicyLevel pl in Levels) {
				pl.Recover ();
				SecurityManager.SavePolicyLevel (pl);
			}
		}

		// -rs
		// -reset
		static bool Reset ()
		{
			Console.WriteLine (Policies ("Resetting "));
			if (Confirm ()) {
				foreach (PolicyLevel pl in Levels) {
					pl.Reset ();
					SecurityManager.SavePolicyLevel (pl);
				}
				return true;
			}
			return false;
		}

		// -s on|off
		// -security on|off
		static bool Security (string value)
		{
			bool on = true;
			if (!OnOff (value, ref on))
				return false;
			SecurityManager.SecurityEnabled = on;
			return SaveSettings ();
		}

		// -e on|off
		// -execution on|off
		static bool Execution (string value)
		{
			bool on = true;
			if (!OnOff (value, ref on))
				return false;
			SecurityManager.CheckExecutionRights = on;
			return SaveSettings ();
		}

		// -b
		// -buildcache
		static bool BuildCache ()
		{
			// TODO
			return false;
		}

		// -pp on|off
		// -polchgprompt on|off
		static bool PolicyChangePrompt (string value)
		{
			bool on = true;
			if (!OnOff (value, ref on))
				return false;
			PolicyChangesConfirmation = on;
			return SaveSettings ();
		}


		// Policy Levels Internal Management

		static PolicyLevel levelEnterprise;
		static PolicyLevel levelMachine;
		static PolicyLevel levelUser;

		static void BuildLevels ()
		{
			IEnumerator e = SecurityManager.PolicyHierarchy ();
			if (e.MoveNext ())
				levelEnterprise = (PolicyLevel) e.Current;
			if (e.MoveNext ())
				levelMachine = (PolicyLevel) e.Current;
			if (e.MoveNext ())
				levelUser = (PolicyLevel) e.Current;
		}

		static PolicyLevel Enterprise {
			get {
				if (levelEnterprise == null)
					BuildLevels ();
				return levelEnterprise;
			}
		}

		static PolicyLevel Machine {
			get {
				if (levelMachine == null)
					BuildLevels ();
				return levelMachine;
			}
		}

		static PolicyLevel User {
			get {
				if (levelUser == null)
					BuildLevels ();
				return levelUser;
			}
		}

		static ArrayList Levels {
			get {
				if (_levels == null)
					_levels = new ArrayList (3);
				return _levels;
			}
		}

		static bool ProcessInstruction (string[] args, ref int i)
		{
			for (; i < args.Length; i++) {
				switch (args [i]) {
				case "-q":
				case "-quiet":
					PolicyChangesConfirmation = false;
					break;
				case "-f":
				case "-force":
					forcePolicyChanges = true;
					break;
				case "-?":
				case "/?":
				case "-h":
				case "-help":
					Help ();
					break;

				case "-a":
				case "-all":
					policyLevelDefault = false;
					Levels.Clear ();
					Levels.Add (Enterprise);
					Levels.Add (Machine);
					Levels.Add (User);
					break;
				case "-ca":
				case "-customall":
					policyLevelDefault = false;
					Levels.Clear ();
					Levels.Add (Enterprise);
					Levels.Add (Machine);
					Levels.Add (SecurityManager.LoadPolicyLevelFromFile (args [++i], PolicyLevelType.User));
					break;
				case "-cu":
				case "-customuser":
					policyLevelDefault = false;
					Levels.Clear ();
					Levels.Add (SecurityManager.LoadPolicyLevelFromFile (args [++i], PolicyLevelType.User));
					break;
				case "-en":
				case "-entreprise":
					policyLevelDefault = false;
					Levels.Clear ();
					Levels.Add (Enterprise);
					break;
				case "-m":
				case "-machine":
					policyLevelDefault = false;
					Levels.Clear ();
					Levels.Add (Machine);
					break;
				case "-u":
				case "-user":
					policyLevelDefault = false;
					Levels.Clear ();
					Levels.Add (User);
					break;

				case "-lg":
				case "-listgroups":
					ListCodeGroups ();
					break;
				case "-ld":
				case "-listdescription":
					ListDescriptions ();
					break;
				case "-lp":
				case "-listpset":
					ListPermissionSets ();
					break;
				case "-lf":
				case "-listfulltrust":
					ListFullTrust ();
					break;
				case "-l":
				case "-list":
					ListCodeGroups ();
					Console.WriteLine ();
					ListPermissionSets ();
					Console.WriteLine ();
					ListFullTrust ();
					break;

				case "-rsg":
				case "-resolvegroup":
					if (!ResolveGroup (args [++i]))
						return false;
					break;
				case "-rsp":
				case "-resolveperm":
					if (!ResolvePermissions (args [++i]))
						return false;
					break;

				case "-ap":
				case "-addpset":
					if (!AddPermissionSet (args, ref i))
						return false;
					break;
				case "-cp":
				case "-chgpset":
					if (!ChangePermissionSet (args, ref i))
						return false;
					break;
				case "-rp":
				case "-rempset":
					if (!RemovePermissionSet (args [++i]))
						return false;
					break;

				case "-af":
				case "-addfulltrust":
					if (!AddFullTrust (args [++i]))
						return false;
					break;
				case "-rf":
				case "-remfulltrust":
					if (!RemoveFullTrust (args [++i]))
						return false;
					break;

				case "-ag":
				case "-addgroup":
					if (!AddCodeGroup (args, ref i))
						return false;
					break;
				case "-cg":
				case "-chggroup":
					if (!ChangeCodeGroup (args, ref i))
						return false;
					break;
				case "-rg":
				case "-remgroup":
					if (!RemoveCodeGroup (args [++i]))
						return false;
					break;

				case "-r":
				case "-recover":
					Recover ();
					break;
				case "-rs":
				case "-reset":
					if (!Reset ())
						return false;
					break;

				case "-s":
				case "-security":
					if (!Security (args [++i]))
						return false;
					break;
				case "-e":
				case "-execution":
					if (!Execution (args [++i]))
						return false;
					break;
				case "-b":
				case "-buildcache":
					if (!BuildCache ())
						return false;
					break;
				case "-pp":
				case "-polchgprompt":
					if (!PolicyChangePrompt (args [++i]))
						return false;
					break;

				default:
					Console.WriteLine ("*** unknown argument {0} ***", args [i]);
					return false;
				}
				Console.WriteLine ();
			}
			return true;
		}

		static void SetDefaultPolicyLevel ()
		{
			// default is User for normal users and Machine for 
			// administrators. Here we define an administrator as
			// someone who can write to the Machine policy files
			try {
				using (FileStream fs = File.OpenWrite (Machine.StoreLocation)) {
					fs.Close ();
				}
				Levels.Add (Machine);
			}
			catch {
				Levels.Add (User);
			}
			// some actions, like resolves, use a different default (all)
			policyLevelDefault = true;
		}

		[STAThread]
		static int Main (string[] args) 
		{
			Console.WriteLine (new AssemblyInfo ().ToString ());
			if (args.Length == 0) {
				Help ();
				return 0;
			}

			try {
				// set default level (when none is specified 
				// by command line options)
				SetDefaultPolicyLevel ();

				// process instructions (i.e. multiple 
				// instructions can be chained)
				for (int i=0; i < args.Length; i++) {
					if (!ProcessInstruction (args, ref i))
						return 1;
				}
			}
			catch (Exception e) {
				Console.WriteLine ("Error: " + e.ToString ());
				Help ();
				return 2;
			}
			Console.WriteLine ("Success");
			return 0;
		}
	}
}