source: branches/eraser6/CodeReview/Eraser.Manager/Plugins.cs @ 1552

Revision 1552, 12.6 KB checked in by lowjoel, 5 years ago (diff)

Combined the functions in AdvApi?, MsCorEEApi and WintrustApi? to Security.cs

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2009 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.IO;
26using System.Reflection;
27using System.Windows.Forms;
28using System.Runtime.InteropServices;
29using System.Security.Cryptography;
30using System.Security.Cryptography.X509Certificates;
31using Eraser.Util;
32
33namespace Eraser.Manager.Plugin
34{
35    /// <summary>
36    /// The plugins host interface which is used for communicating with the host
37    /// program.
38    /// </summary>
39    /// <remarks>Remember to call Load to load the plugins into memory, otherwise
40    /// they will never be loaded.</remarks>
41    public abstract class Host : IDisposable
42    {
43        #region IDisposable members
44        protected virtual void Dispose(bool disposing)
45        {
46        }
47
48        /// <summary>
49        /// Cleans up resources used by the host. Also unloads all loaded plugins.
50        /// </summary>
51        public void Dispose()
52        {
53            Dispose(true);
54            GC.SuppressFinalize(this);
55        }
56        #endregion
57
58        /// <summary>
59        /// Getter that retrieves the global plugin host instance.
60        /// </summary>
61        public static Host Instance
62        {
63            get { return ManagerLibrary.Instance.Host; }
64        }
65
66        /// <summary>
67        /// Retrieves the list of currently loaded plugins.
68        /// </summary>
69        /// <remarks>The returned list is read-only</remarks>
70        public abstract IList<PluginInstance> Plugins
71        {
72            get;
73        }
74
75        /// <summary>
76        /// Loads all plugins into memory.
77        /// </summary>
78        public abstract void Load();
79
80        /// <summary>
81        /// The plugin loaded event.
82        /// </summary>
83        public EventHandler<PluginLoadedEventArgs> PluginLoaded { get; set; }
84
85        /// <summary>
86        /// Event callback executor for the OnPluginLoad Event
87        /// </summary>
88        internal void OnPluginLoaded(object sender, PluginLoadedEventArgs e)
89        {
90            if (PluginLoaded != null)
91                PluginLoaded(sender, e);
92        }
93
94        /// <summary>
95        /// Loads a plugin.
96        /// </summary>
97        /// <param name="filePath">The absolute or relative file path to the
98        /// DLL.</param>
99        public abstract void LoadPlugin(string filePath);
100    }
101
102    /// <summary>
103    /// Event argument for the plugin loaded event.
104    /// </summary>
105    public class PluginLoadedEventArgs : EventArgs
106    {
107        /// <summary>
108        /// Constructor.
109        /// </summary>
110        /// <param name="instance">The plugin instance of the recently loaded plugin.</param>
111        public PluginLoadedEventArgs(PluginInstance instance)
112        {
113            Instance = instance;
114        }
115
116        /// <summary>
117        /// The <see cref="PluginInstance"/> object representing the newly loaded plugin.
118        /// </summary>
119        public PluginInstance Instance { get; private set; }
120    }
121
122    /// <summary>
123    /// The default plugins host implementation.
124    /// </summary>
125    internal class DefaultHost : Host
126    {
127        /// <summary>
128        /// Constructor. Loads all plugins in the Plugins folder.
129        /// </summary>
130        public DefaultHost()
131        {
132        }
133
134        public override void Load()
135        {
136            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
137            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
138            string pluginsFolder = Path.Combine(
139                Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
140                PLUGINSFOLDER //Plugins folder
141            );
142
143            foreach (string fileName in Directory.GetFiles(pluginsFolder))
144            {
145                FileInfo file = new FileInfo(fileName);
146                if (file.Extension.Equals(".dll"))
147                    try
148                    {
149                        LoadPlugin(file.FullName);
150                    }
151                    catch (BadImageFormatException)
152                    {
153                    }
154                    catch (FileLoadException)
155                    {
156                    }
157            }
158        }
159
160        protected override void Dispose(bool disposing)
161        {
162            if (disposing)
163            {
164                //Unload all the plugins. This will cause all the plugins to execute
165                //the cleanup code.
166                foreach (PluginInstance plugin in plugins)
167                    if (plugin.Plugin != null)
168                        plugin.Plugin.Dispose();
169            }
170        }
171
172        /// <summary>
173        /// The path to the folder containing the plugins.
174        /// </summary>
175        public const string PLUGINSFOLDER = "Plugins";
176
177        public override IList<PluginInstance> Plugins
178        {
179            get { return plugins.AsReadOnly(); }
180        }
181
182        public override void LoadPlugin(string filePath)
183        {
184            //Create the PluginInstance structure
185            Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath);
186            PluginInstance instance = new PluginInstance(reflectAssembly, null);
187            Type typePlugin = null;
188
189            //Iterate over every exported type, checking if it implements IPlugin
190            foreach (Type type in instance.Assembly.GetExportedTypes())
191            {
192                //Check for an implementation of IPlugin
193                Type typeInterface = type.GetInterface("Eraser.Manager.Plugin.IPlugin", true);
194                if (typeInterface != null)
195                {
196                    typePlugin = type;
197                    break;
198                }
199            }
200
201            //If the typePlugin type is empty the assembly doesn't implement IPlugin; we
202            //aren't interested.
203            if (typePlugin == null)
204                return;
205
206            //OK this assembly is a plugin
207            lock (plugins)
208                plugins.Add(instance);
209
210            //The plugin was not disabled or it was loaded for the first time. Check
211            //the plugin for the presence of a valid signature.
212            IDictionary<Guid, bool> approvals = ManagerLibrary.Settings.PluginApprovals;
213            if ((reflectAssembly.GetName().GetPublicKey().Length == 0 ||
214                !Security.VerifyStrongName(filePath) ||
215                instance.AssemblyAuthenticode == null) &&
216                !approvals.ContainsKey(instance.AssemblyInfo.Guid))
217            {
218                return;
219            }
220
221            //Load the plugin
222            instance.Assembly = Assembly.LoadFrom(filePath);
223
224            //See if the plugin belongs to us (same signature) and if the assembly
225            //declares an IsCore attribute.
226            if (reflectAssembly.GetName().GetPublicKey().Length ==
227                Assembly.GetExecutingAssembly().GetName().GetPublicKey().Length)
228            {
229                bool sameKey = true;
230                byte[] reflectAssemblyKey = reflectAssembly.GetName().GetPublicKey();
231                byte[] thisAssemblyKey = Assembly.GetExecutingAssembly().GetName().GetPublicKey();
232                for (int i = 0, j = reflectAssemblyKey.Length; i != j; ++i)
233                    if (reflectAssemblyKey[i] != thisAssemblyKey[i])
234                    {
235                        sameKey = false;
236                        break;
237                    }
238
239                //Check for the IsCore attribute.
240                if (sameKey)
241                {
242                    object[] attr = instance.Assembly.GetCustomAttributes(typeof(CoreAttribute), true);
243                    if (attr.Length != 0)
244                        instance.IsCore = true;
245                }
246            }
247
248            //See if the user disabled this plugin (users cannot disable Core plugins)
249            if (approvals.ContainsKey(instance.AssemblyInfo.Guid) &&
250                !approvals[instance.AssemblyInfo.Guid] && !instance.IsCore)
251            {
252                return;
253            }
254
255            //Initialize the plugin
256            IPlugin pluginInterface = (IPlugin)Activator.CreateInstance(
257                instance.Assembly.GetType(typePlugin.ToString()));
258            pluginInterface.Initialize(this);
259            instance.Plugin = pluginInterface;
260
261            //And broadcast the plugin load event
262            OnPluginLoaded(this, new PluginLoadedEventArgs(instance));
263        }
264
265        private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
266        {
267            lock (plugins)
268                foreach (PluginInstance instance in plugins)
269                    if (instance.Assembly.FullName == args.Name)
270                        return instance.Assembly;
271            return null;
272        }
273
274        private Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
275        {
276            return Assembly.ReflectionOnlyLoad(args.Name);
277        }
278
279        private List<PluginInstance> plugins = new List<PluginInstance>();
280    }
281
282    /// <summary>
283    /// Structure holding the instance values of the plugin like handle and path.
284    /// </summary>
285    public class PluginInstance
286    {
287        /// <summary>
288        /// Constructor
289        /// </summary>
290        /// <param name="assembly">The assembly representing this plugin.</param>
291        /// <param name="path">The path to the ass</param>
292        /// <param name="plugin"></param>
293        internal PluginInstance(Assembly assembly, IPlugin plugin)
294        {
295            Assembly = assembly;
296            Plugin = plugin;
297            IsCore = false;
298
299            //Verify the certificate in the assembly.
300            if (WintrustApi.VerifyAuthenticode(assembly.Location))
301            {
302                X509Certificate2 cert = new X509Certificate2(
303                    X509Certificate.CreateFromSignedFile(assembly.Location));
304                AssemblyAuthenticode = cert;
305            }
306        }
307
308        /// <summary>
309        /// Gets the Assembly this plugin instance came from.
310        /// </summary>
311        public Assembly Assembly
312        {
313            get
314            {
315                return assembly;
316            }
317            internal set
318            {
319                assembly = value;
320
321                AssemblyInfo info = new AssemblyInfo();
322                info.Version = assembly.GetName().Version;
323                IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(assembly);
324                foreach (CustomAttributeData attr in attributes)
325                    if (attr.Constructor.DeclaringType == typeof(GuidAttribute))
326                        info.Guid = new Guid((string)attr.ConstructorArguments[0].Value);
327                    else if (attr.Constructor.DeclaringType == typeof(AssemblyCompanyAttribute))
328                        info.Author = (string)attr.ConstructorArguments[0].Value;
329
330                this.AssemblyInfo = info;
331            }
332        }
333
334        /// <summary>
335        /// Gets the attributes of the assembly, loading from reflection-only sources.
336        /// </summary>
337        public AssemblyInfo AssemblyInfo { get; private set; }
338
339        /// <summary>
340        /// The Authenticode signature used for signing the assembly.
341        /// </summary>
342        public X509Certificate2 AssemblyAuthenticode { get; private set; }
343
344        /// <summary>
345        /// Gets whether the plugin is required for the functioning of Eraser (and
346        /// therefore cannot be disabled.)
347        /// </summary>
348        public bool IsCore { get; internal set; }
349
350        /// <summary>
351        /// Gets the IPlugin interface which the plugin exposed. This may be null
352        /// if the plugin was not loaded.
353        /// </summary>
354        public IPlugin Plugin { get; internal set; }
355
356        /// <summary>
357        /// Gets whether this particular plugin is currently loaded in memory.
358        /// </summary>
359        public bool Loaded
360        {
361            get { return Plugin != null; }
362        }
363
364        private Assembly assembly;
365    }
366
367    /// <summary>
368    /// Reflection-only information retrieved from the assembly.
369    /// </summary>
370    public struct AssemblyInfo
371    {
372        /// <summary>
373        /// The GUID of the assembly.
374        /// </summary>
375        public Guid Guid { get; set; }
376
377        /// <summary>
378        /// The publisher of the assembly.
379        /// </summary>
380        public string Author { get; set; }
381
382        /// <summary>
383        /// The version of the assembly.
384        /// </summary>
385        public Version Version { get; set; }
386
387        public override bool Equals(object obj)
388        {
389            if (!(obj is AssemblyInfo))
390                return false;
391            return Equals((AssemblyInfo)obj);
392        }
393
394        public bool Equals(AssemblyInfo other)
395        {
396            return Guid == other.Guid;
397        }
398
399        public static bool operator ==(AssemblyInfo assembly1, AssemblyInfo assembly2)
400        {
401            return assembly1.Equals(assembly2);
402        }
403
404        public static bool operator !=(AssemblyInfo assembly1, AssemblyInfo assembly2)
405        {
406            return !assembly1.Equals(assembly2);
407        }
408
409        public override int GetHashCode()
410        {
411            return Guid.GetHashCode();
412        }
413    }
414
415    /// <summary>
416    /// Basic plugin interface which allows for the main program to utilize the
417    /// functions in the DLL
418    /// </summary>
419    public interface IPlugin : IDisposable
420    {
421        /// <summary>
422        /// Initializer.
423        /// </summary>
424        /// <param name="host">The host object which can be used for two-way
425        /// communication with the program.</param>
426        void Initialize(Host host);
427
428        /// <summary>
429        /// The name of the plug-in, used for descriptive purposes in the UI
430        /// </summary>
431        string Name
432        {
433            get;
434        }
435
436        /// <summary>
437        /// The author of the plug-in, used for display in the UI and for users
438        /// to contact the author about bugs. Must be in the format:
439        ///     (.+) \<([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)\.([a-zA-Z0-9]+)\>
440        /// </summary>
441        /// <example>Joel Low <joel@joelsplace.sg></example>
442        string Author
443        {
444            get;
445        }
446
447        /// <summary>
448        /// Determines whether the plug-in is configurable.
449        /// </summary>
450        bool Configurable
451        {
452            get;
453        }
454
455        /// <summary>
456        /// Fulfil a request to display the settings for this plug-in.
457        /// </summary>
458        /// <param name="parent">The parent control which the settings dialog should
459        /// be parented with.</param>
460        void DisplaySettings(Control parent);
461    }
462
463    /// <summary>
464    /// Declares that the entity referenced is a core plugin and cannot be unloaded.
465    /// Only plugins signed with the same signature as the Manager library will be
466    /// considered to be safe and therefore checked for this attribute.
467    /// </summary>
468    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
469    public sealed class CoreAttribute : Attribute
470    {
471    }
472}
Note: See TracBrowser for help on using the repository browser.